From c63144e78be32977230170c4b6b46b43a37a6324 Mon Sep 17 00:00:00 2001
From: Ivan <8611739+IRBorisov@users.noreply.github.com>
Date: Tue, 12 Aug 2025 15:40:39 +0300
Subject: [PATCH] F: Implementing Nominal cst_type
---
.vscode/settings.json | 2 +
.../apps/rsform/serializers/data_access.py | 17 +-
.../apps/rsform/serializers/io_pyconcept.py | 6 +-
.../rsform/tests/s_views/t_constituenta.py | 156 ++++++++++++------
.../apps/rsform/tests/s_views/t_rsforms.py | 48 ------
rsconcept/frontend/src/components/icons.tsx | 5 +-
.../src/features/ai/models/prompting-api.ts | 6 +-
.../features/help/items/help-thesaurus.tsx | 6 +
.../src/features/library/backend/types.ts | 12 +-
.../src/features/oss/models/oss-api.ts | 18 +-
.../side-panel/toolbar-schema.tsx | 12 +-
.../features/rsform/backend/rsform-loader.ts | 24 ++-
.../src/features/rsform/backend/types.ts | 34 ++--
.../frontend/src/features/rsform/colors.ts | 10 ++
.../rsform/components/badge-constituenta.tsx | 6 +-
.../rsform/components/icon-cst-type.tsx | 4 +-
.../rsform/components/info-constituenta.tsx | 10 +-
.../rsform/components/rsform-stats.tsx | 17 +-
.../components/term-graph/graph/tg-node.tsx | 6 +-
.../dlg-create-cst/form-create-cst.tsx | 15 +-
.../dlg-cst-template/template-state.tsx | 2 +-
.../dialogs/dlg-edit-cst/form-edit-cst.tsx | 50 +++---
.../rsform/dialogs/dlg-graph-params.tsx | 71 ++++----
.../frontend/src/features/rsform/labels.ts | 33 +++-
.../src/features/rsform/models/graph-api.ts | 1 +
.../src/features/rsform/models/rsform-api.ts | 14 +-
.../src/features/rsform/models/rsform.ts | 4 +-
.../editor-constituenta/form-constituenta.tsx | 66 ++++----
.../editor-rsexpression.tsx | 15 ++
.../editor-rsexpression/status-bar.tsx | 2 +-
.../editor-rslist/editor-rslist.tsx | 1 +
.../editor-term-graph/toolbar-term-graph.tsx | 12 +-
.../src/features/rsform/stores/term-graph.ts | 18 +-
.../src/features/users/backend/types.ts | 2 +-
34 files changed, 412 insertions(+), 293 deletions(-)
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 1885f80d..b5b0b321 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -216,6 +216,8 @@
"неинтерпретируемый",
"неитерируемого",
"Никанорова",
+ "Номеноид",
+ "Номеноиды",
"операционализации",
"операционализированных",
"Оргтеор",
diff --git a/rsconcept/backend/apps/rsform/serializers/data_access.py b/rsconcept/backend/apps/rsform/serializers/data_access.py
index 1b75f83f..e20e5c5c 100644
--- a/rsconcept/backend/apps/rsform/serializers/data_access.py
+++ b/rsconcept/backend/apps/rsform/serializers/data_access.py
@@ -27,7 +27,7 @@ class AssociationSerializer(StrictModelSerializer):
class Meta:
''' serializer metadata. '''
model = Association
- fields = ('argument', 'operation')
+ fields = ('container', 'associate')
class CstBaseSerializer(StrictModelSerializer):
@@ -121,11 +121,6 @@ class CstCreateSerializer(StrictModelSerializer):
)
alias = serializers.CharField(max_length=8)
cst_type = serializers.ChoiceField(CstType.choices)
- associations = PKField(
- many=True,
- required=False,
- queryset=Constituenta.objects.all().only('schema_id', 'pk')
- )
class Meta:
''' serializer metadata. '''
@@ -133,7 +128,7 @@ class CstCreateSerializer(StrictModelSerializer):
fields = \
'alias', 'cst_type', 'convention', 'crucial', \
'term_raw', 'definition_raw', 'definition_formal', \
- 'insert_after', 'term_forms', 'associations'
+ 'insert_after', 'term_forms'
def validate(self, attrs):
schema = cast(LibraryItem, self.context['schema'])
@@ -142,12 +137,6 @@ class CstCreateSerializer(StrictModelSerializer):
raise serializers.ValidationError({
'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
@@ -322,6 +311,8 @@ class RSFormParseSerializer(StrictModelSerializer):
def _parse_data(self, data: dict) -> dict:
parse = PyConceptAdapter(data).parse()
for cst_data in data['items']:
+ if cst_data['cst_type'] == CstType.NOMINAL:
+ continue
cst_data['parse'] = next(
cst['parse'] for cst in parse['items']
if cst['id'] == cst_data['id']
diff --git a/rsconcept/backend/apps/rsform/serializers/io_pyconcept.py b/rsconcept/backend/apps/rsform/serializers/io_pyconcept.py
index 47e07857..1966538c 100644
--- a/rsconcept/backend/apps/rsform/serializers/io_pyconcept.py
+++ b/rsconcept/backend/apps/rsform/serializers/io_pyconcept.py
@@ -6,7 +6,7 @@ import pyconcept
from shared import messages as msg
-from ..models import Constituenta
+from ..models import Constituenta, CstType
class PyConceptAdapter:
@@ -34,7 +34,7 @@ class PyConceptAdapter:
result: dict = {
'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:
result['items'].append({
'entityUID': cst.pk,
@@ -51,6 +51,8 @@ class PyConceptAdapter:
'items': []
}
for cst in data['items']:
+ if cst['cst_type'] == CstType.NOMINAL:
+ continue
result['items'].append({
'entityUID': cst['id'],
'cstType': cst['cst_type'],
diff --git a/rsconcept/backend/apps/rsform/tests/s_views/t_constituenta.py b/rsconcept/backend/apps/rsform/tests/s_views/t_constituenta.py
index a0f97f03..e3217730 100644
--- a/rsconcept/backend/apps/rsform/tests/s_views/t_constituenta.py
+++ b/rsconcept/backend/apps/rsform/tests/s_views/t_constituenta.py
@@ -12,7 +12,7 @@ class TestConstituentaAPI(EndpointTester):
self.owned_id = self.owned.model.pk
self.unowned = RSForm.create(title='Test2', alias='T2')
self.unowned_id = self.unowned.model.pk
- self.cst1 = Constituenta.objects.create(
+ self.x1 = Constituenta.objects.create(
alias='X1',
cst_type=CstType.BASE,
schema=self.owned.model,
@@ -21,119 +21,175 @@ class TestConstituentaAPI(EndpointTester):
term_raw='Test1',
term_resolved='Test1R',
term_forms=[{'text': 'form1', 'tags': 'sing,datv'}])
- self.cst2 = Constituenta.objects.create(
+ self.x2 = Constituenta.objects.create(
alias='X2',
cst_type=CstType.BASE,
- schema=self.unowned.model,
- order=0,
+ schema=self.owned.model,
+ order=1,
convention='Test1',
term_raw='Test2',
term_resolved='Test2R'
)
- self.cst3 = Constituenta.objects.create(
+ self.x3 = Constituenta.objects.create(
alias='X3',
schema=self.owned.model,
- order=1,
+ order=2,
term_raw='Test3',
term_resolved='Test3',
definition_raw='Test1',
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):
- data = {'target': self.cst1.pk, 'item_data': {'convention': 'tt'}}
- self.executeForbidden(data, schema=self.unowned_id)
+ data = {'target': self.x1.pk, 'item_data': {'convention': 'tt'}}
+ self.executeForbidden(data, item=self.unowned_id)
self.logout()
- self.executeForbidden(data, schema=self.owned_id)
+ self.executeForbidden(data, item=self.owned_id)
self.login()
self.executeOK(data)
- self.cst1.refresh_from_db()
- self.assertEqual(self.cst1.convention, 'tt')
+ self.x1.refresh_from_db()
+ self.assertEqual(self.x1.convention, 'tt')
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):
- data = {'target': self.cst1.pk, 'item_data': {'alias': self.cst3.alias}}
- self.executeBadData(data, schema=self.owned_id)
+ data = {'target': self.x1.pk, 'item_data': {'alias': self.x3.alias}}
+ self.executeBadData(data, item=self.owned_id)
d1 = self.owned.insert_last(
alias='D1',
term_raw='@{X1|plur}',
definition_formal='X1'
)
- self.assertEqual(self.cst1.order, 0)
- self.assertEqual(self.cst1.alias, 'X1')
- self.assertEqual(self.cst1.cst_type, CstType.BASE)
+ self.assertEqual(self.x1.order, 0)
+ self.assertEqual(self.x1.alias, 'X1')
+ self.assertEqual(self.x1.cst_type, CstType.BASE)
- data = {'target': self.cst1.pk, 'item_data': {'alias': 'D2', 'cst_type': CstType.TERM}}
- self.executeOK(data, schema=self.owned_id)
+ data = {'target': self.x1.pk, 'item_data': {'alias': 'D2', 'cst_type': CstType.TERM}}
+ self.executeOK(data, item=self.owned_id)
d1.refresh_from_db()
- self.cst1.refresh_from_db()
+ self.x1.refresh_from_db()
self.assertEqual(d1.term_resolved, '')
self.assertEqual(d1.term_raw, '@{D2|plur}')
- self.assertEqual(self.cst1.order, 0)
- self.assertEqual(self.cst1.alias, 'D2')
- self.assertEqual(self.cst1.cst_type, CstType.TERM)
+ self.assertEqual(self.x1.order, 0)
+ self.assertEqual(self.x1.alias, 'D2')
+ 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):
data = {
- 'target': self.cst3.pk,
+ 'target': self.x3.pk,
'item_data': {
'term_raw': 'New term',
'definition_raw': 'New def'
}
}
- self.executeOK(data, schema=self.owned_id)
- self.cst3.refresh_from_db()
- self.assertEqual(self.cst3.term_resolved, 'New term')
- self.assertEqual(self.cst3.definition_resolved, 'New def')
+ self.executeOK(data, item=self.owned_id)
+ self.x3.refresh_from_db()
+ self.assertEqual(self.x3.term_resolved, 'New term')
+ 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):
data = {
- 'target': self.cst3.pk,
+ 'target': self.x3.pk,
'item_data': {
'term_raw': '@{X1|nomn,sing}',
'definition_raw': '@{X1|nomn,sing} @{X1|sing,datv}'
}
}
- self.executeOK(data, schema=self.owned_id)
- self.cst3.refresh_from_db()
- self.assertEqual(self.cst3.term_resolved, self.cst1.term_resolved)
- self.assertEqual(self.cst3.definition_resolved, f'{self.cst1.term_resolved} form1')
+ self.executeOK(data, item=self.owned_id)
+ self.x3.refresh_from_db()
+ self.assertEqual(self.x3.term_resolved, self.x1.term_resolved)
+ 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):
data = {
- 'target': self.cst3.pk,
+ 'target': self.x3.pk,
'item_data': {
'definition_raw': '@{X3|sing,datv}',
'term_forms': [{'text': 'form1', 'tags': 'sing,datv'}]
}
}
- self.executeOK(data, schema=self.owned_id)
- self.cst3.refresh_from_db()
- self.assertEqual(self.cst3.definition_resolved, 'form1')
- self.assertEqual(self.cst3.term_forms, data['item_data']['term_forms'])
+ self.executeOK(data, item=self.owned_id)
+ self.x3.refresh_from_db()
+ self.assertEqual(self.x3.definition_resolved, 'form1')
+ 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):
- data = {'target': [self.cst1.pk], 'value': True}
- self.executeForbidden(data, schema=self.unowned_id)
+ data = {'target': [self.x1.pk], 'value': True}
+ self.executeForbidden(data, item=self.unowned_id)
self.logout()
- self.executeForbidden(data, schema=self.owned_id)
+ self.executeForbidden(data, item=self.owned_id)
self.login()
- self.executeOK(data, schema=self.owned_id)
- self.cst1.refresh_from_db()
- self.assertEqual(self.cst1.crucial, True)
+ self.executeOK(data, item=self.owned_id)
+ self.x1.refresh_from_db()
+ 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)
diff --git a/rsconcept/backend/apps/rsform/tests/s_views/t_rsforms.py b/rsconcept/backend/apps/rsform/tests/s_views/t_rsforms.py
index a054581c..388a2681 100644
--- a/rsconcept/backend/apps/rsform/tests/s_views/t_rsforms.py
+++ b/rsconcept/backend/apps/rsform/tests/s_views/t_rsforms.py
@@ -200,54 +200,6 @@ class TestRSFormViewset(EndpointTester):
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')
def test_substitute_multiple(self):
self.set_params(item=self.owned_id)
diff --git a/rsconcept/frontend/src/components/icons.tsx b/rsconcept/frontend/src/components/icons.tsx
index e8164574..c0fdf7ef 100644
--- a/rsconcept/frontend/src/components/icons.tsx
+++ b/rsconcept/frontend/src/components/icons.tsx
@@ -12,7 +12,7 @@ export { BiX as IconRemove } from 'react-icons/bi';
export { BiTrash as IconDestroy } from 'react-icons/bi';
export { BiReset as IconReset } from 'react-icons/bi';
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 { BiSearchAlt2 as IconSearch } 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 { TbAssembly as IconRSFormOwned } 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 { TbHexagonLetterC as IconCstConstSet } 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 { RiShieldKeyholeLine as IconPrivate } from 'react-icons/ri';
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 { BiStopCircle as IconStatusIncalculable } from 'react-icons/bi';
export { BiPauseCircle as IconStatusProperty } from 'react-icons/bi';
diff --git a/rsconcept/frontend/src/features/ai/models/prompting-api.ts b/rsconcept/frontend/src/features/ai/models/prompting-api.ts
index 3c2eb3be..fe4333e6 100644
--- a/rsconcept/frontend/src/features/ai/models/prompting-api.ts
+++ b/rsconcept/frontend/src/features/ai/models/prompting-api.ts
@@ -89,7 +89,9 @@ export function varSchemaGraph(schema: IRSForm): string {
/** Generates a prompt for a schema type graph variable. */
export function varSchemaTypeGraph(schema: IRSForm): string {
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`;
result += `[${schema.alias}] Описание: "${schema.description}"\n\n`;
@@ -154,6 +156,6 @@ export function varSyntaxTree(cst: IConstituenta): string {
let result = `Конституента: ${cst.alias}\n`;
result += `Формальное выражение: ${cst.definition_formal}\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;
}
diff --git a/rsconcept/frontend/src/features/help/items/help-thesaurus.tsx b/rsconcept/frontend/src/features/help/items/help-thesaurus.tsx
index 1f88b453..fe8c504f 100644
--- a/rsconcept/frontend/src/features/help/items/help-thesaurus.tsx
+++ b/rsconcept/frontend/src/features/help/items/help-thesaurus.tsx
@@ -7,6 +7,7 @@ import {
IconCstBaseSet,
IconCstConstSet,
IconCstFunction,
+ IconCstNominal,
IconCstPredicate,
IconCstStructured,
IconCstTerm,
@@ -118,6 +119,11 @@ export function HelpThesaurus() {
Типы конституент
+ -
+
+ {'\u2009'}Номеноид (N#) – предметная сущность, не имеющая четкого определения, используемая для ассоциативной
+ группировки конституент и предварительной фиксации содержательных отношений.
+
-
{'\u2009'}Базисное множество (X#) – неопределяемое понятие, представленное множеством различимых элементов.
diff --git a/rsconcept/frontend/src/features/library/backend/types.ts b/rsconcept/frontend/src/features/library/backend/types.ts
index 4c795222..1808d2f4 100644
--- a/rsconcept/frontend/src/features/library/backend/types.ts
+++ b/rsconcept/frontend/src/features/library/backend/types.ts
@@ -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 schemaLibraryItem = z.strictObject({
- id: z.coerce.number(),
+ id: z.number(),
item_type: schemaLibraryItemType,
alias: z.string().nonempty(),
@@ -72,9 +72,9 @@ export const schemaLibraryItem = z.strictObject({
location: z.string(),
access_policy: schemaAccessPolicy,
- time_create: z.string().datetime({ offset: true }),
- time_update: z.string().datetime({ offset: true }),
- owner: z.coerce.number().nullable()
+ time_create: z.iso.datetime({ offset: true }),
+ time_update: z.iso.datetime({ offset: true }),
+ owner: z.number().nullable()
});
export const schemaLibraryItemArray = z.array(schemaLibraryItem);
@@ -126,10 +126,10 @@ export const schemaUpdateLibraryItem = schemaInputLibraryItem
});
export const schemaVersionInfo = z.strictObject({
- id: z.coerce.number(),
+ id: z.number(),
version: z.string(),
description: z.string(),
- time_create: z.string().datetime({ offset: true })
+ time_create: z.iso.datetime({ offset: true })
});
const schemaVersionInput = z.strictObject({
diff --git a/rsconcept/frontend/src/features/oss/models/oss-api.ts b/rsconcept/frontend/src/features/oss/models/oss-api.ts
index df3ae5da..2c57c568 100644
--- a/rsconcept/frontend/src/features/oss/models/oss-api.ts
+++ b/rsconcept/frontend/src/features/oss/models/oss-api.ts
@@ -175,7 +175,11 @@ export class SubstitutionValidator {
if (!original || !substitution) {
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]);
}
switch (substitution.cst_type) {
@@ -247,6 +251,9 @@ export class SubstitutionValidator {
continue;
}
graph.addNode(cst.id);
+ if (!cst.parse) {
+ continue;
+ }
const parents = extractGlobals(cst.parse.typification);
for (const arg of cst.parse.args) {
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(
applyAliasMapping(original.parse.typification, baseMappings.get(original.schema)!),
typeMappings
@@ -365,7 +379,7 @@ export class SubstitutionValidator {
const substitution = this.cstByID.get(item.substitution)!;
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];
} else {
substitutionText = applyAliasMapping(substitution.parse.typification, baseMappings.get(substitution.schema)!);
diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/toolbar-schema.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/toolbar-schema.tsx
index 15a9c056..67d25681 100644
--- a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/toolbar-schema.tsx
+++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/toolbar-schema.tsx
@@ -181,11 +181,13 @@ export function ToolbarSchema({
}
function handleShowTypeGraph() {
- const typeInfo = schema.items.map(item => ({
- alias: item.alias,
- result: item.parse.typification,
- args: item.parse.args
- }));
+ const typeInfo = schema.items
+ .filter(item => !!item.parse)
+ .map(item => ({
+ alias: item.alias,
+ result: item.parse!.typification,
+ args: item.parse!.args
+ }));
showTypeGraph({ items: typeInfo });
}
diff --git a/rsconcept/frontend/src/features/rsform/backend/rsform-loader.ts b/rsconcept/frontend/src/features/rsform/backend/rsform-loader.ts
index 50f5eae5..a1420c2a 100644
--- a/rsconcept/frontend/src/features/rsform/backend/rsform-loader.ts
+++ b/rsconcept/frontend/src/features/rsform/backend/rsform-loader.ts
@@ -5,7 +5,7 @@
import { Graph } from '@/models/graph';
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 { extractGlobals, isSimpleExpression, splitTemplateDefinition } from '../models/rslang-api';
@@ -25,7 +25,7 @@ export class RSFormLoader {
private cstByID = new Map();
constructor(input: RO) {
- this.schema = structuredClone(input) as IRSForm;
+ this.schema = structuredClone(input) as unknown as IRSForm;
this.schema.version = input.version ?? 'latest';
}
@@ -79,7 +79,7 @@ export class RSFormLoader {
order.forEach(cstID => {
const cst = this.cstByID.get(cstID)!;
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.cst_class = inferClass(cst.cst_type, cst.is_template);
cst.spawn = [];
@@ -184,11 +184,20 @@ export class RSFormLoader {
return {
count_all: items.length,
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_property: items.reduce((sum, cst) => sum + (cst.parse.valueClass === ValueClass.PROPERTY ? 1 : 0), 0),
+ count_errors: items.reduce(
+ (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(
(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
),
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_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_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)
};
}
}
diff --git a/rsconcept/frontend/src/features/rsform/backend/types.ts b/rsconcept/frontend/src/features/rsform/backend/types.ts
index 80f477a7..073154a0 100644
--- a/rsconcept/frontend/src/features/rsform/backend/types.ts
+++ b/rsconcept/frontend/src/features/rsform/backend/types.ts
@@ -7,6 +7,7 @@ import { errorMsg } from '@/utils/labels';
/** Represents {@link IConstituenta} type. */
export const CstType = {
+ NOMINAL: 'nominal',
BASE: 'basic',
STRUCTURED: 'structure',
TERM: 'term',
@@ -276,7 +277,7 @@ export const schemaTokenID = z.enum(TokenID);
export const schemaRSErrorType = z.enum(RSErrorType);
export const schemaConstituentaBasics = z.strictObject({
- id: z.coerce.number(),
+ id: z.number(),
alias: z.string().nonempty(errorMsg.requiredField),
convention: z.string(),
crucial: z.boolean(),
@@ -290,31 +291,34 @@ export const schemaConstituentaBasics = z.strictObject({
});
export const schemaConstituenta = schemaConstituentaBasics.extend({
- parse: z.strictObject({
- status: schemaParsingStatus,
- valueClass: schemaValueClass,
- typification: z.string(),
- syntaxTree: z.string(),
- args: z.array(z.strictObject({ alias: z.string(), typification: z.string() }))
- })
+ parse: z
+ .strictObject({
+ status: schemaParsingStatus,
+ valueClass: schemaValueClass,
+ typification: z.string(),
+ syntaxTree: z.string(),
+ args: z.array(z.strictObject({ alias: z.string(), typification: z.string() }))
+ })
+ .optional()
});
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),
items: z.array(schemaConstituenta),
+ association: z.array(z.strictObject({ container: z.number(), associate: z.number() })),
inheritance: z.array(
z.strictObject({
- child: z.coerce.number(),
- child_source: z.coerce.number(),
- parent: z.coerce.number(),
- parent_source: z.coerce.number()
+ child: z.number(),
+ child_source: z.number(),
+ parent: z.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({
diff --git a/rsconcept/frontend/src/features/rsform/colors.ts b/rsconcept/frontend/src/features/rsform/colors.ts
index 505d8ba5..7c01d555 100644
--- a/rsconcept/frontend/src/features/rsform/colors.ts
+++ b/rsconcept/frontend/src/features/rsform/colors.ts
@@ -196,6 +196,7 @@ export function colorFgCstStatus(status: ExpressionStatus): string {
export function colorBgCstClass(cstClass: CstClass): string {
// prettier-ignore
switch (cstClass) {
+ case CstClass.NOMINAL: return APP_COLORS.bgOrange;
case CstClass.BASIC: return APP_COLORS.bgGreen;
case CstClass.DERIVED: return APP_COLORS.bgBlue;
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. */
export function colorBgSchemas(schema_index: number): string {
if (schema_index === 0) {
diff --git a/rsconcept/frontend/src/features/rsform/components/badge-constituenta.tsx b/rsconcept/frontend/src/features/rsform/components/badge-constituenta.tsx
index 2370d229..871f8a84 100644
--- a/rsconcept/frontend/src/features/rsform/components/badge-constituenta.tsx
+++ b/rsconcept/frontend/src/features/rsform/components/badge-constituenta.tsx
@@ -4,8 +4,8 @@ import clsx from 'clsx';
import { globalIDs } from '@/utils/constants';
-import { colorFgCstStatus } from '../colors';
-import { CstClass, type IConstituenta } from '../models/rsform';
+import { colorBgBadge, colorFgCstStatus } from '../colors';
+import { type IConstituenta } from '../models/rsform';
import { useCstTooltipStore } from '../stores/cst-tooltip';
interface BadgeConstituentaProps {
@@ -29,7 +29,7 @@ export function BadgeConstituenta({ value, prefixID }: BadgeConstituentaProps) {
'cc-badge-constituenta',
value.is_inherited && 'border-dashed',
value.crucial && 'cc-badge-inner-shadow',
- value.cst_class === CstClass.BASIC ? 'bg-accent-green25' : 'bg-input'
+ colorBgBadge(value)
)}
style={{
borderColor: colorFgCstStatus(value.status),
diff --git a/rsconcept/frontend/src/features/rsform/components/icon-cst-type.tsx b/rsconcept/frontend/src/features/rsform/components/icon-cst-type.tsx
index ab2448b3..2aef51fe 100644
--- a/rsconcept/frontend/src/features/rsform/components/icon-cst-type.tsx
+++ b/rsconcept/frontend/src/features/rsform/components/icon-cst-type.tsx
@@ -1,4 +1,4 @@
-import { type DomIconProps } from '@/components/icons';
+import { type DomIconProps, IconCstNominal } from '@/components/icons';
import {
IconCstAxiom,
IconCstBaseSet,
@@ -15,6 +15,8 @@ import { CstType } from '../backend/types';
/** Icon for constituenta type. */
export function IconCstType({ value, size = '1.25rem', className }: DomIconProps) {
switch (value) {
+ case CstType.NOMINAL:
+ return ;
case CstType.BASE:
return ;
case CstType.CONSTANT:
diff --git a/rsconcept/frontend/src/features/rsform/components/info-constituenta.tsx b/rsconcept/frontend/src/features/rsform/components/info-constituenta.tsx
index e89d0e68..b6a6c5de 100644
--- a/rsconcept/frontend/src/features/rsform/components/info-constituenta.tsx
+++ b/rsconcept/frontend/src/features/rsform/components/info-constituenta.tsx
@@ -23,10 +23,12 @@ export function InfoConstituenta({ data, className, ...restProps }: InfoConstitu
{data.term_resolved || data.term_raw}
) : null}
-
- Типизация:
- {labelCstTypification(data)}
-
+ {data.parse ? (
+
+ Типизация:
+ {labelCstTypification(data)}
+
+ ) : null}
{data.definition_formal ? (
Выражение:
diff --git a/rsconcept/frontend/src/features/rsform/components/rsform-stats.tsx b/rsconcept/frontend/src/features/rsform/components/rsform-stats.tsx
index 1b89d0ee..ca3ce6ae 100644
--- a/rsconcept/frontend/src/features/rsform/components/rsform-stats.tsx
+++ b/rsconcept/frontend/src/features/rsform/components/rsform-stats.tsx
@@ -6,6 +6,7 @@ import {
IconCstBaseSet,
IconCstConstSet,
IconCstFunction,
+ IconCstNominal,
IconCstPredicate,
IconCstStructured,
IconCstTerm,
@@ -14,7 +15,6 @@ import {
IconPredecessor,
IconStatusError,
IconStatusIncalculable,
- IconStatusOK,
IconStatusProperty,
IconTerminology
} from '@/components/icons';
@@ -48,18 +48,11 @@ export function RSFormStats({ className, stats }: RSFormStatsProps) {
titleHtml='Наследованные'
/>
- }
- value={stats.count_all - stats.count_errors - stats.count_property - stats.count_incalculable}
- />
}
- value={stats.count_errors}
+ value={stats.count_property}
/>
0 ? 'text-destructive' : undefined} />}
value={stats.count_errors}
/>
+ 0 ? 'text-destructive' : undefined} />}
+ value={stats.count_nominal}
+ />
Типизация: ${typification}Содержание: ${
- contents ? contents : 'отсутствует'
- }`;
+ return `${cst.alias}: ${cst.term_resolved}${
+ cst.parse ? `Типизация: ${typification}` : ''
+ }Содержание: ${contents ? contents : 'отсутствует'}`;
}
diff --git a/rsconcept/frontend/src/features/rsform/dialogs/dlg-create-cst/form-create-cst.tsx b/rsconcept/frontend/src/features/rsform/dialogs/dlg-create-cst/form-create-cst.tsx
index b81e6337..3180f153 100644
--- a/rsconcept/frontend/src/features/rsform/dialogs/dlg-create-cst/form-create-cst.tsx
+++ b/rsconcept/frontend/src/features/rsform/dialogs/dlg-create-cst/form-create-cst.tsx
@@ -13,9 +13,9 @@ import { CstType, type ICreateConstituentaDTO } from '../../backend/types';
import { IconCrucialValue } from '../../components/icon-crucial-value';
import { RSInput } from '../../components/rs-input';
import { SelectCstType } from '../../components/select-cst-type';
-import { getRSDefinitionPlaceholder } from '../../labels';
+import { getRSDefinitionPlaceholder, labelRSExpression } from '../../labels';
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 {
schema: IRSForm;
@@ -33,9 +33,8 @@ export function FormCreateCst({ schema }: FormCreateCstProps) {
const cst_type = useWatch({ control, name: 'cst_type' });
const convention = useWatch({ control, name: 'convention' });
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 isFunction = isFunctional(cst_type);
const showConvention = !!convention || forceComment || isBasic;
function handleTypeChange(target: CstType) {
@@ -91,13 +90,7 @@ export function FormCreateCst({ schema }: FormCreateCstProps) {
{
function onChangePrototype(newPrototype: IConstituenta) {
setPrototype(newPrototype);
setArguments(
- newPrototype.parse.args.map(arg => ({
+ newPrototype.parse!.args.map(arg => ({
alias: arg.alias,
typification: arg.typification,
value: ''
diff --git a/rsconcept/frontend/src/features/rsform/dialogs/dlg-edit-cst/form-edit-cst.tsx b/rsconcept/frontend/src/features/rsform/dialogs/dlg-edit-cst/form-edit-cst.tsx
index c9fa3d73..a00b955e 100644
--- a/rsconcept/frontend/src/features/rsform/dialogs/dlg-edit-cst/form-edit-cst.tsx
+++ b/rsconcept/frontend/src/features/rsform/dialogs/dlg-edit-cst/form-edit-cst.tsx
@@ -1,6 +1,9 @@
import { useState } from 'react';
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 { TextArea, TextInput } from '@/components/input';
@@ -8,9 +11,9 @@ import { CstType, type IUpdateConstituentaDTO } from '../../backend/types';
import { IconCrucialValue } from '../../components/icon-crucial-value';
import { RSInput } from '../../components/rs-input';
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 { generateAlias, isBaseSet, isBasicConcept, isFunctional } from '../../models/rsform-api';
+import { generateAlias, isBaseSet, isBasicConcept } from '../../models/rsform-api';
interface FormEditCstProps {
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 convention = useWatch({ control, name: 'item_data.convention' });
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 isFunction = isFunctional(cst_type);
const showConvention = !!convention || forceComment || isBasic;
function handleTypeChange(newValue: CstType) {
@@ -67,6 +69,7 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
{...register('item_data.alias')}
error={errors.item_data?.alias}
/>
+
-
+ {cst_type !== CstType.NOMINAL ? (
+
+ ) : null}
state.filter);
@@ -71,47 +73,32 @@ export function DlgGraphParams() {
/>
-
Типы конституент
-
}
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
+ Типы конституент
+
+ {Object.values(CstType).map(cstType => {
+ const fieldName = cstTypeToFilterKey[cstType];
+ return (
+ (
+ field.onChange(!field.value)}
+ title={labelCstType(cstType)}
+ icon={
+
+ }
+ />
+ )}
+ />
+ );
+ })}
+
);
diff --git a/rsconcept/frontend/src/features/rsform/labels.ts b/rsconcept/frontend/src/features/rsform/labels.ts
index e79f44a2..475db24e 100644
--- a/rsconcept/frontend/src/features/rsform/labels.ts
+++ b/rsconcept/frontend/src/features/rsform/labels.ts
@@ -18,6 +18,7 @@ import { type GraphColoring } from './stores/term-graph';
// --- Records for label/describe functions ---
const labelCstTypeRecord: Record = {
+ [CstType.NOMINAL]: 'Номеноид',
[CstType.BASE]: 'Базисное множество',
[CstType.CONSTANT]: 'Константное множество',
[CstType.STRUCTURED]: 'Родовая структура',
@@ -34,6 +35,7 @@ const labelReferenceTypeRecord: Record = {
};
const labelCstClassRecord: Record = {
+ [CstClass.NOMINAL]: 'номинальный',
[CstClass.BASIC]: 'базовый',
[CstClass.DERIVED]: 'производный',
[CstClass.STATEMENT]: 'утверждение',
@@ -41,6 +43,7 @@ const labelCstClassRecord: Record = {
};
const describeCstClassRecord: Record = {
+ [CstClass.NOMINAL]: 'номинальная сущность',
[CstClass.BASIC]: 'неопределяемое понятие',
[CstClass.DERIVED]: 'определяемое понятие',
[CstClass.STATEMENT]: 'логическое утверждение',
@@ -158,15 +161,28 @@ const labelGrammemeRecord: Partial> = {
[Grammeme.Litr]: 'Стиль: литературный'
};
+const labelRSExpressionsRecord: Record = {
+ [CstType.NOMINAL]: 'Определяющие конституенты',
+ [CstType.BASE]: 'Формальное определение',
+ [CstType.CONSTANT]: 'Формальное определение',
+ [CstType.STRUCTURED]: 'Область определения',
+ [CstType.TERM]: 'Формальное определение',
+ [CstType.THEOREM]: 'Формальное определение',
+ [CstType.AXIOM]: 'Формальное определение',
+ [CstType.FUNCTION]: 'Определение функции',
+ [CstType.PREDICATE]: 'Определение функции'
+};
+
const rsDefinitionPlaceholderRecord: Record = {
+ [CstType.NOMINAL]: 'Например, X1 D1 N1',
+ [CstType.BASE]: 'Не предусмотрено',
+ [CstType.CONSTANT]: 'Не предусмотрено',
[CstType.STRUCTURED]: 'Пример: ℬ(X1×D2)',
[CstType.TERM]: 'Пример: D{ξ∈S1 | Pr1(ξ)∩Pr2(ξ)=∅}',
[CstType.THEOREM]: 'Пример: D11=∅',
[CstType.AXIOM]: 'Пример: D11=∅',
[CstType.FUNCTION]: 'Пример: [α∈X1, β∈ℬ(X1×X2)] Pr2(Fi1[{α}](β))',
- [CstType.PREDICATE]: 'Пример: [α∈X1, β∈ℬ(X1)] α∈β & card(β)>1',
- [CstType.CONSTANT]: 'Формальное выражение',
- [CstType.BASE]: 'Формальное выражение'
+ [CstType.PREDICATE]: 'Пример: [α∈X1, β∈ℬ(X1)] α∈β & card(β)>1'
};
const cstTypeShortcutKeyRecord: Record = {
@@ -177,7 +193,8 @@ const cstTypeShortcutKeyRecord: Record = {
[CstType.FUNCTION]: 'Q',
[CstType.PREDICATE]: 'W',
[CstType.CONSTANT]: '5',
- [CstType.THEOREM]: '6'
+ [CstType.THEOREM]: '6',
+ [CstType.NOMINAL]: '7'
};
const labelTokenRecord: Partial> = {
@@ -331,6 +348,11 @@ export function getCstTypeShortcut(type: CstType) {
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}. */
export function getRSDefinitionPlaceholder(type: CstType): string {
return rsDefinitionPlaceholderRecord[type] ?? 'Формальное выражение';
@@ -445,6 +467,9 @@ export function labelTypification({
* Generates label for {@link IConstituenta} typification.
*/
export function labelCstTypification(cst: RO): string {
+ if (!cst.parse) {
+ return 'N/A';
+ }
return labelTypification({
isValid: cst.parse.status === ParsingStatus.VERIFIED,
resultType: cst.parse.typification,
diff --git a/rsconcept/frontend/src/features/rsform/models/graph-api.ts b/rsconcept/frontend/src/features/rsform/models/graph-api.ts
index 047fa03a..17123f92 100644
--- a/rsconcept/frontend/src/features/rsform/models/graph-api.ts
+++ b/rsconcept/frontend/src/features/rsform/models/graph-api.ts
@@ -69,6 +69,7 @@ export function produceFilteredGraph(schema: IRSForm, params: GraphFilterParams,
if (params.allowPredicate) result.push(CstType.PREDICATE);
if (params.allowConstant) result.push(CstType.CONSTANT);
if (params.allowTheorem) result.push(CstType.THEOREM);
+ if (params.allowNominal) result.push(CstType.NOMINAL);
return result;
})();
diff --git a/rsconcept/frontend/src/features/rsform/models/rsform-api.ts b/rsconcept/frontend/src/features/rsform/models/rsform-api.ts
index fc4ea36b..072d9914 100644
--- a/rsconcept/frontend/src/features/rsform/models/rsform-api.ts
+++ b/rsconcept/frontend/src/features/rsform/models/rsform-api.ts
@@ -95,6 +95,7 @@ export function inferClass(type: CstType, isTemplate: boolean = false): CstClass
}
// prettier-ignore
switch (type) {
+ case CstType.NOMINAL: return CstClass.NOMINAL;
case CstType.BASE: return CstClass.BASIC;
case CstType.CONSTANT: 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.NOMINAL]: 'N',
[CstType.BASE]: 'X',
[CstType.CONSTANT]: 'C',
[CstType.STRUCTURED]: 'S',
@@ -148,6 +150,7 @@ export function guessCstType(hint: string, defaultType: CstType = CstType.TERM):
}
// prettier-ignore
switch (hint) {
+ case 'N': return CstType.NOMINAL;
case 'X': return CstType.BASE;
case 'C': return CstType.CONSTANT;
case 'S': return CstType.STRUCTURED;
@@ -166,6 +169,7 @@ export function guessCstType(hint: string, defaultType: CstType = CstType.TERM):
export function isBasicConcept(type: CstType): boolean {
// prettier-ignore
switch (type) {
+ case CstType.NOMINAL: return true;
case CstType.BASE: return true;
case CstType.CONSTANT: return true;
case CstType.STRUCTURED: return true;
@@ -183,6 +187,7 @@ export function isBasicConcept(type: CstType): boolean {
export function isBaseSet(type: CstType): boolean {
// prettier-ignore
switch (type) {
+ case CstType.NOMINAL: return false;
case CstType.BASE: return true;
case CstType.CONSTANT: return true;
case CstType.STRUCTURED: return false;
@@ -200,6 +205,7 @@ export function isBaseSet(type: CstType): boolean {
export function isFunctional(type: CstType): boolean {
// prettier-ignore
switch (type) {
+ case CstType.NOMINAL: return false;
case CstType.BASE: return false;
case CstType.CONSTANT: 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.
*/
export function canProduceStructure(cst: RO): 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
+ );
}
/**
diff --git a/rsconcept/frontend/src/features/rsform/models/rsform.ts b/rsconcept/frontend/src/features/rsform/models/rsform.ts
index 9eda306c..75627c94 100644
--- a/rsconcept/frontend/src/features/rsform/models/rsform.ts
+++ b/rsconcept/frontend/src/features/rsform/models/rsform.ts
@@ -20,6 +20,7 @@ export const CATEGORY_CST_TYPE = CstType.THEOREM;
/** Represents Constituenta classification in terms of system of concepts. */
export const CstClass = {
+ NOMINAL: 'nominal',
BASIC: 'basic',
DERIVED: 'derived',
STATEMENT: 'statement',
@@ -58,7 +59,7 @@ export interface IConstituenta {
term_resolved: string;
term_forms: TermForm[];
- parse: {
+ parse?: {
status: ParsingStatus;
valueClass: ValueClass;
typification: string;
@@ -122,6 +123,7 @@ export interface IRSFormStats {
count_function: number;
count_predicate: number;
count_theorem: number;
+ count_nominal: number;
}
/** Represents inheritance data for {@link IRSForm}. */
diff --git a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-constituenta/form-constituenta.tsx b/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-constituenta/form-constituenta.tsx
index 394886db..e3b3921c 100644
--- a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-constituenta/form-constituenta.tsx
+++ b/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-constituenta/form-constituenta.tsx
@@ -30,9 +30,14 @@ import {
import { useMutatingRSForm } from '../../../backend/use-mutating-rsform';
import { useUpdateConstituenta } from '../../../backend/use-update-constituenta';
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 { isBaseSet, isBasicConcept, isFunctional } from '../../../models/rsform-api';
+import { isBaseSet, isBasicConcept } from '../../../models/rsform-api';
import { EditorRSExpression } from '../editor-rsexpression';
interface FormConstituentaProps {
@@ -90,15 +95,18 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
);
const typeInfo = useMemo(
- () => ({
- alias: activeCst.alias,
- result: localParse ? localParse.typification : activeCst.parse.typification,
- args: localParse ? localParse.args : activeCst.parse.args
- }),
+ () =>
+ activeCst.parse
+ ? {
+ alias: activeCst.alias,
+ result: localParse ? localParse.typification : activeCst.parse.typification,
+ args: localParse ? localParse.args : activeCst.parse.args
+ }
+ : null,
[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 showConvention = !!activeCst.convention || forceComment || isBasic;
@@ -140,7 +148,11 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
}
function handleTypeGraph(event: React.MouseEvent) {
- if ((localParse && !localParse.parseResult) || activeCst.parse.status !== ParsingStatus.VERIFIED) {
+ if (
+ (localParse && !localParse.parseResult) ||
+ !activeCst.parse ||
+ activeCst.parse.status !== ParsingStatus.VERIFIED
+ ) {
toast.error(errorMsg.typeStructureFailed);
return;
}
@@ -227,19 +239,21 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
)}
/>
-
+ {activeCst.cst_type !== CstType.NOMINAL ? (
+
+ ) : null}
{!!activeCst.definition_formal || !isElementary ? (
(
+ );
+ }
+
return (
{
- if (isModified) {
+ if (isModified || !activeCst.parse) {
return ExpressionStatus.UNKNOWN;
}
if (parseData) {
diff --git a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-rslist/editor-rslist.tsx b/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-rslist/editor-rslist.tsx
index 1d6ab599..64e68169 100644
--- a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-rslist/editor-rslist.tsx
+++ b/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-rslist/editor-rslist.tsx
@@ -96,6 +96,7 @@ export function EditorRSList() {
case 'KeyW': createCst(CstType.PREDICATE, true); return true;
case 'Digit5': createCst(CstType.CONSTANT, true); return true;
case 'Digit6': createCst(CstType.THEOREM, true); return true;
+ case 'Digit7': createCst(CstType.NOMINAL, true); return true;
}
return false;
}
diff --git a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/toolbar-term-graph.tsx b/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/toolbar-term-graph.tsx
index 1c0df580..2f92e227 100644
--- a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/toolbar-term-graph.tsx
+++ b/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/toolbar-term-graph.tsx
@@ -63,11 +63,13 @@ export function ToolbarTermGraph({ className }: ToolbarTermGraphProps) {
const { addSelectedNodes } = store.getState();
function handleShowTypeGraph() {
- const typeInfo = schema.items.map(item => ({
- alias: item.alias,
- result: item.parse.typification,
- args: item.parse.args
- }));
+ const typeInfo = schema.items
+ .filter(item => !!item.parse)
+ .map(item => ({
+ alias: item.alias,
+ result: item.parse!.typification,
+ args: item.parse!.args
+ }));
showTypeGraph({ items: typeInfo });
}
diff --git a/rsconcept/frontend/src/features/rsform/stores/term-graph.ts b/rsconcept/frontend/src/features/rsform/stores/term-graph.ts
index acdca33d..c67d0e7c 100644
--- a/rsconcept/frontend/src/features/rsform/stores/term-graph.ts
+++ b/rsconcept/frontend/src/features/rsform/stores/term-graph.ts
@@ -1,6 +1,8 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
+import { CstType } from '../backend/types';
+
export const graphColorings = ['none', 'status', 'type', 'schemas'] as const;
/**
@@ -29,8 +31,21 @@ export interface GraphFilterParams {
allowPredicate: boolean;
allowConstant: boolean;
allowTheorem: boolean;
+ allowNominal: boolean;
}
+export const cstTypeToFilterKey: Record = {
+ [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 {
filter: GraphFilterParams;
setFilter: (value: GraphFilterParams) => void;
@@ -66,7 +81,8 @@ export const useTermGraphStore = create()(
allowFunction: true,
allowPredicate: true,
allowConstant: true,
- allowTheorem: true
+ allowTheorem: true,
+ allowNominal: true
},
setFilter: value => set({ filter: value }),
toggleFocusInputs: () =>
diff --git a/rsconcept/frontend/src/features/users/backend/types.ts b/rsconcept/frontend/src/features/users/backend/types.ts
index db2132ce..59cad73f 100644
--- a/rsconcept/frontend/src/features/users/backend/types.ts
+++ b/rsconcept/frontend/src/features/users/backend/types.ts
@@ -17,7 +17,7 @@ export type IUpdateProfileDTO = z.infer;
// ========= SCHEMAS ========
export const schemaUser = z.strictObject({
- id: z.coerce.number(),
+ id: z.number(),
username: z.string().nonempty(errorMsg.requiredField),
is_staff: z.boolean(),
email: z.email(errorMsg.emailField),