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() {