fix(replay): bug TypeError log + flag pré-check OCR off par défaut (démo GHT)
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>
@@ -4508,7 +4508,18 @@ async def resolve_target(request: ResolveTargetRequest):
|
||||
# présent. Attrape les cas où la cascade rend des coords plausibles
|
||||
# mais pointant sur un autre élément (ex : clic sur "Dossier en cours"
|
||||
# du menu au lieu de "Synthèse Urgences" du tab plus bas).
|
||||
if result and result.get("resolved"):
|
||||
#
|
||||
# 8 mai 2026 : désactivé par défaut pour la démo GHT. Calibrage du
|
||||
# radius_px et min_token_ratio à finaliser post-démo (cf. rapport
|
||||
# docs/E2E_TEST_RUN_2026-05-08.md). Le pré-check était trop strict
|
||||
# sur les onglets à 2 tokens (Examens cliniques, Synthèse Urgences)
|
||||
# → faux rejets → cascade locale Léa V1 → clic au pif. Réactivable
|
||||
# via env RPA_ENABLE_TEXT_PRECHECK=true. Le code et les tests
|
||||
# restent en place pour reprise post-démo.
|
||||
_text_precheck_enabled = os.environ.get(
|
||||
"RPA_ENABLE_TEXT_PRECHECK", "false"
|
||||
).lower() in ("true", "1", "yes")
|
||||
if _text_precheck_enabled and result and result.get("resolved"):
|
||||
_by_text = (request.target_spec.get("by_text") or "").strip()
|
||||
if _by_text:
|
||||
from agent_v0.server_v1.resolve_engine import _validate_text_at_position
|
||||
@@ -4542,11 +4553,17 @@ async def resolve_target(request: ResolveTargetRequest):
|
||||
}
|
||||
|
||||
# [REPLAY] log structuré de sortie résolution (après validation)
|
||||
# Note: x_pct/y_pct peuvent être None quand le pré-check OCR rejette
|
||||
# (rejected_text_mismatch). result.get('x_pct', 0) renvoie alors None
|
||||
# — la clé existe, le default 0 est ignoré — et None:.4f lève
|
||||
# TypeError. Fix : `(... or 0)` traite None/None/0 uniformément.
|
||||
_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.get('resolved', False) if result else False} "
|
||||
f"method='{result.get('method', '?') if result else 'none'}' "
|
||||
f"coords=({result.get('x_pct', 0):.4f}, {result.get('y_pct', 0):.4f}) "
|
||||
f"coords=({(_x or 0):.4f}, {(_y or 0):.4f}) "
|
||||
f"score={result.get('score', 0) if result else 0} "
|
||||
f"from_memory={bool(result.get('from_memory', False)) if result else False} "
|
||||
f"reason='{result.get('reason', '') if result else ''}'"
|
||||
|
||||
460
docs/E2E_TEST_RUN_2026-05-08.md
Normal file
@@ -0,0 +1,460 @@
|
||||
# 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)
|
||||
152
tests/e2e/fixtures/urgence_aiva_demo/_ocr_inventory.json
Normal file
@@ -0,0 +1,152 @@
|
||||
{
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773792436.png": {
|
||||
"found": [],
|
||||
"size": 415142,
|
||||
"ocr_dt": 5.3
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773792383.png": {
|
||||
"found": [],
|
||||
"size": 412395,
|
||||
"ocr_dt": 5.2
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773792331.png": {
|
||||
"found": [],
|
||||
"size": 407364,
|
||||
"ocr_dt": 5.2
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773792278.png": {
|
||||
"found": [],
|
||||
"size": 409614,
|
||||
"ocr_dt": 5.4
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773792225.png": {
|
||||
"found": [],
|
||||
"size": 410632,
|
||||
"ocr_dt": 5.3
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773792172.png": {
|
||||
"found": [],
|
||||
"size": 601747,
|
||||
"ocr_dt": 6.1
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773792119.png": {
|
||||
"found": [],
|
||||
"size": 524070,
|
||||
"ocr_dt": 5.6
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773792066.png": {
|
||||
"found": [],
|
||||
"size": 495872,
|
||||
"ocr_dt": 5.2
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773791846.png": {
|
||||
"found": [],
|
||||
"size": 349923,
|
||||
"ocr_dt": 4.7
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773791581.png": {
|
||||
"found": [],
|
||||
"size": 351106,
|
||||
"ocr_dt": 5.0
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773791381.png": {
|
||||
"found": [],
|
||||
"size": 469478,
|
||||
"ocr_dt": 5.7
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773791283.png": {
|
||||
"found": [],
|
||||
"size": 419376,
|
||||
"ocr_dt": 5.9
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773791053.png": {
|
||||
"found": [],
|
||||
"size": 451460,
|
||||
"ocr_dt": 7.4
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773790629.png": {
|
||||
"found": [],
|
||||
"size": 402427,
|
||||
"ocr_dt": 4.4
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773790480.png": {
|
||||
"found": [],
|
||||
"size": 403940,
|
||||
"ocr_dt": 5.3
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773789256.png": {
|
||||
"found": [],
|
||||
"size": 366536,
|
||||
"ocr_dt": 5.3
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773788105.png": {
|
||||
"found": [],
|
||||
"size": 414903,
|
||||
"ocr_dt": 5.3
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773787561.png": {
|
||||
"found": [],
|
||||
"size": 378032,
|
||||
"ocr_dt": 5.2
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773786527.png": {
|
||||
"found": [],
|
||||
"size": 1622254,
|
||||
"ocr_dt": 5.4
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773785450.png": {
|
||||
"found": [],
|
||||
"size": 353892,
|
||||
"ocr_dt": 5.2
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773785264.png": {
|
||||
"found": [],
|
||||
"size": 407159,
|
||||
"ocr_dt": 5.5
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773785115.png": {
|
||||
"found": [],
|
||||
"size": 375099,
|
||||
"ocr_dt": 5.4
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773784779.png": {
|
||||
"found": [],
|
||||
"size": 1029130,
|
||||
"ocr_dt": 7.4
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773784695.png": {
|
||||
"found": [],
|
||||
"size": 1729091,
|
||||
"ocr_dt": 5.5
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773784592.png": {
|
||||
"found": [],
|
||||
"size": 357796,
|
||||
"ocr_dt": 4.7
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773784539.png": {
|
||||
"found": [],
|
||||
"size": 420256,
|
||||
"ocr_dt": 4.4
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773783685.png": {
|
||||
"found": [],
|
||||
"size": 558014,
|
||||
"ocr_dt": 6.3
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773783627.png": {
|
||||
"found": [],
|
||||
"size": 582681,
|
||||
"ocr_dt": 5.2
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773783543.png": {
|
||||
"found": [],
|
||||
"size": 1208817,
|
||||
"ocr_dt": 5.3
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773783484.png": {
|
||||
"found": [],
|
||||
"size": 451052,
|
||||
"ocr_dt": 4.8
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/DESKTOP-58D5CAC_windows/sess_20260505T093148_6bf7eb/shots/shot_0001_full.png": {
|
||||
"found": [],
|
||||
"preview": "0 | Mode veille | Dites | Sortie de veille de l'accès vocal | ou appuyez | le bouton micro pour activer l'accès vocal. | 883 | 0 | 0 | [ € | M | @ | *l * | * | Q | 2<1616 * *l | Claude (MCP) | 0 @ + | 0 X | gitlabs laurinebazin design/?repo-search-query =1 | ABP | X | 8 | = | Claude (MCP) | Claude"
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/DESKTOP-58D5CAC_windows/sess_20260505T093148_6bf7eb/shots/shot_0002_full.png": {
|
||||
"found": [],
|
||||
"preview": "0 | Mode veille | Dites | Sortie de veille de l'accès vocal | ou appuyez | le bouton micro pour activer l'accès vocal. | {83 | Q | [ Q | 78777 | @ | X * * | Q | 2 0 0 * * * | Claude (MCP) | 6 @ + | gitlabs laurinebazin design/?repo-search-query =1 | ABP | X | 8 | = | Claude (MCP) | Claude | 88 | Al "
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/DESKTOP-58D5CAC_windows/sess_20260505T093148_6bf7eb/shots/shot_0003_full.png": {
|
||||
"found": [],
|
||||
"preview": "0 | Mode veille | Dites < Sortie de veille de l'accès vocal > ou appuyez sur le bouton micro pour activer l'accès vocal. | {83 | Enregistrement automatique | 8 9 ~ @ | Document3 | Rechercher | DB | X | Claude (MCP) | 6 @ + | Fichier | Accueil | Insertion | Dessin | Conception | Mise en page | Référe"
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/DESKTOP-58D5CAC_windows/sess_20260505T093148_6bf7eb/shots/shot_0004_full.png": {
|
||||
"found": [],
|
||||
"preview": "0 | Mode veille | Dites | Sortie de veille de l'accès vocal | ou appuyez sur le bouton micro pour activer l'accès vocal. | {83 | Enregistrement automatique | 8 2 ~ @ | Document3 | Rechercher | DB | X | Claude (MCP) | 6 @ + | Fichier | Accueil | Insertion | Dessin | Conception | Mise en page | Référe"
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/DESKTOP-58D5CAC_windows/sess_20260505T093148_6bf7eb/shots/shot_0005_full.png": {
|
||||
"found": [],
|
||||
"preview": "0 | Mode veille | Dites | Sortie de veille de l'accès vocal | ou appuyez sur le bouton micro pour activer l'accès vocal. | {83 | Enregistrement automatique | 8 2 ~ @ | Document3 | Rechercher | DB | X | Claude (MCP) | 6 @ + | Fichier | Accueil | Insertion | Dessin | Conception | Mise en page | Référe"
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/DESKTOP-58D5CAC_windows/sess_20260505T093148_6bf7eb/shots/heartbeat_1777966309.png": {
|
||||
"found": [],
|
||||
"preview": "0 | Mode veille | Dites | Sortie de veille de l'accès vocal | ou appuyez | le bouton micro pour activer l'accès vocal. | 883 | 0 | 0 | [ € | M | @ | *l * | * | Q | 2<1616 * *l | Claude (MCP) | 0 @ + | 0 X | gitlabs laurinebazin design/?repo-search-query =1 | ABP | X | 8 | = | Claude (MCP) | Claude"
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/DESKTOP-58D5CAC_windows/sess_20260505T093148_6bf7eb/shots/heartbeat_1777966315.png": {
|
||||
"found": [],
|
||||
"preview": "0 | Mode veille | Dites | Sortie de veille de l'accès vocal | ou appuyez | le bouton micro pour activer l'accès vocal. | {83 | Q | [ Q | 78777 | @ | X * * | Q | 2 0 0 * * * | Claude (MCP) | 6 @ + | gitlabs laurinebazin design/?repo-search-query =1 | ABP | X | 8 | = | Claude (MCP) | Claude | 88 | Al "
|
||||
},
|
||||
"/home/dom/ai/rpa_vision_v3/data/training/live_sessions/DESKTOP-58D5CAC_windows/sess_20260505T093148_6bf7eb/shots/heartbeat_1777966322.png": {
|
||||
"found": [],
|
||||
"preview": "0 | Mode veille | Dites | Sortie de veille de l'accès vocal | ou appuyez sur le bouton micro pour activer l'accès vocal. | {83 | Q | [ Q | 78777 | @ | X * * | Q | 2 0 0 * *l | Claude (MCP) | 6 @ + | gitlabs laurinebazin design/?repo-search-query =1 | ABP | X | 8 | = | Claude (MCP) | Claude | 88 | Al"
|
||||
}
|
||||
}
|
||||
168
tests/e2e/fixtures/urgence_aiva_demo/_run_resolve_results.json
Normal file
@@ -0,0 +1,168 @@
|
||||
[
|
||||
{
|
||||
"by_text": "25003284",
|
||||
"fixture": "live/landing.png",
|
||||
"result": {
|
||||
"resolved": true,
|
||||
"method": "hybrid_text_direct",
|
||||
"x_pct": 0.0302734375,
|
||||
"y_pct": 0.1987847222222222,
|
||||
"score": 1.0,
|
||||
"matched_text": "25003284",
|
||||
"_dt_ms": 1542.8798198699951,
|
||||
"_recorded": [
|
||||
0.4928,
|
||||
0.4512
|
||||
],
|
||||
"_screen_size": [
|
||||
1920,
|
||||
1080
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"by_text": "Examens cliniques",
|
||||
"fixture": "live/dossier_motif.png",
|
||||
"result": {
|
||||
"resolved": false,
|
||||
"method": "fallback",
|
||||
"reason": "analysis_error",
|
||||
"detail": "unsupported format string passed to NoneType.__format__",
|
||||
"x_pct": 0.498046875,
|
||||
"y_pct": 0.4928125,
|
||||
"_dt_ms": 1420.240879058838,
|
||||
"_recorded": [
|
||||
0.498,
|
||||
0.4928
|
||||
],
|
||||
"_screen_size": [
|
||||
1920,
|
||||
1080
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"by_text": "Imagerie",
|
||||
"fixture": "live/dossier_examens-cliniques.png",
|
||||
"result": {
|
||||
"resolved": true,
|
||||
"method": "hybrid_text_direct",
|
||||
"x_pct": 0.2255859375,
|
||||
"y_pct": 0.1267361111111111,
|
||||
"score": 0.8,
|
||||
"matched_text": "Motif d'admission Examens cliniques Imagerie Notes médicales Synthèse Urgences Codage >",
|
||||
"_dt_ms": 1372.1542358398438,
|
||||
"_recorded": [
|
||||
0.498,
|
||||
0.4928
|
||||
],
|
||||
"_screen_size": [
|
||||
1920,
|
||||
1080
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"by_text": "Notes médicales",
|
||||
"fixture": "live/dossier_imagerie.png",
|
||||
"result": {
|
||||
"resolved": true,
|
||||
"method": "hybrid_text_direct",
|
||||
"x_pct": 0.22265625,
|
||||
"y_pct": 0.12586805555555555,
|
||||
"score": 0.8,
|
||||
"matched_text": "Motif d'admission Examens cliniques Imagerie Notes médicales Synthèse Urgences Codage >>",
|
||||
"_dt_ms": 975.5856990814209,
|
||||
"_recorded": [
|
||||
0.202,
|
||||
0.28
|
||||
],
|
||||
"_screen_size": [
|
||||
1920,
|
||||
1080
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"by_text": "Synthèse Urgences",
|
||||
"fixture": "live/dossier_notes-medicales.png",
|
||||
"result": {
|
||||
"resolved": false,
|
||||
"method": "fallback",
|
||||
"reason": "analysis_error",
|
||||
"detail": "unsupported format string passed to NoneType.__format__",
|
||||
"x_pct": 0.2705078125,
|
||||
"y_pct": 0.279375,
|
||||
"_dt_ms": 1341.4692878723145,
|
||||
"_recorded": [
|
||||
0.2705,
|
||||
0.2794
|
||||
],
|
||||
"_screen_size": [
|
||||
1920,
|
||||
1080
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"by_text": "Codage",
|
||||
"fixture": "live/dossier_synthese-urgences.png",
|
||||
"result": {
|
||||
"resolved": true,
|
||||
"method": "hybrid_text_direct",
|
||||
"x_pct": 0.13916015625,
|
||||
"y_pct": 0.05381944444444445,
|
||||
"score": 0.8,
|
||||
"matched_text": "Patients Planning Dossier en cours Codage Statistiques",
|
||||
"_dt_ms": 1252.6636123657227,
|
||||
"_recorded": [
|
||||
0.3189,
|
||||
0.2281
|
||||
],
|
||||
"_screen_size": [
|
||||
1920,
|
||||
1080
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"by_text": "Coller ou saisir le dossier patient",
|
||||
"fixture": "live/dossier_codage.png",
|
||||
"result": {
|
||||
"resolved": false,
|
||||
"method": "strict_vlm_template_failed",
|
||||
"reason": "vlm_and_template_all_failed",
|
||||
"x_pct": 0.0748046875,
|
||||
"y_pct": 0.44125,
|
||||
"_dt_ms": 4233.16764831543,
|
||||
"_recorded": [
|
||||
0.0748,
|
||||
0.4412
|
||||
],
|
||||
"_screen_size": [
|
||||
1920,
|
||||
1080
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"by_text": "Justification de la décision",
|
||||
"fixture": "live/dossier_codage.png",
|
||||
"result": {
|
||||
"resolved": false,
|
||||
"method": "strict_vlm_template_failed",
|
||||
"reason": "vlm_and_template_all_failed",
|
||||
"x_pct": 0.6482421875,
|
||||
"y_pct": 0.6228125,
|
||||
"_dt_ms": 3586.3852500915527,
|
||||
"_recorded": [
|
||||
0.6482,
|
||||
0.6228
|
||||
],
|
||||
"_screen_size": [
|
||||
1920,
|
||||
1080
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
BIN
tests/e2e/fixtures/urgence_aiva_demo/live/dossier_codage.png
Normal file
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 140 KiB |
BIN
tests/e2e/fixtures/urgence_aiva_demo/live/dossier_imagerie.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
tests/e2e/fixtures/urgence_aiva_demo/live/dossier_motif.png
Normal file
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 140 KiB |
BIN
tests/e2e/fixtures/urgence_aiva_demo/live/landing.png
Normal file
|
After Width: | Height: | Size: 149 KiB |
@@ -1,81 +1,138 @@
|
||||
workflow_session_id: test_e2e_sess_20260507T220822_c91f30
|
||||
screenshot: /home/dom/ai/rpa_vision_v3/data/training/live_sessions/bg_DESKTOP-58D5CAC_windows/shots/heartbeat_1773792436.png
|
||||
# Attendus E2E pour wf_a38aeebea5e6_1778162737 (Urgence_aiva_demo).
|
||||
#
|
||||
# Mis à jour 2026-05-08 sur fixtures Easily Assure capturées en live
|
||||
# (`tests/e2e/fixtures/urgence_aiva_demo/live/*.png`, headless Chrome
|
||||
# 1920x1080) — donc representatives du screen tel que vu par Léa.
|
||||
#
|
||||
# Tolérance : la résolution de coordonnées varie de quelques pixels d'un
|
||||
# run à l'autre (anti-aliasing OCR, EasyOCR non déterministe). On se
|
||||
# limite donc à valider :
|
||||
# - status (OK / FAIL)
|
||||
# - method (préfixe)
|
||||
# - score ≥ seuil
|
||||
# - position dans une bbox attendue (en pourcentages, large)
|
||||
#
|
||||
# Steps NON couverts ici :
|
||||
# - 1, 4-7, 9, 11, 13, 15-16, 19, 21 (extract_text, keyboard_shortcut,
|
||||
# type_text, t2a_decision, pause_for_human → exécutés serveur ou
|
||||
# simulés client, pas de dépendance à la cascade visuelle).
|
||||
#
|
||||
# Couverts (click_anchor) :
|
||||
# 3, 8, 10, 12, 14, 17, 18, 20.
|
||||
#
|
||||
# Steps 18 (Coller textarea DPI) et 20 (Justification) attendus en
|
||||
# pause_supervisée si l'écran courant est la maquette urgences (et non
|
||||
# aiva-vision) — cf. §"Limitations fixtures" du rapport.
|
||||
|
||||
workflow_id: wf_a38aeebea5e6_1778162737
|
||||
fixtures_dir: tests/e2e/fixtures/urgence_aiva_demo/live
|
||||
generated_at: '2026-05-08'
|
||||
screen_size_default: [1920, 1080]
|
||||
|
||||
steps:
|
||||
- order: 1
|
||||
action_id: wait_before_start
|
||||
action_type: wait
|
||||
by_text: ''
|
||||
method: simulated
|
||||
score: 0.0
|
||||
x_pct: null
|
||||
y_pct: null
|
||||
status: OK
|
||||
diag: wait simulé
|
||||
elapsed_ms: 1.013040542602539
|
||||
- order: 2
|
||||
action_id: replay_free_74c2d90b
|
||||
action_type: pause:user_request
|
||||
by_text: ''
|
||||
method: ''
|
||||
score: 0.0
|
||||
x_pct: null
|
||||
y_pct: null
|
||||
status: PAUSED
|
||||
diag: 'Léa : j''ai trouvé ces dossiers : []. Pour la démo je vais traiter MOREL
|
||||
Catherin'
|
||||
elapsed_ms: 0.0
|
||||
- order: 3
|
||||
action_id: step_288d0bceea90_1778162737752
|
||||
action_type: click
|
||||
action_type: click_anchor
|
||||
by_text: '25003284'
|
||||
method: fallback
|
||||
score: 0.0
|
||||
x_pct: 0.5
|
||||
y_pct: 0.5
|
||||
status: FAIL
|
||||
diag: template_matching_failed
|
||||
elapsed_ms: 1064.7194385528564
|
||||
- order: 4
|
||||
action_id: step_288d0bceea90_1778162737752_retry1
|
||||
action_type: click
|
||||
by_text: '25003284'
|
||||
method: fallback
|
||||
score: 0.0
|
||||
x_pct: 0.5
|
||||
y_pct: 0.5
|
||||
status: FAIL
|
||||
diag: template_matching_failed
|
||||
elapsed_ms: 1075.0248432159424
|
||||
- order: 5
|
||||
action_id: wait_retry_381c1b
|
||||
action_type: wait
|
||||
by_text: ''
|
||||
method: simulated
|
||||
score: 0.0
|
||||
x_pct: null
|
||||
y_pct: null
|
||||
status: OK
|
||||
diag: wait simulé
|
||||
elapsed_ms: 12.79759407043457
|
||||
- order: 6
|
||||
action_id: step_288d0bceea90_1778162737752_retry2
|
||||
action_type: click
|
||||
by_text: '25003284'
|
||||
method: fallback
|
||||
score: 0.0
|
||||
x_pct: 0.5
|
||||
y_pct: 0.5
|
||||
status: FAIL
|
||||
diag: template_matching_failed
|
||||
elapsed_ms: 1037.236213684082
|
||||
- order: 7
|
||||
action_id: step_288d0bceea90_1778162737752_retry3
|
||||
action_type: click
|
||||
by_text: '25003284'
|
||||
method: fallback
|
||||
score: 0.0
|
||||
x_pct: 0.5
|
||||
y_pct: 0.5
|
||||
status: FAIL
|
||||
diag: template_matching_failed
|
||||
elapsed_ms: 1051.6366958618164
|
||||
fixture: live/landing.png
|
||||
expected:
|
||||
resolved: true
|
||||
method_prefix: hybrid_text_direct
|
||||
score_min: 0.80
|
||||
x_pct_range: [0.01, 0.10] # IPP en début de ligne, colonne gauche
|
||||
y_pct_range: [0.18, 0.30] # 1ère ligne tableau patients
|
||||
max_elapsed_ms: 5000
|
||||
|
||||
- order: 8
|
||||
action_type: click_anchor
|
||||
by_text: 'Examens cliniques'
|
||||
fixture: live/dossier_motif.png
|
||||
expected:
|
||||
resolved: true
|
||||
method_prefix: hybrid_text_direct
|
||||
score_min: 0.80
|
||||
x_pct_range: [0.18, 0.30] # tab gauche-centre
|
||||
y_pct_range: [0.10, 0.16] # bandeau onglets
|
||||
max_elapsed_ms: 5000
|
||||
notes: |
|
||||
Régression confirmée 2026-05-08 sur cette cible : pre-check OCR
|
||||
(radius 200) ne capte pas le mot "Examens" (tronqué) et fait crash
|
||||
le log RESOLVE_EXIT (NoneType format). Voir rapport
|
||||
docs/E2E_TEST_RUN_2026-05-08.md, correctif #1 et #2.
|
||||
|
||||
- order: 10
|
||||
action_type: click_anchor
|
||||
by_text: 'Imagerie'
|
||||
fixture: live/dossier_examens-cliniques.png
|
||||
expected:
|
||||
resolved: true
|
||||
method_prefix: hybrid_text_direct
|
||||
score_min: 0.80
|
||||
x_pct_range: [0.20, 0.32]
|
||||
y_pct_range: [0.10, 0.16]
|
||||
max_elapsed_ms: 5000
|
||||
|
||||
- order: 12
|
||||
action_type: click_anchor
|
||||
by_text: 'Notes médicales'
|
||||
fixture: live/dossier_imagerie.png
|
||||
expected:
|
||||
resolved: true
|
||||
method_prefix: hybrid_text_direct
|
||||
score_min: 0.80
|
||||
x_pct_range: [0.20, 0.32]
|
||||
y_pct_range: [0.10, 0.16]
|
||||
max_elapsed_ms: 5000
|
||||
|
||||
- order: 14
|
||||
action_type: click_anchor
|
||||
by_text: 'Synthèse Urgences'
|
||||
fixture: live/dossier_notes-medicales.png
|
||||
expected:
|
||||
resolved: true
|
||||
method_prefix: hybrid_text_direct
|
||||
score_min: 0.80
|
||||
x_pct_range: [0.22, 0.36]
|
||||
y_pct_range: [0.10, 0.16]
|
||||
max_elapsed_ms: 5000
|
||||
notes: |
|
||||
Régression confirmée — même cause que step 8 : pre-check radius 200
|
||||
voit 0/2 tokens. Correctif #1 résout.
|
||||
|
||||
- order: 17
|
||||
action_type: click_anchor
|
||||
by_text: 'Codage'
|
||||
fixture: live/dossier_synthese-urgences.png
|
||||
expected:
|
||||
resolved: true
|
||||
method_prefix: hybrid_text_direct
|
||||
score_min: 0.80
|
||||
x_pct_range: [0.10, 0.20]
|
||||
y_pct_range: [0.04, 0.08] # bouton barre de menu (top)
|
||||
max_elapsed_ms: 5000
|
||||
|
||||
- order: 18
|
||||
action_type: click_anchor
|
||||
by_text: 'Coller ou saisir le dossier patient'
|
||||
# Cette cible est sur la page aiva-vision (https://aiva-vision.test/...)
|
||||
# PAS sur la maquette urgences. À documenter avec une fixture dédiée
|
||||
# ou exécuter en démo réelle.
|
||||
fixture: live/dossier_codage.png # placeholder — devrait être aiva-vision
|
||||
expected:
|
||||
resolved: false # avec le placeholder
|
||||
reason: vlm_and_template_all_failed
|
||||
method_prefix: strict_vlm_template_failed
|
||||
notes: |
|
||||
Fixture non représentative — l'agent doit naviguer vers
|
||||
aiva-vision (étape 17 ouvre Codage onglet, qui redirige vers
|
||||
la page aiva). À recapturer sur le replay réel.
|
||||
|
||||
- order: 20
|
||||
action_type: click_anchor
|
||||
by_text: 'Justification de la décision'
|
||||
fixture: live/dossier_codage.png # idem step 18
|
||||
expected:
|
||||
resolved: false
|
||||
reason: vlm_and_template_all_failed
|
||||
method_prefix: strict_vlm_template_failed
|
||||
notes: |
|
||||
Idem step 18 — page aiva-vision non capturée dans cette suite.
|
||||
|
||||