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:
''' 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']

View File

@ -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'],

View File

@ -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)

View File

@ -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)

View File

@ -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';

View File

@ -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;
}

View File

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

View File

@ -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)!);

View File

@ -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 });
}

View File

@ -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<number, IConstituenta>();
constructor(input: RO<IRSFormDTO>) {
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)
};
}
}

View File

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

View File

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

View File

@ -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),

View File

@ -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<CstType>) {
switch (value) {
case CstType.NOMINAL:
return <IconCstNominal size={size} className={className ?? 'text-primary'} />;
case CstType.BASE:
return <IconCstBaseSet size={size} className={className ?? 'text-constructive'} />;
case CstType.CONSTANT:

View File

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

View File

@ -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='Наследованные'
/>
<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
id='count_property'
title='Неразмерные'
icon={<IconStatusProperty size='1.25rem' />}
value={stats.count_errors}
value={stats.count_property}
/>
<ValueStats
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} />}
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
id='count_base'

View File

@ -75,7 +75,7 @@ function describeCstNode(cst: IConstituenta) {
? cst.convention
: cst.definition_resolved || cst.definition_formal || cst.convention;
const typification = labelCstTypification(cst);
return `${cst.alias}: ${cst.term_resolved}</br><b>Типизация:</b> ${typification}</br><b>Содержание:</b> ${
contents ? contents : 'отсутствует'
}`;
return `${cst.alias}: ${cst.term_resolved}</br>${
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 { 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) {
<RSInput
id='dlg_cst_expression'
noTooltip
label={
cst_type === CstType.STRUCTURED
? 'Область определения'
: isFunction
? 'Определение функции'
: 'Формальное определение'
}
label={labelRSExpression(cst_type)}
placeholder={getRSDefinitionPlaceholder(cst_type)}
value={field.value}
onChange={field.onChange}

View File

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

View File

@ -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}
/>
<BadgeHelp topic={HelpTopic.CC_CONSTITUENTA} offset={16} contentClass='sm:max-w-160' />
</div>
<TextArea
@ -75,23 +78,26 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
spellCheck
label='Термин'
className='max-h-15 disabled:min-h-9'
placeholder='Обозначение для текстовых определений'
{...register('item_data.term_raw')}
error={errors.item_data?.term_raw}
/>
<TextArea
id='cst_typification'
fitContent
dense
noResize
noBorder
noOutline
transparent
readOnly
label='Типизация'
value={labelCstTypification(target)}
className='cursor-default'
/>
{cst_type !== CstType.NOMINAL ? (
<TextArea
id='cst_typification'
fitContent
dense
noResize
noBorder
noOutline
transparent
readOnly
label='Типизация'
value={labelCstTypification(target)}
className='cursor-default'
/>
) : null}
<Controller
control={control}
@ -101,13 +107,7 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
<RSInput
id='dlg_cst_expression'
noTooltip
label={
cst_type === CstType.STRUCTURED
? 'Область определения'
: isFunction
? 'Определение функции'
: 'Формальное определение'
}
label={labelRSExpression(cst_type)}
placeholder={getRSDefinitionPlaceholder(cst_type)}
className='max-h-15'
schema={schema}
@ -131,6 +131,7 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
fitContent
spellCheck
label='Текстовое определение'
placeholder='Текстовая интерпретация формального выражения'
className='max-h-15'
value={field.value}
onChange={field.onChange}
@ -158,6 +159,7 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
fitContent
spellCheck
label={isBasic ? 'Конвенция' : 'Комментарий'}
placeholder={isBasic ? 'Договоренность об интерпретации' : 'Пояснение разработчика'}
className='max-h-20 disabled:min-h-9'
{...register('item_data.convention')}
error={errors.item_data?.convention}

View File

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

View File

@ -18,6 +18,7 @@ import { type GraphColoring } from './stores/term-graph';
// --- Records for label/describe functions ---
const labelCstTypeRecord: Record<CstType, string> = {
[CstType.NOMINAL]: 'Номеноид',
[CstType.BASE]: 'Базисное множество',
[CstType.CONSTANT]: 'Константное множество',
[CstType.STRUCTURED]: 'Родовая структура',
@ -34,6 +35,7 @@ const labelReferenceTypeRecord: Record<ReferenceType, string> = {
};
const labelCstClassRecord: Record<CstClass, string> = {
[CstClass.NOMINAL]: 'номинальный',
[CstClass.BASIC]: 'базовый',
[CstClass.DERIVED]: 'производный',
[CstClass.STATEMENT]: 'утверждение',
@ -41,6 +43,7 @@ const labelCstClassRecord: Record<CstClass, string> = {
};
const describeCstClassRecord: Record<CstClass, string> = {
[CstClass.NOMINAL]: 'номинальная сущность',
[CstClass.BASIC]: 'неопределяемое понятие',
[CstClass.DERIVED]: 'определяемое понятие',
[CstClass.STATEMENT]: 'логическое утверждение',
@ -158,15 +161,28 @@ const labelGrammemeRecord: Partial<Record<Grammeme, string>> = {
[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> = {
[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<CstType, string> = {
@ -177,7 +193,8 @@ const cstTypeShortcutKeyRecord: Record<CstType, string> = {
[CstType.FUNCTION]: 'Q',
[CstType.PREDICATE]: 'W',
[CstType.CONSTANT]: '5',
[CstType.THEOREM]: '6'
[CstType.THEOREM]: '6',
[CstType.NOMINAL]: '7'
};
const labelTokenRecord: Partial<Record<TokenID, string>> = {
@ -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<IConstituenta>): string {
if (!cst.parse) {
return 'N/A';
}
return labelTypification({
isValid: cst.parse.status === ParsingStatus.VERIFIED,
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.allowConstant) result.push(CstType.CONSTANT);
if (params.allowTheorem) result.push(CstType.THEOREM);
if (params.allowNominal) result.push(CstType.NOMINAL);
return result;
})();

View File

@ -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, string> = {
[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<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. */
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}. */

View File

@ -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<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);
return;
}
@ -227,19 +239,21 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
)}
/>
<TextArea
id='cst_typification'
fitContent
dense
noResize
noBorder
noOutline
transparent
readOnly
label='Типизация'
value={typification}
className='cursor-default'
/>
{activeCst.cst_type !== CstType.NOMINAL ? (
<TextArea
id='cst_typification'
fitContent
dense
noResize
noBorder
noOutline
transparent
readOnly
label='Типизация'
value={typification}
className='cursor-default'
/>
) : null}
{!!activeCst.definition_formal || !isElementary ? (
<Controller
@ -248,13 +262,7 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
render={({ field }) => (
<EditorRSExpression
id='cst_expression'
label={
activeCst.cst_type === CstType.STRUCTURED
? 'Область определения'
: isFunctional(activeCst.cst_type)
? 'Определение функции'
: 'Формальное определение'
}
label={labelRSExpression(activeCst.cst_type)}
placeholder={disabled ? '' : getRSDefinitionPlaceholder(activeCst.cst_type)}
value={field.value ?? ''}
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 (
<div className='relative'>
<ToolbarRSExpression

View File

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

View File

@ -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;
}

View File

@ -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 });
}

View File

@ -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, 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 {
filter: GraphFilterParams;
setFilter: (value: GraphFilterParams) => void;
@ -66,7 +81,8 @@ export const useTermGraphStore = create<TermGraphStore>()(
allowFunction: true,
allowPredicate: true,
allowConstant: true,
allowTheorem: true
allowTheorem: true,
allowNominal: true
},
setFilter: value => set({ filter: value }),
toggleFocusInputs: () =>

View File

@ -17,7 +17,7 @@ export type IUpdateProfileDTO = z.infer<typeof schemaUpdateProfile>;
// ========= 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),