Skip to content

Commit 2c93a9b

Browse files
committed
docs: add milestones planning
1 parent 024e998 commit 2c93a9b

5 files changed

Lines changed: 741 additions & 0 deletions

File tree

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
# M1 — Data Layer
2+
3+
> Objectif : aligner le schema DB et les Pydantic sur l'architecture KB / work_order
4+
> décidée dans `technical.md` §2.4.
5+
> Bloquant pour M2, M3, M4.
6+
7+
---
8+
9+
## Issue M1.1 — Migration 007 : `equipment_kb` colonnes manquantes
10+
11+
**Scope.** Étendre la table `equipment_kb` (créée en migration 005) avec les colonnes que
12+
les agents et le frontend Onboarding attendent.
13+
14+
**Fichier.** `backend/infrastructure/database/migrations/versions/007_aria_kb_workorder_extension.up.sql`
15+
16+
**Colonnes à ajouter.**
17+
18+
| Colonne | Type | Default | Rôle |
19+
|-----------------------|---------------|---------------|-----------------------------------------|
20+
| `structured_data` | `jsonb` | `'{}'::jsonb` | Document principal validé Pydantic |
21+
| `raw_markdown` | `text` | `null` | Sortie brute Opus avant parsing (audit) |
22+
| `confidence_score` | `real` | `0.0` | Complétude 0.0–1.0 |
23+
| `last_enriched_at` | `timestamptz` | `null` | Trace dernier enrichissement |
24+
| `onboarding_complete` | `boolean` | `false` | Flag UI Onboarding |
25+
26+
**Décision — migration destructive.**
27+
**DROP** des 3 colonnes héritées (`nominal_specs`, `common_failure_modes`,
28+
`maintenance_recommendations`). On est en dev, on drop la DB et on rejoue les
29+
migrations from scratch. `structured_data` est la seule source de vérité KB.
30+
31+
À faire dans la même migration 007 :
32+
```sql
33+
ALTER TABLE equipment_kb
34+
DROP COLUMN nominal_specs,
35+
DROP COLUMN common_failure_modes,
36+
DROP COLUMN maintenance_recommendations;
37+
```
38+
39+
Mettre à jour `006_aria_seed_p02.up.sql` pour seeder directement `structured_data`
40+
avec un blob `EquipmentKB` minimal valide (Grundfos CR 32-2 réaliste pour P-02).
41+
42+
**Acceptance.**
43+
- [ ] `make reset-db` (drop + recreate + apply 001→007) passe sans erreur
44+
- [ ] `\d equipment_kb` ne montre QUE les nouvelles colonnes (plus les 3 héritées)
45+
- [ ] Seed 006 produit un `structured_data` parsable par `EquipmentKB.model_validate()`
46+
- [ ] Pas de fichier `.down.sql`
47+
48+
---
49+
50+
## Issue M1.2 — Migration 007 : `work_order` colonnes manquantes
51+
52+
**Scope.** Étendre `work_order` pour porter les sorties d'agents.
53+
54+
**Colonnes à ajouter.**
55+
56+
| Colonne | Type | Default | Rôle |
57+
|------------------------|---------------|---------|------------------------------|
58+
| `rca_summary` | `text` | `null` | RCA produit par Investigator |
59+
| `recommended_actions` | `jsonb` | `null` | Actions structurées (steps) |
60+
| `generated_by_agent` | `boolean` | `false` | Distinguer manuels vs agents |
61+
| `trigger_anomaly_time` | `timestamptz` | `null` | Timestamp anomalie source |
62+
63+
**Note sur `status`.** La contrainte actuelle est
64+
`CHECK (status IN ('open','in_progress','completed','cancelled'))`. Le flow agent
65+
introduit `'detected'` (Sentinel) et `'analyzed'` (post-Investigator).
66+
67+
**DÉCIDÉ — étendre le CHECK.** Migration 007 :
68+
```sql
69+
ALTER TABLE work_order DROP CONSTRAINT work_order_status_check;
70+
ALTER TABLE work_order ADD CONSTRAINT work_order_status_check
71+
CHECK (status IN ('detected','analyzed','open','in_progress','completed','cancelled'));
72+
```
73+
Flow : `detected` (Sentinel) → `analyzed` (Investigator a posé `rca_summary`) →
74+
`open` (Work Order Generator a posé `recommended_actions`) → `in_progress`
75+
`completed`. Frontend filtre/colore par statut.
76+
77+
**Acceptance.**
78+
- [ ] Migration applique proprement
79+
- [ ] INSERT avec `status='detected'` accepté
80+
- [ ] `WorkOrderCreate` Pydantic mis à jour pour accepter ces champs (issue M1.6)
81+
82+
---
83+
84+
## Issue M1.3 — Migration 007 : `failure_history.signal_patterns`
85+
86+
**Scope.** Ajouter `signal_patterns jsonb` à `failure_history` pour que l'Investigator
87+
puisse stocker la signature signaux de la panne (utile pour le pattern matching dans
88+
les futures investigations).
89+
90+
**Colonne.** `signal_patterns jsonb DEFAULT NULL`
91+
92+
**Acceptance.**
93+
- [ ] Colonne présente
94+
- [ ] `FailureHistoryOut` Pydantic accepte le champ
95+
96+
---
97+
98+
## Issue M1.4 — Pydantic `EquipmentKB` complet
99+
100+
**Scope.** Créer `backend/modules/kb/kb_schema.py` avec les 4 classes définies dans
101+
`technical.md` §2.4 (`ThresholdValue`, `FailurePattern`, `MaintenanceProcedure`,
102+
`EquipmentKB`).
103+
104+
**Critères de design.**
105+
- Tous les sous-champs ont des defaults sains pour permettre une KB partielle
106+
(pendant l'onboarding, beaucoup de champs sont vides)
107+
- `EquipmentKB.kb_meta` doit contenir au minimum :
108+
`{version, completeness_score, onboarding_complete, last_calibrated_by}`
109+
- Une méthode utilitaire `EquipmentKB.compute_completeness() -> float` qui retourne
110+
un score 0.0–1.0
111+
112+
**DÉCIDÉ — `completeness_score` pondéré.** Algorithme :
113+
```
114+
weights = {
115+
"thresholds": 0.50, # cœur de la valeur (Sentinel les utilise)
116+
"failure_patterns": 0.20, # base du pattern matching Investigator
117+
"maintenance_procedures": 0.20, # nourrit le Work Order Generator
118+
"equipment": 0.10, # métadonnées identifiantes
119+
}
120+
score = Σ weight_i × (champs_remplis_i / champs_attendus_i)
121+
```
122+
Retourne float ∈ [0.0, 1.0]. Un threshold compte comme "rempli" si
123+
`alert IS NOT NULL`. Test seuil démo : Onboarding doit faire passer P-02 de ~0.40
124+
(PDF only) à ~0.85 (après calibration opérateur) — c'est le moment "aha".
125+
126+
**Acceptance.**
127+
- [ ] `from modules.kb.kb_schema import EquipmentKB; EquipmentKB(equipment={}, thresholds={}, ...).model_dump()` passe
128+
- [ ] `EquipmentKB.model_validate(json.loads(structured_data))` fonctionne sur seed P-02
129+
130+
---
131+
132+
## Issue M1.5 — Adapter `KbRepository.upsert()` pour `structured_data`
133+
134+
**Scope.** Modifier `backend/modules/kb/repository.py` :
135+
- Étendre `JSON_FIELDS` pour inclure `structured_data` (et `signal_patterns` côté failures)
136+
- Réécrire `EquipmentKbUpsert` Pydantic dans `schemas.py` :
137+
`structured_data: EquipmentKB`, `raw_markdown: str | None`,
138+
`confidence_score: float`, `last_enriched_at: datetime | None`,
139+
`onboarding_complete: bool`
140+
- **Supprimer** toute référence aux 3 anciens champs (`nominal_specs`,
141+
`common_failure_modes`, `maintenance_recommendations`) dans `schemas.py`,
142+
`repository.py`, `router.py`. Pas de retro-compat (cf. M1.1, on drop la DB).
143+
144+
**DÉCIDÉ — pas de retro-compat.** Suppression complète des 3 anciens champs
145+
dans le code. Une seule source : `structured_data`.
146+
147+
**Acceptance.**
148+
- [ ] `PUT /api/v1/kb/equipment` avec body contenant `structured_data` persiste correctement
149+
- [ ] `GET /api/v1/kb/equipment/2` retourne le `structured_data` parsé
150+
- [ ] Test : roundtrip Pydantic → JSON → DB → JSON → Pydantic identique
151+
152+
---
153+
154+
## Issue M1.6 — Mettre à jour `WorkOrderCreate` / `WorkOrderUpdate` Pydantic
155+
156+
**Scope.** Ajouter dans `backend/modules/work_order/schemas.py` les nouveaux champs
157+
de l'issue M1.2 :
158+
- `rca_summary: str | None`
159+
- `recommended_actions: Any | None` (JSONB)
160+
- `generated_by_agent: bool = False`
161+
- `trigger_anomaly_time: datetime | None`
162+
163+
Étendre `JSON_FIELDS` du `WorkOrderRepository` pour inclure `recommended_actions`.
164+
165+
**Acceptance.**
166+
- [ ] Création d'un work_order avec tous les nouveaux champs via API → 201
167+
- [ ] Lecture renvoie les champs correctement décodés
168+
169+
---
170+
171+
## Bloque
172+
173+
- M2 (les tools `get_equipment_kb` et `update_equipment_kb` ont besoin du schema)
174+
- M3 (KB Builder produit du `structured_data`)
175+
- M4 (Sentinel lit `thresholds` depuis `structured_data`, Investigator écrit
176+
`rca_summary` + `failure_history.signal_patterns`)
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
# M2 — MCP Server (14 tools)
2+
3+
> Objectif : exposer les 14 tools MCP via FastMCP monté sur FastAPI, et offrir un
4+
> client MCP (`MCPClient`) singleton pour les agents (cf. `technical.md` §2.1, §2.2).
5+
> Bloquant pour M3, M4, M5.
6+
7+
---
8+
9+
## Issue M2.1 — FastMCP server + mount
10+
11+
**Scope.** Créer `backend/mcp/server.py` qui instancie `FastMCP("aria-tools")` et
12+
expose un getter pour le `mount` côté `main.py`.
13+
14+
**Fichiers.**
15+
- `backend/mcp/__init__.py`
16+
- `backend/mcp/server.py`
17+
- `backend/main.py` (ajouter `app.mount("/mcp", mcp.streamable_http_app())`)
18+
19+
**Décision API.** FastMCP a deux modes HTTP : `streamable_http_app()` (recommandé) et
20+
SSE legacy. Utiliser **streamable HTTP** — c'est ce que l'Anthropic SDK + le Python
21+
client MCP supportent.
22+
23+
**Acceptance.**
24+
- [ ] `curl http://localhost:8000/mcp/` répond avec un endpoint MCP valide
25+
- [ ] `npx @modelcontextprotocol/inspector http://localhost:8000/mcp` se connecte et liste 0 tools (pour l'instant)
26+
27+
---
28+
29+
## Issue M2.2 — `tools.py` : 4 tools KPI
30+
31+
**Scope.** Implémenter les 4 tools KPI en wrappers async autour de `KpiRepository`
32+
existant (déjà branché sur les fonctions SQL `fn_oee`, `fn_mtbf`, `fn_mttr`).
33+
34+
**Tools.**
35+
- `get_oee(cell_ids: list[int], window_start: str, window_end: str) -> dict`
36+
- `get_mtbf(cell_ids: list[int], window_start: str, window_end: str) -> dict`
37+
- `get_mttr(cell_ids: list[int], window_start: str, window_end: str) -> dict`
38+
- `get_downtime_events(cell_ids: list[int], window_start: str, window_end: str, categories: list[str] | None = None) -> list[dict]`
39+
40+
**Pattern d'accès DB.** Les tools tournent dans le contexte du process FastAPI. Réutiliser
41+
la pool `db.pool` du module `core.database`.
42+
43+
**DÉCIDÉ — helper `_with_conn()`.** Créer dans `backend/mcp/tools.py` un
44+
async context manager qui acquire+release proprement la connection à chaque tool
45+
call :
46+
```python
47+
from contextlib import asynccontextmanager
48+
from core.database import db
49+
50+
@asynccontextmanager
51+
async def _with_conn():
52+
async with db.pool.acquire() as conn:
53+
yield conn
54+
```
55+
Chaque tool fait `async with _with_conn() as conn: ...`. Pas de DI FastAPI ici (les
56+
tools FastMCP n'ont pas accès à `Depends(get_db)`), pas de session persistante
57+
(évite les leaks si un agent boucle).
58+
59+
**Acceptance.**
60+
- [ ] Test : `MCPClient.call_tool("get_oee", {...})` sur P-02 retourne un dict cohérent
61+
62+
---
63+
64+
## Issue M2.3 — `tools.py` : 2 tools Signaux
65+
66+
**Tools.**
67+
- `get_signal_trends(signal_def_id: int, window_start: str, window_end: str, aggregation: str = "1m") -> list[dict]`
68+
→ query `process_signal_data` agrégée
69+
- `get_signal_anomalies(cell_id: int, window_start: str, window_end: str) -> list[dict]`
70+
→ compare valeurs vs `equipment_kb.structured_data.thresholds.*.alert`
71+
72+
**Dépendance.** `get_signal_anomalies` lit la KB → bloqué tant que M1.5 pas mergé.
73+
74+
**Acceptance.**
75+
- [ ] Tendance vibration P-02 sur 24h → liste de buckets temps/valeur
76+
- [ ] Si la KB P-02 a `vibration_mm_s.alert = 2.8`, et la valeur dépasse, l'anomalie remonte
77+
78+
---
79+
80+
## Issue M2.4 — `tools.py` : 3 tools Contexte humain
81+
82+
**Tools.**
83+
- `get_logbook_entries(cell_id: int, window_start: str, window_end: str) -> list[dict]`
84+
- `get_shift_assignments(cell_id: int, date_start: str, date_end: str) -> list[dict]`
85+
- `get_work_orders(cell_id: int | None, status: str | None, date_start: str | None, date_end: str | None) -> list[dict]`
86+
87+
Wrappers directs sur `LogbookRepository`, `ShiftRepository`, `WorkOrderRepository` existants.
88+
89+
**Acceptance.**
90+
- [ ] Chaque tool retourne les rows seedées pour P-02
91+
92+
---
93+
94+
## Issue M2.5 — `tools.py` : 3 tools KB
95+
96+
**Tools.**
97+
- `get_equipment_kb(cell_id: int) -> dict` → renvoie `structured_data` parsé
98+
- `get_failure_history(cell_id: int, limit: int = 50) -> list[dict]`
99+
- `update_equipment_kb(cell_id: int, structured_data_patch: dict, source: str, calibrated_by: str) -> dict`
100+
→ merge partiel sur `structured_data`, append `calibration_log`
101+
102+
> **DÉCIDÉ — exposer le tool tel quel (Option A).** Faire confiance au system
103+
> prompt du KB Builder. L'agent qui appelle ce tool est orchestré par notre code
104+
> (pas un agent libre tiers), le risque de pollution KB est maîtrisé. Bonus :
105+
> chaque write append une entry dans `calibration_log` avec `source` et
106+
> `calibrated_by`, donc tout est auditable.
107+
108+
**Acceptance.**
109+
- [ ] Patch sur `thresholds.vibration_mm_s.alert` est appliqué et visible via `get_equipment_kb`
110+
- [ ] `calibration_log` contient une nouvelle entrée
111+
112+
---
113+
114+
## Issue M2.6 — `tools.py` : 2 tools Production (peuvent être skippés)
115+
116+
**Tools.**
117+
- `get_quality_metrics(cell_ids: list[int], window_start: str, window_end: str) -> dict`
118+
- `get_production_stats(cell_ids: list[int], date_start: str, date_end: str) -> dict`
119+
120+
> **DÉCIDÉ — premier sacrifice si débord.** Le scénario démo P-02 ne consomme
121+
> pas ces tools dans le RCA. Ordre de priorité :
122+
> 1. Si M2.1→M2.5 OK à J4 midi → implem ces 2 tools (renforce le pitch "14 tools")
123+
> 2. Si retard → skip, livrer 12 tools, mentionner les 2 manquants comme
124+
> "scope étendu post-démo" dans le pitch
125+
> 3. Ne JAMAIS bloquer M3/M4/M5 pour ces 2 tools
126+
127+
**Acceptance.** Optionnelle.
128+
129+
---
130+
131+
## Issue M2.7 — `MCPClient` singleton (auto-discovery + call_tool)
132+
133+
**Scope.** Créer `backend/mcp/client.py` selon le pattern décrit dans `technical.md`
134+
§2.2 :
135+
- Classe `MCPClient(url: str)`
136+
- `await get_tools_schema() -> list[dict]` : connexion HTTP courte → `list_tools()`
137+
conversion format Anthropic SDK → cache mémoire
138+
- `await call_tool(name, arguments) -> str` : connexion HTTP courte → `call_tool`
139+
return text content
140+
141+
**Pattern lifecycle.** Connexion HTTP **par appel**, pas de session persistante (cf.
142+
note `technical.md` §2.2 sur le bug de closure). Overhead ~5–15ms localhost.
143+
144+
**Singleton.** Module-level instance : `mcp_client = MCPClient("http://localhost:8000/mcp")`.
145+
146+
> **DÉCIDÉ — HTTP loopback intra-process accepté.** Le `MCPClient` qui
147+
> appelle `localhost:8000/mcp` parle au même process FastAPI. Overhead ~5–15ms,
148+
> pas un bug. Avantage : un seul endpoint MCP réutilisable par d'autres clients
149+
> (Claude Desktop, MCP Inspector, futurs agents externes). Avantage clé pour le
150+
> pitch hackathon : "notre serveur MCP est exposable tel quel à n'importe quel
151+
> assistant LLM".
152+
153+
**Acceptance.**
154+
- [ ] `await mcp_client.get_tools_schema()` retourne la liste des 12–14 tools en format Anthropic
155+
- [ ] `await mcp_client.call_tool("get_oee", {...})` retourne le résultat sérialisé
156+
157+
---
158+
159+
## Issue M2.8 — Script de test isolation
160+
161+
**Scope.** `backend/tests/test_mcp_tools.py` (script standalone, pas pytest formel) :
162+
- Pour chaque tool, appel direct via `MCPClient` avec args sur P-02 (cell_id=2)
163+
- Print du résultat + assert non-vide
164+
165+
**But.** Avant J4, valider que les 14 tools tournent en isolation. Sans ça, debugger
166+
un agent loop est un cauchemar.
167+
168+
**Acceptance.**
169+
- [ ] `python -m backend.tests.test_mcp_tools` → 14 ✅ (ou 12 ✅ si M2.6 skippé)
170+
171+
---
172+
173+
## Bloque
174+
175+
- M3 (KB Builder utilise `update_equipment_kb` indirectement)
176+
- M4 (Sentinel lit signaux + KB ; Investigator a besoin des 14 tools)
177+
- M5 (Q&A consomme les 14 tools)

0 commit comments

Comments
 (0)