Diagnostic post-bench E2E (rapport docs/E2E_TEST_RUN_2026-05-08.md) :
1. BUG SILENCIEUX MAJEUR (api_stream.py:4549) — quand le pré-check OCR
rejette, mon code de rejet hier soir met x_pct=None / y_pct=None.
Le log structuré faisait result.get('x_pct', 0):.4f → None:.4f →
TypeError → réponse "analysis_error" qui MASQUE le vrai motif
"rejected_text_mismatch". Conséquence : pendant toute la session
du 7 mai soir, les rejets pré-check ont été silencieusement
transformés en erreurs analyse → cascade locale Léa V1 → clic au pif.
Fix : `(result.get('x_pct') or 0):.4f` traite None | None | 0
uniformément.
2. FLAG ENV pré-check OFF par défaut — le pré-check
_validate_text_at_position introduit hier soir a 2 défauts
identifiés par le bench E2E sur 8 click_anchor :
* radius_px=200 trop petit pour les tabs à 2 tokens (Examens
cliniques, Synthèse Urgences) — OCR voit un crop tronqué
"Maquette POC ler en cours Codage Statistiques" qui n'inclut
pas "Examens" → fuzzy match 1/2 = 50% < seuil 0.60 → REJET.
À radius 300/400 le mot est inclus → match passe.
* min_token_ratio=0.60 trop strict pour cibles 2 tokens.
Solution démo : flag env RPA_ENABLE_TEXT_PRECHECK (défaut "false").
Le pré-check est désactivé par défaut → retour au comportement
stable d'avant-hier (hybrid_text_direct ≥ 0.80 utilisé direct,
exemption drift préservée). Code et fonction _validate_text_at_position
conservés en place pour reprise post-démo après calibrage radius
adaptatif (≈ 0.17 × min(screen_w, screen_h)) et token_ratio descendu
à 0.50.
Pour ré-activer en dev/test : `RPA_ENABLE_TEXT_PRECHECK=true`
dans .env.local ou env du service rpa-streaming.
Inclus aussi :
- docs/E2E_TEST_RUN_2026-05-08.md (rapport agent test E2E ~1700 mots)
- tests/e2e/urgence_aiva_demo_expected.yaml (tolérances re-écrites)
- tests/e2e/fixtures/urgence_aiva_demo/live/*.png (8 fixtures
recapturées headless 1920x1080 pour itérer demain)
- _ocr_inventory.json + _run_resolve_results.json (raw runs)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
461 lines
20 KiB
Markdown
461 lines
20 KiB
Markdown
# Run E2E `Urgence_aiva_demo` — 8 mai 2026
|
||
|
||
> Audit ingénieur test/automation senior réalisé en J0 démo GHT Sud 95.
|
||
>
|
||
> Objectif : exécuter `tools/test_replay_e2e.py` sur des fixtures
|
||
> pertinentes (vrais screens de la maquette Easily Assure contenant
|
||
> les textes cibles), comparer les résolutions step-par-step à la
|
||
> baseline attendue, identifier les régressions concrètes introduites
|
||
> par les patches serveur du 7 mai soir (cure b584bbabc + pré-check
|
||
> OCR + exemption hybrid_text), et proposer des correctifs précis.
|
||
>
|
||
> AUCUN code serveur n'a été modifié. Lecture + harness + rapport
|
||
> uniquement.
|
||
|
||
## TL;DR (synthèse pour décision avant démo)
|
||
|
||
- **Cascade fonctionnelle sur 5/6 cibles testables** (`hybrid_text_direct`
|
||
résout `25003284`, `Imagerie`, `Notes médicales`, `Codage`,
|
||
`Coller dossier patient` lorsque la fixture représente le bon écran).
|
||
- **Régression confirmée** : pour `Examens cliniques` et `Synthèse
|
||
Urgences` (deux tabs en haut d'écran), le pre-check OCR à
|
||
`radius_px=200` voit un crop **trop étroit** pour capter le mot
|
||
cible → REJET → exception non rattrapée dans le log → réponse
|
||
fallback `analysis_error`. Touche au minimum **2 steps sur 6 démo**.
|
||
- **2 correctifs chirurgicaux** proposés (radius proportionnel à la
|
||
résolution écran, garde NoneType sur le format string). Effort
|
||
~10 lignes, risque très faible. Détails §5.
|
||
- Pour la démo dans la journée : **2 chemins** sont défendables —
|
||
(A) appliquer les correctifs (10 minutes, faible risque, valider en
|
||
retesting harness), ou (B) ne rien toucher et compter sur la
|
||
policy serveur qui transformera l'`analysis_error` en pause
|
||
supervisée + Plan B (fallback recorded coords). Recommandation :
|
||
**(A) si possible**, sinon (B) avec briefing préalable.
|
||
|
||
---
|
||
|
||
## 1. Inventaire fixtures
|
||
|
||
### 1.1 Diagnostic des heartbeats sur disque
|
||
|
||
Premier réflexe : utiliser les `heartbeat_*.png` du PC Windows.
|
||
Échec total — toutes les fixtures inspectées (300+ heartbeats des
|
||
bg_DESKTOP-58D5CAC_windows depuis mars 2026, sessions sess_* du 5
|
||
mai) montrent l'**explorateur Windows ou Chrome lambda**, pas la
|
||
maquette Easily Assure. Le workflow `Urgence_aiva_demo` a été
|
||
construit le 7 mai 2026 — il n'existe pas de heartbeat capturé
|
||
durant un usage réel de cette maquette.
|
||
|
||
Inventaire OCR (EasyOCR fr+en) sur 30 heartbeats stratifiés :
|
||
**0 fixture** ne contient un texte cible. Voir
|
||
`tests/e2e/fixtures/urgence_aiva_demo/_ocr_inventory.json` et
|
||
`_ocr_inventory_may5.json`.
|
||
|
||
### 1.2 Solution adoptée — fixtures live
|
||
|
||
Capture headless Chrome en 1920×1080 et 2560×1600 directement contre
|
||
la maquette en ligne (`https://urgence.labs.laurinebazin.design`,
|
||
auth basic `lea:Medecin2026!`), une fixture par écran d'intérêt :
|
||
|
||
| Step | by_text | Fixture (1920×1080) | OCR cible présent ? |
|
||
|------|---------|---------------------|---------------------|
|
||
| 3 | `25003284` | `live/landing.png` | OK |
|
||
| 8 | `Examens cliniques` | `live/dossier_motif.png` | OK |
|
||
| 10 | `Imagerie` | `live/dossier_examens-cliniques.png`| OK |
|
||
| 12 | `Notes médicales` | `live/dossier_imagerie.png` | OK |
|
||
| 14 | `Synthèse Urgences` | `live/dossier_notes-medicales.png` | OK |
|
||
| 17 | `Codage` | `live/dossier_synthese-urgences.png`| OK |
|
||
| 18 | `Coller ou saisir le dossier patient` | `live/dossier_codage.png` (proxy) | NON (page aiva-vision absente) |
|
||
| 20 | `Justification de la décision` | `live/dossier_codage.png` (proxy) | NON |
|
||
|
||
Limitations connues : la maquette ne route pas correctement les hash
|
||
URL (`#examens-cliniques`, `#imagerie`, ...) — tous les onglets
|
||
renvoient le même HTML. L'OCR confirme néanmoins que les 6 onglets
|
||
sont visibles dans le bandeau, ce qui suffit pour valider la
|
||
résolution `by_text` sur ces tabs. Les steps 18 et 20 ciblent la
|
||
page `aiva-vision` (en aval du clic sur "Codage >"), non capturée
|
||
ici — voir §6.
|
||
|
||
### 1.3 Anchor images comme fixtures alternatives
|
||
|
||
J'ai aussi téléchargé les 8 images d'ancres depuis VWB
|
||
(`/api/v3/anchor/<id>/image`) sous
|
||
`tests/e2e/fixtures/urgence_aiva_demo/step*.png` (2560×1600).
|
||
**Elles contiennent toutes leur `by_text`** mais sont des crops
|
||
zoomés (la position est non-représentative). Elles servent à valider
|
||
qu'`hybrid_text_direct` fonctionne (étape 0.5) mais leur drift par
|
||
rapport aux coords enregistrées est artefactuel — voir un précédent
|
||
run dans `_run_resolve_results.json`.
|
||
|
||
---
|
||
|
||
## 2. Run du harness
|
||
|
||
### 2.1 Méthode
|
||
|
||
Plutôt que `tools/test_replay_e2e.py` qui force le replay du
|
||
workflow complet (et bouclerait à cause des extract_text serveur,
|
||
pause_for_human, etc.), j'ai utilisé un appel direct ciblé à
|
||
`/api/v1/traces/stream/replay/resolve_target` avec, pour chaque
|
||
step click_anchor :
|
||
|
||
- `screenshot_b64` = la fixture du step
|
||
- `target_spec` = exactement ce que VWB compose
|
||
(`by_text`, `by_text_source: "ocr"`, `anchor_image_base64`,
|
||
`anchor_id`, `bounding_box`, `screen_resolution`)
|
||
- `fallback_x_pct` / `_y_pct` = centre normalisé de la bbox de
|
||
l'ancre (= les coords enregistrées)
|
||
- `strict_mode = True` (replay sessions)
|
||
|
||
Script : `/tmp/run_resolve_per_step.py` (non versionné).
|
||
|
||
> ATTENTION REPRO : la clé est `anchor_image_base64`, pas
|
||
> `anchor_image_b64`. Sans cette clé, le serveur tombe en mode
|
||
> non-strict (`has_anchor=False`), saute l'étape 0.5
|
||
> `hybrid_text_direct` et tape direct VLM puis ScreenAnalyzer
|
||
> (qui retourne `screen_analyzer_unavailable`). Premier run
|
||
> totalement faux à cause de cette typo — corrigé.
|
||
|
||
### 2.2 Résultats sur fixtures live (1920×1080)
|
||
|
||
| # | by_text | resolved | méthode | score | pos résolue | recorded | reason | ms |
|
||
|---|------------------------------------------|----------|-------------------------------|-------|-------------------|------------------|--------------------------------|-------|
|
||
| 1 | `25003284` | True | `hybrid_text_direct` | 1.000 | (0.0303, 0.1988) | (0.4928, 0.4512) | _drift IGNORÉ (exemption)_ | 1543 |
|
||
| 2 | `Examens cliniques` | **False**| `fallback` | 0.000 | (0.4980, 0.4928) | (0.498, 0.4928) | **`analysis_error`** | 1420 |
|
||
| 3 | `Imagerie` | True | `hybrid_text_direct` | 0.800 | (0.2256, 0.1267) | (0.498, 0.4928) | _drift IGNORÉ_ | 1372 |
|
||
| 4 | `Notes médicales` | True | `hybrid_text_direct` | 0.800 | (0.2227, 0.1259) | (0.202, 0.28) | drift OK | 976 |
|
||
| 5 | `Synthèse Urgences` | **False**| `fallback` | 0.000 | (0.2705, 0.2794) | (0.2705, 0.2794) | **`analysis_error`** | 1341 |
|
||
| 6 | `Codage` | True | `hybrid_text_direct` | 0.800 | (0.1392, 0.0538) | (0.3189, 0.2281) | _drift IGNORÉ_ | 1253 |
|
||
| 7 | `Coller ou saisir le dossier patient` | False | `strict_vlm_template_failed` | 0.000 | (0.0748, 0.4412) | - | `vlm_and_template_all_failed` (fixture invalide — page absente) | 4233 |
|
||
| 8 | `Justification de la décision` | False | `strict_vlm_template_failed` | 0.000 | (0.6482, 0.6228) | - | idem | 3586 |
|
||
|
||
> Score final côté cascade : **5 OK / 2 FAIL régression / 1 FAIL
|
||
> attendu (fixture mauvaise page) sur 8** quand on n'évalue que les
|
||
> steps avec fixture représentative. Régression brute = 2/6 = **33 %
|
||
> d'échecs sur les onglets démo**.
|
||
|
||
---
|
||
|
||
## 3. Divergences vs baseline
|
||
|
||
### 3.1 Bug #1 — Pre-check OCR rejette à tort sur `Examens cliniques` et `Synthèse Urgences` (radius trop petit)
|
||
|
||
Logs serveur (steps 8 et 14) :
|
||
|
||
```
|
||
Pre-check OCR REJET : 'Examens cliniques' attendu @ (0.2256, 0.1267) via hybrid_text_direct
|
||
mais OCR voit 'Maquette POC ler en cours Codage Statistiques Catherine Néle)le 14/03/1947 77 an' (80ms)
|
||
```
|
||
|
||
Reproduction isolée via `_validate_text_at_position` (script de
|
||
test inline) — sensibilité au radius :
|
||
|
||
| Cible | r=100 | r=150 | **r=200 (actuel)** | r=250 | r=300 | r=400 |
|
||
|--------------------|--------|--------|--------------------|--------|--------|--------|
|
||
| Examens cliniques | 0/2 | 0/2 | **1/2 (50 %)** | 2/2 OK | 2/2 OK | 2/2 OK |
|
||
| Synthèse Urgences | 0/2 | 0/2 | **0/2 (0 %)** | 1/2 | 2/2 OK | 2/2 OK |
|
||
| Notes médicales | 1/2 | 2/2 OK | 2/2 OK | OK | OK | OK |
|
||
| Imagerie | 1/1 OK | 1/1 OK | 1/1 OK | OK | OK | OK |
|
||
|
||
Sur 2560×1600 (resolution Windows réelle de Dom), même phénomène
|
||
mais déplacé : `Examens cliniques` reste FAIL jusqu'à r=400 (le tab
|
||
"Examens cliniques" est physiquement plus large en pixels qu'à
|
||
1920×1080).
|
||
|
||
**Cause profonde** : `radius_px=200` est **fixé en pixels absolus**
|
||
(resolve_engine.py:2246), or les éléments UI (largeur d'un tab)
|
||
varient avec la résolution. Pour des cibles courtes (1 token,
|
||
type "Imagerie") c'est OK ; pour des cibles à 2 tokens (`Examens
|
||
cliniques`, `Synthèse Urgences`) sur des bandeaux d'onglets à mi-écran
|
||
en haut, le crop tronque.
|
||
|
||
Aggravant : le seuil fuzzy à `0.60` exige 100 % des tokens pour les
|
||
cibles à 2 tokens (60 % de 2 = 1.2 → arrondi sup → 2/2). Si OCR
|
||
rate un token sur deux, REJET sec.
|
||
|
||
### 3.2 Bug #2 — Crash log RESOLVE_EXIT sur résultat None
|
||
|
||
Quand le pre-check rejette, `result` est remplacé par
|
||
(api_stream.py:4534-4542) :
|
||
|
||
```python
|
||
result = {
|
||
"resolved": False,
|
||
"method": "rejected_text_mismatch",
|
||
"reason": ...,
|
||
"x_pct": None,
|
||
"y_pct": None,
|
||
}
|
||
```
|
||
|
||
Puis le log (api_stream.py:4549) :
|
||
|
||
```python
|
||
f"coords=({result.get('x_pct', 0):.4f}, {result.get('y_pct', 0):.4f}) "
|
||
```
|
||
|
||
→ `result.get('x_pct', 0)` retourne **`None`** (la clé EXISTE et vaut
|
||
None — la valeur par défaut `0` n'est utilisée que si la clé est
|
||
absente). `None:.4f` lève `TypeError: unsupported format string
|
||
passed to NoneType.__format__`.
|
||
|
||
Conséquence : exception remontée → `_fallback_response("analysis_error",
|
||
str(e))` retourné côté client → la cascade côté `replay_engine.py`
|
||
voit `resolved=False, reason="analysis_error"` au lieu de
|
||
`reason="rejected_text_mismatch"`. La couche supérieure ne peut donc
|
||
plus traiter le rejet sémantique pour ce qu'il est — elle voit une
|
||
erreur d'analyse système.
|
||
|
||
Cumul des deux bugs : **le pre-check OCR fait perdre le clic en
|
||
cascade**, là où il aurait dû seulement rejeter ce candidat et
|
||
laisser la cascade continuer (VLM, SoM, template).
|
||
|
||
### 3.3 Drift exemption — fonctionne correctement
|
||
|
||
L'exemption hybrid_text_direct ≥ 0.80 fonctionne nominalement : 4
|
||
résolutions sur 5 ont un drift > 0.20 mais sont acceptées. Logs :
|
||
|
||
```
|
||
Drift (0.463, 0.252) > 0.20 IGNORÉ : score=1.000 sur hybrid_text_direct — résultat visuel fiable, on l'utilise
|
||
```
|
||
|
||
Aucun cas observé où l'exemption ait **fait passer un faux positif
|
||
visible**. Sur les fixtures testées, l'OCR direct trouve toujours
|
||
le bon texte exact (score 1.0) ou le bon avec OCR un peu bruité
|
||
(0.8). À surveiller en démo réelle si plusieurs occurrences du même
|
||
texte coexistent sur l'écran (ex : tableau patients avec plusieurs
|
||
IPP commençant par "2500..." — risque que `25003284` soit confondu
|
||
avec un voisin lexical).
|
||
|
||
---
|
||
|
||
## 4. Reproduction en isolation
|
||
|
||
```bash
|
||
cd /home/dom/ai/rpa_vision_v3 && source .venv/bin/activate
|
||
|
||
# Fixtures live (à recapturer à chaque démo si la maquette change)
|
||
mkdir -p tests/e2e/fixtures/urgence_aiva_demo/live
|
||
google-chrome --headless --disable-gpu --no-sandbox --window-size=1920,1080 \
|
||
--user-data-dir=/tmp/chrome_e2e \
|
||
--screenshot=tests/e2e/fixtures/urgence_aiva_demo/live/dossier_motif.png \
|
||
'https://lea:Medecin2026!@urgence.labs.laurinebazin.design/dossier.html?id=25003284'
|
||
|
||
# Test ciblé d'un step (exemple : step 8 Examens cliniques)
|
||
python3 - <<'PY'
|
||
import sys; sys.path.insert(0, '.')
|
||
from agent_v0.server_v1.resolve_engine import _validate_text_at_position
|
||
from PIL import Image
|
||
fp = 'tests/e2e/fixtures/urgence_aiva_demo/live/dossier_motif.png'
|
||
sw, sh = Image.open(fp).size
|
||
for r in (200, 250, 300, 350):
|
||
ok, obs, ms = _validate_text_at_position(fp, 0.2256, 0.1267, 'Examens cliniques', sw, sh, radius_px=r)
|
||
print(f'r={r} → valid={ok} ({ms:.0f}ms) obs={obs[:80]!r}')
|
||
PY
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Correctifs proposés (NON appliqués)
|
||
|
||
### Correctif #1 — Radius proportionnel à la résolution + fuzzy 0.50
|
||
|
||
**Fichier** : `agent_v0/server_v1/resolve_engine.py`
|
||
|
||
**Avant (ligne 2246)** :
|
||
|
||
```python
|
||
def _validate_text_at_position(
|
||
screenshot_path: str,
|
||
x_pct: float,
|
||
y_pct: float,
|
||
expected_text: str,
|
||
screen_width: int,
|
||
screen_height: int,
|
||
radius_px: int = 200,
|
||
) -> tuple:
|
||
```
|
||
|
||
**Après** :
|
||
|
||
```python
|
||
def _validate_text_at_position(
|
||
screenshot_path: str,
|
||
x_pct: float,
|
||
y_pct: float,
|
||
expected_text: str,
|
||
screen_width: int,
|
||
screen_height: int,
|
||
radius_px: Optional[int] = None,
|
||
) -> tuple:
|
||
# Radius proportionnel à la dimension écran la plus petite (≈ 17 % d'écran).
|
||
# Sur 1920×1080 → 184 px ; sur 2560×1600 → 272 px ; sur 3840×2160 → 367 px.
|
||
# Couvre les bandeaux d'onglets type Easily Assure tout en restant
|
||
# localement sémantique (pas la moitié d'écran).
|
||
if radius_px is None:
|
||
radius_px = int(0.17 * min(screen_width, screen_height))
|
||
```
|
||
|
||
Effet attendu sur la run : `Examens cliniques` à r=204 (au lieu de
|
||
200) reste tronqué côté droit ; à r=272 sur 2560×1600 c'est OK.
|
||
Combiné avec le correctif fuzzy ↓ ça passe.
|
||
|
||
**Avant (ligne 2285)** :
|
||
|
||
```python
|
||
is_valid = _text_match_fuzzy(expected_text, observed, min_token_ratio=0.60)
|
||
```
|
||
|
||
**Après** :
|
||
|
||
```python
|
||
is_valid = _text_match_fuzzy(expected_text, observed, min_token_ratio=0.50)
|
||
```
|
||
|
||
Justification : pour cibles à 2 tokens (`Examens cliniques`, `Synthèse
|
||
Urgences`, `Notes médicales`), 0.60 force 2/2 (= exact). 0.50 autorise
|
||
1/2 — suffisant pour valider que le bon zone OCR est probable, sans
|
||
sacrifier la spécificité (un token rare comme "synthèse" ou "examens"
|
||
suffit). Pour cibles à 4+ tokens (`Coller ou saisir le dossier
|
||
patient`), 0.50 demande 2/4 — cohérent avec le commentaire historique
|
||
de la fonction.
|
||
|
||
**Risque** : faux positif rare où un mot d'une cible apparaît dans une
|
||
zone sans rapport. Mitigé par le fait que :
|
||
- Le pre-check est appelé sur la zone où la cascade a déjà résolu
|
||
(donc visuellement fortement filtrée).
|
||
- Le seuil de score amont (`hybrid_text_direct ≥ 0.80`) garantit déjà
|
||
que le **mot exact** a été identifié.
|
||
|
||
**Steps impactés** : 8 (Examens cliniques), 14 (Synthèse Urgences) →
|
||
résolution OK au lieu d'échec.
|
||
|
||
### Correctif #2 — Garde NoneType sur le format string
|
||
|
||
**Fichier** : `agent_v0/server_v1/api_stream.py`
|
||
|
||
**Avant (ligne 4549)** :
|
||
|
||
```python
|
||
f"coords=({result.get('x_pct', 0):.4f}, {result.get('y_pct', 0):.4f}) "
|
||
```
|
||
|
||
**Après** :
|
||
|
||
```python
|
||
f"coords=({(result.get('x_pct') or 0):.4f}, {(result.get('y_pct') or 0):.4f}) "
|
||
```
|
||
|
||
Ou plus explicite et défensif pour les autres champs :
|
||
|
||
```python
|
||
_x = result.get('x_pct') if result else None
|
||
_y = result.get('y_pct') if result else None
|
||
logger.info(
|
||
f"[REPLAY] RESOLVE_EXIT session={request.session_id} "
|
||
f"resolved={(result or {}).get('resolved', False)} "
|
||
f"method='{(result or {}).get('method', 'none')}' "
|
||
f"coords=({(_x if _x is not None else 0):.4f}, {(_y if _y is not None else 0):.4f}) "
|
||
f"score={(result or {}).get('score', 0)} "
|
||
f"from_memory={bool((result or {}).get('from_memory', False))} "
|
||
f"reason='{(result or {}).get('reason', '')}'"
|
||
)
|
||
```
|
||
|
||
Effet attendu : pas d'exception. Le client reçoit le **vrai**
|
||
`{resolved: False, method: 'rejected_text_mismatch', reason: ...}`
|
||
au lieu d'un masque `analysis_error`. La couche supérieure peut
|
||
décider de retenter avec une autre méthode plutôt que de partir en
|
||
pause supervisée.
|
||
|
||
**Risque** : nul. Pure défensive contre None. Effort < 5 lignes.
|
||
|
||
### Correctif #3 (optionnel, à n'appliquer qu'après #1+#2) — Fallback de résolution post-rejet
|
||
|
||
Quand le pre-check rejette, ne pas tomber direct en `resolved: False`.
|
||
Continuer la cascade (VLM Quick Find, SoM) qui peut lever l'ambiguïté.
|
||
|
||
**Idée** : dans `api_stream.py` après le bloc pre-check (ligne ~4543),
|
||
si `result.method == "rejected_text_mismatch"`, ré-appeler
|
||
`_resolve_target_sync` avec `target_spec["__skip_ocr_direct"] = True`
|
||
pour forcer VLM/SoM. Trop intrusif pour le jour-J — à reporter.
|
||
|
||
---
|
||
|
||
## 6. Limitations & angles morts
|
||
|
||
- **Steps 18 et 20** non couverts : la fixture utilisée
|
||
(`dossier_codage.png`) ne contient pas la page aiva-vision
|
||
(textarea + bouton Justification). Pour les tester, il faudrait :
|
||
- soit cliquer "Codage >" et capturer la page aval (scriptable
|
||
avec puppeteer/playwright, ~1h),
|
||
- soit simuler un replay réel sur la maquette en démo et
|
||
enregistrer les heartbeats au passage. À planifier post-démo.
|
||
|
||
- **Fixtures statiques** : la maquette peut évoluer (Laurine peut
|
||
modifier le CSS/HTML à tout moment). Re-capturer
|
||
`tests/e2e/fixtures/urgence_aiva_demo/live/*.png` avant chaque
|
||
démo majeure.
|
||
|
||
- **Pas de test de la cascade VLM/SoM** : tous les steps testés ici
|
||
ont passé sur `hybrid_text_direct` (étape 0.5). La cascade VLM,
|
||
SoM, template_matching et ScreenAnalyzer n'a pas été stressée. Le
|
||
serveur a montré qu'elle est invocable (steps 7-8 sont allés
|
||
jusqu'au bout du `strict_vlm_template_failed`). Mais le timing
|
||
exact, les seuils, la cohérence des coords sur ces chemins
|
||
alternatifs — non couverts ici. Idéal : ajouter une fixture
|
||
délibérément sans le texte cible (juste l'icône) pour forcer
|
||
template_matching, et mesurer le score.
|
||
|
||
- **Drift exemption pas testée en mode adversarial** : aucun cas où
|
||
l'exemption a fait passer un mauvais clic. Sur la maquette
|
||
d'Easily Assure, les textes cibles sont uniques. Sur un DPI réel
|
||
(ex : 8 patients avec des IPP qui commencent par "2500..."), il
|
||
faut vérifier que `hybrid_text_direct` retourne le **bon** match
|
||
et pas le premier rencontré. À tester en démo.
|
||
|
||
- **Ce rapport est INCOMPLET** sur le point demandé "appel direct à
|
||
`_resolve_by_ocr_text` puis `_validate_text_at_position` avec
|
||
paramètres variés" : fait pour la validation seulement. Le vrai
|
||
test paramétrique de `_resolve_by_ocr_text` (variations de seuil
|
||
fuzzy interne, normalisation, langue OCR) reste à faire — peu
|
||
prioritaire car les scores actuels (0.8–1.0) sont sains.
|
||
|
||
---
|
||
|
||
## 7. YAML attendu mis à jour
|
||
|
||
Voir `tests/e2e/urgence_aiva_demo_expected.yaml` (re-écrit ce jour)
|
||
— format basé sur tolérances (range x_pct, y_pct, score_min) plutôt
|
||
que coordonnées rigides, pour ne pas casser à chaque ré-OCR.
|
||
|
||
---
|
||
|
||
## 8. Prochaines actions recommandées
|
||
|
||
1. **Maintenant** (avant démo si fenêtre disponible) : appliquer les
|
||
correctifs #1 et #2. Re-tester le harness. Risque très faible.
|
||
2. **Pendant démo** : si Synthèse Urgences ou Examens cliniques
|
||
échouent, c'est l'`analysis_error` — Plan B (recorded coords ou
|
||
pause supervisée) prend le relais. Briefing à Amina sur ce point.
|
||
3. **Post-démo** : capturer un replay réel complet, sauvegarder les
|
||
heartbeats, alimenter `tests/e2e/fixtures/urgence_aiva_demo/`
|
||
pour avoir des fixtures dossier+aiva-vision authentiques.
|
||
Valider `_run_resolve_results.json` comme baseline non-régressive.
|
||
4. **Plus tard** : intégrer le harness dans `pytest` avec marqueur
|
||
`@pytest.mark.e2e` (fixture par YAML, comparaison avec
|
||
tolérances). 1h d'effort.
|
||
|
||
---
|
||
|
||
*Auteur : Claude (agent test/automation senior). Aucune modification
|
||
de code ; rapport seul. Reproductions : voir §4. Fichiers livrés :*
|
||
|
||
- `docs/E2E_TEST_RUN_2026-05-08.md` (ce rapport)
|
||
- `tests/e2e/urgence_aiva_demo_expected.yaml` (YAML attendus
|
||
mis à jour)
|
||
- `tests/e2e/fixtures/urgence_aiva_demo/live/*.png` (fixtures
|
||
recapturées de la maquette en ligne)
|
||
- `tests/e2e/fixtures/urgence_aiva_demo/_run_resolve_results.json`
|
||
(dernier run brut)
|