@rsconcept/rstool 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +4 -4
  2. package/dist/{agent-workflow-Gk0Vfnv1.d.ts → agent-workflow-D22FeODL.d.ts} +6 -6
  3. package/dist/{analysis-LLnPhmGa.d.ts → analysis-DA44pfgA.d.ts} +4 -3
  4. package/dist/common-D6TFl6l_.d.ts +11 -0
  5. package/dist/{constituenta-DnGR6bnM.d.ts → constituenta-QY5w1x91.d.ts} +7 -7
  6. package/dist/diagnostic-C6TRoBgt.d.ts +38 -0
  7. package/dist/diagnostic-assembly-DRunIcKQ.js +54 -0
  8. package/dist/diagnostic-assembly-DRunIcKQ.js.map +1 -0
  9. package/dist/{evaluation-Cns8BFm4.d.ts → evaluation-Cd4S7-3t.d.ts} +4 -3
  10. package/dist/index.d.ts +12 -12
  11. package/dist/index.js +4 -3
  12. package/dist/mappers/model-adapter.d.ts +3 -3
  13. package/dist/mappers/model-adapter.js +1 -199
  14. package/dist/mappers/schema-adapter.d.ts +5 -5
  15. package/dist/mappers/schema-adapter.js +8 -8
  16. package/dist/mappers/schema-adapter.js.map +1 -1
  17. package/dist/mappers/types.d.ts +6 -6
  18. package/dist/mappers/types.js +6 -10
  19. package/dist/mappers/types.js.map +1 -1
  20. package/dist/model-adapter-D-YmIvQW.js +225 -0
  21. package/dist/model-adapter-D-YmIvQW.js.map +1 -0
  22. package/dist/{model-value-BbonPzMz.d.ts → model-value-BUhr2IjA.d.ts} +2 -2
  23. package/dist/models/agent-workflow.d.ts +1 -1
  24. package/dist/models/analysis.d.ts +1 -1
  25. package/dist/models/common.d.ts +2 -2
  26. package/dist/models/common.js +1 -1
  27. package/dist/models/constituenta.d.ts +1 -1
  28. package/dist/models/diagnostic.d.ts +2 -2
  29. package/dist/models/diagnostic.js +3 -1
  30. package/dist/models/evaluation.d.ts +1 -1
  31. package/dist/models/index.d.ts +11 -11
  32. package/dist/models/index.js +3 -2
  33. package/dist/models/model-value.d.ts +1 -1
  34. package/dist/models/rstool-agent.d.ts +1 -1
  35. package/dist/models/rstool-agent.js +1 -1
  36. package/dist/models/session.d.ts +1 -1
  37. package/dist/models/tool-contract.d.ts +1 -1
  38. package/dist/models/tool-contract.js +1 -1
  39. package/dist/models/tool-contract.js.map +1 -1
  40. package/dist/{models-9G6ur_pg.js → models-DTLctDmB.js} +72 -15
  41. package/dist/models-DTLctDmB.js.map +1 -0
  42. package/dist/{rstool-agent-D2cQze_b.d.ts → rstool-agent-BMVAyMAG.d.ts} +11 -10
  43. package/dist/session/session-store.d.ts +6 -6
  44. package/dist/session/session-store.js +1 -1
  45. package/dist/{session-ChexW8i7.d.ts → session-CI4rLYby.d.ts} +3 -3
  46. package/dist/{tool-contract-0uRGhEfW.d.ts → tool-contract-sQfr-DYD.d.ts} +12 -12
  47. package/dist/wrapper/stdio-wrapper.js +1 -1
  48. package/docs/DIAGNOSTICS.md +63 -20
  49. package/examples/README.md +2 -0
  50. package/examples/agent-client.ts +15 -3
  51. package/examples/audit-diagnostics.ts +40 -0
  52. package/examples/chocolate-nim/build-rsform.ts +15 -0
  53. package/examples/chocolate-nim/build-rsmodel.ts +15 -22
  54. package/examples/chocolate-nim/model-demo.ts +11 -0
  55. package/examples/chocolate-nim/rsform-session.json +329 -310
  56. package/examples/chocolate-nim/rsmodel-session.json +311 -311
  57. package/examples/diagnostics-utils.ts +25 -0
  58. package/examples/expression-bank/bank-constituents.ts +10 -10
  59. package/examples/expression-bank/build-rsform.ts +8 -0
  60. package/examples/expression-bank/rsform-session.json +1501 -1501
  61. package/examples/kinship/build-rsform.ts +16 -1
  62. package/examples/kinship/build-rsmodel.ts +15 -56
  63. package/examples/kinship/model-demo.ts +35 -0
  64. package/examples/kinship/rsform-session.json +300 -215
  65. package/examples/kinship/rsmodel-session.json +216 -216
  66. package/examples/movd/build-rsform.ts +15 -0
  67. package/examples/movd/build-rsmodel.ts +15 -46
  68. package/examples/movd/model-demo.ts +31 -0
  69. package/examples/movd/rsform-session.json +359 -270
  70. package/examples/movd/rsmodel-session.json +263 -263
  71. package/examples/sample/build-rsform.ts +43 -3
  72. package/examples/sample/build-rsmodel.ts +26 -2
  73. package/examples/sample/rsform-session.json +41 -12
  74. package/examples/sample/rsmodel-session.json +37 -37
  75. package/examples/template-apply/build-rsform.ts +26 -9
  76. package/examples/template-apply/rsform-session.json +100 -45
  77. package/package.json +3 -2
  78. package/skills/rstool-helper/EXAMPLES.md +3 -1
  79. package/skills/rstool-helper/GUIDE.md +7 -6
  80. package/skills/rstool-helper/REFERENCE.md +19 -19
  81. package/src/index.ts +7 -4
  82. package/src/mappers/diagnostic-assembly.ts +96 -0
  83. package/src/mappers/diagnostics-collector.test.ts +63 -0
  84. package/src/mappers/diagnostics-collector.ts +73 -0
  85. package/src/mappers/model-adapter.ts +28 -99
  86. package/src/mappers/rsform-builder.ts +127 -0
  87. package/src/mappers/schema-adapter.ts +19 -15
  88. package/src/mappers/types.ts +12 -12
  89. package/src/models/agent-workflow.ts +2 -2
  90. package/src/models/analysis.ts +3 -2
  91. package/src/models/common.ts +0 -12
  92. package/src/models/constituenta.ts +4 -4
  93. package/src/models/diagnostic.ts +15 -10
  94. package/src/models/evaluation.ts +3 -8
  95. package/src/models/index.ts +8 -2
  96. package/src/models/rstool-agent.test.ts +66 -5
  97. package/src/models/rstool-agent.ts +22 -10
  98. package/src/models/tool-contract.ts +4 -4
  99. package/src/session/persistence.ts +2 -2
  100. package/src/session/session-store.ts +14 -7
  101. package/dist/common-DHJalS-Q.d.ts +0 -22
  102. package/dist/diagnostic-D9yl_mEL.d.ts +0 -19
  103. package/dist/mappers/model-adapter.js.map +0 -1
  104. package/dist/models-9G6ur_pg.js.map +0 -1
@@ -84,12 +84,13 @@ kind `auto` (default) определит `portal-details` или `portal-schema`
84
84
 
85
85
  Когда `analysis.success === false` или `listDiagnostics` не пуст. Коды и типичные ошибки — [DIAGNOSTICS.md](../../docs/DIAGNOSTICS.md).
86
86
 
87
- 1. Запусти `analyzeExpression` (черновик без сохранения).
88
- 2. Прочитай `code`, `from`, `to`, `params` в `analysis.diagnostics` или через `listDiagnostics`.
89
- 3. Сопоставь `code` с исправлением в [DIAGNOSTICS.md](../../docs/DIAGNOSTICS.md).
90
- 4. Исправь именно диапазон в `definitionFormal`.
91
- 5. Не повторяй вызов без изменения ввода.
92
- 6. После успеха — ещё один `applySchemaPatch` с исправленным элементом.
87
+ 1. Вызови `listDiagnostics()` — смотри `kind`: `expression` (формула), `schema` (омонимы, дубликаты, термин/конвенция), `model` (интерпретация).
88
+ 2. Для черновика формулы — `analyzeExpression` (без сохранения).
89
+ 3. Прочитай `name`, `severity`, `alias`, `from`, `to`, `params` в `analysis.diagnostics` или через `listDiagnostics({ kind })`.
90
+ 4. Сопоставь `name` (или `code`) с исправлением в [DIAGNOSTICS.md](../../docs/DIAGNOSTICS.md).
91
+ 5. Исправь именно диапазон в `definitionFormal` (expression) или метаданные/данные модели (schema/model).
92
+ 6. Не повторяй вызов без изменения ввода.
93
+ 7. После успеха — ещё один `applySchemaPatch` / `setModelValues` с исправлением.
93
94
 
94
95
  ### Проверка на маленькой КМ
95
96
 
@@ -3,30 +3,30 @@
3
3
  ## Контракт rstool
4
4
 
5
5
  - Пакет: `@rsconcept/rstool`
6
- - Версия контракта: `2.0.0` (`CONTRACT_VERSION`)
6
+ - Версия контракта: `3.0.0` (`CONTRACT_VERSION`)
7
7
  - Основной класс: `RSToolAgent`
8
8
  - Публичные импорты: `@rsconcept/rstool` и `@rsconcept/rstool/wrapper`
9
9
 
10
10
  ### Методы library API
11
11
 
12
- | Метод | Назначение |
13
- | ---------------------------------------------- | -------------------------------------------- |
14
- | `ensureSession(initial?)` | Вернуть текущую сессию или создать новую |
15
- | `createSession(initial?)` | Новая in-memory сессия |
16
- | `getCurrentSession()` | Текущая активная сессия или `null` |
17
- | `setCurrentSession(sessionId)` | Сделать сессию текущей |
18
- | `applySchemaPatch(input, sessionId?)` | Единственный путь правки схемы |
19
- | `getSessionState(detail?, sessionId?)` | `summary` (default) или `full` |
20
- | `listDiagnostics(filters?, sessionId?)` | Активные диагностики |
21
- | `analyzeExpression(input, sessionId?)` | Разбор без сохранения (`recordDiagnostics?`) |
22
- | `commitStep(message?, sessionId?)` | Ревизия |
23
- | `exportSession(sessionId?)` | JSON сессии |
24
- | `exportPortal({ kind, format? }, sessionId?)` | Portal Load from JSON |
25
- | `importData(payload, kind?)` | Импорт сессии / Portal (`kind` см. ниже) |
26
- | `setModelValues({ set?, clear? }, sessionId?)` | Значения модели (async — нужен `await`) |
27
- | `getModelState(sessionId?)` | Состояние интерпретации |
28
- | `evaluate(input, sessionId?)` | Scratch или конституента |
29
- | `recalculateModel(sessionId?)` | Пересчёт |
12
+ | Метод | Назначение |
13
+ | ---------------------------------------------- | ------------------------------------------------------------------ |
14
+ | `ensureSession(initial?)` | Вернуть текущую сессию или создать новую |
15
+ | `createSession(initial?)` | Новая in-memory сессия |
16
+ | `getCurrentSession()` | Текущая активная сессия или `null` |
17
+ | `setCurrentSession(sessionId)` | Сделать сессию текущей |
18
+ | `applySchemaPatch(input, sessionId?)` | Единственный путь правки схемы |
19
+ | `getSessionState(detail?, sessionId?)` | `summary` (default) или `full` |
20
+ | `listDiagnostics(filters?, sessionId?)` | Активные диагностики (`kind`: `expression` \| `schema` \| `model`) |
21
+ | `analyzeExpression(input, sessionId?)` | Разбор без сохранения (`recordDiagnostics?`) |
22
+ | `commitStep(message?, sessionId?)` | Ревизия |
23
+ | `exportSession(sessionId?)` | JSON сессии |
24
+ | `exportPortal({ kind, format? }, sessionId?)` | Portal Load from JSON |
25
+ | `importData(payload, kind?)` | Импорт сессии / Portal (`kind` см. ниже) |
26
+ | `setModelValues({ set?, clear? }, sessionId?)` | Значения модели (async — нужен `await`) |
27
+ | `getModelState(sessionId?)` | Состояние интерпретации |
28
+ | `evaluate(input, sessionId?)` | Scratch или конституента |
29
+ | `recalculateModel(sessionId?)` | Пересчёт |
30
30
 
31
31
  ### `applySchemaPatch`
32
32
 
package/src/index.ts CHANGED
@@ -2,14 +2,16 @@
2
2
  * Public API for `@rsconcept/rstool`.
3
3
  *
4
4
  * Primary entry points: {@link RSToolAgent} (in-process) and {@link RSToolWrapperClient}
5
- * (stdio child process). Types describe the v2 agent contract.
5
+ * (stdio child process). Types describe the v3 agent contract.
6
6
  */
7
- export { toPublicAnalysis, toPublicError, type DomainAnalysisLike, type DomainErrorLike } from './mappers/types';
7
+ export { toDiagnostic, toPublicAnalysis, type DomainAnalysisLike, type DomainErrorLike } from './mappers/types';
8
8
  export {
9
9
  CONTRACT_VERSION,
10
10
  CstType,
11
+ DiagnosticKind,
11
12
  EvalStatus,
12
13
  RSErrorCode,
14
+ RSDiagnosticCode,
13
15
  RSToolAgent,
14
16
  ValueClass,
15
17
  type AgentConstituentaPatch,
@@ -20,7 +22,9 @@ export {
20
22
  type BasicBinding,
21
23
  type ConstituentaDraft,
22
24
  type ConstituentaState,
23
- type DiagnosticRecord,
25
+ type CstDiagnostic,
26
+ type Diagnostic,
27
+ type DiagnosticSeverity,
24
28
  type EvaluateInput,
25
29
  type EvaluationResult,
26
30
  type ExportFormat,
@@ -40,7 +44,6 @@ export {
40
44
  type RecalculateModelResult,
41
45
  type RSToolAgentContract,
42
46
  type RSToolAgentOptions,
43
- type RSToolErrorDescription,
44
47
  type RSToolValue,
45
48
  type SessionHandle,
46
49
  type SessionModelState,
@@ -0,0 +1,96 @@
1
+ import {
2
+ type Constituenta,
3
+ type CstDiagnostic,
4
+ DiagnosticKind,
5
+ modelStatusCstDiagnostic,
6
+ RSDiagnosticCode
7
+ } from '@rsconcept/domain/library';
8
+ import { EvalStatus } from '@rsconcept/domain/library/rsmodel';
9
+ import { isCritical, RSErrorCode, type RSErrorDescription } from '@rsconcept/domain/rslang/error';
10
+
11
+ export { DiagnosticKind, RSDiagnosticCode };
12
+
13
+ /** Severity of a {@link Diagnostic}. */
14
+ export type DiagnosticSeverity = 'error' | 'warning';
15
+
16
+ /** Agent-facing diagnostic with redundant fields for autonomous repair. */
17
+ export interface Diagnostic extends CstDiagnostic {
18
+ name: string;
19
+ severity: DiagnosticSeverity;
20
+ constituentId?: number;
21
+ alias?: string;
22
+ expression: string;
23
+ from: number;
24
+ to: number;
25
+ }
26
+
27
+ /** Context for attributing a {@link Diagnostic} to a constituent. */
28
+ export interface DiagnosticTarget {
29
+ constituentId?: number;
30
+ alias?: string;
31
+ }
32
+
33
+ const DIAGNOSTIC_NAMES: ReadonlyMap<number, string> = new Map([
34
+ ...Object.entries(RSErrorCode).map(([name, code]) => [code, name] as const),
35
+ ...Object.entries(RSDiagnosticCode).map(([name, code]) => [code, name] as const)
36
+ ]);
37
+
38
+ export function getDiagnosticName(code: number): string {
39
+ return DIAGNOSTIC_NAMES.get(code) ?? `0x${code.toString(16).toUpperCase()}`;
40
+ }
41
+
42
+ export function getDiagnosticSeverity(code: number): DiagnosticSeverity {
43
+ return isCritical(code as RSErrorCode) ? 'error' : 'warning';
44
+ }
45
+
46
+ export function expandCstDiagnostic(cst: Constituenta, diagnostic: CstDiagnostic): Diagnostic {
47
+ const expression = expressionForCstDiagnostic(cst, diagnostic);
48
+ return {
49
+ ...diagnostic,
50
+ name: getDiagnosticName(diagnostic.code),
51
+ severity: getDiagnosticSeverity(diagnostic.code),
52
+ constituentId: cst.id,
53
+ alias: cst.alias,
54
+ expression,
55
+ from: 0,
56
+ to: diagnostic.kind === DiagnosticKind.MODEL ? 0 : Math.max(expression.length, 1)
57
+ };
58
+ }
59
+
60
+ export function expressionDiagnostic(
61
+ error: RSErrorDescription,
62
+ expression: string,
63
+ target?: DiagnosticTarget
64
+ ): Diagnostic {
65
+ return {
66
+ kind: DiagnosticKind.EXPRESSION,
67
+ code: error.code,
68
+ name: getDiagnosticName(error.code),
69
+ severity: getDiagnosticSeverity(error.code),
70
+ constituentId: target?.constituentId,
71
+ alias: target?.alias,
72
+ expression,
73
+ from: error.from,
74
+ to: error.to,
75
+ ...(error.params ? { params: error.params } : {})
76
+ };
77
+ }
78
+
79
+ export function modelStatusDiagnostic(status: EvalStatus, cst: Constituenta): Diagnostic | null {
80
+ const base = modelStatusCstDiagnostic(status, cst.cst_type);
81
+ return base ? expandCstDiagnostic(cst, base) : null;
82
+ }
83
+
84
+ function expressionForCstDiagnostic(cst: Constituenta, diagnostic: CstDiagnostic): string {
85
+ switch (diagnostic.code) {
86
+ case RSDiagnosticCode.schemaHomonym:
87
+ case RSDiagnosticCode.schemaMissingTerm:
88
+ return cst.term_resolved;
89
+ case RSDiagnosticCode.schemaMissingConvention:
90
+ return cst.convention;
91
+ case RSDiagnosticCode.schemaTypeMismatch:
92
+ return cst.typification_manual;
93
+ default:
94
+ return cst.definition_formal;
95
+ }
96
+ }
@@ -0,0 +1,63 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { CstType, RSDiagnosticCode, type SessionState, ValueClass } from '../models';
4
+ import { collectSessionDiagnostics } from './diagnostics-collector';
5
+
6
+ describe('collectSessionDiagnostics', () => {
7
+ it('collects expression, schema, and model diagnostics together', () => {
8
+ const session = {
9
+ sessionId: 'test',
10
+ alias: '',
11
+ title: '',
12
+ comment: '',
13
+ createdAt: '',
14
+ updatedAt: '',
15
+ revisions: [],
16
+ model: { items: [] },
17
+ items: [
18
+ {
19
+ id: 1,
20
+ alias: 'X1',
21
+ cstType: CstType.BASE,
22
+ definitionFormal: 'Z',
23
+ term: '',
24
+ definitionText: '',
25
+ convention: '',
26
+ analysis: {
27
+ success: false,
28
+ type: null,
29
+ valueClass: null,
30
+ diagnostics: [{ code: 0x8862, from: 0, to: 1, params: [CstType.BASE, 'X1'] }]
31
+ }
32
+ },
33
+ {
34
+ id: 2,
35
+ alias: 'X2',
36
+ cstType: CstType.BASE,
37
+ definitionFormal: '',
38
+ term: 'person',
39
+ definitionText: '',
40
+ convention: 'people',
41
+ analysis: { success: true, type: {}, valueClass: ValueClass.VALUE, diagnostics: [] }
42
+ },
43
+ {
44
+ id: 3,
45
+ alias: 'X3',
46
+ cstType: CstType.BASE,
47
+ definitionFormal: '',
48
+ term: 'Person',
49
+ definitionText: '',
50
+ convention: 'other',
51
+ analysis: { success: true, type: {}, valueClass: ValueClass.VALUE, diagnostics: [] }
52
+ }
53
+ ]
54
+ };
55
+
56
+ const records = collectSessionDiagnostics(session as SessionState);
57
+ expect(records.some(record => record.kind === 'expression')).toBe(true);
58
+ expect(records.some(record => record.code === RSDiagnosticCode.schemaHomonym)).toBe(true);
59
+ expect(records.some(record => record.code === RSDiagnosticCode.schemaMissingTerm)).toBe(true);
60
+ expect(records.some(record => record.code === RSDiagnosticCode.modelEmpty)).toBe(true);
61
+ expect(records.every(record => record.name.length > 0)).toBe(true);
62
+ });
63
+ });
@@ -0,0 +1,73 @@
1
+ import { isModelIssue } from '@rsconcept/domain/library/rsmodel-api';
2
+ import { type RSErrorDescription } from '@rsconcept/domain/rslang';
3
+
4
+ import { type SessionState } from '../models';
5
+ import {
6
+ DiagnosticKind,
7
+ expandCstDiagnostic,
8
+ expressionDiagnostic,
9
+ modelStatusDiagnostic,
10
+ type Diagnostic
11
+ } from './diagnostic-assembly';
12
+ import { ModelAdapter } from './model-adapter';
13
+ import { enrichSessionConstituents } from './rsform-builder';
14
+
15
+ const modelAdapter = new ModelAdapter();
16
+
17
+ /** Collect expression, schema, and model diagnostics for a session (Portal parity). */
18
+ export function collectSessionDiagnostics(session: SessionState): Diagnostic[] {
19
+ const records: Diagnostic[] = [];
20
+
21
+ for (const item of session.items) {
22
+ for (const diagnostic of item.analysis.diagnostics) {
23
+ const base = diagnostic.name
24
+ ? diagnostic
25
+ : expressionDiagnostic(
26
+ {
27
+ code: diagnostic.code,
28
+ from: diagnostic.from,
29
+ to: diagnostic.to,
30
+ params: diagnostic.params
31
+ } as RSErrorDescription,
32
+ item.definitionFormal,
33
+ {
34
+ constituentId: item.id,
35
+ alias: item.alias
36
+ }
37
+ );
38
+ records.push({
39
+ ...base,
40
+ kind: DiagnosticKind.EXPRESSION,
41
+ constituentId: base.constituentId ?? item.id,
42
+ alias: base.alias ?? item.alias
43
+ });
44
+ }
45
+ }
46
+
47
+ const enriched = enrichSessionConstituents(session);
48
+ for (const { cst } of enriched) {
49
+ for (const diagnostic of cst.diagnostics) {
50
+ if (diagnostic.kind !== DiagnosticKind.SCHEMA) {
51
+ continue;
52
+ }
53
+ records.push(expandCstDiagnostic(cst, diagnostic));
54
+ }
55
+ }
56
+
57
+ if (session.items.length > 0) {
58
+ const schema = enriched.map(entry => entry.cst);
59
+ const engine = modelAdapter.createEngine(session);
60
+ engine.recalculateAll();
61
+ for (const cst of schema) {
62
+ if (!isModelIssue(engine, cst)) {
63
+ continue;
64
+ }
65
+ const diagnostic = modelStatusDiagnostic(engine.getCstStatus(cst.id), cst);
66
+ if (diagnostic) {
67
+ records.push(diagnostic);
68
+ }
69
+ }
70
+ }
71
+
72
+ return records;
73
+ }
@@ -1,8 +1,7 @@
1
- import { Graph } from '@rsconcept/domain/graph/graph';
2
- import { extractGlobals } from '@rsconcept/domain/rslang/api';
3
- import { type ExpressionType, RSLangAnalyzer, type Value, type ValueClass } from '@rsconcept/domain/rslang';
1
+ import { DiagnosticKind, expressionDiagnostic } from './diagnostic-assembly';
2
+ import { type RSErrorDescription, type Value } from '@rsconcept/domain/rslang';
4
3
  import { isBaseSet } from '@rsconcept/domain/library/rsform-api';
5
- import { type Constituenta, CstType, type RSForm } from '@rsconcept/domain/library/rsform';
4
+ import { CstType, type RSForm } from '@rsconcept/domain/library/rsform';
6
5
  import { RSEngine, type RSEngineServices } from '@rsconcept/domain/library/rsengine';
7
6
  import { type BasicBinding, EvalStatus, type RSModel } from '@rsconcept/domain/library/rsmodel';
8
7
  import {
@@ -14,14 +13,13 @@ import {
14
13
  } from '@rsconcept/domain/library/rsmodel-api';
15
14
 
16
15
  import {
17
- type ConstituentaState,
18
16
  type EvaluationResult,
19
17
  type RecalculateModelResult,
20
18
  type SessionModelState,
21
19
  type SessionState,
22
20
  type SetConstituentaValueInput
23
21
  } from '../models';
24
- import { toPublicError } from './types';
22
+ import { buildRSFormFromSession } from './rsform-builder';
25
23
 
26
24
  const SESSION_MODEL_ID = 0;
27
25
 
@@ -73,7 +71,14 @@ export class ModelAdapter {
73
71
  ? EvalStatus.EVAL_FAIL
74
72
  : EvalStatus.EMPTY
75
73
  : EvalStatus.HAS_DATA;
76
- return toPublicEvaluationResult(result.value, result.errors, result.iterations, result.cacheHits, status);
74
+ return toPublicEvaluationResult(
75
+ result.value,
76
+ result.errors,
77
+ result.iterations,
78
+ result.cacheHits,
79
+ status,
80
+ expression
81
+ );
77
82
  }
78
83
 
79
84
  public evaluateConstituenta(session: SessionState, constituentId: number): EvaluationResult {
@@ -84,7 +89,15 @@ export class ModelAdapter {
84
89
  const engine = this.createEngine(session);
85
90
  const result = engine.calculateCst(constituentId);
86
91
  const status = engine.getCstStatus(constituentId);
87
- return toPublicEvaluationResult(result.value, result.errors, result.iterations, result.cacheHits, status);
92
+ return toPublicEvaluationResult(
93
+ result.value,
94
+ result.errors,
95
+ result.iterations,
96
+ result.cacheHits,
97
+ status,
98
+ cst.definitionFormal,
99
+ { constituentId: cst.id, alias: cst.alias }
100
+ );
88
101
  }
89
102
 
90
103
  public recalculateModel(session: SessionState): RecalculateModelResult {
@@ -155,55 +168,6 @@ function createInMemoryServices(session: SessionState): RSEngineServices {
155
168
  };
156
169
  }
157
170
 
158
- function buildRSFormFromSession(session: SessionState): RSForm {
159
- const graph = new Graph<number>();
160
- const cstByAlias = new Map<string, Constituenta>();
161
- const cstByID = new Map<number, Constituenta>();
162
- const analyzer = new RSLangAnalyzer();
163
-
164
- const items = session.items.map(item => {
165
- const cst = toFrontendConstituenta(item);
166
- cstByAlias.set(cst.alias, cst);
167
- cstByID.set(cst.id, cst);
168
- graph.addNode(cst.id);
169
- if (item.cstType === CstType.BASE) {
170
- analyzer.addBase(cst.alias);
171
- }
172
- if (cst.effectiveType) {
173
- analyzer.setGlobal(cst.alias, cst.effectiveType, cst.analysis.valueClass as ValueClass | null);
174
- }
175
- return cst;
176
- });
177
-
178
- for (const cst of items) {
179
- for (const alias of extractGlobals(cst.definition_formal)) {
180
- const source = cstByAlias.get(alias);
181
- if (source) {
182
- graph.addEdge(source.id, cst.id);
183
- }
184
- }
185
- }
186
-
187
- return {
188
- id: 0,
189
- items,
190
- cstByAlias,
191
- cstByID,
192
- graph,
193
- analyzer,
194
- inheritance: [],
195
- attribution: [],
196
- attribution_graph: graph.clone(),
197
- oss: [],
198
- models: [],
199
- editors: [],
200
- versions: [],
201
- is_produced: false,
202
- is_attributive: false,
203
- version: 'latest'
204
- } as unknown as RSForm;
205
- }
206
-
207
171
  function buildRSModelFromSession(session: SessionState): RSModel {
208
172
  return {
209
173
  id: SESSION_MODEL_ID,
@@ -217,54 +181,19 @@ function buildRSModelFromSession(session: SessionState): RSModel {
217
181
  } as unknown as RSModel;
218
182
  }
219
183
 
220
- function toFrontendConstituenta(item: ConstituentaState): Constituenta {
221
- const effectiveType = (item.analysis.type ?? null) as ExpressionType | null;
222
- return {
223
- id: item.id,
224
- alias: item.alias,
225
- cst_type: item.cstType,
226
- definition_formal: item.definitionFormal,
227
- definition_raw: item.definitionFormal,
228
- definition_resolved: item.definitionFormal,
229
- term_raw: item.term,
230
- term_resolved: item.term,
231
- term_forms: [],
232
- convention: item.convention,
233
- typification_manual: '',
234
- value_is_property: false,
235
- crucial: false,
236
- attributes: [],
237
- homonyms: [],
238
- formalDuplicates: [],
239
- analysis: {
240
- success: item.analysis.success,
241
- type: effectiveType,
242
- valueClass: item.analysis.valueClass
243
- },
244
- effectiveType,
245
- is_type_mismatch: false,
246
- schema: 0,
247
- cst_class: 'derived',
248
- status: item.analysis.success ? 'verified' : 'incorrect',
249
- is_template: false,
250
- is_simple_expression: true,
251
- parent_schema_index: 0,
252
- parent_schema: null,
253
- is_inherited: false,
254
- has_inherited_children: false,
255
- spawn: [],
256
- spawn_alias: []
257
- } as unknown as Constituenta;
258
- }
259
-
260
184
  function toPublicEvaluationResult(
261
185
  value: Value | null,
262
186
  errors: { code: number; from: number; to: number; params?: readonly string[] }[],
263
187
  iterations: number,
264
188
  cacheHits: number,
265
- status: EvalStatus
189
+ status: EvalStatus,
190
+ expression: string,
191
+ target?: { constituentId?: number; alias?: string }
266
192
  ): EvaluationResult {
267
- const diagnostics = errors.map(toPublicError);
193
+ const diagnostics = errors.map(error => ({
194
+ ...expressionDiagnostic(error as RSErrorDescription, expression, target),
195
+ kind: DiagnosticKind.MODEL
196
+ }));
268
197
  return {
269
198
  success: diagnostics.length === 0 && value !== null,
270
199
  value: value as EvaluationResult['value'],
@@ -0,0 +1,127 @@
1
+ import { Graph } from '@rsconcept/domain/graph/graph';
2
+ import { extractGlobals } from '@rsconcept/domain/rslang/api';
3
+ import { type ExpressionType, RSLangAnalyzer, type ValueClass } from '@rsconcept/domain/rslang';
4
+ import { assignSchemaDiagnostics } from '@rsconcept/domain/library';
5
+ import { isBasicConcept, isLogical, normalizeExpression } from '@rsconcept/domain/library/rsform-api';
6
+ import { type Constituenta, CstType, type RSForm } from '@rsconcept/domain/library/rsform';
7
+
8
+ import { type ConstituentaState, type SessionState } from '../models';
9
+
10
+ export interface EnrichedConstituenta {
11
+ item: ConstituentaState;
12
+ cst: Constituenta;
13
+ }
14
+
15
+ /** Build a domain {@link RSForm} view from session state (same shape as Portal loader output). */
16
+ export function buildRSFormFromSession(session: SessionState): RSForm {
17
+ const graph = new Graph<number>();
18
+ const cstByAlias = new Map<string, Constituenta>();
19
+ const cstByID = new Map<number, Constituenta>();
20
+ const analyzer = new RSLangAnalyzer();
21
+
22
+ const items = session.items.map(item => {
23
+ const cst = toFrontendConstituenta(item);
24
+ cstByAlias.set(cst.alias, cst);
25
+ cstByID.set(cst.id, cst);
26
+ graph.addNode(cst.id);
27
+ if (item.cstType === CstType.BASE) {
28
+ analyzer.addBase(cst.alias);
29
+ }
30
+ if (cst.effectiveType) {
31
+ analyzer.setGlobal(cst.alias, cst.effectiveType, cst.analysis.valueClass as ValueClass | null);
32
+ }
33
+ return cst;
34
+ });
35
+
36
+ for (const cst of items) {
37
+ for (const alias of extractGlobals(cst.definition_formal)) {
38
+ const source = cstByAlias.get(alias);
39
+ if (source) {
40
+ graph.addEdge(source.id, cst.id);
41
+ }
42
+ }
43
+ }
44
+
45
+ return {
46
+ id: 0,
47
+ items,
48
+ cstByAlias,
49
+ cstByID,
50
+ graph,
51
+ analyzer,
52
+ inheritance: [],
53
+ attribution: [],
54
+ attribution_graph: graph.clone(),
55
+ oss: [],
56
+ models: [],
57
+ editors: [],
58
+ versions: [],
59
+ is_produced: false,
60
+ is_attributive: false,
61
+ version: 'latest'
62
+ } as unknown as RSForm;
63
+ }
64
+
65
+ /** Enrich session constituents with Portal-style schema metadata used for diagnostics. */
66
+ export function enrichSessionConstituents(session: SessionState): EnrichedConstituenta[] {
67
+ const schema = buildRSFormFromSession(session);
68
+ const normalizedDefinitions = new Map<number, string>();
69
+ const resolveAlias = (id: number) => schema.cstByID.get(id)?.alias ?? String(id);
70
+
71
+ for (const item of session.items) {
72
+ const normalized = normalizeExpression(item.definitionFormal, item.cstType);
73
+ if (normalized) {
74
+ normalizedDefinitions.set(item.id, normalized);
75
+ }
76
+ }
77
+
78
+ assignSchemaDiagnostics(schema.items, resolveAlias, normalizedDefinitions);
79
+
80
+ return session.items.map(item => ({
81
+ item,
82
+ cst: schema.cstByID.get(item.id)!
83
+ }));
84
+ }
85
+
86
+ function toFrontendConstituenta(item: ConstituentaState): Constituenta {
87
+ const effectiveType = (item.analysis.type ?? null) as ExpressionType | null;
88
+ return {
89
+ id: item.id,
90
+ alias: item.alias,
91
+ cst_type: item.cstType,
92
+ definition_formal: item.definitionFormal,
93
+ definition_raw: item.definitionFormal,
94
+ definition_resolved: item.definitionFormal,
95
+ term_raw: item.term,
96
+ term_resolved: item.term,
97
+ term_forms: [],
98
+ convention: item.convention,
99
+ typification_manual: '',
100
+ value_is_property: false,
101
+ crucial: false,
102
+ attributes: [],
103
+ diagnostics: [],
104
+ analysis: {
105
+ success: item.analysis.success,
106
+ type: effectiveType,
107
+ valueClass: item.analysis.valueClass
108
+ },
109
+ effectiveType,
110
+ is_type_mismatch: false,
111
+ schema: 0,
112
+ cst_class: 'derived',
113
+ status: item.analysis.success ? 'verified' : 'incorrect',
114
+ is_template: false,
115
+ is_simple_expression: true,
116
+ parent_schema_index: 0,
117
+ parent_schema: null,
118
+ is_inherited: false,
119
+ has_inherited_children: false,
120
+ spawn: [],
121
+ spawn_alias: []
122
+ } as unknown as Constituenta;
123
+ }
124
+
125
+ export function needsBasicMetadata(item: ConstituentaState): boolean {
126
+ return isBasicConcept(item.cstType) && !isLogical(item.cstType);
127
+ }