F: Implementing Nominal cst_type

This commit is contained in:
Ivan 2025-08-12 15:40:39 +03:00
parent 876a98ea8c
commit c63144e78b
34 changed files with 412 additions and 293 deletions

View File

@ -216,6 +216,8 @@
"неинтерпретируемый", "неинтерпретируемый",
"неитерируемого", "неитерируемого",
"Никанорова", "Никанорова",
"Номеноид",
"Номеноиды",
"операционализации", "операционализации",
"операционализированных", "операционализированных",
"Оргтеор", "Оргтеор",

View File

@ -27,7 +27,7 @@ class AssociationSerializer(StrictModelSerializer):
class Meta: class Meta:
''' serializer metadata. ''' ''' serializer metadata. '''
model = Association model = Association
fields = ('argument', 'operation') fields = ('container', 'associate')
class CstBaseSerializer(StrictModelSerializer): class CstBaseSerializer(StrictModelSerializer):
@ -121,11 +121,6 @@ class CstCreateSerializer(StrictModelSerializer):
) )
alias = serializers.CharField(max_length=8) alias = serializers.CharField(max_length=8)
cst_type = serializers.ChoiceField(CstType.choices) cst_type = serializers.ChoiceField(CstType.choices)
associations = PKField(
many=True,
required=False,
queryset=Constituenta.objects.all().only('schema_id', 'pk')
)
class Meta: class Meta:
''' serializer metadata. ''' ''' serializer metadata. '''
@ -133,7 +128,7 @@ class CstCreateSerializer(StrictModelSerializer):
fields = \ fields = \
'alias', 'cst_type', 'convention', 'crucial', \ 'alias', 'cst_type', 'convention', 'crucial', \
'term_raw', 'definition_raw', 'definition_formal', \ 'term_raw', 'definition_raw', 'definition_formal', \
'insert_after', 'term_forms', 'associations' 'insert_after', 'term_forms'
def validate(self, attrs): def validate(self, attrs):
schema = cast(LibraryItem, self.context['schema']) schema = cast(LibraryItem, self.context['schema'])
@ -142,12 +137,6 @@ class CstCreateSerializer(StrictModelSerializer):
raise serializers.ValidationError({ raise serializers.ValidationError({
'insert_after': msg.constituentaNotInRSform(schema.title) 'insert_after': msg.constituentaNotInRSform(schema.title)
}) })
associations = attrs.get('associations', [])
for assoc in associations:
if assoc.schema_id != schema.pk:
raise serializers.ValidationError({
'associations': msg.constituentaNotInRSform(schema.title)
})
return attrs return attrs
@ -322,6 +311,8 @@ class RSFormParseSerializer(StrictModelSerializer):
def _parse_data(self, data: dict) -> dict: def _parse_data(self, data: dict) -> dict:
parse = PyConceptAdapter(data).parse() parse = PyConceptAdapter(data).parse()
for cst_data in data['items']: for cst_data in data['items']:
if cst_data['cst_type'] == CstType.NOMINAL:
continue
cst_data['parse'] = next( cst_data['parse'] = next(
cst['parse'] for cst in parse['items'] cst['parse'] for cst in parse['items']
if cst['id'] == cst_data['id'] if cst['id'] == cst_data['id']

View File

@ -6,7 +6,7 @@ import pyconcept
from shared import messages as msg from shared import messages as msg
from ..models import Constituenta from ..models import Constituenta, CstType
class PyConceptAdapter: class PyConceptAdapter:
@ -34,7 +34,7 @@ class PyConceptAdapter:
result: dict = { result: dict = {
'items': [] 'items': []
} }
items = Constituenta.objects.filter(schema_id=schemaID).order_by('order') items = Constituenta.objects.filter(schema_id=schemaID).exclude(cst_type=CstType.NOMINAL).order_by('order')
for cst in items: for cst in items:
result['items'].append({ result['items'].append({
'entityUID': cst.pk, 'entityUID': cst.pk,
@ -51,6 +51,8 @@ class PyConceptAdapter:
'items': [] 'items': []
} }
for cst in data['items']: for cst in data['items']:
if cst['cst_type'] == CstType.NOMINAL:
continue
result['items'].append({ result['items'].append({
'entityUID': cst['id'], 'entityUID': cst['id'],
'cstType': cst['cst_type'], 'cstType': cst['cst_type'],

View File

@ -12,7 +12,7 @@ class TestConstituentaAPI(EndpointTester):
self.owned_id = self.owned.model.pk self.owned_id = self.owned.model.pk
self.unowned = RSForm.create(title='Test2', alias='T2') self.unowned = RSForm.create(title='Test2', alias='T2')
self.unowned_id = self.unowned.model.pk self.unowned_id = self.unowned.model.pk
self.cst1 = Constituenta.objects.create( self.x1 = Constituenta.objects.create(
alias='X1', alias='X1',
cst_type=CstType.BASE, cst_type=CstType.BASE,
schema=self.owned.model, schema=self.owned.model,
@ -21,119 +21,175 @@ class TestConstituentaAPI(EndpointTester):
term_raw='Test1', term_raw='Test1',
term_resolved='Test1R', term_resolved='Test1R',
term_forms=[{'text': 'form1', 'tags': 'sing,datv'}]) term_forms=[{'text': 'form1', 'tags': 'sing,datv'}])
self.cst2 = Constituenta.objects.create( self.x2 = Constituenta.objects.create(
alias='X2', alias='X2',
cst_type=CstType.BASE, cst_type=CstType.BASE,
schema=self.unowned.model, schema=self.owned.model,
order=0, order=1,
convention='Test1', convention='Test1',
term_raw='Test2', term_raw='Test2',
term_resolved='Test2R' term_resolved='Test2R'
) )
self.cst3 = Constituenta.objects.create( self.x3 = Constituenta.objects.create(
alias='X3', alias='X3',
schema=self.owned.model, schema=self.owned.model,
order=1, order=2,
term_raw='Test3', term_raw='Test3',
term_resolved='Test3', term_resolved='Test3',
definition_raw='Test1', definition_raw='Test1',
definition_resolved='Test2' definition_resolved='Test2'
) )
self.invalid_cst = self.cst3.pk + 1337 self.unowned_cst = self.unowned.insert_last(alias='X1', cst_type=CstType.BASE)
self.invalid_cst = self.x3.pk + 1337
@decl_endpoint('/api/rsforms/{schema}/update-cst', method='patch') @decl_endpoint('/api/rsforms/{item}/update-cst', method='patch')
def test_partial_update(self): def test_partial_update(self):
data = {'target': self.cst1.pk, 'item_data': {'convention': 'tt'}} data = {'target': self.x1.pk, 'item_data': {'convention': 'tt'}}
self.executeForbidden(data, schema=self.unowned_id) self.executeForbidden(data, item=self.unowned_id)
self.logout() self.logout()
self.executeForbidden(data, schema=self.owned_id) self.executeForbidden(data, item=self.owned_id)
self.login() self.login()
self.executeOK(data) self.executeOK(data)
self.cst1.refresh_from_db() self.x1.refresh_from_db()
self.assertEqual(self.cst1.convention, 'tt') self.assertEqual(self.x1.convention, 'tt')
self.executeOK(data) self.executeOK(data)
@decl_endpoint('/api/rsforms/{schema}/update-cst', method='patch') @decl_endpoint('/api/rsforms/{item}/update-cst', method='patch')
def test_partial_update_rename(self): def test_partial_update_rename(self):
data = {'target': self.cst1.pk, 'item_data': {'alias': self.cst3.alias}} data = {'target': self.x1.pk, 'item_data': {'alias': self.x3.alias}}
self.executeBadData(data, schema=self.owned_id) self.executeBadData(data, item=self.owned_id)
d1 = self.owned.insert_last( d1 = self.owned.insert_last(
alias='D1', alias='D1',
term_raw='@{X1|plur}', term_raw='@{X1|plur}',
definition_formal='X1' definition_formal='X1'
) )
self.assertEqual(self.cst1.order, 0) self.assertEqual(self.x1.order, 0)
self.assertEqual(self.cst1.alias, 'X1') self.assertEqual(self.x1.alias, 'X1')
self.assertEqual(self.cst1.cst_type, CstType.BASE) self.assertEqual(self.x1.cst_type, CstType.BASE)
data = {'target': self.cst1.pk, 'item_data': {'alias': 'D2', 'cst_type': CstType.TERM}} data = {'target': self.x1.pk, 'item_data': {'alias': 'D2', 'cst_type': CstType.TERM}}
self.executeOK(data, schema=self.owned_id) self.executeOK(data, item=self.owned_id)
d1.refresh_from_db() d1.refresh_from_db()
self.cst1.refresh_from_db() self.x1.refresh_from_db()
self.assertEqual(d1.term_resolved, '') self.assertEqual(d1.term_resolved, '')
self.assertEqual(d1.term_raw, '@{D2|plur}') self.assertEqual(d1.term_raw, '@{D2|plur}')
self.assertEqual(self.cst1.order, 0) self.assertEqual(self.x1.order, 0)
self.assertEqual(self.cst1.alias, 'D2') self.assertEqual(self.x1.alias, 'D2')
self.assertEqual(self.cst1.cst_type, CstType.TERM) self.assertEqual(self.x1.cst_type, CstType.TERM)
@decl_endpoint('/api/rsforms/{schema}/update-cst', method='patch') @decl_endpoint('/api/rsforms/{item}/update-cst', method='patch')
def test_update_resolved_no_refs(self): def test_update_resolved_no_refs(self):
data = { data = {
'target': self.cst3.pk, 'target': self.x3.pk,
'item_data': { 'item_data': {
'term_raw': 'New term', 'term_raw': 'New term',
'definition_raw': 'New def' 'definition_raw': 'New def'
} }
} }
self.executeOK(data, schema=self.owned_id) self.executeOK(data, item=self.owned_id)
self.cst3.refresh_from_db() self.x3.refresh_from_db()
self.assertEqual(self.cst3.term_resolved, 'New term') self.assertEqual(self.x3.term_resolved, 'New term')
self.assertEqual(self.cst3.definition_resolved, 'New def') self.assertEqual(self.x3.definition_resolved, 'New def')
@decl_endpoint('/api/rsforms/{schema}/update-cst', method='patch') @decl_endpoint('/api/rsforms/{item}/update-cst', method='patch')
def test_update_resolved_refs(self): def test_update_resolved_refs(self):
data = { data = {
'target': self.cst3.pk, 'target': self.x3.pk,
'item_data': { 'item_data': {
'term_raw': '@{X1|nomn,sing}', 'term_raw': '@{X1|nomn,sing}',
'definition_raw': '@{X1|nomn,sing} @{X1|sing,datv}' 'definition_raw': '@{X1|nomn,sing} @{X1|sing,datv}'
} }
} }
self.executeOK(data, schema=self.owned_id) self.executeOK(data, item=self.owned_id)
self.cst3.refresh_from_db() self.x3.refresh_from_db()
self.assertEqual(self.cst3.term_resolved, self.cst1.term_resolved) self.assertEqual(self.x3.term_resolved, self.x1.term_resolved)
self.assertEqual(self.cst3.definition_resolved, f'{self.cst1.term_resolved} form1') self.assertEqual(self.x3.definition_resolved, f'{self.x1.term_resolved} form1')
@decl_endpoint('/api/rsforms/{schema}/update-cst', method='patch')
@decl_endpoint('/api/rsforms/{item}/update-cst', method='patch')
def test_update_term_forms(self): def test_update_term_forms(self):
data = { data = {
'target': self.cst3.pk, 'target': self.x3.pk,
'item_data': { 'item_data': {
'definition_raw': '@{X3|sing,datv}', 'definition_raw': '@{X3|sing,datv}',
'term_forms': [{'text': 'form1', 'tags': 'sing,datv'}] 'term_forms': [{'text': 'form1', 'tags': 'sing,datv'}]
} }
} }
self.executeOK(data, schema=self.owned_id) self.executeOK(data, item=self.owned_id)
self.cst3.refresh_from_db() self.x3.refresh_from_db()
self.assertEqual(self.cst3.definition_resolved, 'form1') self.assertEqual(self.x3.definition_resolved, 'form1')
self.assertEqual(self.cst3.term_forms, data['item_data']['term_forms']) self.assertEqual(self.x3.term_forms, data['item_data']['term_forms'])
@decl_endpoint('/api/rsforms/{schema}/update-crucial', method='patch')
@decl_endpoint('/api/rsforms/{item}/update-crucial', method='patch')
def test_update_crucial(self): def test_update_crucial(self):
data = {'target': [self.cst1.pk], 'value': True} data = {'target': [self.x1.pk], 'value': True}
self.executeForbidden(data, schema=self.unowned_id) self.executeForbidden(data, item=self.unowned_id)
self.logout() self.logout()
self.executeForbidden(data, schema=self.owned_id) self.executeForbidden(data, item=self.owned_id)
self.login() self.login()
self.executeOK(data, schema=self.owned_id) self.executeOK(data, item=self.owned_id)
self.cst1.refresh_from_db() self.x1.refresh_from_db()
self.assertEqual(self.cst1.crucial, True) self.assertEqual(self.x1.crucial, True)
@decl_endpoint('/api/rsforms/{item}/create-cst', method='post')
def test_create_constituenta(self):
data = {'alias': 'X4', 'cst_type': CstType.BASE}
self.executeForbidden(data, item=self.unowned_id)
data = {'alias': 'X4'}
self.executeBadData(item=self.owned_id)
self.executeBadData(data)
data['cst_type'] = 'invalid'
self.executeBadData(data)
data = {
'alias': 'X4',
'cst_type': CstType.BASE,
'term_raw': 'test',
'term_forms': [{'text': 'form1', 'tags': 'sing,datv'}],
'definition_formal': 'invalid',
'crucial': True
}
response = self.executeCreated(data)
self.assertEqual(response.data['new_cst']['alias'], data['alias'])
x4 = Constituenta.objects.get(alias=response.data['new_cst']['alias'])
self.assertEqual(x4.order, 3)
self.assertEqual(x4.term_raw, data['term_raw'])
self.assertEqual(x4.term_forms, data['term_forms'])
self.assertEqual(x4.definition_formal, data['definition_formal'])
self.assertEqual(x4.crucial, data['crucial'])
@decl_endpoint('/api/rsforms/{item}/create-cst', method='post')
def test_create_constituenta_after(self):
self.set_params(item=self.owned_id)
data = {'alias': 'X4', 'cst_type': CstType.BASE, 'insert_after': self.invalid_cst}
self.executeBadData(data)
data['insert_after'] = self.unowned_cst.pk
self.executeBadData(data)
data = {
'alias': 'X4',
'cst_type': CstType.BASE,
'insert_after': self.x2.pk,
}
response = self.executeCreated(data)
self.assertEqual(response.data['new_cst']['alias'], data['alias'])
x4 = Constituenta.objects.get(alias=response.data['new_cst']['alias'])
self.x3.refresh_from_db()
self.assertEqual(x4.order, 2)
self.assertEqual(self.x3.order, 3)

View File

@ -200,54 +200,6 @@ class TestRSFormViewset(EndpointTester):
self.assertIn('document.json', zipped_file.namelist()) self.assertIn('document.json', zipped_file.namelist())
@decl_endpoint('/api/rsforms/{item}/create-cst', method='post')
def test_create_constituenta(self):
data = {'alias': 'X3', 'cst_type': CstType.BASE}
self.executeForbidden(data, item=self.unowned_id)
data = {'alias': 'X3'}
self.owned.insert_last('X1')
x2 = self.owned.insert_last('X2')
self.executeBadData(item=self.owned_id)
self.executeBadData(data)
data['cst_type'] = 'invalid'
self.executeBadData(data)
data['cst_type'] = CstType.BASE
response = self.executeCreated(data)
self.assertEqual(response.data['new_cst']['alias'], 'X3')
x3 = Constituenta.objects.get(alias=response.data['new_cst']['alias'])
self.assertEqual(x3.order, 2)
data = {
'alias': 'X4',
'cst_type': CstType.BASE,
'insert_after': x2.pk,
'term_raw': 'test',
'term_forms': [{'text': 'form1', 'tags': 'sing,datv'}],
'definition_formal': 'invalid',
'crucial': True
}
response = self.executeCreated(data)
self.assertEqual(response.data['new_cst']['alias'], data['alias'])
x4 = Constituenta.objects.get(alias=response.data['new_cst']['alias'])
self.assertEqual(x4.order, 2)
self.assertEqual(x4.term_raw, data['term_raw'])
self.assertEqual(x4.term_forms, data['term_forms'])
self.assertEqual(x4.definition_formal, data['definition_formal'])
self.assertEqual(x4.crucial, data['crucial'])
data = {
'alias': 'X5',
'cst_type': CstType.BASE,
'insert_after': None,
'term_raw': 'test5'
}
response = self.executeCreated(data)
self.assertEqual(response.data['new_cst']['alias'], data['alias'])
@decl_endpoint('/api/rsforms/{item}/substitute', method='patch') @decl_endpoint('/api/rsforms/{item}/substitute', method='patch')
def test_substitute_multiple(self): def test_substitute_multiple(self):
self.set_params(item=self.owned_id) self.set_params(item=self.owned_id)

View File

@ -12,7 +12,7 @@ export { BiX as IconRemove } from 'react-icons/bi';
export { BiTrash as IconDestroy } from 'react-icons/bi'; export { BiTrash as IconDestroy } from 'react-icons/bi';
export { BiReset as IconReset } from 'react-icons/bi'; export { BiReset as IconReset } from 'react-icons/bi';
export { TbArrowsDiagonal2 as IconResize } from 'react-icons/tb'; export { TbArrowsDiagonal2 as IconResize } from 'react-icons/tb';
export { FiEdit as IconEdit } from 'react-icons/fi'; export { FiEdit as IconEdit } from 'react-icons/fi';
export { AiOutlineEdit as IconEdit2 } from 'react-icons/ai'; export { AiOutlineEdit as IconEdit2 } from 'react-icons/ai';
export { BiSearchAlt2 as IconSearch } from 'react-icons/bi'; export { BiSearchAlt2 as IconSearch } from 'react-icons/bi';
export { BiDownload as IconDownload } from 'react-icons/bi'; export { BiDownload as IconDownload } from 'react-icons/bi';
@ -87,6 +87,7 @@ export { MdOutlineSelectAll as IconConceptBlock } from 'react-icons/md';
export { TbHexagon as IconRSForm } from 'react-icons/tb'; export { TbHexagon as IconRSForm } from 'react-icons/tb';
export { TbAssembly as IconRSFormOwned } from 'react-icons/tb'; export { TbAssembly as IconRSFormOwned } from 'react-icons/tb';
export { TbBallFootball as IconRSFormImported } from 'react-icons/tb'; export { TbBallFootball as IconRSFormImported } from 'react-icons/tb';
export { TbHexagonLetterN as IconCstNominal } from 'react-icons/tb';
export { TbHexagonLetterX as IconCstBaseSet } from 'react-icons/tb'; export { TbHexagonLetterX as IconCstBaseSet } from 'react-icons/tb';
export { TbHexagonLetterC as IconCstConstSet } from 'react-icons/tb'; export { TbHexagonLetterC as IconCstConstSet } from 'react-icons/tb';
export { TbHexagonLetterS as IconCstStructured } from 'react-icons/tb'; export { TbHexagonLetterS as IconCstStructured } from 'react-icons/tb';
@ -125,7 +126,7 @@ export { RiOpenSourceLine as IconPublic } from 'react-icons/ri';
export { RiShieldLine as IconProtected } from 'react-icons/ri'; export { RiShieldLine as IconProtected } from 'react-icons/ri';
export { RiShieldKeyholeLine as IconPrivate } from 'react-icons/ri'; export { RiShieldKeyholeLine as IconPrivate } from 'react-icons/ri';
export { BiBug as IconStatusError } from 'react-icons/bi'; export { BiBug as IconStatusError } from 'react-icons/bi';
export { BiCheckCircle as IconStatusOK } from 'react-icons/bi'; export { LuThumbsUp as IconStatusOK } from 'react-icons/lu';
export { BiHelpCircle as IconStatusUnknown } from 'react-icons/bi'; export { BiHelpCircle as IconStatusUnknown } from 'react-icons/bi';
export { BiStopCircle as IconStatusIncalculable } from 'react-icons/bi'; export { BiStopCircle as IconStatusIncalculable } from 'react-icons/bi';
export { BiPauseCircle as IconStatusProperty } from 'react-icons/bi'; export { BiPauseCircle as IconStatusProperty } from 'react-icons/bi';

View File

@ -89,7 +89,9 @@ export function varSchemaGraph(schema: IRSForm): string {
/** Generates a prompt for a schema type graph variable. */ /** Generates a prompt for a schema type graph variable. */
export function varSchemaTypeGraph(schema: IRSForm): string { export function varSchemaTypeGraph(schema: IRSForm): string {
const graph = new TypificationGraph(); const graph = new TypificationGraph();
schema.items.forEach(item => graph.addConstituenta(item.alias, item.parse.typification, item.parse.args)); schema.items.forEach(item => {
if (item.parse) graph.addConstituenta(item.alias, item.parse.typification, item.parse.args);
});
let result = `Название концептуальной схемы: ${schema.title}\n`; let result = `Название концептуальной схемы: ${schema.title}\n`;
result += `[${schema.alias}] Описание: "${schema.description}"\n\n`; result += `[${schema.alias}] Описание: "${schema.description}"\n\n`;
@ -154,6 +156,6 @@ export function varSyntaxTree(cst: IConstituenta): string {
let result = `Конституента: ${cst.alias}\n`; let result = `Конституента: ${cst.alias}\n`;
result += `Формальное выражение: ${cst.definition_formal}\n`; result += `Формальное выражение: ${cst.definition_formal}\n`;
result += `Дерево синтаксического разбора:\n`; result += `Дерево синтаксического разбора:\n`;
result += JSON.stringify(cst.parse.syntaxTree, null, PARAMETER.indentJSON); result += cst.parse ? JSON.stringify(cst.parse.syntaxTree, null, PARAMETER.indentJSON) : 'не определено';
return result; return result;
} }

View File

@ -7,6 +7,7 @@ import {
IconCstBaseSet, IconCstBaseSet,
IconCstConstSet, IconCstConstSet,
IconCstFunction, IconCstFunction,
IconCstNominal,
IconCstPredicate, IconCstPredicate,
IconCstStructured, IconCstStructured,
IconCstTerm, IconCstTerm,
@ -118,6 +119,11 @@ export function HelpThesaurus() {
<ul> <ul>
<b>Типы конституент</b> <b>Типы конституент</b>
<li>
<IconCstNominal size='1rem' className='inline-icon' />
{'\u2009'}Номеноид (N#) предметная сущность, не имеющая четкого определения, используемая для ассоциативной
группировки конституент и предварительной фиксации содержательных отношений.
</li>
<li> <li>
<IconCstBaseSet size='1rem' className='inline-icon' /> <IconCstBaseSet size='1rem' className='inline-icon' />
{'\u2009'}Базисное множество (X#) неопределяемое понятие, представленное множеством различимых элементов. {'\u2009'}Базисное множество (X#) неопределяемое понятие, представленное множеством различимых элементов.

View File

@ -60,7 +60,7 @@ export const schemaLibraryItemType = z.enum(Object.values(LibraryItemType) as [L
export const schemaAccessPolicy = z.enum(Object.values(AccessPolicy) as [AccessPolicy, ...AccessPolicy[]]); export const schemaAccessPolicy = z.enum(Object.values(AccessPolicy) as [AccessPolicy, ...AccessPolicy[]]);
export const schemaLibraryItem = z.strictObject({ export const schemaLibraryItem = z.strictObject({
id: z.coerce.number(), id: z.number(),
item_type: schemaLibraryItemType, item_type: schemaLibraryItemType,
alias: z.string().nonempty(), alias: z.string().nonempty(),
@ -72,9 +72,9 @@ export const schemaLibraryItem = z.strictObject({
location: z.string(), location: z.string(),
access_policy: schemaAccessPolicy, access_policy: schemaAccessPolicy,
time_create: z.string().datetime({ offset: true }), time_create: z.iso.datetime({ offset: true }),
time_update: z.string().datetime({ offset: true }), time_update: z.iso.datetime({ offset: true }),
owner: z.coerce.number().nullable() owner: z.number().nullable()
}); });
export const schemaLibraryItemArray = z.array(schemaLibraryItem); export const schemaLibraryItemArray = z.array(schemaLibraryItem);
@ -126,10 +126,10 @@ export const schemaUpdateLibraryItem = schemaInputLibraryItem
}); });
export const schemaVersionInfo = z.strictObject({ export const schemaVersionInfo = z.strictObject({
id: z.coerce.number(), id: z.number(),
version: z.string(), version: z.string(),
description: z.string(), description: z.string(),
time_create: z.string().datetime({ offset: true }) time_create: z.iso.datetime({ offset: true })
}); });
const schemaVersionInput = z.strictObject({ const schemaVersionInput = z.strictObject({

View File

@ -175,7 +175,11 @@ export class SubstitutionValidator {
if (!original || !substitution) { if (!original || !substitution) {
return this.reportError(SubstitutionErrorType.invalidIDs, []); return this.reportError(SubstitutionErrorType.invalidIDs, []);
} }
if (original.parse.status === ParsingStatus.INCORRECT || substitution.parse.status === ParsingStatus.INCORRECT) { if (
substitution.parse &&
original.parse &&
(original.parse.status === ParsingStatus.INCORRECT || substitution.parse.status === ParsingStatus.INCORRECT)
) {
return this.reportError(SubstitutionErrorType.incorrectCst, [substitution.alias, original.alias]); return this.reportError(SubstitutionErrorType.incorrectCst, [substitution.alias, original.alias]);
} }
switch (substitution.cst_type) { switch (substitution.cst_type) {
@ -247,6 +251,9 @@ export class SubstitutionValidator {
continue; continue;
} }
graph.addNode(cst.id); graph.addNode(cst.id);
if (!cst.parse) {
continue;
}
const parents = extractGlobals(cst.parse.typification); const parents = extractGlobals(cst.parse.typification);
for (const arg of cst.parse.args) { for (const arg of cst.parse.args) {
for (const alias of extractGlobals(arg.typification)) { for (const alias of extractGlobals(arg.typification)) {
@ -299,6 +306,13 @@ export class SubstitutionValidator {
} }
} }
if (!!original.parse !== !!substitution.parse) {
return this.reportError(SubstitutionErrorType.unequalTypification, [substitution.alias, original.alias]);
}
if (!original.parse || !substitution.parse) {
continue;
}
const originalType = applyTypificationMapping( const originalType = applyTypificationMapping(
applyAliasMapping(original.parse.typification, baseMappings.get(original.schema)!), applyAliasMapping(original.parse.typification, baseMappings.get(original.schema)!),
typeMappings typeMappings
@ -365,7 +379,7 @@ export class SubstitutionValidator {
const substitution = this.cstByID.get(item.substitution)!; const substitution = this.cstByID.get(item.substitution)!;
let substitutionText = ''; let substitutionText = '';
if (substitution.cst_type === original.cst_type) { if (substitution.cst_type === original.cst_type || !substitution.parse) {
substitutionText = baseMappings.get(substitution.schema)![substitution.alias]; substitutionText = baseMappings.get(substitution.schema)![substitution.alias];
} else { } else {
substitutionText = applyAliasMapping(substitution.parse.typification, baseMappings.get(substitution.schema)!); substitutionText = applyAliasMapping(substitution.parse.typification, baseMappings.get(substitution.schema)!);

View File

@ -181,11 +181,13 @@ export function ToolbarSchema({
} }
function handleShowTypeGraph() { function handleShowTypeGraph() {
const typeInfo = schema.items.map(item => ({ const typeInfo = schema.items
alias: item.alias, .filter(item => !!item.parse)
result: item.parse.typification, .map(item => ({
args: item.parse.args alias: item.alias,
})); result: item.parse!.typification,
args: item.parse!.args
}));
showTypeGraph({ items: typeInfo }); showTypeGraph({ items: typeInfo });
} }

View File

@ -5,7 +5,7 @@
import { Graph } from '@/models/graph'; import { Graph } from '@/models/graph';
import { type RO } from '@/utils/meta'; import { type RO } from '@/utils/meta';
import { type IConstituenta, type IRSForm, type IRSFormStats } from '../models/rsform'; import { ExpressionStatus, type IConstituenta, type IRSForm, type IRSFormStats } from '../models/rsform';
import { inferClass, inferStatus, inferTemplate, isBaseSet, isFunctional } from '../models/rsform-api'; import { inferClass, inferStatus, inferTemplate, isBaseSet, isFunctional } from '../models/rsform-api';
import { extractGlobals, isSimpleExpression, splitTemplateDefinition } from '../models/rslang-api'; import { extractGlobals, isSimpleExpression, splitTemplateDefinition } from '../models/rslang-api';
@ -25,7 +25,7 @@ export class RSFormLoader {
private cstByID = new Map<number, IConstituenta>(); private cstByID = new Map<number, IConstituenta>();
constructor(input: RO<IRSFormDTO>) { constructor(input: RO<IRSFormDTO>) {
this.schema = structuredClone(input) as IRSForm; this.schema = structuredClone(input) as unknown as IRSForm;
this.schema.version = input.version ?? 'latest'; this.schema.version = input.version ?? 'latest';
} }
@ -79,7 +79,7 @@ export class RSFormLoader {
order.forEach(cstID => { order.forEach(cstID => {
const cst = this.cstByID.get(cstID)!; const cst = this.cstByID.get(cstID)!;
cst.schema = this.schema.id; cst.schema = this.schema.id;
cst.status = inferStatus(cst.parse.status, cst.parse.valueClass); cst.status = cst.parse ? inferStatus(cst.parse.status, cst.parse.valueClass) : ExpressionStatus.UNKNOWN;
cst.is_template = inferTemplate(cst.definition_formal); cst.is_template = inferTemplate(cst.definition_formal);
cst.cst_class = inferClass(cst.cst_type, cst.is_template); cst.cst_class = inferClass(cst.cst_type, cst.is_template);
cst.spawn = []; cst.spawn = [];
@ -184,11 +184,20 @@ export class RSFormLoader {
return { return {
count_all: items.length, count_all: items.length,
count_crucial: items.reduce((sum, cst) => sum + (cst.crucial ? 1 : 0), 0), count_crucial: items.reduce((sum, cst) => sum + (cst.crucial ? 1 : 0), 0),
count_errors: items.reduce((sum, cst) => sum + (cst.parse.status === ParsingStatus.INCORRECT ? 1 : 0), 0), count_errors: items.reduce(
count_property: items.reduce((sum, cst) => sum + (cst.parse.valueClass === ValueClass.PROPERTY ? 1 : 0), 0), (sum, cst) => sum + (cst.parse && cst.parse.status === ParsingStatus.INCORRECT ? 1 : 0),
0
),
count_property: items.reduce(
(sum, cst) => sum + (cst.parse && cst.parse.valueClass === ValueClass.PROPERTY ? 1 : 0),
0
),
count_incalculable: items.reduce( count_incalculable: items.reduce(
(sum, cst) => (sum, cst) =>
sum + (cst.parse.status === ParsingStatus.VERIFIED && cst.parse.valueClass === ValueClass.INVALID ? 1 : 0), sum +
(cst.parse && cst.parse.status === ParsingStatus.VERIFIED && cst.parse.valueClass === ValueClass.INVALID
? 1
: 0),
0 0
), ),
count_inherited: items.reduce((sum, cst) => sum + (cst.is_inherited ? 1 : 0), 0), count_inherited: items.reduce((sum, cst) => sum + (cst.is_inherited ? 1 : 0), 0),
@ -204,7 +213,8 @@ export class RSFormLoader {
count_term: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.TERM ? 1 : 0), 0), count_term: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.TERM ? 1 : 0), 0),
count_function: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.FUNCTION ? 1 : 0), 0), count_function: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.FUNCTION ? 1 : 0), 0),
count_predicate: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.PREDICATE ? 1 : 0), 0), count_predicate: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.PREDICATE ? 1 : 0), 0),
count_theorem: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.THEOREM ? 1 : 0), 0) count_theorem: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.THEOREM ? 1 : 0), 0),
count_nominal: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.NOMINAL ? 1 : 0), 0)
}; };
} }
} }

View File

@ -7,6 +7,7 @@ import { errorMsg } from '@/utils/labels';
/** Represents {@link IConstituenta} type. */ /** Represents {@link IConstituenta} type. */
export const CstType = { export const CstType = {
NOMINAL: 'nominal',
BASE: 'basic', BASE: 'basic',
STRUCTURED: 'structure', STRUCTURED: 'structure',
TERM: 'term', TERM: 'term',
@ -276,7 +277,7 @@ export const schemaTokenID = z.enum(TokenID);
export const schemaRSErrorType = z.enum(RSErrorType); export const schemaRSErrorType = z.enum(RSErrorType);
export const schemaConstituentaBasics = z.strictObject({ export const schemaConstituentaBasics = z.strictObject({
id: z.coerce.number(), id: z.number(),
alias: z.string().nonempty(errorMsg.requiredField), alias: z.string().nonempty(errorMsg.requiredField),
convention: z.string(), convention: z.string(),
crucial: z.boolean(), crucial: z.boolean(),
@ -290,31 +291,34 @@ export const schemaConstituentaBasics = z.strictObject({
}); });
export const schemaConstituenta = schemaConstituentaBasics.extend({ export const schemaConstituenta = schemaConstituentaBasics.extend({
parse: z.strictObject({ parse: z
status: schemaParsingStatus, .strictObject({
valueClass: schemaValueClass, status: schemaParsingStatus,
typification: z.string(), valueClass: schemaValueClass,
syntaxTree: z.string(), typification: z.string(),
args: z.array(z.strictObject({ alias: z.string(), typification: z.string() })) syntaxTree: z.string(),
}) args: z.array(z.strictObject({ alias: z.string(), typification: z.string() }))
})
.optional()
}); });
export const schemaRSForm = schemaLibraryItem.extend({ export const schemaRSForm = schemaLibraryItem.extend({
editors: z.array(z.coerce.number()), editors: z.array(z.number()),
version: z.coerce.number().optional(), version: z.number().optional(),
versions: z.array(schemaVersionInfo), versions: z.array(schemaVersionInfo),
items: z.array(schemaConstituenta), items: z.array(schemaConstituenta),
association: z.array(z.strictObject({ container: z.number(), associate: z.number() })),
inheritance: z.array( inheritance: z.array(
z.strictObject({ z.strictObject({
child: z.coerce.number(), child: z.number(),
child_source: z.coerce.number(), child_source: z.number(),
parent: z.coerce.number(), parent: z.number(),
parent_source: z.coerce.number() parent_source: z.number()
}) })
), ),
oss: z.array(z.strictObject({ id: z.coerce.number(), alias: z.string() })) oss: z.array(z.strictObject({ id: z.number(), alias: z.string() }))
}); });
export const schemaVersionCreatedResponse = z.strictObject({ export const schemaVersionCreatedResponse = z.strictObject({

View File

@ -196,6 +196,7 @@ export function colorFgCstStatus(status: ExpressionStatus): string {
export function colorBgCstClass(cstClass: CstClass): string { export function colorBgCstClass(cstClass: CstClass): string {
// prettier-ignore // prettier-ignore
switch (cstClass) { switch (cstClass) {
case CstClass.NOMINAL: return APP_COLORS.bgOrange;
case CstClass.BASIC: return APP_COLORS.bgGreen; case CstClass.BASIC: return APP_COLORS.bgGreen;
case CstClass.DERIVED: return APP_COLORS.bgBlue; case CstClass.DERIVED: return APP_COLORS.bgBlue;
case CstClass.STATEMENT: return APP_COLORS.bgRed; case CstClass.STATEMENT: return APP_COLORS.bgRed;
@ -203,6 +204,15 @@ export function colorBgCstClass(cstClass: CstClass): string {
} }
} }
/** Determines background color for {@link IConstituenta} badge. */
export function colorBgBadge(item: IConstituenta) {
switch (item.cst_class) {
case CstClass.BASIC:
return 'bg-accent-green25';
}
return 'bg-input';
}
/** Determines background color for {@link IConstituenta} depending on its parent schema index. */ /** Determines background color for {@link IConstituenta} depending on its parent schema index. */
export function colorBgSchemas(schema_index: number): string { export function colorBgSchemas(schema_index: number): string {
if (schema_index === 0) { if (schema_index === 0) {

View File

@ -4,8 +4,8 @@ import clsx from 'clsx';
import { globalIDs } from '@/utils/constants'; import { globalIDs } from '@/utils/constants';
import { colorFgCstStatus } from '../colors'; import { colorBgBadge, colorFgCstStatus } from '../colors';
import { CstClass, type IConstituenta } from '../models/rsform'; import { type IConstituenta } from '../models/rsform';
import { useCstTooltipStore } from '../stores/cst-tooltip'; import { useCstTooltipStore } from '../stores/cst-tooltip';
interface BadgeConstituentaProps { interface BadgeConstituentaProps {
@ -29,7 +29,7 @@ export function BadgeConstituenta({ value, prefixID }: BadgeConstituentaProps) {
'cc-badge-constituenta', 'cc-badge-constituenta',
value.is_inherited && 'border-dashed', value.is_inherited && 'border-dashed',
value.crucial && 'cc-badge-inner-shadow', value.crucial && 'cc-badge-inner-shadow',
value.cst_class === CstClass.BASIC ? 'bg-accent-green25' : 'bg-input' colorBgBadge(value)
)} )}
style={{ style={{
borderColor: colorFgCstStatus(value.status), borderColor: colorFgCstStatus(value.status),

View File

@ -1,4 +1,4 @@
import { type DomIconProps } from '@/components/icons'; import { type DomIconProps, IconCstNominal } from '@/components/icons';
import { import {
IconCstAxiom, IconCstAxiom,
IconCstBaseSet, IconCstBaseSet,
@ -15,6 +15,8 @@ import { CstType } from '../backend/types';
/** Icon for constituenta type. */ /** Icon for constituenta type. */
export function IconCstType({ value, size = '1.25rem', className }: DomIconProps<CstType>) { export function IconCstType({ value, size = '1.25rem', className }: DomIconProps<CstType>) {
switch (value) { switch (value) {
case CstType.NOMINAL:
return <IconCstNominal size={size} className={className ?? 'text-primary'} />;
case CstType.BASE: case CstType.BASE:
return <IconCstBaseSet size={size} className={className ?? 'text-constructive'} />; return <IconCstBaseSet size={size} className={className ?? 'text-constructive'} />;
case CstType.CONSTANT: case CstType.CONSTANT:

View File

@ -23,10 +23,12 @@ export function InfoConstituenta({ data, className, ...restProps }: InfoConstitu
{data.term_resolved || data.term_raw} {data.term_resolved || data.term_raw}
</p> </p>
) : null} ) : null}
<p className='break-all'> {data.parse ? (
<b>Типизация: </b> <p className='break-all'>
<span className='font-math'>{labelCstTypification(data)}</span> <b>Типизация: </b>
</p> <span className='font-math'>{labelCstTypification(data)}</span>
</p>
) : null}
{data.definition_formal ? ( {data.definition_formal ? (
<p className='break-all'> <p className='break-all'>
<b>Выражение: </b> <b>Выражение: </b>

View File

@ -6,6 +6,7 @@ import {
IconCstBaseSet, IconCstBaseSet,
IconCstConstSet, IconCstConstSet,
IconCstFunction, IconCstFunction,
IconCstNominal,
IconCstPredicate, IconCstPredicate,
IconCstStructured, IconCstStructured,
IconCstTerm, IconCstTerm,
@ -14,7 +15,6 @@ import {
IconPredecessor, IconPredecessor,
IconStatusError, IconStatusError,
IconStatusIncalculable, IconStatusIncalculable,
IconStatusOK,
IconStatusProperty, IconStatusProperty,
IconTerminology IconTerminology
} from '@/components/icons'; } from '@/components/icons';
@ -48,18 +48,11 @@ export function RSFormStats({ className, stats }: RSFormStatsProps) {
titleHtml='Наследованные' titleHtml='Наследованные'
/> />
<ValueStats
id='count_ok'
title='Корректные'
className='col-start-1'
icon={<IconStatusOK size='1.25rem' />}
value={stats.count_all - stats.count_errors - stats.count_property - stats.count_incalculable}
/>
<ValueStats <ValueStats
id='count_property' id='count_property'
title='Неразмерные' title='Неразмерные'
icon={<IconStatusProperty size='1.25rem' />} icon={<IconStatusProperty size='1.25rem' />}
value={stats.count_errors} value={stats.count_property}
/> />
<ValueStats <ValueStats
id='count_incalculable' id='count_incalculable'
@ -73,6 +66,12 @@ export function RSFormStats({ className, stats }: RSFormStatsProps) {
icon={<IconStatusError size='1.25rem' className={stats.count_errors > 0 ? 'text-destructive' : undefined} />} icon={<IconStatusError size='1.25rem' className={stats.count_errors > 0 ? 'text-destructive' : undefined} />}
value={stats.count_errors} value={stats.count_errors}
/> />
<ValueStats
id='count_nominal'
title='Номеноиды'
icon={<IconCstNominal size='1.25rem' className={stats.count_nominal > 0 ? 'text-destructive' : undefined} />}
value={stats.count_nominal}
/>
<ValueStats <ValueStats
id='count_base' id='count_base'

View File

@ -75,7 +75,7 @@ function describeCstNode(cst: IConstituenta) {
? cst.convention ? cst.convention
: cst.definition_resolved || cst.definition_formal || cst.convention; : cst.definition_resolved || cst.definition_formal || cst.convention;
const typification = labelCstTypification(cst); const typification = labelCstTypification(cst);
return `${cst.alias}: ${cst.term_resolved}</br><b>Типизация:</b> ${typification}</br><b>Содержание:</b> ${ return `${cst.alias}: ${cst.term_resolved}</br>${
contents ? contents : 'отсутствует' cst.parse ? `<b>Типизация:</b> ${typification}</br>` : ''
}`; }<b>Содержание:</b> ${contents ? contents : 'отсутствует'}`;
} }

View File

@ -13,9 +13,9 @@ import { CstType, type ICreateConstituentaDTO } from '../../backend/types';
import { IconCrucialValue } from '../../components/icon-crucial-value'; import { IconCrucialValue } from '../../components/icon-crucial-value';
import { RSInput } from '../../components/rs-input'; import { RSInput } from '../../components/rs-input';
import { SelectCstType } from '../../components/select-cst-type'; import { SelectCstType } from '../../components/select-cst-type';
import { getRSDefinitionPlaceholder } from '../../labels'; import { getRSDefinitionPlaceholder, labelRSExpression } from '../../labels';
import { type IRSForm } from '../../models/rsform'; import { type IRSForm } from '../../models/rsform';
import { generateAlias, isBaseSet, isBasicConcept, isFunctional } from '../../models/rsform-api'; import { generateAlias, isBaseSet, isBasicConcept } from '../../models/rsform-api';
interface FormCreateCstProps { interface FormCreateCstProps {
schema: IRSForm; schema: IRSForm;
@ -33,9 +33,8 @@ export function FormCreateCst({ schema }: FormCreateCstProps) {
const cst_type = useWatch({ control, name: 'cst_type' }); const cst_type = useWatch({ control, name: 'cst_type' });
const convention = useWatch({ control, name: 'convention' }); const convention = useWatch({ control, name: 'convention' });
const crucial = useWatch({ control, name: 'crucial' }); const crucial = useWatch({ control, name: 'crucial' });
const isBasic = isBasicConcept(cst_type); const isBasic = isBasicConcept(cst_type) || cst_type === CstType.NOMINAL;
const isElementary = isBaseSet(cst_type); const isElementary = isBaseSet(cst_type);
const isFunction = isFunctional(cst_type);
const showConvention = !!convention || forceComment || isBasic; const showConvention = !!convention || forceComment || isBasic;
function handleTypeChange(target: CstType) { function handleTypeChange(target: CstType) {
@ -91,13 +90,7 @@ export function FormCreateCst({ schema }: FormCreateCstProps) {
<RSInput <RSInput
id='dlg_cst_expression' id='dlg_cst_expression'
noTooltip noTooltip
label={ label={labelRSExpression(cst_type)}
cst_type === CstType.STRUCTURED
? 'Область определения'
: isFunction
? 'Определение функции'
: 'Формальное определение'
}
placeholder={getRSDefinitionPlaceholder(cst_type)} placeholder={getRSDefinitionPlaceholder(cst_type)}
value={field.value} value={field.value}
onChange={field.onChange} onChange={field.onChange}

View File

@ -37,7 +37,7 @@ export const TemplateState = ({ children }: React.PropsWithChildren) => {
function onChangePrototype(newPrototype: IConstituenta) { function onChangePrototype(newPrototype: IConstituenta) {
setPrototype(newPrototype); setPrototype(newPrototype);
setArguments( setArguments(
newPrototype.parse.args.map(arg => ({ newPrototype.parse!.args.map(arg => ({
alias: arg.alias, alias: arg.alias,
typification: arg.typification, typification: arg.typification,
value: '' value: ''

View File

@ -1,6 +1,9 @@
import { useState } from 'react'; import { useState } from 'react';
import { Controller, useFormContext, useWatch } from 'react-hook-form'; import { Controller, useFormContext, useWatch } from 'react-hook-form';
import { HelpTopic } from '@/features/help';
import { BadgeHelp } from '@/features/help/components/badge-help';
import { MiniButton } from '@/components/control'; import { MiniButton } from '@/components/control';
import { TextArea, TextInput } from '@/components/input'; import { TextArea, TextInput } from '@/components/input';
@ -8,9 +11,9 @@ import { CstType, type IUpdateConstituentaDTO } from '../../backend/types';
import { IconCrucialValue } from '../../components/icon-crucial-value'; import { IconCrucialValue } from '../../components/icon-crucial-value';
import { RSInput } from '../../components/rs-input'; import { RSInput } from '../../components/rs-input';
import { SelectCstType } from '../../components/select-cst-type'; import { SelectCstType } from '../../components/select-cst-type';
import { getRSDefinitionPlaceholder, labelCstTypification } from '../../labels'; import { getRSDefinitionPlaceholder, labelCstTypification, labelRSExpression } from '../../labels';
import { type IConstituenta, type IRSForm } from '../../models/rsform'; import { type IConstituenta, type IRSForm } from '../../models/rsform';
import { generateAlias, isBaseSet, isBasicConcept, isFunctional } from '../../models/rsform-api'; import { generateAlias, isBaseSet, isBasicConcept } from '../../models/rsform-api';
interface FormEditCstProps { interface FormEditCstProps {
schema: IRSForm; schema: IRSForm;
@ -30,9 +33,8 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
const cst_type = useWatch({ control, name: 'item_data.cst_type' }) ?? CstType.BASE; const cst_type = useWatch({ control, name: 'item_data.cst_type' }) ?? CstType.BASE;
const convention = useWatch({ control, name: 'item_data.convention' }); const convention = useWatch({ control, name: 'item_data.convention' });
const crucial = useWatch({ control, name: 'item_data.crucial' }) ?? false; const crucial = useWatch({ control, name: 'item_data.crucial' }) ?? false;
const isBasic = isBasicConcept(cst_type); const isBasic = isBasicConcept(cst_type) || cst_type === CstType.NOMINAL;
const isElementary = isBaseSet(cst_type); const isElementary = isBaseSet(cst_type);
const isFunction = isFunctional(cst_type);
const showConvention = !!convention || forceComment || isBasic; const showConvention = !!convention || forceComment || isBasic;
function handleTypeChange(newValue: CstType) { function handleTypeChange(newValue: CstType) {
@ -67,6 +69,7 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
{...register('item_data.alias')} {...register('item_data.alias')}
error={errors.item_data?.alias} error={errors.item_data?.alias}
/> />
<BadgeHelp topic={HelpTopic.CC_CONSTITUENTA} offset={16} contentClass='sm:max-w-160' />
</div> </div>
<TextArea <TextArea
@ -75,23 +78,26 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
spellCheck spellCheck
label='Термин' label='Термин'
className='max-h-15 disabled:min-h-9' className='max-h-15 disabled:min-h-9'
placeholder='Обозначение для текстовых определений'
{...register('item_data.term_raw')} {...register('item_data.term_raw')}
error={errors.item_data?.term_raw} error={errors.item_data?.term_raw}
/> />
<TextArea {cst_type !== CstType.NOMINAL ? (
id='cst_typification' <TextArea
fitContent id='cst_typification'
dense fitContent
noResize dense
noBorder noResize
noOutline noBorder
transparent noOutline
readOnly transparent
label='Типизация' readOnly
value={labelCstTypification(target)} label='Типизация'
className='cursor-default' value={labelCstTypification(target)}
/> className='cursor-default'
/>
) : null}
<Controller <Controller
control={control} control={control}
@ -101,13 +107,7 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
<RSInput <RSInput
id='dlg_cst_expression' id='dlg_cst_expression'
noTooltip noTooltip
label={ label={labelRSExpression(cst_type)}
cst_type === CstType.STRUCTURED
? 'Область определения'
: isFunction
? 'Определение функции'
: 'Формальное определение'
}
placeholder={getRSDefinitionPlaceholder(cst_type)} placeholder={getRSDefinitionPlaceholder(cst_type)}
className='max-h-15' className='max-h-15'
schema={schema} schema={schema}
@ -131,6 +131,7 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
fitContent fitContent
spellCheck spellCheck
label='Текстовое определение' label='Текстовое определение'
placeholder='Текстовая интерпретация формального выражения'
className='max-h-15' className='max-h-15'
value={field.value} value={field.value}
onChange={field.onChange} onChange={field.onChange}
@ -158,6 +159,7 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
fitContent fitContent
spellCheck spellCheck
label={isBasic ? 'Конвенция' : 'Комментарий'} label={isBasic ? 'Конвенция' : 'Комментарий'}
placeholder={isBasic ? 'Договоренность об интерпретации' : 'Пояснение разработчика'}
className='max-h-20 disabled:min-h-9' className='max-h-20 disabled:min-h-9'
{...register('item_data.convention')} {...register('item_data.convention')}
error={errors.item_data?.convention} error={errors.item_data?.convention}

View File

@ -2,12 +2,14 @@
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import { MiniButton } from '@/components/control';
import { Checkbox } from '@/components/input'; import { Checkbox } from '@/components/input';
import { ModalForm } from '@/components/modal'; import { ModalForm } from '@/components/modal';
import { CstType } from '../backend/types'; import { CstType } from '../backend/types';
import { IconCstType } from '../components/icon-cst-type';
import { labelCstType } from '../labels'; import { labelCstType } from '../labels';
import { type GraphFilterParams, useTermGraphStore } from '../stores/term-graph'; import { cstTypeToFilterKey, type GraphFilterParams, useTermGraphStore } from '../stores/term-graph';
export function DlgGraphParams() { export function DlgGraphParams() {
const params = useTermGraphStore(state => state.filter); const params = useTermGraphStore(state => state.filter);
@ -71,47 +73,32 @@ export function DlgGraphParams() {
/> />
</div> </div>
<div className='flex flex-col gap-1'> <div className='flex flex-col gap-1'>
<h1 className='mb-2'>Типы конституент</h1> <h1 className='mb-1'>Типы конституент</h1>
<Controller <div>
control={control} {Object.values(CstType).map(cstType => {
name='allowBase' const fieldName = cstTypeToFilterKey[cstType];
render={({ field }) => <Checkbox {...field} label={labelCstType(CstType.BASE)} />} return (
/> <Controller
<Controller key={fieldName}
control={control} control={control}
name='allowStruct' name={fieldName}
render={({ field }) => <Checkbox {...field} label={labelCstType(CstType.STRUCTURED)} />} render={({ field }) => (
/> <MiniButton
<Controller onClick={() => field.onChange(!field.value)}
control={control} title={labelCstType(cstType)}
name='allowTerm' icon={
render={({ field }) => <Checkbox {...field} label={labelCstType(CstType.TERM)} />} <IconCstType
/> size='2rem'
<Controller value={cstType}
control={control} className={field.value ? 'text-constructive' : 'text-destructive'}
name='allowAxiom' />
render={({ field }) => <Checkbox {...field} label={labelCstType(CstType.AXIOM)} />} }
/> />
<Controller )}
control={control} />
name='allowFunction' );
render={({ field }) => <Checkbox {...field} label={labelCstType(CstType.FUNCTION)} />} })}
/> </div>
<Controller
control={control}
name='allowPredicate'
render={({ field }) => <Checkbox {...field} label={labelCstType(CstType.PREDICATE)} />}
/>
<Controller
control={control}
name='allowConstant'
render={({ field }) => <Checkbox {...field} label={labelCstType(CstType.CONSTANT)} />}
/>
<Controller
control={control}
name='allowTheorem'
render={({ field }) => <Checkbox {...field} label={labelCstType(CstType.THEOREM)} />}
/>
</div> </div>
</ModalForm> </ModalForm>
); );

View File

@ -18,6 +18,7 @@ import { type GraphColoring } from './stores/term-graph';
// --- Records for label/describe functions --- // --- Records for label/describe functions ---
const labelCstTypeRecord: Record<CstType, string> = { const labelCstTypeRecord: Record<CstType, string> = {
[CstType.NOMINAL]: 'Номеноид',
[CstType.BASE]: 'Базисное множество', [CstType.BASE]: 'Базисное множество',
[CstType.CONSTANT]: 'Константное множество', [CstType.CONSTANT]: 'Константное множество',
[CstType.STRUCTURED]: 'Родовая структура', [CstType.STRUCTURED]: 'Родовая структура',
@ -34,6 +35,7 @@ const labelReferenceTypeRecord: Record<ReferenceType, string> = {
}; };
const labelCstClassRecord: Record<CstClass, string> = { const labelCstClassRecord: Record<CstClass, string> = {
[CstClass.NOMINAL]: 'номинальный',
[CstClass.BASIC]: 'базовый', [CstClass.BASIC]: 'базовый',
[CstClass.DERIVED]: 'производный', [CstClass.DERIVED]: 'производный',
[CstClass.STATEMENT]: 'утверждение', [CstClass.STATEMENT]: 'утверждение',
@ -41,6 +43,7 @@ const labelCstClassRecord: Record<CstClass, string> = {
}; };
const describeCstClassRecord: Record<CstClass, string> = { const describeCstClassRecord: Record<CstClass, string> = {
[CstClass.NOMINAL]: 'номинальная сущность',
[CstClass.BASIC]: 'неопределяемое понятие', [CstClass.BASIC]: 'неопределяемое понятие',
[CstClass.DERIVED]: 'определяемое понятие', [CstClass.DERIVED]: 'определяемое понятие',
[CstClass.STATEMENT]: 'логическое утверждение', [CstClass.STATEMENT]: 'логическое утверждение',
@ -158,15 +161,28 @@ const labelGrammemeRecord: Partial<Record<Grammeme, string>> = {
[Grammeme.Litr]: 'Стиль: литературный' [Grammeme.Litr]: 'Стиль: литературный'
}; };
const labelRSExpressionsRecord: Record<CstType, string> = {
[CstType.NOMINAL]: 'Определяющие конституенты',
[CstType.BASE]: 'Формальное определение',
[CstType.CONSTANT]: 'Формальное определение',
[CstType.STRUCTURED]: 'Область определения',
[CstType.TERM]: 'Формальное определение',
[CstType.THEOREM]: 'Формальное определение',
[CstType.AXIOM]: 'Формальное определение',
[CstType.FUNCTION]: 'Определение функции',
[CstType.PREDICATE]: 'Определение функции'
};
const rsDefinitionPlaceholderRecord: Record<CstType, string> = { const rsDefinitionPlaceholderRecord: Record<CstType, string> = {
[CstType.NOMINAL]: 'Например, X1 D1 N1',
[CstType.BASE]: 'Не предусмотрено',
[CstType.CONSTANT]: 'Не предусмотрено',
[CstType.STRUCTURED]: 'Пример: (X1×D2)', [CstType.STRUCTURED]: 'Пример: (X1×D2)',
[CstType.TERM]: 'Пример: D{ξ∈S1 | Pr1(ξ)∩Pr2(ξ)=∅}', [CstType.TERM]: 'Пример: D{ξ∈S1 | Pr1(ξ)∩Pr2(ξ)=∅}',
[CstType.THEOREM]: 'Пример: D11=∅', [CstType.THEOREM]: 'Пример: D11=∅',
[CstType.AXIOM]: 'Пример: D11=∅', [CstType.AXIOM]: 'Пример: D11=∅',
[CstType.FUNCTION]: 'Пример: [α∈X1, β∈ℬ(X1×X2)] Pr2(Fi1[{α}](β))', [CstType.FUNCTION]: 'Пример: [α∈X1, β∈ℬ(X1×X2)] Pr2(Fi1[{α}](β))',
[CstType.PREDICATE]: 'Пример: [α∈X1, β∈ℬ(X1)] α∈β & card(β)>1', [CstType.PREDICATE]: 'Пример: [α∈X1, β∈ℬ(X1)] α∈β & card(β)>1'
[CstType.CONSTANT]: 'Формальное выражение',
[CstType.BASE]: 'Формальное выражение'
}; };
const cstTypeShortcutKeyRecord: Record<CstType, string> = { const cstTypeShortcutKeyRecord: Record<CstType, string> = {
@ -177,7 +193,8 @@ const cstTypeShortcutKeyRecord: Record<CstType, string> = {
[CstType.FUNCTION]: 'Q', [CstType.FUNCTION]: 'Q',
[CstType.PREDICATE]: 'W', [CstType.PREDICATE]: 'W',
[CstType.CONSTANT]: '5', [CstType.CONSTANT]: '5',
[CstType.THEOREM]: '6' [CstType.THEOREM]: '6',
[CstType.NOMINAL]: '7'
}; };
const labelTokenRecord: Partial<Record<TokenID, string>> = { const labelTokenRecord: Partial<Record<TokenID, string>> = {
@ -331,6 +348,11 @@ export function getCstTypeShortcut(type: CstType) {
return key ? `${labelCstType(type)} [Alt + ${key}]` : labelCstType(type); return key ? `${labelCstType(type)} [Alt + ${key}]` : labelCstType(type);
} }
/** Generates label for RS expression based on {@link CstType}. */
export function labelRSExpression(type: CstType): string {
return labelRSExpressionsRecord[type] ?? 'Формальное выражение';
}
/** Generates placeholder for RS definition based on {@link CstType}. */ /** Generates placeholder for RS definition based on {@link CstType}. */
export function getRSDefinitionPlaceholder(type: CstType): string { export function getRSDefinitionPlaceholder(type: CstType): string {
return rsDefinitionPlaceholderRecord[type] ?? 'Формальное выражение'; return rsDefinitionPlaceholderRecord[type] ?? 'Формальное выражение';
@ -445,6 +467,9 @@ export function labelTypification({
* Generates label for {@link IConstituenta} typification. * Generates label for {@link IConstituenta} typification.
*/ */
export function labelCstTypification(cst: RO<IConstituenta>): string { export function labelCstTypification(cst: RO<IConstituenta>): string {
if (!cst.parse) {
return 'N/A';
}
return labelTypification({ return labelTypification({
isValid: cst.parse.status === ParsingStatus.VERIFIED, isValid: cst.parse.status === ParsingStatus.VERIFIED,
resultType: cst.parse.typification, resultType: cst.parse.typification,

View File

@ -69,6 +69,7 @@ export function produceFilteredGraph(schema: IRSForm, params: GraphFilterParams,
if (params.allowPredicate) result.push(CstType.PREDICATE); if (params.allowPredicate) result.push(CstType.PREDICATE);
if (params.allowConstant) result.push(CstType.CONSTANT); if (params.allowConstant) result.push(CstType.CONSTANT);
if (params.allowTheorem) result.push(CstType.THEOREM); if (params.allowTheorem) result.push(CstType.THEOREM);
if (params.allowNominal) result.push(CstType.NOMINAL);
return result; return result;
})(); })();

View File

@ -95,6 +95,7 @@ export function inferClass(type: CstType, isTemplate: boolean = false): CstClass
} }
// prettier-ignore // prettier-ignore
switch (type) { switch (type) {
case CstType.NOMINAL: return CstClass.NOMINAL;
case CstType.BASE: return CstClass.BASIC; case CstType.BASE: return CstClass.BASIC;
case CstType.CONSTANT: return CstClass.BASIC; case CstType.CONSTANT: return CstClass.BASIC;
case CstType.STRUCTURED: return CstClass.BASIC; case CstType.STRUCTURED: return CstClass.BASIC;
@ -124,6 +125,7 @@ export function applyFilterCategory(start: IConstituenta, schema: IRSForm): ICon
} }
const cstTypePrefixRecord: Record<CstType, string> = { const cstTypePrefixRecord: Record<CstType, string> = {
[CstType.NOMINAL]: 'N',
[CstType.BASE]: 'X', [CstType.BASE]: 'X',
[CstType.CONSTANT]: 'C', [CstType.CONSTANT]: 'C',
[CstType.STRUCTURED]: 'S', [CstType.STRUCTURED]: 'S',
@ -148,6 +150,7 @@ export function guessCstType(hint: string, defaultType: CstType = CstType.TERM):
} }
// prettier-ignore // prettier-ignore
switch (hint) { switch (hint) {
case 'N': return CstType.NOMINAL;
case 'X': return CstType.BASE; case 'X': return CstType.BASE;
case 'C': return CstType.CONSTANT; case 'C': return CstType.CONSTANT;
case 'S': return CstType.STRUCTURED; case 'S': return CstType.STRUCTURED;
@ -166,6 +169,7 @@ export function guessCstType(hint: string, defaultType: CstType = CstType.TERM):
export function isBasicConcept(type: CstType): boolean { export function isBasicConcept(type: CstType): boolean {
// prettier-ignore // prettier-ignore
switch (type) { switch (type) {
case CstType.NOMINAL: return true;
case CstType.BASE: return true; case CstType.BASE: return true;
case CstType.CONSTANT: return true; case CstType.CONSTANT: return true;
case CstType.STRUCTURED: return true; case CstType.STRUCTURED: return true;
@ -183,6 +187,7 @@ export function isBasicConcept(type: CstType): boolean {
export function isBaseSet(type: CstType): boolean { export function isBaseSet(type: CstType): boolean {
// prettier-ignore // prettier-ignore
switch (type) { switch (type) {
case CstType.NOMINAL: return false;
case CstType.BASE: return true; case CstType.BASE: return true;
case CstType.CONSTANT: return true; case CstType.CONSTANT: return true;
case CstType.STRUCTURED: return false; case CstType.STRUCTURED: return false;
@ -200,6 +205,7 @@ export function isBaseSet(type: CstType): boolean {
export function isFunctional(type: CstType): boolean { export function isFunctional(type: CstType): boolean {
// prettier-ignore // prettier-ignore
switch (type) { switch (type) {
case CstType.NOMINAL: return false;
case CstType.BASE: return false; case CstType.BASE: return false;
case CstType.CONSTANT: return false; case CstType.CONSTANT: return false;
case CstType.STRUCTURED: return false; case CstType.STRUCTURED: return false;
@ -215,7 +221,13 @@ export function isFunctional(type: CstType): boolean {
* Evaluate if {@link IConstituenta} can be used produce structure. * Evaluate if {@link IConstituenta} can be used produce structure.
*/ */
export function canProduceStructure(cst: RO<IConstituenta>): boolean { export function canProduceStructure(cst: RO<IConstituenta>): boolean {
return !!cst.parse.typification && cst.cst_type !== CstType.BASE && cst.cst_type !== CstType.CONSTANT; return (
!!cst.parse &&
!!cst.parse.typification &&
cst.cst_type !== CstType.BASE &&
cst.cst_type !== CstType.CONSTANT &&
cst.cst_type !== CstType.NOMINAL
);
} }
/** /**

View File

@ -20,6 +20,7 @@ export const CATEGORY_CST_TYPE = CstType.THEOREM;
/** Represents Constituenta classification in terms of system of concepts. */ /** Represents Constituenta classification in terms of system of concepts. */
export const CstClass = { export const CstClass = {
NOMINAL: 'nominal',
BASIC: 'basic', BASIC: 'basic',
DERIVED: 'derived', DERIVED: 'derived',
STATEMENT: 'statement', STATEMENT: 'statement',
@ -58,7 +59,7 @@ export interface IConstituenta {
term_resolved: string; term_resolved: string;
term_forms: TermForm[]; term_forms: TermForm[];
parse: { parse?: {
status: ParsingStatus; status: ParsingStatus;
valueClass: ValueClass; valueClass: ValueClass;
typification: string; typification: string;
@ -122,6 +123,7 @@ export interface IRSFormStats {
count_function: number; count_function: number;
count_predicate: number; count_predicate: number;
count_theorem: number; count_theorem: number;
count_nominal: number;
} }
/** Represents inheritance data for {@link IRSForm}. */ /** Represents inheritance data for {@link IRSForm}. */

View File

@ -30,9 +30,14 @@ import {
import { useMutatingRSForm } from '../../../backend/use-mutating-rsform'; import { useMutatingRSForm } from '../../../backend/use-mutating-rsform';
import { useUpdateConstituenta } from '../../../backend/use-update-constituenta'; import { useUpdateConstituenta } from '../../../backend/use-update-constituenta';
import { RefsInput } from '../../../components/refs-input'; import { RefsInput } from '../../../components/refs-input';
import { getRSDefinitionPlaceholder, labelCstTypification, labelTypification } from '../../../labels'; import {
getRSDefinitionPlaceholder,
labelCstTypification,
labelRSExpression,
labelTypification
} from '../../../labels';
import { type IConstituenta, type IRSForm } from '../../../models/rsform'; import { type IConstituenta, type IRSForm } from '../../../models/rsform';
import { isBaseSet, isBasicConcept, isFunctional } from '../../../models/rsform-api'; import { isBaseSet, isBasicConcept } from '../../../models/rsform-api';
import { EditorRSExpression } from '../editor-rsexpression'; import { EditorRSExpression } from '../editor-rsexpression';
interface FormConstituentaProps { interface FormConstituentaProps {
@ -90,15 +95,18 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
); );
const typeInfo = useMemo( const typeInfo = useMemo(
() => ({ () =>
alias: activeCst.alias, activeCst.parse
result: localParse ? localParse.typification : activeCst.parse.typification, ? {
args: localParse ? localParse.args : activeCst.parse.args alias: activeCst.alias,
}), result: localParse ? localParse.typification : activeCst.parse.typification,
args: localParse ? localParse.args : activeCst.parse.args
}
: null,
[activeCst, localParse] [activeCst, localParse]
); );
const isBasic = isBasicConcept(activeCst.cst_type); const isBasic = isBasicConcept(activeCst.cst_type) || activeCst.cst_type === CstType.NOMINAL;
const isElementary = isBaseSet(activeCst.cst_type); const isElementary = isBaseSet(activeCst.cst_type);
const showConvention = !!activeCst.convention || forceComment || isBasic; const showConvention = !!activeCst.convention || forceComment || isBasic;
@ -140,7 +148,11 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
} }
function handleTypeGraph(event: React.MouseEvent<Element>) { function handleTypeGraph(event: React.MouseEvent<Element>) {
if ((localParse && !localParse.parseResult) || activeCst.parse.status !== ParsingStatus.VERIFIED) { if (
(localParse && !localParse.parseResult) ||
!activeCst.parse ||
activeCst.parse.status !== ParsingStatus.VERIFIED
) {
toast.error(errorMsg.typeStructureFailed); toast.error(errorMsg.typeStructureFailed);
return; return;
} }
@ -227,19 +239,21 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
)} )}
/> />
<TextArea {activeCst.cst_type !== CstType.NOMINAL ? (
id='cst_typification' <TextArea
fitContent id='cst_typification'
dense fitContent
noResize dense
noBorder noResize
noOutline noBorder
transparent noOutline
readOnly transparent
label='Типизация' readOnly
value={typification} label='Типизация'
className='cursor-default' value={typification}
/> className='cursor-default'
/>
) : null}
{!!activeCst.definition_formal || !isElementary ? ( {!!activeCst.definition_formal || !isElementary ? (
<Controller <Controller
@ -248,13 +262,7 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
render={({ field }) => ( render={({ field }) => (
<EditorRSExpression <EditorRSExpression
id='cst_expression' id='cst_expression'
label={ label={labelRSExpression(activeCst.cst_type)}
activeCst.cst_type === CstType.STRUCTURED
? 'Область определения'
: isFunctional(activeCst.cst_type)
? 'Определение функции'
: 'Формальное определение'
}
placeholder={disabled ? '' : getRSDefinitionPlaceholder(activeCst.cst_type)} placeholder={disabled ? '' : getRSDefinitionPlaceholder(activeCst.cst_type)}
value={field.value ?? ''} value={field.value ?? ''}
activeCst={activeCst} activeCst={activeCst}

View File

@ -155,6 +155,21 @@ export function EditorRSExpression({
} }
} }
if (!activeCst.parse) {
return (
<RSInput
value={value}
schema={schema}
minHeight='3.75rem'
maxHeight='8rem'
onChange={handleChange}
onOpenEdit={onOpenEdit}
disabled={disabled}
{...restProps}
/>
);
}
return ( return (
<div className='relative'> <div className='relative'>
<ToolbarRSExpression <ToolbarRSExpression

View File

@ -30,7 +30,7 @@ interface StatusBarProps {
export function StatusBar({ className, isModified, processing, activeCst, parseData, onAnalyze }: StatusBarProps) { export function StatusBar({ className, isModified, processing, activeCst, parseData, onAnalyze }: StatusBarProps) {
const status = (() => { const status = (() => {
if (isModified) { if (isModified || !activeCst.parse) {
return ExpressionStatus.UNKNOWN; return ExpressionStatus.UNKNOWN;
} }
if (parseData) { if (parseData) {

View File

@ -96,6 +96,7 @@ export function EditorRSList() {
case 'KeyW': createCst(CstType.PREDICATE, true); return true; case 'KeyW': createCst(CstType.PREDICATE, true); return true;
case 'Digit5': createCst(CstType.CONSTANT, true); return true; case 'Digit5': createCst(CstType.CONSTANT, true); return true;
case 'Digit6': createCst(CstType.THEOREM, true); return true; case 'Digit6': createCst(CstType.THEOREM, true); return true;
case 'Digit7': createCst(CstType.NOMINAL, true); return true;
} }
return false; return false;
} }

View File

@ -63,11 +63,13 @@ export function ToolbarTermGraph({ className }: ToolbarTermGraphProps) {
const { addSelectedNodes } = store.getState(); const { addSelectedNodes } = store.getState();
function handleShowTypeGraph() { function handleShowTypeGraph() {
const typeInfo = schema.items.map(item => ({ const typeInfo = schema.items
alias: item.alias, .filter(item => !!item.parse)
result: item.parse.typification, .map(item => ({
args: item.parse.args alias: item.alias,
})); result: item.parse!.typification,
args: item.parse!.args
}));
showTypeGraph({ items: typeInfo }); showTypeGraph({ items: typeInfo });
} }

View File

@ -1,6 +1,8 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { persist } from 'zustand/middleware'; import { persist } from 'zustand/middleware';
import { CstType } from '../backend/types';
export const graphColorings = ['none', 'status', 'type', 'schemas'] as const; export const graphColorings = ['none', 'status', 'type', 'schemas'] as const;
/** /**
@ -29,8 +31,21 @@ export interface GraphFilterParams {
allowPredicate: boolean; allowPredicate: boolean;
allowConstant: boolean; allowConstant: boolean;
allowTheorem: boolean; allowTheorem: boolean;
allowNominal: boolean;
} }
export const cstTypeToFilterKey: Record<CstType, keyof GraphFilterParams> = {
[CstType.BASE]: 'allowBase',
[CstType.STRUCTURED]: 'allowStruct',
[CstType.TERM]: 'allowTerm',
[CstType.AXIOM]: 'allowAxiom',
[CstType.FUNCTION]: 'allowFunction',
[CstType.PREDICATE]: 'allowPredicate',
[CstType.CONSTANT]: 'allowConstant',
[CstType.THEOREM]: 'allowTheorem',
[CstType.NOMINAL]: 'allowNominal'
};
interface TermGraphStore { interface TermGraphStore {
filter: GraphFilterParams; filter: GraphFilterParams;
setFilter: (value: GraphFilterParams) => void; setFilter: (value: GraphFilterParams) => void;
@ -66,7 +81,8 @@ export const useTermGraphStore = create<TermGraphStore>()(
allowFunction: true, allowFunction: true,
allowPredicate: true, allowPredicate: true,
allowConstant: true, allowConstant: true,
allowTheorem: true allowTheorem: true,
allowNominal: true
}, },
setFilter: value => set({ filter: value }), setFilter: value => set({ filter: value }),
toggleFocusInputs: () => toggleFocusInputs: () =>

View File

@ -17,7 +17,7 @@ export type IUpdateProfileDTO = z.infer<typeof schemaUpdateProfile>;
// ========= SCHEMAS ======== // ========= SCHEMAS ========
export const schemaUser = z.strictObject({ export const schemaUser = z.strictObject({
id: z.coerce.number(), id: z.number(),
username: z.string().nonempty(errorMsg.requiredField), username: z.string().nonempty(errorMsg.requiredField),
is_staff: z.boolean(), is_staff: z.boolean(),
email: z.email(errorMsg.emailField), email: z.email(errorMsg.emailField),