@mostajs/medical-cases 0.2.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/CHANGELOG.md +30 -0
- package/README.md +49 -0
- package/llms.txt +48 -0
- package/package.json +39 -0
- package/src/index.js +4 -0
- package/src/medical-cases.js +120 -0
- package/src/memory-repo.js +7 -0
- package/src/schemas.js +29 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Changelog — @mostajs/medical-cases
|
|
2
|
+
|
|
3
|
+
Format [Keep a Changelog](https://keepachangelog.com/fr/1.0.0/) · [SemVer](https://semver.org/lang/fr/).
|
|
4
|
+
Auteur : Dr Hamid MADANI <drmdh@msn.com>
|
|
5
|
+
|
|
6
|
+
## [0.2.0] — 2026-06-21
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- **exams.cover** (prise en charge financière → status 'covered') — délègue à `@mostajs/aid-grants` (`aids.request`) si injecté, sinon référence fournie.
|
|
10
|
+
- **exams.orient** ({centerName} → 'oriented') · **exams.schedule** ({slot}/`@mostajs/booking-stack` → 'scheduled', `bookingRef`).
|
|
11
|
+
- **Codification CIM-10/CCAM de bout en bout** vérifiée en **intégration avec `@mostajs/nomenclature` réel** (`HEALTH_SEEDS`) : `diagnosis` (cim-10) et `exams.type` (ccam) validés/refusés (test `medical-cases-nomenclature.test.mjs`).
|
|
12
|
+
- Tests : +1 unit (cover/orient/schedule, DI stubs) + 2 intégration nomenclature. Total **7 unit + 2 intégration verts**.
|
|
13
|
+
|
|
14
|
+
## [0.1.0] — 2026-06-21
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- Cœur métier MINCE **dossier patient + examens + circuit de prise en charge** (P3 Salsabil — patients cancer). Patron `@mostajs/incubator`/`advisory`.
|
|
18
|
+
- `createMedicalCases({ repositories, nomenclature?, ged?, image?, statemachine?, booking?, aidGrants?, numbering?, audit?, now? })` — façade composant les capacités transverses par DI (aucune réimplémentation).
|
|
19
|
+
- **cases** : `open` (patient + diagnostic CIM-10 optionnel), `get`/`list`/`listByStatus`, `advance` (circuit 9 étapes), `setStatus`, `close`.
|
|
20
|
+
- **documents** : `add`/`list` (réf. `@mostajs/ged`).
|
|
21
|
+
- **exams** : `request` (crée le bulletin), `get`/`listByCase`, `realize`, `result` (réf. ged).
|
|
22
|
+
- **Codification optionnelle** : `diagnosis` validé en `cim-10` et `exams.request().type` en `ccam` si `@mostajs/nomenclature` injecté (seeder `HEALTH_SEEDS` — chacun son métier) ; sinon saisie libre.
|
|
23
|
+
- `EXAM_CATEGORIES` (imaging/specialized/biology/anapath), `cycle` (9 étapes), `statusLabel` (7 statuts métier), `dashboard`, `reportingSource` (→ `@mostajs/reporting`).
|
|
24
|
+
- Schémas `PatientCaseSchema`, `ExamSchema` + `createMemoryRepositories`.
|
|
25
|
+
- **6 tests `@mostajs/mjs-unit`** + **exemple §12** (`examples/prise-en-charge`, parcours complet vérifié).
|
|
26
|
+
- Proposition `docs/00-PROPOSITION-PLAN-MEDICAL-CASES-21062026.md` + livrables §9 #1–#3 (`docs/01..03`).
|
|
27
|
+
|
|
28
|
+
### À venir
|
|
29
|
+
- 0.2.0 : codification CIM/CCAM câblée bout-en-bout ; `exams.cover` (aid-grants) / `orient` / `schedule` (booking).
|
|
30
|
+
- 1.0.0 : imagerie (image), export, i18n, 14 livrables + #15/#16 (DPIA secret médical) + #17.
|
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# @mostajs/medical-cases
|
|
2
|
+
|
|
3
|
+
**Auteur** : Dr Hamid MADANI <drmdh@msn.com> · Licence : AGPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
Cœur métier **mince** : **dossier patient + examens + circuit de prise en charge** (imagerie / biologie / anatomopathologie). DB-agnostique. **Compose** (jamais ne réimplémente) : `@mostajs/nomenclature` (diagnostic CIM-10 / examen CCAM), `@mostajs/ged` (documents), `@mostajs/image` (imagerie), `@mostajs/statemachine`, `@mostajs/booking-stack`, `@mostajs/aid-grants` (financement), `@mostajs/numbering`, `@mostajs/audit`.
|
|
6
|
+
|
|
7
|
+
> Données de santé = **secret médical** : voir §11.3 (informatif) — base légale, minimisation, chiffrement au repos (`storage`), résidence pilotée par `.env`.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm i @mostajs/medical-cases
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Exemple minimal
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
import { createMedicalCases, createMemoryRepositories } from '@mostajs/medical-cases';
|
|
19
|
+
// Codification santé (chacun son métier) : injecter @mostajs/nomenclature seedé HEALTH_SEEDS.
|
|
20
|
+
// import { createNomenclature, HEALTH_SEEDS } from '@mostajs/nomenclature';
|
|
21
|
+
// const nomenclature = createNomenclature({ repositories, dataset: HEALTH_SEEDS, defaultScheme: 'cim-10' });
|
|
22
|
+
|
|
23
|
+
const m = createMedicalCases({ repositories: createMemoryRepositories() /*, nomenclature, ged, aidGrants, booking */ });
|
|
24
|
+
|
|
25
|
+
const c = await m.cases.open({ patient: { name: 'Fatima Z.' }, diagnosis: 'C50', physician: 'Dr Benali', facility: 'CAC' });
|
|
26
|
+
await m.documents.add(c.id, { gedDocId: 'ged-cr-001', kind: 'CR' });
|
|
27
|
+
const e = await m.exams.request(c.id, { type: 'IMG-TDM', category: 'imaging' }); // crée le bulletin
|
|
28
|
+
await m.exams.realize(e.id);
|
|
29
|
+
await m.exams.result(e.id, { gedDocId: 'ged-res-001' });
|
|
30
|
+
await m.cases.advance(c.id); // circuit de prise en charge (9 étapes → clôture)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## API (0.1.0)
|
|
34
|
+
|
|
35
|
+
- `createMedicalCases({ repositories, nomenclature?, ged?, image?, statemachine?, booking?, aidGrants?, numbering?, audit?, now? })`
|
|
36
|
+
- **cases** : `open({patient,diagnosis?,physician?,facility?,caregiver?})` · `get` · `list(filter)` · `listByStatus(s)` · `advance(id)` · `setStatus(id,s)` · `close(id,{outcome})`
|
|
37
|
+
- **documents** : `add(id,{gedDocId,kind?,label?})` · `list(id)`
|
|
38
|
+
- **exams** : `request(caseId,{type,category?})` (→ bulletin) · `get` · `listByCase` · `realize(examId)` · `result(examId,{gedDocId})`
|
|
39
|
+
- **dashboard()** · **reportingSource()** (→ `@mostajs/reporting`)
|
|
40
|
+
- `cycle` (9 étapes) · `categories` (= `EXAM_CATEGORIES`) · `statusLabel(s)` (7 statuts métier)
|
|
41
|
+
- `createMemoryRepositories()` · `PatientCaseSchema`, `ExamSchema`
|
|
42
|
+
|
|
43
|
+
## Codification (composer `@mostajs/nomenclature`)
|
|
44
|
+
|
|
45
|
+
Si `nomenclature` est injecté, `diagnosis` est validé en **`cim-10`** et `exams.request().type` en **`ccam`** (sinon saisie libre — dégradation gracieuse). Côté app santé : seeder `HEALTH_SEEDS`.
|
|
46
|
+
|
|
47
|
+
## Tests & exemple
|
|
48
|
+
|
|
49
|
+
`npm test` (`@mostajs/mjs-unit`, 6 tests) · `npm run example` (parcours de prise en charge, assertions).
|
package/llms.txt
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# @mostajs/medical-cases — llms.txt
|
|
2
|
+
|
|
3
|
+
RÔLE
|
|
4
|
+
Cœur métier MINCE : dossier patient + examens + circuit de prise en charge (imagerie/biologie/anatomopathologie).
|
|
5
|
+
Responsabilité UNIQUE : le domaine médical (patient, examens, PEC). AUCUNE capacité transverse réimplémentée.
|
|
6
|
+
Consommateur : associations d'aide aux malades / centres de prise en charge (P3 Salsabil — patients cancer).
|
|
7
|
+
Données de SANTÉ → secret médical (DEVRULES §11.3 informatif au déploiement).
|
|
8
|
+
|
|
9
|
+
COMPOSE (injection de dépendances, toutes optionnelles sauf repositories)
|
|
10
|
+
repositories OBLIGATOIRE — { cases, exams }. createMemoryRepositories() pour tests ; @mostajs/orm en prod.
|
|
11
|
+
nomenclature @mostajs/nomenclature — validation diagnostic (scheme 'cim-10') + type d'examen (scheme 'ccam').
|
|
12
|
+
Côté app santé : seeder HEALTH_SEEDS. Si absent → saisie libre (dégradation gracieuse).
|
|
13
|
+
ged @mostajs/ged — documents (CR, ordonnances, résultats). Réf. gedDocId seulement.
|
|
14
|
+
image @mostajs/image — imagerie (vignettes/EXIF) [0.3.0].
|
|
15
|
+
statemachine @mostajs/statemachine — moteur du circuit (en interne : cycle 9 étapes).
|
|
16
|
+
booking @mostajs/booking-stack — programmation d'examen / RDV [0.2.0].
|
|
17
|
+
aidGrants @mostajs/aid-grants — financement (prise en charge) de l'examen [0.2.0].
|
|
18
|
+
numbering @mostajs/numbering — n° dossier (MED-…) + bulletin (EXA-…).
|
|
19
|
+
audit @mostajs/audit — journal (traçabilité, secret médical).
|
|
20
|
+
HORS PÉRIMÈTRE : aides (aid-grants), documents (ged), RDV (booking), reporting (reporting), donateurs (donors).
|
|
21
|
+
|
|
22
|
+
EXPORTS (src/index.js)
|
|
23
|
+
createMedicalCases({ repositories, nomenclature?, ged?, image?, statemachine?, booking?, aidGrants?, numbering?, audit?, now? }) -> api
|
|
24
|
+
createMemoryRepositories() -> { cases, exams }
|
|
25
|
+
EXAM_CATEGORIES = ['imaging','specialized','biology','anapath']
|
|
26
|
+
PatientCaseSchema, ExamSchema (./schemas)
|
|
27
|
+
|
|
28
|
+
API
|
|
29
|
+
cases.open({patient{name,...}, diagnosis?(cim-10), physician?, facility?, caregiver?}) -> case (status 'requested')
|
|
30
|
+
cases.get/list(filter)/listByStatus(s) ; cases.advance(id) ; cases.setStatus(id,s) ; cases.close(id,{outcome})
|
|
31
|
+
documents.add(id,{gedDocId,kind?,label?}) ; documents.list(id)
|
|
32
|
+
exams.request(caseId,{type(ccam),category?}) -> exam (status 'bulletin', bulletinNo) ; exams.get/listByCase
|
|
33
|
+
exams.cover(examId,{grantRef?|amount,budgetLine?,type?}) -> 'covered' (délègue aid-grants.aids.request si injecté)
|
|
34
|
+
exams.orient(examId,{centerName}) -> 'oriented' ; exams.schedule(examId,{slot,bookingRef?}) -> 'scheduled' (délègue booking si injecté)
|
|
35
|
+
exams.realize(examId) ; exams.result(examId,{gedDocId})
|
|
36
|
+
dashboard() -> {total,enCours,clotures} ; reportingSource() -> () => cases[]
|
|
37
|
+
cycle (9): requested→bulletin→studying→covered→oriented→realized→results→archived→closed
|
|
38
|
+
statusLabel(s) -> 7 statuts métier spec ; categories = EXAM_CATEGORIES
|
|
39
|
+
|
|
40
|
+
PIÈGES
|
|
41
|
+
- repositories.cases ET repositories.exams requis.
|
|
42
|
+
- diagnosis/type validés UNIQUEMENT si nomenclature injecté (sinon stockés tels quels).
|
|
43
|
+
- Codes nomenclature normalisés sans points (C50.9→C509) — voir @mostajs/nomenclature.
|
|
44
|
+
- Le module ne porte PAS aides/documents/RDV/reporting : il les COMPOSE.
|
|
45
|
+
|
|
46
|
+
VERSIONS
|
|
47
|
+
0.1.0 : dossier + circuit + documents + exams.request/realize/result + dashboard + reportingSource (6 tests, exemple).
|
|
48
|
+
0.2.0 (prévu) : codification cim-10/ccam câblée + exams.cover(aid-grants)/orient/schedule(booking).
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mostajs/medical-cases",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Cœur métier MINCE de dossier patient + examens + circuit de prise en charge (imagerie/biologie/anapath). DB-agnostique ; COMPOSE nomenclature (cim-10/ccam), ged, image, statemachine, booking, aid-grants, numbering, audit. Générique santé (P3 Salsabil).",
|
|
5
|
+
"license": "AGPL-3.0-or-later",
|
|
6
|
+
"author": "Dr Hamid MADANI <drmdh@msn.com>",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "src/index.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"src",
|
|
11
|
+
"llms.txt",
|
|
12
|
+
"README.md",
|
|
13
|
+
"CHANGELOG.md"
|
|
14
|
+
],
|
|
15
|
+
"exports": {
|
|
16
|
+
".": "./src/index.js",
|
|
17
|
+
"./schemas": "./src/schemas.js"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"mostajs",
|
|
21
|
+
"medical",
|
|
22
|
+
"dossier-patient",
|
|
23
|
+
"prise-en-charge",
|
|
24
|
+
"examens",
|
|
25
|
+
"imagerie",
|
|
26
|
+
"anapath",
|
|
27
|
+
"cim-10",
|
|
28
|
+
"ccam",
|
|
29
|
+
"sante",
|
|
30
|
+
"salsabil"
|
|
31
|
+
],
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@mostajs/mjs-unit": "^0.3.0"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"test": "bash test-scripts/run-tests.sh",
|
|
37
|
+
"example": "node examples/prise-en-charge/run.mjs"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// @mostajs/medical-cases — point d'entrée. Author: Dr Hamid MADANI <drmdh@msn.com>
|
|
2
|
+
export { createMedicalCases, EXAM_CATEGORIES } from './medical-cases.js';
|
|
3
|
+
export { createMemoryRepositories } from './memory-repo.js';
|
|
4
|
+
export { PatientCaseSchema, ExamSchema } from './schemas.js';
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// @mostajs/medical-cases — cœur métier MINCE : dossier patient + examens + circuit de prise en charge. Author: Dr Hamid MADANI <drmdh@msn.com>
|
|
2
|
+
// N'implémente QUE le domaine médical (patient + examens + PEC). COMPOSE (injecté, optionnel) :
|
|
3
|
+
// nomenclature (cim-10 diagnostic / ccam examen), ged (documents), image (imagerie), statemachine, booking (programmation),
|
|
4
|
+
// aid-grants (financement), numbering (n° dossier/bulletin), audit. Secret médical → §11.3 (informatif) au déploiement.
|
|
5
|
+
|
|
6
|
+
// Circuit de prise en charge (9 étapes spec) :
|
|
7
|
+
const CYCLE = ['requested', 'bulletin', 'studying', 'covered', 'oriented', 'realized', 'results', 'archived', 'closed'];
|
|
8
|
+
// Correspondance informative vers les 7 statuts métier de la spec :
|
|
9
|
+
const STATUS_LABEL = {
|
|
10
|
+
requested: "en cours d'étude", bulletin: 'en attente de validation', studying: "en cours d'étude",
|
|
11
|
+
covered: 'prise en charge accordée', oriented: 'examen programmé', realized: 'examen réalisé',
|
|
12
|
+
results: 'résultats reçus', archived: 'résultats reçus', closed: 'dossier clôturé',
|
|
13
|
+
};
|
|
14
|
+
export const EXAM_CATEGORIES = ['imaging', 'specialized', 'biology', 'anapath'];
|
|
15
|
+
|
|
16
|
+
export function createMedicalCases({ repositories, ged, image, nomenclature, statemachine, booking, aidGrants, numbering, audit, now = () => new Date() } = {}) {
|
|
17
|
+
if (!repositories?.cases) throw new Error('createMedicalCases: repositories.cases requis');
|
|
18
|
+
if (!repositories?.exams) throw new Error('createMedicalCases: repositories.exams requis');
|
|
19
|
+
const { cases, exams } = repositories;
|
|
20
|
+
const caseNo = () => (numbering?.next ? numbering.next('medical-case') : `MED-${now().getFullYear()}-${String(Math.floor(now().getTime() % 100000)).padStart(5, '0')}`);
|
|
21
|
+
const bulletinNo = () => (numbering?.next ? numbering.next('exam-bulletin') : `EXA-${now().getFullYear()}-${String(Math.floor(now().getTime() % 100000)).padStart(5, '0')}`);
|
|
22
|
+
const trace = (type, data) => { audit?.log?.({ type, ...data, at: now() }); };
|
|
23
|
+
|
|
24
|
+
// Validation codifiée OPTIONNELLE (chacun dans son métier : nomenclature seedée HEALTH_SEEDS côté app santé).
|
|
25
|
+
const validateCode = async (code, scheme) => {
|
|
26
|
+
if (!code || !nomenclature?.codes?.validate) return code || null;
|
|
27
|
+
const v = await nomenclature.codes.validate(code, { scheme });
|
|
28
|
+
if (!v.ok) throw new Error(`code ${scheme} invalide: ${code}`);
|
|
29
|
+
return v.normalized || code;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const api = {
|
|
33
|
+
cycle: CYCLE, categories: EXAM_CATEGORIES, statusLabel: (s) => STATUS_LABEL[s] || s,
|
|
34
|
+
|
|
35
|
+
cases: {
|
|
36
|
+
/** Ouvre un dossier patient (status 'requested'). `diagnosis` = code CIM-10 (validé si nomenclature injectée). */
|
|
37
|
+
async open({ patient, diagnosis, physician, facility, caregiver } = {}) {
|
|
38
|
+
if (!patient?.name) throw new Error('medical.open: patient.name requis');
|
|
39
|
+
const dx = await validateCode(diagnosis, 'cim-10');
|
|
40
|
+
const r = await cases.create({ no: caseNo(), patient, diagnosis: dx, physician: physician || null, facility: facility || null, caregiver: caregiver || null, status: 'requested', documents: [] });
|
|
41
|
+
trace('medical.opened', { caseId: r.id }); return r;
|
|
42
|
+
},
|
|
43
|
+
get: (id) => cases.findById(id),
|
|
44
|
+
list: (f = {}) => cases.find((r) => Object.entries(f).every(([k, v]) => r[k] === v)),
|
|
45
|
+
listByStatus: (s) => cases.find((r) => r.status === s),
|
|
46
|
+
/** Avance d'une étape dans le circuit de prise en charge. */
|
|
47
|
+
async advance(id) {
|
|
48
|
+
const r = await cases.findById(id); if (!r) throw new Error('dossier introuvable');
|
|
49
|
+
const i = CYCLE.indexOf(r.status);
|
|
50
|
+
if (i < 0 || i >= CYCLE.length - 1) throw new Error(`transition impossible depuis '${r.status}'`);
|
|
51
|
+
const status = CYCLE[i + 1]; const patch = { status }; if (status === 'closed') patch.closedAt = now();
|
|
52
|
+
const u = await cases.update(id, patch); trace('medical.advanced', { caseId: id, status }); return u;
|
|
53
|
+
},
|
|
54
|
+
setStatus: async (id, status) => { if (!CYCLE.includes(status)) throw new Error('statut inconnu'); return cases.update(id, { status }); },
|
|
55
|
+
async close(id, { outcome } = {}) { const u = await cases.update(id, { status: 'closed', outcome: outcome || null, closedAt: now() }); trace('medical.closed', { caseId: id }); return u; },
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
documents: {
|
|
59
|
+
/** Rattache un document @mostajs/ged (CR, ordonnance, résultat, imagerie). Les octets restent dans ged. */
|
|
60
|
+
async add(id, { gedDocId, kind, label } = {}) {
|
|
61
|
+
const r = await cases.findById(id); if (!r) throw new Error('dossier introuvable');
|
|
62
|
+
if (!gedDocId) throw new Error('documents.add: gedDocId requis');
|
|
63
|
+
const documents = [...(r.documents || []), { gedDocId, kind: kind || null, label: label || null }];
|
|
64
|
+
return cases.update(id, { documents });
|
|
65
|
+
},
|
|
66
|
+
list: async (id) => { const r = await cases.findById(id); return r?.documents || []; },
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
exams: {
|
|
70
|
+
/** Demande un examen → crée le bulletin (status 'bulletin'). `type` = code CCAM (validé si nomenclature injectée). */
|
|
71
|
+
async request(caseId, { type, category } = {}) {
|
|
72
|
+
const r = await cases.findById(caseId); if (!r) throw new Error('dossier introuvable');
|
|
73
|
+
if (!type) throw new Error('exams.request: type requis');
|
|
74
|
+
if (category && !EXAM_CATEGORIES.includes(category)) throw new Error(`catégorie inconnue: ${category}`);
|
|
75
|
+
const t = await validateCode(type, 'ccam');
|
|
76
|
+
const e = await exams.create({ caseId, type: t, category: category || null, status: 'bulletin', bulletinNo: bulletinNo(), centerName: null, grantRef: null, bookingRef: null, resultDocId: null });
|
|
77
|
+
trace('exam.requested', { examId: e.id, caseId, type: t }); return e;
|
|
78
|
+
},
|
|
79
|
+
get: (examId) => exams.findById(examId),
|
|
80
|
+
listByCase: (caseId) => exams.find((e) => e.caseId === caseId),
|
|
81
|
+
/** Prise en charge financière de l'examen → status 'covered'. Délègue à @mostajs/aid-grants si injecté (sinon réf. fournie). */
|
|
82
|
+
async cover(examId, { grantRef, amount = 0, budgetLine, type = 'medicale' } = {}) {
|
|
83
|
+
const e = await exams.findById(examId); if (!e) throw new Error('examen introuvable');
|
|
84
|
+
let ref = grantRef || null;
|
|
85
|
+
if (!ref && aidGrants?.aids?.request) {
|
|
86
|
+
const aid = await aidGrants.aids.request(e.caseId, { type, amount, note: `Examen ${e.type} (${e.bulletinNo})`, budgetLine: budgetLine || null });
|
|
87
|
+
ref = aid?.ref || aid?.id || null;
|
|
88
|
+
}
|
|
89
|
+
const u = await exams.update(examId, { status: 'covered', grantRef: ref }); trace('exam.covered', { examId, grantRef: ref }); return u;
|
|
90
|
+
},
|
|
91
|
+
/** Orientation vers le centre de réalisation → status 'oriented'. */
|
|
92
|
+
async orient(examId, { centerName } = {}) {
|
|
93
|
+
const e = await exams.findById(examId); if (!e) throw new Error('examen introuvable');
|
|
94
|
+
if (!centerName) throw new Error('exams.orient: centerName requis');
|
|
95
|
+
return exams.update(examId, { status: 'oriented', centerName });
|
|
96
|
+
},
|
|
97
|
+
/** Programmation de l'examen → status 'scheduled'. Délègue à @mostajs/booking-stack si injecté (sinon réf./slot fournis). */
|
|
98
|
+
async schedule(examId, { slot, bookingRef } = {}) {
|
|
99
|
+
const e = await exams.findById(examId); if (!e) throw new Error('examen introuvable');
|
|
100
|
+
let ref = bookingRef || null;
|
|
101
|
+
if (!ref && booking?.book) { const b = await booking.book({ ref: e.bulletinNo, slot }); ref = b?.id || b?.ref || null; }
|
|
102
|
+
const u = await exams.update(examId, { status: 'scheduled', bookingRef: ref, slot: slot || null }); trace('exam.scheduled', { examId, bookingRef: ref }); return u;
|
|
103
|
+
},
|
|
104
|
+
/** Marque l'examen réalisé. */
|
|
105
|
+
async realize(examId) { const e = await exams.findById(examId); if (!e) throw new Error('examen introuvable'); return exams.update(examId, { status: 'realized', realizedAt: now() }); },
|
|
106
|
+
/** Enregistre le résultat (réf. document ged) → status 'results'. */
|
|
107
|
+
async result(examId, { gedDocId } = {}) { const e = await exams.findById(examId); if (!e) throw new Error('examen introuvable'); if (!gedDocId) throw new Error('exams.result: gedDocId requis'); return exams.update(examId, { status: 'results', resultDocId: gedDocId }); },
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
async dashboard() {
|
|
111
|
+
const all = await cases.find();
|
|
112
|
+
const by = (s) => all.filter((r) => r.status === s).length;
|
|
113
|
+
return { total: all.length, enCours: all.filter((r) => r.status !== 'closed').length, clotures: by('closed') };
|
|
114
|
+
},
|
|
115
|
+
/** Source pour @mostajs/reporting. */
|
|
116
|
+
reportingSource() { return async () => cases.find(); },
|
|
117
|
+
repositories,
|
|
118
|
+
};
|
|
119
|
+
return api;
|
|
120
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// @mostajs/medical-cases — repos in-mem pour tests/exemples (DB réelle via @mostajs/orm au déploiement). Author: Dr Hamid MADANI <drmdh@msn.com>
|
|
2
|
+
function coll() { const m = new Map(); return {
|
|
3
|
+
async create(d) { const id = d.id || globalThis.crypto.randomUUID(); const now = new Date(); const r = { id, createdAt: now, updatedAt: now, ...d }; m.set(id, r); return { ...r }; },
|
|
4
|
+
async findById(id) { const r = m.get(id); return r ? { ...r } : null; },
|
|
5
|
+
async update(id, p) { const r = m.get(id); if (!r) return null; const x = { ...r, ...p, updatedAt: new Date() }; m.set(id, x); return { ...x }; },
|
|
6
|
+
async find(f = () => true) { return [...m.values()].filter(f).map((r) => ({ ...r })); } }; }
|
|
7
|
+
export function createMemoryRepositories() { return { cases: coll(), exams: coll() }; }
|
package/src/schemas.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// @mostajs/medical-cases — schémas minces (dossier patient + examen). Author: Dr Hamid MADANI <drmdh@msn.com>
|
|
2
|
+
// Le patient est porté ici (assoc. d'aide aux malades) ; documents/imagerie → @mostajs/ged/@mostajs/image (réf. seulement) ;
|
|
3
|
+
// diagnostic (cim-10) & type d'examen (ccam) → @mostajs/nomenclature (codes seulement) ; financement → @mostajs/aid-grants (réf.).
|
|
4
|
+
|
|
5
|
+
export const PatientCaseSchema = { name: 'PatientCase', collection: 'medical_cases', timestamps: true, fields: {
|
|
6
|
+
no: { type: 'string' }, // → @mostajs/numbering (MED-2026-…)
|
|
7
|
+
patient: { type: 'json', required: true }, // { name, birthDate?, phone?, address? } — secret médical (§11.3)
|
|
8
|
+
diagnosis: { type: 'string', default: null }, // code CIM-10 → @mostajs/nomenclature (scheme 'cim-10')
|
|
9
|
+
physician: { type: 'string', default: null }, // médecin traitant
|
|
10
|
+
facility: { type: 'string', default: null }, // établissement de suivi
|
|
11
|
+
caregiver: { type: 'string', default: null }, // accompagnateur / tuteur
|
|
12
|
+
status: { type: 'string', enum: ['requested','bulletin','studying','covered','oriented','realized','results','archived','closed'], default: 'requested' },
|
|
13
|
+
documents: { type: 'array', default: [] }, // [{ gedDocId, kind, label }] réf. @mostajs/ged
|
|
14
|
+
outcome: { type: 'string', default: null },
|
|
15
|
+
closedAt: { type: 'date', default: null } },
|
|
16
|
+
indexes: [{ fields: { status: 'asc' } }] };
|
|
17
|
+
|
|
18
|
+
export const ExamSchema = { name: 'Exam', collection: 'medical_exams', timestamps: true, fields: {
|
|
19
|
+
caseId: { type: 'string', required: true }, // → PatientCase
|
|
20
|
+
type: { type: 'string', required: true }, // code CCAM → @mostajs/nomenclature (scheme 'ccam')
|
|
21
|
+
category: { type: 'string', enum: ['imaging','specialized','biology','anapath'], default: null },
|
|
22
|
+
status: { type: 'string', enum: ['bulletin','covered','oriented','scheduled','realized','results'], default: 'bulletin' },
|
|
23
|
+
bulletinNo: { type: 'string' }, // → @mostajs/numbering (EXA-2026-…)
|
|
24
|
+
centerName: { type: 'string', default: null }, // centre de réalisation (orientation)
|
|
25
|
+
grantRef: { type: 'string', default: null }, // → @mostajs/aid-grants (prise en charge financière)
|
|
26
|
+
bookingRef: { type: 'string', default: null }, // → @mostajs/booking-stack (programmation)
|
|
27
|
+
resultDocId: { type: 'string', default: null }, // → @mostajs/ged (résultats)
|
|
28
|
+
realizedAt: { type: 'date', default: null } },
|
|
29
|
+
indexes: [{ fields: { caseId: 'asc' } }] };
|