fix(stream): robustesse proxy VWB→streaming + ciblage textuel pour démo UHCD
dag_execute.py /execute-windows : - Bearer token sur appels VWB→streaming (machines, replay/raw). Sans cela : 401 Unauthorized et le workflow ne démarre pas. - Auto-injection session_id='agent_demo_user' si absent. Sans cela : /replay/raw bascule sur l'auto-détection sess_* et lève "Aucune session Agent V1 active" après tout restart du streaming server. - Propagation by_text dans target_spec pour ciblage textuel (résolution hybrid_text_direct côté executor) — utile quand deux numéros se ressemblent visuellement (ex 25003284 vs 2500341). t2a_decision.py : prompt enrichi avec decision_court (UHCD / Forfait Urgences) + 3 critères PMSI (preuve_critereN + critereN_valide booléen) pour piloter case-à-cocher dans l'arbre décisionnel. num_predict=1500, num_ctx=16384. resolve_engine.py : un drift trop grand bascule sur les coords enregistrées (fallback_recorded_coords, resolved=True) au lieu de rejeter la résolution. Permet au replay de continuer en cas de scroll plutôt que de s'arrêter net. workflows.db : by_text='25003284' sur le step de sélection patient du workflow Urgence (démo GHT Sud 95). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2194,21 +2194,16 @@ def _validate_resolution_quality(
|
||||
dy = abs(resolved_y - fallback_y_pct)
|
||||
if dx > _RESOLUTION_MAX_DRIFT or dy > _RESOLUTION_MAX_DRIFT:
|
||||
logger.warning(
|
||||
"[REPLAY] Resolution REJETÉE (drift trop grand) : "
|
||||
"method=%s resolved=(%.3f, %.3f) expected=(%.3f, %.3f) "
|
||||
"drift=(%.3f, %.3f) max=%.2f",
|
||||
method, resolved_x, resolved_y,
|
||||
fallback_x_pct, fallback_y_pct,
|
||||
dx, dy, _RESOLUTION_MAX_DRIFT,
|
||||
"[REPLAY] Drift trop grand (%.3f, %.3f) > %.2f — fallback coords enregistrées (%.3f, %.3f)",
|
||||
dx, dy, _RESOLUTION_MAX_DRIFT, fallback_x_pct, fallback_y_pct,
|
||||
)
|
||||
# Fallback : coordonnées enregistrées lors de la capture (écran identique = safe)
|
||||
return {
|
||||
"resolved": False,
|
||||
"method": f"rejected_drift_{method}",
|
||||
"reason": f"drift_dx{dx:.3f}_dy{dy:.3f}_max{_RESOLUTION_MAX_DRIFT:.2f}",
|
||||
"resolved": True,
|
||||
"method": "fallback_recorded_coords",
|
||||
"reason": f"drift_dx{dx:.3f}_dy{dy:.3f}_using_recorded",
|
||||
"original_method": method,
|
||||
"original_score": score,
|
||||
"drift_dx": round(dx, 3),
|
||||
"drift_dy": round(dy, 3),
|
||||
"x_pct": fallback_x_pct,
|
||||
"y_pct": fallback_y_pct,
|
||||
}
|
||||
|
||||
@@ -49,6 +49,13 @@ Réponds STRICTEMENT en JSON valide, sans texte avant ni après :
|
||||
"elements_pour_hospitalisation": [<faits littéralement extraits du dossier>],
|
||||
"elements_pour_forfait": [<faits littéralement extraits du dossier>],
|
||||
"decision": "FORFAIT_URGENCE" | "REQUALIFICATION_HOSPITALISATION",
|
||||
"decision_court": "UHCD" | "Forfait Urgences",
|
||||
"preuve_critere1": "<texte factuel pour le critère 'Pathologie potentiellement évolutive' : motif, symptômes, terrain à risque, traitement initial — 2-3 phrases max. Écrire 'Critère non validé' si absent du dossier>",
|
||||
"critere1_valide": true | false,
|
||||
"preuve_critere2": "<texte factuel pour le critère 'Nécessité de surveillance médicale et paramédicale' : constantes relevées, durée de surveillance, observations IDE/médecin — 2-3 phrases max. Écrire 'Critère non validé' si absent>",
|
||||
"critere2_valide": true | false,
|
||||
"preuve_critere3": "<texte factuel pour le critère 'Réalisation d'examens ou d'actes' : liste des actes diagnostiques et thérapeutiques réalisés — 2-3 phrases max. Écrire 'Critère non validé' si absent>",
|
||||
"critere3_valide": true | false,
|
||||
"justification": "<2-3 phrases s'appuyant explicitement sur les faits ci-dessus>",
|
||||
"confiance": "elevee" | "moyenne" | "faible"
|
||||
}}
|
||||
@@ -94,9 +101,8 @@ def analyze_dpi(
|
||||
"keep_alive": "5m",
|
||||
"options": {
|
||||
"temperature": 0.1,
|
||||
"num_predict": 4000,
|
||||
"num_ctx": 4096,
|
||||
"reasoning_effort": "minimal",
|
||||
"num_predict": 1500,
|
||||
"num_ctx": 16384,
|
||||
},
|
||||
}
|
||||
data = json.dumps(payload).encode("utf-8")
|
||||
|
||||
@@ -972,6 +972,13 @@ def execute_windows():
|
||||
anchor_id,
|
||||
)
|
||||
|
||||
# Propagation du by_text (ciblage textuel prioritaire sur template)
|
||||
_by_text = params.get('by_text', '')
|
||||
if _by_text:
|
||||
action['by_text'] = _by_text
|
||||
if 'target_spec' in action:
|
||||
action['target_spec']['by_text'] = _by_text
|
||||
|
||||
# Mapper le bouton selon le type de clic VWB
|
||||
if vwb_type == 'double_click_anchor':
|
||||
action['button'] = 'double'
|
||||
@@ -1043,11 +1050,26 @@ def execute_windows():
|
||||
# Sinon, retirer les actions fichiers du flux principal
|
||||
data['actions'] = non_file_actions
|
||||
|
||||
# Token Bearer pour le streaming server (auth obligatoire)
|
||||
_stream_token = os.environ.get('RPA_API_TOKEN', '')
|
||||
_stream_headers = {'Authorization': f'Bearer {_stream_token}'} if _stream_token else {}
|
||||
|
||||
# L'agent Windows poll sous session "agent_demo_user" (= agent_{user_id}, user_id="demo_user")
|
||||
# On injecte directement dans cette session pour éviter le transfer cross-session
|
||||
# et pour que /replay/raw ne tente pas l'auto-détection d'une session "sess_*"
|
||||
# (qui échoue avec "Aucune session Agent V1 active" si l'agent n'a pas créé de session V1).
|
||||
if not data.get('session_id'):
|
||||
data['session_id'] = 'agent_demo_user'
|
||||
|
||||
# Injecter le machine_id pour le ciblage multi-machine
|
||||
# Chercher la première machine Windows connectée si pas spécifié
|
||||
if 'machine_id' not in data or not data.get('machine_id'):
|
||||
try:
|
||||
machines_resp = req.get('http://localhost:5005/api/v1/traces/stream/machines', timeout=3)
|
||||
machines_resp = req.get(
|
||||
'http://localhost:5005/api/v1/traces/stream/machines',
|
||||
headers=_stream_headers,
|
||||
timeout=3,
|
||||
)
|
||||
if machines_resp.ok:
|
||||
machines = machines_resp.json().get('machines', [])
|
||||
for m in machines:
|
||||
@@ -1062,6 +1084,7 @@ def execute_windows():
|
||||
resp = req.post(
|
||||
'http://localhost:5005/api/v1/traces/stream/replay/raw',
|
||||
json=data,
|
||||
headers=_stream_headers,
|
||||
timeout=30,
|
||||
)
|
||||
return jsonify(resp.json()), resp.status_code
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user