feat(p1): persist workflows and semantic learning artifacts

This commit is contained in:
Dom
2026-06-02 16:20:38 +02:00
parent 7a1a5cb6fd
commit 86b3c8f7e7
21 changed files with 3816 additions and 31 deletions

View File

@@ -126,6 +126,25 @@ def build_workflow_replay(
"x_relative": "",
},
}
_merge_semantic_target_fields(
step_action["target_spec"],
target,
params,
step,
)
target_label = _first_non_empty_text(
step_action["target_spec"].get("by_text"),
step_action["target_spec"].get("target_text"),
step_action["target_spec"].get("description"),
step_action["target_spec"].get("ocr_description"),
step_action["target_spec"].get("vlm_description"),
)
if target_label:
step_action.setdefault(
"target_text",
step_action["target_spec"].get("target_text") or target_label,
)
step_action.setdefault("target_description", target_label)
# Ajouter le crop anchor si disponible
_attach_anchor(step_action, step, session_dir)
@@ -171,6 +190,58 @@ def _map_action_type(step_type: str) -> str:
return mapping.get(step_type, step_type)
_TARGET_SEMANTIC_KEYS = (
"by_text",
"by_role",
"anchor_id",
"target_text",
"ocr_description",
"description",
"vlm_description",
"by_text_source",
"anchor_bbox",
"original_size",
)
def _first_non_empty_text(*values: Any) -> str:
for value in values:
text = str(value or "").strip()
if text and text.casefold() not in {"none", "null"}:
return text
return ""
def _merge_semantic_target_fields(
target_spec: Dict[str, Any],
*sources: Dict[str, Any],
) -> None:
for source in sources:
if not isinstance(source, dict):
continue
visual_anchor = source.get("visual_anchor") or {}
if isinstance(visual_anchor, dict):
_merge_semantic_target_fields(target_spec, visual_anchor)
for key in _TARGET_SEMANTIC_KEYS:
value = source.get(key)
if value and not target_spec.get(key):
target_spec[key] = value
if not target_spec.get("by_text"):
target_text = _first_non_empty_text(target_spec.get("target_text"))
if target_text:
target_spec["by_text"] = target_text
target_spec.setdefault("by_text_source", "visual_anchor")
if not target_spec.get("vlm_description"):
description = _first_non_empty_text(
target_spec.get("description"),
target_spec.get("ocr_description"),
)
if description:
target_spec["vlm_description"] = description
def _attach_anchor(action: dict, step: dict, session_dir: str) -> None:
"""Attacher le crop anchor au target_spec si disponible."""
import base64