@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.
- package/README.md +3 -3
- package/dist/mappers/model-adapter.d.ts +1 -2
- package/dist/mappers/schema-adapter.d.ts +1 -2
- package/dist/mappers/types.d.ts +4 -5
- package/dist/models/rstool-agent.js +1 -2
- package/dist/session/session-store.d.ts +3 -3
- package/dist/session/session-store.js +1 -2
- package/dist/wrapper/client.d.ts +3 -4
- package/docs/BRIEF.md +93 -0
- package/docs/CONCEPTUAL-SCHEMA.md +16 -1
- package/docs/CONSTRUCTIONS.md +14 -2
- package/docs/DIAGNOSTICS.md +3 -2
- package/docs/DOMAIN.md +12 -0
- package/docs/METHOD.md +208 -0
- package/docs/PORTAL-API.md +38 -285
- package/docs/PORTAL-MODELS.md +24 -0
- package/docs/PORTAL-OSS.md +56 -0
- package/docs/PORTAL-PUBLISH.md +39 -0
- package/docs/PORTAL-READ.md +301 -0
- package/docs/README.md +7 -4
- package/docs/REQUIREMENTS.md +61 -0
- package/docs/SYNTAX.md +4 -3
- package/docs/TYPIFICATION.md +1 -1
- package/docs/brief.schema.json +173 -0
- package/examples/README.md +16 -2
- package/examples/access-control/NOTES.md +23 -0
- package/examples/access-control/anti/collapsed.json +439 -0
- package/examples/access-control/anti/weakened.json +439 -0
- package/examples/access-control/brief-v1.json +231 -0
- package/examples/access-control/brief.json +310 -0
- package/examples/access-control/build-rsform.ts +151 -0
- package/examples/access-control/requirements-v1.md +9 -0
- package/examples/access-control/requirements-v2.md +4 -0
- package/examples/access-control/rsform-session-v1.json +439 -0
- package/examples/access-control/rsform-session.json +523 -0
- package/examples/audit-diagnostics.ts +9 -1
- package/examples/briefs.test.ts +126 -0
- package/examples/clinic-booking/NOTES.md +24 -0
- package/examples/clinic-booking/anti/collapsed.json +615 -0
- package/examples/clinic-booking/anti/species-as-genus.json +568 -0
- package/examples/clinic-booking/brief-v1.json +332 -0
- package/examples/clinic-booking/brief.json +405 -0
- package/examples/clinic-booking/build-rsform.ts +174 -0
- package/examples/clinic-booking/requirements-v1.md +9 -0
- package/examples/clinic-booking/requirements-v2.md +4 -0
- package/examples/clinic-booking/rsform-session-v1.json +615 -0
- package/examples/clinic-booking/rsform-session.json +700 -0
- package/examples/diagnostics-utils.ts +26 -0
- package/examples/kinship/brief.json +337 -0
- package/examples/kinship/build-rsform.ts +7 -4
- package/examples/kinship/rsform-session.json +20 -63
- package/examples/kinship/rsmodel-session.json +14 -14
- package/examples/kinship-subordination/NOTES.md +49 -0
- package/examples/kinship-subordination/brief.json +161 -0
- package/examples/kinship-subordination/build-rsform.ts +100 -0
- package/examples/kinship-subordination/rsform-session.json +795 -0
- package/examples/library-loans/NOTES.md +46 -0
- package/examples/library-loans/anti/collapsed.json +481 -0
- package/examples/library-loans/anti/species-as-genus.json +334 -0
- package/examples/library-loans/anti/uncovered.json +406 -0
- package/examples/library-loans/anti/weakened.json +464 -0
- package/examples/library-loans/brief-v1.json +340 -0
- package/examples/library-loans/brief.json +502 -0
- package/examples/library-loans/build-rsform.ts +239 -0
- package/examples/library-loans/requirements-v1.md +11 -0
- package/examples/library-loans/requirements-v2.md +5 -0
- package/examples/library-loans/rsform-session-v1.json +481 -0
- package/examples/library-loans/rsform-session.json +652 -0
- package/examples/library-loans/source-markup.md +18 -0
- package/examples/movd/brief.json +220 -0
- package/examples/movd/build-rsform.ts +5 -2
- package/examples/movd/rsform-session.json +14 -46
- package/examples/subordination/NOTES.md +59 -0
- package/examples/subordination/anti/collapsed.json +556 -0
- package/examples/subordination/anti/single-relation.json +125 -0
- package/examples/subordination/anti/transitive.json +573 -0
- package/examples/subordination/anti/weakened.json +539 -0
- package/examples/subordination/brief.json +386 -0
- package/examples/subordination/build-rsform.ts +63 -0
- package/examples/subordination/constants.ts +115 -0
- package/examples/subordination/rsform-session.json +556 -0
- package/examples/subordination/source-markup.md +19 -0
- package/examples/subordination/source.md +40 -0
- package/package.json +18 -10
- package/skills/INSTALL.md +28 -29
- package/skills/README.md +25 -10
- package/skills/rs-core/SKILL.md +56 -0
- package/skills/rs-explain/SKILL.md +28 -0
- package/skills/rs-explicate/SKILL.md +71 -0
- package/skills/rs-interpret/SKILL.md +67 -0
- package/skills/rs-portal/SKILL.md +51 -0
- package/skills/rs-review/SKILL.md +79 -0
- package/skills/rs-scope/SKILL.md +67 -0
- package/skills/rs-synthesis/SKILL.md +52 -0
- package/skills/rstool/SKILL.md +70 -0
- package/skills/rstool-helper/GUIDE.md +18 -299
- package/skills/rstool-helper/SKILL.md +8 -58
- package/skills/{rstool-helper → rstool-runtime}/EXAMPLES.md +16 -12
- package/skills/{rstool-helper → rstool-runtime}/REFERENCE.md +7 -5
- package/skills/rstool-runtime/SKILL.md +96 -0
- package/skills/scripts/brief-check.test.ts +165 -0
- package/skills/scripts/brief-check.ts +826 -0
- package/skills/scripts/check-brief.ts +94 -0
- package/skills/scripts/rstool-skills.mjs +119 -0
- 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. Оплата приёма — через кассу или онлайн.
|