@rsconcept/rstool 1.9.0 → 2.0.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 (105) hide show
  1. package/README.md +3 -3
  2. package/dist/mappers/model-adapter.d.ts +1 -2
  3. package/dist/mappers/schema-adapter.d.ts +1 -2
  4. package/dist/mappers/types.d.ts +4 -5
  5. package/dist/models/rstool-agent.js +1 -2
  6. package/dist/session/session-store.d.ts +3 -3
  7. package/dist/session/session-store.js +1 -2
  8. package/dist/wrapper/client.d.ts +3 -4
  9. package/docs/BRIEF.md +93 -0
  10. package/docs/CONCEPTUAL-SCHEMA.md +16 -1
  11. package/docs/CONSTRUCTIONS.md +14 -2
  12. package/docs/DIAGNOSTICS.md +3 -2
  13. package/docs/DOMAIN.md +12 -0
  14. package/docs/METHOD.md +208 -0
  15. package/docs/PORTAL-API.md +38 -285
  16. package/docs/PORTAL-MODELS.md +24 -0
  17. package/docs/PORTAL-OSS.md +56 -0
  18. package/docs/PORTAL-PUBLISH.md +39 -0
  19. package/docs/PORTAL-READ.md +301 -0
  20. package/docs/README.md +7 -4
  21. package/docs/REQUIREMENTS.md +61 -0
  22. package/docs/SYNTAX.md +4 -3
  23. package/docs/TYPIFICATION.md +1 -1
  24. package/docs/brief.schema.json +173 -0
  25. package/examples/README.md +16 -2
  26. package/examples/access-control/NOTES.md +23 -0
  27. package/examples/access-control/anti/collapsed.json +439 -0
  28. package/examples/access-control/anti/weakened.json +439 -0
  29. package/examples/access-control/brief-v1.json +231 -0
  30. package/examples/access-control/brief.json +310 -0
  31. package/examples/access-control/build-rsform.ts +151 -0
  32. package/examples/access-control/requirements-v1.md +9 -0
  33. package/examples/access-control/requirements-v2.md +4 -0
  34. package/examples/access-control/rsform-session-v1.json +439 -0
  35. package/examples/access-control/rsform-session.json +523 -0
  36. package/examples/audit-diagnostics.ts +9 -1
  37. package/examples/briefs.test.ts +126 -0
  38. package/examples/clinic-booking/NOTES.md +24 -0
  39. package/examples/clinic-booking/anti/collapsed.json +615 -0
  40. package/examples/clinic-booking/anti/species-as-genus.json +568 -0
  41. package/examples/clinic-booking/brief-v1.json +332 -0
  42. package/examples/clinic-booking/brief.json +405 -0
  43. package/examples/clinic-booking/build-rsform.ts +174 -0
  44. package/examples/clinic-booking/requirements-v1.md +9 -0
  45. package/examples/clinic-booking/requirements-v2.md +4 -0
  46. package/examples/clinic-booking/rsform-session-v1.json +615 -0
  47. package/examples/clinic-booking/rsform-session.json +700 -0
  48. package/examples/diagnostics-utils.ts +26 -0
  49. package/examples/kinship/brief.json +337 -0
  50. package/examples/kinship/build-rsform.ts +7 -4
  51. package/examples/kinship/rsform-session.json +20 -63
  52. package/examples/kinship/rsmodel-session.json +14 -14
  53. package/examples/kinship-subordination/NOTES.md +49 -0
  54. package/examples/kinship-subordination/brief.json +161 -0
  55. package/examples/kinship-subordination/build-rsform.ts +100 -0
  56. package/examples/kinship-subordination/rsform-session.json +795 -0
  57. package/examples/library-loans/NOTES.md +46 -0
  58. package/examples/library-loans/anti/collapsed.json +481 -0
  59. package/examples/library-loans/anti/species-as-genus.json +334 -0
  60. package/examples/library-loans/anti/uncovered.json +406 -0
  61. package/examples/library-loans/anti/weakened.json +464 -0
  62. package/examples/library-loans/brief-v1.json +340 -0
  63. package/examples/library-loans/brief.json +502 -0
  64. package/examples/library-loans/build-rsform.ts +239 -0
  65. package/examples/library-loans/requirements-v1.md +11 -0
  66. package/examples/library-loans/requirements-v2.md +5 -0
  67. package/examples/library-loans/rsform-session-v1.json +481 -0
  68. package/examples/library-loans/rsform-session.json +652 -0
  69. package/examples/library-loans/source-markup.md +18 -0
  70. package/examples/movd/brief.json +220 -0
  71. package/examples/movd/build-rsform.ts +5 -2
  72. package/examples/movd/rsform-session.json +14 -46
  73. package/examples/subordination/NOTES.md +59 -0
  74. package/examples/subordination/anti/collapsed.json +556 -0
  75. package/examples/subordination/anti/single-relation.json +125 -0
  76. package/examples/subordination/anti/transitive.json +573 -0
  77. package/examples/subordination/anti/weakened.json +539 -0
  78. package/examples/subordination/brief.json +386 -0
  79. package/examples/subordination/build-rsform.ts +63 -0
  80. package/examples/subordination/constants.ts +115 -0
  81. package/examples/subordination/rsform-session.json +556 -0
  82. package/examples/subordination/source-markup.md +19 -0
  83. package/examples/subordination/source.md +40 -0
  84. package/package.json +18 -10
  85. package/skills/INSTALL.md +28 -29
  86. package/skills/README.md +25 -10
  87. package/skills/rs-core/SKILL.md +56 -0
  88. package/skills/rs-explain/SKILL.md +28 -0
  89. package/skills/rs-explicate/SKILL.md +71 -0
  90. package/skills/rs-interpret/SKILL.md +67 -0
  91. package/skills/rs-portal/SKILL.md +51 -0
  92. package/skills/rs-review/SKILL.md +79 -0
  93. package/skills/rs-scope/SKILL.md +67 -0
  94. package/skills/rs-synthesis/SKILL.md +52 -0
  95. package/skills/rstool/SKILL.md +70 -0
  96. package/skills/rstool-helper/GUIDE.md +18 -299
  97. package/skills/rstool-helper/SKILL.md +8 -58
  98. package/skills/{rstool-helper → rstool-runtime}/EXAMPLES.md +16 -12
  99. package/skills/{rstool-helper → rstool-runtime}/REFERENCE.md +7 -5
  100. package/skills/rstool-runtime/SKILL.md +96 -0
  101. package/skills/scripts/brief-check.test.ts +165 -0
  102. package/skills/scripts/brief-check.ts +826 -0
  103. package/skills/scripts/check-brief.ts +94 -0
  104. package/skills/scripts/rstool-skills.mjs +119 -0
  105. package/skills/scripts/skill-stubs.test.ts +48 -0
@@ -0,0 +1,405 @@
1
+ {
2
+ "version": 1,
3
+ "status": "confirmed",
4
+ "approach": "project",
5
+ "object": "Запись пациентов на приём к врачу",
6
+ "task": "Эксплицировать слоты, записи и приёмы для проектирования модуля записи и его проверок",
7
+ "source": "requirements-v1.md, requirements-v2.md",
8
+ "restrictions": [
9
+ {
10
+ "id": "L1",
11
+ "excludes": "уведомления (п. 6)",
12
+ "inexpressible": ["отправленное напоминание"]
13
+ },
14
+ {
15
+ "id": "L2",
16
+ "excludes": "оплата (п. 7)",
17
+ "inexpressible": ["оплаченный приём", "стоимость"]
18
+ },
19
+ {
20
+ "id": "L3",
21
+ "excludes": "отмены и история записей",
22
+ "inexpressible": ["отменённая запись", "неявка в прошлом месяце"]
23
+ }
24
+ ],
25
+ "expressiveness": [
26
+ {
27
+ "id": "E1",
28
+ "concept": "свободные слоты",
29
+ "ref": "п. 3",
30
+ "via": "слоты вне Pr2 записей",
31
+ "constituent": "D1"
32
+ },
33
+ {
34
+ "id": "E2",
35
+ "concept": "слоты, доступные пациенту",
36
+ "ref": "п. 4",
37
+ "via": "свободные без пересечения с записями пациента",
38
+ "constituent": "F4"
39
+ },
40
+ {
41
+ "id": "E3",
42
+ "concept": "пересечение слотов",
43
+ "ref": "п. 2, 4",
44
+ "via": "тот же день и перекрытие отрезков",
45
+ "constituent": "P1"
46
+ },
47
+ {
48
+ "id": "E4",
49
+ "concept": "состоявшиеся приёмы",
50
+ "ref": "п. 5",
51
+ "via": "родовая структура (пациент, слот) по DC3",
52
+ "constituent": "S5"
53
+ },
54
+ {
55
+ "id": "E5",
56
+ "concept": "слоты врача",
57
+ "ref": "п. 2",
58
+ "via": "фильтр «врач слота»",
59
+ "constituent": "F2"
60
+ },
61
+ {
62
+ "id": "E8",
63
+ "concept": "специальности врача",
64
+ "ref": "п. 1",
65
+ "via": "фильтр S1 по врачу",
66
+ "constituent": "F6"
67
+ },
68
+ {
69
+ "id": "E6",
70
+ "concept": "повторные записи",
71
+ "ref": "v2 п. 1",
72
+ "via": "родовая структура ⊆ записей по DC5",
73
+ "constituent": "S6"
74
+ },
75
+ {
76
+ "id": "E7",
77
+ "concept": "врачи, у которых пациент был",
78
+ "ref": "v2 п. 1",
79
+ "via": "врачи слотов состоявшихся приёмов",
80
+ "constituent": "F5"
81
+ }
82
+ ],
83
+ "distinctions": [
84
+ {
85
+ "id": "V1",
86
+ "between": ["E1", "E2"],
87
+ "text": "свободный слот ≠ доступный пациенту"
88
+ },
89
+ {
90
+ "id": "V2",
91
+ "between": ["S4", "S5"],
92
+ "text": "запись ≠ приём"
93
+ }
94
+ ],
95
+ "invariants": [
96
+ {
97
+ "id": "I1",
98
+ "text": "На слот не больше одной записи",
99
+ "ref": "п. 3",
100
+ "constituent": "A1",
101
+ "positive": "SC1",
102
+ "negative": "SC2"
103
+ },
104
+ {
105
+ "id": "I2",
106
+ "text": "Слоты одного врача не пересекаются",
107
+ "ref": "п. 2",
108
+ "constituent": "A2",
109
+ "positive": "SC1",
110
+ "negative": "SC3"
111
+ },
112
+ {
113
+ "id": "I3",
114
+ "text": "У слота один врач и одно время",
115
+ "constituent": "A3",
116
+ "positive": "SC1",
117
+ "negative": "SC6"
118
+ },
119
+ {
120
+ "id": "I4",
121
+ "text": "Начало слота раньше конца",
122
+ "constituent": "A4",
123
+ "positive": "SC1",
124
+ "negative": "SC4"
125
+ },
126
+ {
127
+ "id": "I5",
128
+ "text": "Приём бывает только по записи",
129
+ "ref": "п. 5",
130
+ "constituent": "A5",
131
+ "positive": "SC1",
132
+ "negative": "SC5"
133
+ },
134
+ {
135
+ "id": "I7",
136
+ "text": "У врача есть хотя бы одна специальность",
137
+ "ref": "п. 1",
138
+ "constituent": "A7",
139
+ "positive": "SC1",
140
+ "negative": "SC9"
141
+ },
142
+ {
143
+ "id": "I6",
144
+ "text": "Повторная запись — только к врачу, у которого пациент был",
145
+ "ref": "v2 п. 1",
146
+ "constituent": "A6",
147
+ "positive": "SC7",
148
+ "negative": "SC8"
149
+ }
150
+ ],
151
+ "decisions": [
152
+ {
153
+ "id": "DC1",
154
+ "topic": "слот",
155
+ "alternatives": ["слот как кортеж (врач, день, начало, конец)", "слот как сущность X4 с врачом и временем"],
156
+ "choice": "X4 и структуры S2, S3",
157
+ "rationale": "на слот ссылаются записи и приёмы; кортеж-слот пришлось бы повторять в каждой паре"
158
+ },
159
+ {
160
+ "id": "DC2",
161
+ "topic": "время",
162
+ "choice": "день и минуты как Z",
163
+ "rationale": "пересечение требует порядка и сравнения; календарные даты исключены (L3)"
164
+ },
165
+ {
166
+ "id": "DC3",
167
+ "topic": "приём",
168
+ "alternatives": ["приём = запись", "отдельная структура S5 ⊆ записей"],
169
+ "choice": "S5 и аксиома A5",
170
+ "rationale": "запись ≠ приём (п. 5): пациент может не прийти"
171
+ },
172
+ {
173
+ "id": "DC4",
174
+ "topic": "специальность записи",
175
+ "choice": "без аксиомы",
176
+ "rationale": "в требованиях нет правила о специальности при записи"
177
+ },
178
+ {
179
+ "id": "DC5",
180
+ "topic": "первичная и повторная запись",
181
+ "alternatives": [
182
+ "классификация: две структуры первичных и повторных + аксиомы разбиения",
183
+ "структура повторных; первичные — остаток"
184
+ ],
185
+ "choice": "S6 повторные записи; первичные — остальные записи",
186
+ "rationale": "разбиение полное по построению; аксиома нужна только «S6 ⊆ записей»"
187
+ }
188
+ ],
189
+ "scenarios": [
190
+ {
191
+ "id": "SC1",
192
+ "title": "Два врача, пациент p1 записан на s1 и пришёл",
193
+ "values": {
194
+ "X1": ["house", "cuddy"],
195
+ "X2": ["therapy", "surgery"],
196
+ "X3": ["p1", "p2"],
197
+ "X4": ["s1", "s2", "s3", "s4"],
198
+ "S1": [
199
+ ["house", "therapy"],
200
+ ["cuddy", "surgery"],
201
+ ["cuddy", "therapy"]
202
+ ],
203
+ "S2": [
204
+ ["s1", "house"],
205
+ ["s2", "house"],
206
+ ["s3", "cuddy"],
207
+ ["s4", "cuddy"]
208
+ ],
209
+ "S3": [
210
+ ["s1", 1, 540, 570],
211
+ ["s2", 1, 570, 600],
212
+ ["s3", 1, 540, 600],
213
+ ["s4", 2, 600, 630]
214
+ ],
215
+ "S4": [["p1", "s1"]],
216
+ "S5": [["p1", "s1"]]
217
+ },
218
+ "expect": [
219
+ {
220
+ "of": "D1",
221
+ "value": ["s2", "s3", "s4"]
222
+ },
223
+ {
224
+ "of": "F4",
225
+ "args": ["p1"],
226
+ "value": ["s2", "s4"],
227
+ "note": "s3 свободен, но пересекается с s1"
228
+ },
229
+ {
230
+ "of": "F4",
231
+ "args": ["p2"],
232
+ "value": ["s2", "s3", "s4"]
233
+ },
234
+ {
235
+ "of": "P1",
236
+ "args": ["s1", "s3"],
237
+ "holds": true
238
+ },
239
+ {
240
+ "of": "P1",
241
+ "args": ["s1", "s2"],
242
+ "holds": false,
243
+ "note": "стык 570 — не пересечение"
244
+ },
245
+ {
246
+ "of": "F2",
247
+ "args": ["house"],
248
+ "value": ["s1", "s2"]
249
+ },
250
+ {
251
+ "of": "F6",
252
+ "args": ["cuddy"],
253
+ "value": ["therapy", "surgery"]
254
+ }
255
+ ]
256
+ },
257
+ {
258
+ "id": "SC2",
259
+ "title": "Двойная запись на s1",
260
+ "base": "SC1",
261
+ "values": {
262
+ "S4": [
263
+ ["p1", "s1"],
264
+ ["p2", "s1"]
265
+ ]
266
+ },
267
+ "expect": [
268
+ {
269
+ "of": "A1",
270
+ "holds": false
271
+ }
272
+ ]
273
+ },
274
+ {
275
+ "id": "SC3",
276
+ "title": "Пересечение слотов врача",
277
+ "base": "SC1",
278
+ "values": {
279
+ "S3": [
280
+ ["s1", 1, 540, 570],
281
+ ["s2", 1, 560, 600],
282
+ ["s3", 1, 540, 600],
283
+ ["s4", 2, 600, 630]
284
+ ]
285
+ },
286
+ "expect": [
287
+ {
288
+ "of": "A2",
289
+ "holds": false
290
+ }
291
+ ]
292
+ },
293
+ {
294
+ "id": "SC4",
295
+ "title": "Слот задом наперёд",
296
+ "base": "SC1",
297
+ "values": {
298
+ "S3": [
299
+ ["s1", 1, 570, 540],
300
+ ["s2", 1, 570, 600],
301
+ ["s3", 1, 540, 600],
302
+ ["s4", 2, 600, 630]
303
+ ]
304
+ },
305
+ "expect": [
306
+ {
307
+ "of": "A4",
308
+ "holds": false
309
+ }
310
+ ]
311
+ },
312
+ {
313
+ "id": "SC5",
314
+ "title": "Приём без записи",
315
+ "base": "SC1",
316
+ "values": {
317
+ "S5": [
318
+ ["p1", "s1"],
319
+ ["p2", "s3"]
320
+ ]
321
+ },
322
+ "expect": [
323
+ {
324
+ "of": "A5",
325
+ "holds": false
326
+ }
327
+ ]
328
+ },
329
+ {
330
+ "id": "SC6",
331
+ "title": "Слот без врача",
332
+ "base": "SC1",
333
+ "values": {
334
+ "S2": [
335
+ ["s1", "house"],
336
+ ["s2", "house"],
337
+ ["s3", "cuddy"]
338
+ ]
339
+ },
340
+ "expect": [
341
+ {
342
+ "of": "A3",
343
+ "holds": false
344
+ }
345
+ ]
346
+ },
347
+ {
348
+ "id": "SC7",
349
+ "title": "Повторная запись к house",
350
+ "base": "SC1",
351
+ "values": {
352
+ "S4": [
353
+ ["p1", "s1"],
354
+ ["p1", "s2"]
355
+ ],
356
+ "S6": [["p1", "s2"]]
357
+ },
358
+ "expect": [
359
+ {
360
+ "of": "F5",
361
+ "args": ["p1"],
362
+ "value": ["house"]
363
+ },
364
+ {
365
+ "of": "A6",
366
+ "holds": true
367
+ }
368
+ ]
369
+ },
370
+ {
371
+ "id": "SC8",
372
+ "title": "Повторная запись к cuddy",
373
+ "base": "SC1",
374
+ "values": {
375
+ "S4": [
376
+ ["p1", "s1"],
377
+ ["p1", "s4"]
378
+ ],
379
+ "S6": [["p1", "s4"]]
380
+ },
381
+ "expect": [
382
+ {
383
+ "of": "A6",
384
+ "holds": false
385
+ }
386
+ ]
387
+ },
388
+ {
389
+ "id": "SC9",
390
+ "title": "Врач без специальности",
391
+ "base": "SC1",
392
+ "values": {
393
+ "S1": [["cuddy", "surgery"]]
394
+ },
395
+ "expect": [
396
+ {
397
+ "of": "A7",
398
+ "holds": false
399
+ }
400
+ ]
401
+ }
402
+ ],
403
+ "assumptions": ["у слота ровно один врач"],
404
+ "openQuestions": ["нужно ли правило «записываются к врачу нужной специальности»?"]
405
+ }
@@ -0,0 +1,174 @@
1
+ /**
2
+ * «Запись к врачу»: doctors' slots, bookings and visits (v1), repeat bookings (v2); anti-examples.
3
+ * Run from the package root: `pnpm run example:build-clinic-booking-schema`.
4
+ */
5
+ import { mkdirSync, writeFileSync } from 'node:fs';
6
+ import { resolve } from 'node:path';
7
+
8
+ import { type AgentConstituentaPatch, RSToolAgent } from '../../src';
9
+
10
+ import { describeBrief, formatDiagnostics } from '../diagnostics-utils';
11
+
12
+ const V1_ITEMS: AgentConstituentaPatch[] = [
13
+ { alias: 'X1', term: 'врачи', convention: 'Врачи клиники.' },
14
+ { alias: 'X2', term: 'специальности', convention: 'Врачебные специальности (терапевт, хирург, …).' },
15
+ { alias: 'X3', term: 'пациенты', convention: 'Пациенты, зарегистрированные в клинике.' },
16
+ {
17
+ alias: 'X4',
18
+ term: 'слоты',
19
+ convention: 'Слоты приёма: отрезок времени конкретного врача в конкретный день.'
20
+ },
21
+ {
22
+ alias: 'S1',
23
+ term: 'специальности врача',
24
+ definitionFormal: 'ℬ(X1×X2)',
25
+ convention: 'Пара (врач, специальность).'
26
+ },
27
+ {
28
+ alias: 'S2',
29
+ term: 'врач слота',
30
+ definitionFormal: 'ℬ(X4×X1)',
31
+ convention: 'Пара (слот, врач): врач ведёт приём в слоте.'
32
+ },
33
+ {
34
+ alias: 'S3',
35
+ term: 'время слота',
36
+ definitionFormal: 'ℬ(X4×Z×Z×Z)',
37
+ convention: 'Четвёрка (слот, день, начало, конец); начало и конец — минуты от начала дня.'
38
+ },
39
+ {
40
+ alias: 'S4',
41
+ term: 'записи',
42
+ definitionFormal: 'ℬ(X3×X4)',
43
+ convention: 'Пара (пациент, слот): пациент записан на слот.'
44
+ },
45
+ {
46
+ alias: 'S5',
47
+ term: 'состоявшиеся приёмы',
48
+ definitionFormal: 'ℬ(X3×X4)',
49
+ convention: 'Пара (пациент, слот): пациент пришёл на приём по записи.'
50
+ },
51
+ {
52
+ alias: 'F1',
53
+ term: 'время',
54
+ definitionFormal: '[σ∈X4] Pr2,3,4(Fi1[{σ}](S3))',
55
+ definitionText: 'День, начало и конец данного слота.'
56
+ },
57
+ {
58
+ alias: 'P1',
59
+ term: 'слоты пересекаются',
60
+ definitionFormal: '[σ∈X4, τ∈X4] σ≠τ & ∃(d1,b1,e1)∈F1[σ] ∃(d2,b2,e2)∈F1[τ] (d1=d2 & b1<e2 & b2<e1)',
61
+ definitionText: 'Два разных слота в один день перекрываются по времени.'
62
+ },
63
+ {
64
+ alias: 'F2',
65
+ term: 'слоты врача',
66
+ definitionFormal: '[δ∈X1] Pr1(Fi2[{δ}](S2))',
67
+ definitionText: 'Слоты, в которых ведёт приём данный врач.'
68
+ },
69
+ { alias: 'D1', term: 'свободные слоты', definitionFormal: 'X4\\Pr2(S4)', definitionText: 'Слоты без записи.' },
70
+ {
71
+ alias: 'F3',
72
+ term: 'слоты записей пациента',
73
+ definitionFormal: '[π∈X3] Pr2(Fi1[{π}](S4))',
74
+ definitionText: 'Слоты, на которые записан данный пациент.'
75
+ },
76
+ {
77
+ alias: 'F4',
78
+ term: 'слоты, доступные пациенту',
79
+ definitionFormal: '[π∈X3] D{σ∈D1 | ∀τ∈F3[π] ¬P1[σ,τ]}',
80
+ definitionText: 'Свободные слоты, которые не пересекаются с другими записями пациента.'
81
+ },
82
+ {
83
+ alias: 'A1',
84
+ term: 'одна запись на слот',
85
+ definitionFormal: 'card(Pr2(S4))=card(S4)',
86
+ definitionText: 'п. 3.'
87
+ },
88
+ {
89
+ alias: 'A2',
90
+ term: 'слоты врача не пересекаются',
91
+ definitionFormal: '∀δ∈X1 ∀σ,τ∈F2[δ] ¬P1[σ,τ]',
92
+ definitionText: 'п. 2.'
93
+ },
94
+ {
95
+ alias: 'A3',
96
+ term: 'у слота один врач и одно время',
97
+ definitionFormal: 'card(S2)=card(Pr1(S2)) & Pr1(S2)=X4 & card(S3)=card(Pr1(S3)) & Pr1(S3)=X4',
98
+ definitionText: 'Каждый слот принадлежит ровно одному врачу и имеет ровно одно время.'
99
+ },
100
+ {
101
+ alias: 'A4',
102
+ term: 'начало раньше конца',
103
+ definitionFormal: '∀σ∈S3 pr3(σ)<pr4(σ)',
104
+ definitionText: 'У слота положительная длительность.'
105
+ },
106
+ { alias: 'A5', term: 'приём только по записи', definitionFormal: 'S5⊆S4', definitionText: 'п. 5.' },
107
+ {
108
+ alias: 'F6',
109
+ term: 'специальности данного врача',
110
+ definitionFormal: '[δ∈X1] Pr2(Fi1[{δ}](S1))',
111
+ definitionText: 'Специальности, по которым ведёт приём данный врач.'
112
+ },
113
+ {
114
+ alias: 'A7',
115
+ term: 'у врача есть специальность',
116
+ definitionFormal: '∀δ∈X1 F6[δ]≠∅',
117
+ definitionText: 'п. 1: у врача одна или несколько специальностей.'
118
+ }
119
+ ];
120
+
121
+ const V2_ITEMS: AgentConstituentaPatch[] = [
122
+ {
123
+ alias: 'S6',
124
+ term: 'повторные записи',
125
+ definitionFormal: 'ℬ(X3×X4)',
126
+ convention: 'Пара (пациент, слот): запись отмечена как повторная. Остальные записи — первичные.'
127
+ },
128
+ {
129
+ alias: 'F5',
130
+ term: 'врачи, у которых пациент был',
131
+ definitionFormal: '[π∈X3] Pr2(Fi1[Pr2(Fi1[{π}](S5))](S2))',
132
+ definitionText: 'Врачи слотов состоявшихся приёмов данного пациента.'
133
+ },
134
+ {
135
+ alias: 'A6',
136
+ term: 'повторная запись — к знакомому врачу',
137
+ definitionFormal: 'S6⊆S4 & ∀(π,σ)∈S6 Pr2(Fi1[{σ}](S2))⊆F5[π]',
138
+ definitionText: 'Повторная запись — это запись, и её врач уже принимал пациента (v2 п. 1).'
139
+ }
140
+ ];
141
+
142
+ const ANTI_EXAMPLES: Record<string, AgentConstituentaPatch[]> = {
143
+ 'collapsed': V1_ITEMS.map(item => (item.alias === 'F4' ? { ...item, definitionFormal: '[π∈X3] D1\\F3[π]' } : item)),
144
+ 'species-as-genus': V1_ITEMS.filter(item => item.alias !== 'S5' && item.alias !== 'A5')
145
+ };
146
+
147
+ function build(title: string, brief: string, ...batches: AgentConstituentaPatch[][]): string {
148
+ const tool = new RSToolAgent();
149
+ tool.createSession({ alias: 'CLINIC', title, comment: describeBrief(resolve(import.meta.dirname, brief)) });
150
+ for (const [index, items] of batches.entries()) {
151
+ const result = tool.applySchemaPatch({ items, commitMessage: `${title}: шаг ${index + 1}` });
152
+ if (!result.success) {
153
+ throw new Error(`${title}: ${formatDiagnostics(result.diagnostics)}`);
154
+ }
155
+ }
156
+ const diagnostics = tool.listDiagnostics();
157
+ if (diagnostics.length > 0) {
158
+ throw new Error(`${title}: ${formatDiagnostics(diagnostics)}`);
159
+ }
160
+ return tool.exportSession() + '\n';
161
+ }
162
+
163
+ function run() {
164
+ const root = import.meta.dirname;
165
+ writeFileSync(resolve(root, 'rsform-session-v1.json'), build('Запись к врачу', 'brief-v1.json', V1_ITEMS));
166
+ writeFileSync(resolve(root, 'rsform-session.json'), build('Запись к врачу', 'brief.json', V1_ITEMS, V2_ITEMS));
167
+ mkdirSync(resolve(root, 'anti'), { recursive: true });
168
+ for (const [name, items] of Object.entries(ANTI_EXAMPLES)) {
169
+ writeFileSync(resolve(root, 'anti', `${name}.json`), build(`Антипример: ${name}`, 'brief-v1.json', items));
170
+ }
171
+ console.log(`clinic-booking: v1, v2 + ${Object.keys(ANTI_EXAMPLES).length} anti-examples`);
172
+ }
173
+
174
+ run();
@@ -0,0 +1,9 @@
1
+ # Требования «Запись к врачу», v1 (фрагмент)
2
+
3
+ 1. У врача одна или несколько специальностей.
4
+ 2. Врач ведёт приём в слотах: слот — время в конкретный день (начало и конец в минутах от начала дня). Слоты одного врача не пересекаются.
5
+ 3. Пациент записывается на свободный слот. На один слот — не больше одной записи.
6
+ 4. Пациенту показываются слоты, доступные для записи: свободные и не пересекающиеся по времени с его другими записями.
7
+ 5. Приём состоялся, если пациент пришёл по записи. Состоявшийся приём бывает только по записи.
8
+ 6. Пациент получает SMS-напоминание за сутки.
9
+ 7. Оплата приёма — через кассу или онлайн.
@@ -0,0 +1,4 @@
1
+ # Запрос на изменение v2
2
+
3
+ 1. Запись бывает первичной или повторной. Повторная запись — только к врачу, у которого пациент уже был на приёме.
4
+ 2. Всё из v1 работает как раньше.