Files
ewoooc/services/pixelrag_rag_candidate_replay_service.py

339 lines
12 KiB
Python

"""Read-only PixelRAG receipt to internal RAG candidate replay bridge.
The bridge only converts saved visual evidence receipts into machine-readable
candidate payloads. It does not OCR, call a model, write ai_insights, or promote
anything into the production price tables.
"""
from __future__ import annotations
import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Mapping
from services.pixelrag_crawler_integration_service import (
DEFAULT_ARTIFACT_MAX_AGE_HOURS,
DEFAULT_ARTIFACT_ROOT,
ECOMMERCE_PLATFORM_PROFILES,
_parse_iso_datetime,
_portable_receipt_file_path,
_receipt_candidates,
_safe_int,
_visual_barrier_reason,
)
POLICY = "read_only_pixelrag_rag_candidate_replay_v1"
INTERNAL_RAG_TARGET = "rag_service.learning_episode_candidate_preview"
DEFAULT_LIMIT = 50
def _status_counts(items: list[dict[str, Any]]) -> dict[str, int]:
counts: dict[str, int] = {}
for item in items:
status = str(item.get("candidate_status") or "unknown")
counts[status] = counts.get(status, 0) + 1
return counts
def _platform_from_receipt(receipt_path: Path, receipt: Mapping[str, Any]) -> str:
capture_target = receipt.get("capture_target") or {}
platform = str(capture_target.get("platform") or "").strip().lower()
if platform:
return platform
try:
return receipt_path.parent.parent.name
except IndexError:
return "unknown"
def _manifest_id_from_receipt(receipt_path: Path, receipt: Mapping[str, Any]) -> str:
return str(receipt.get("manifest_id") or receipt_path.parent.name or "").strip()
def _count_receipt_files(receipt_path: Path, receipt: Mapping[str, Any]) -> dict[str, Any]:
tile_file_count = 0
missing_file_count = 0
file_statuses: list[dict[str, Any]] = []
for item in list(receipt.get("files") or []):
kind = item.get("kind")
path = _portable_receipt_file_path(receipt_path, item.get("path"), kind)
exists = path.exists()
if kind == "tile":
tile_file_count += 1
if not exists:
missing_file_count += 1
file_statuses.append({
"kind": kind,
"path": str(path),
"exists": exists,
})
return {
"tile_file_count": tile_file_count,
"missing_file_count": missing_file_count,
"files": file_statuses,
}
def _candidate_text(
*,
platform: str,
manifest_id: str,
receipt: Mapping[str, Any],
file_summary: Mapping[str, Any],
barrier_reason: str,
) -> str:
capture_target = receipt.get("capture_target") or {}
page_metrics = receipt.get("page_metrics") or {}
profile = ECOMMERCE_PLATFORM_PROFILES.get(platform) or {}
url = str(capture_target.get("url") or page_metrics.get("final_url") or "").strip()
title = str(page_metrics.get("title") or "").strip()
data_targets = ", ".join(str(item) for item in list(profile.get("data_targets") or [])[:8])
parts = [
f"platform={platform}",
f"manifest_id={manifest_id}",
f"title={title or 'unknown'}",
f"url={url or 'unknown'}",
f"http_status={_safe_int(receipt.get('http_status'))}",
f"capture_status={receipt.get('status') or 'unknown'}",
f"tile_file_count={int(file_summary.get('tile_file_count') or 0)}",
f"missing_file_count={int(file_summary.get('missing_file_count') or 0)}",
]
if data_targets:
parts.append(f"intended_fields={data_targets}")
if barrier_reason:
parts.append(f"visual_barrier={barrier_reason}")
return " | ".join(parts)
def _candidate_from_receipt_path(
receipt_path: Path,
*,
now: datetime,
max_age_hours: int,
) -> dict[str, Any]:
errors: list[str] = []
try:
receipt = json.loads(receipt_path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
receipt = {}
errors.append(str(exc)[:300])
platform = _platform_from_receipt(receipt_path, receipt)
manifest_id = _manifest_id_from_receipt(receipt_path, receipt)
generated_at = _parse_iso_datetime(receipt.get("generated_at"))
age_hours = ((now - generated_at).total_seconds() / 3600) if generated_at else None
stale = age_hours is None or age_hours > max_age_hours
file_summary = _count_receipt_files(receipt_path, receipt)
missing_file_count = int(file_summary.get("missing_file_count") or 0)
barrier_reason = _visual_barrier_reason(receipt)
capture_status = str(receipt.get("status") or "").strip().lower()
http_status = _safe_int(receipt.get("http_status"))
blocked_reasons: list[str] = []
if errors:
blocked_reasons.append("receipt_parse_error")
if capture_status != "captured":
blocked_reasons.append("receipt_not_captured")
if stale:
blocked_reasons.append("receipt_stale_or_missing_timestamp")
if missing_file_count:
blocked_reasons.append("receipt_missing_files")
if barrier_reason:
blocked_reasons.append(f"visual_barrier:{barrier_reason}")
if http_status >= 400 and not barrier_reason:
blocked_reasons.append(f"http_status_{http_status}")
eligible = not blocked_reasons
candidate_status = "eligible" if eligible else ("invalid" if errors else "blocked")
capture_target = receipt.get("capture_target") or {}
page_metrics = receipt.get("page_metrics") or {}
return {
"candidate_status": candidate_status,
"eligible_for_rag_candidate_replay": eligible,
"blocked_reasons": blocked_reasons,
"platform": platform,
"manifest_id": manifest_id,
"receipt_path": str(receipt_path),
"generated_at": receipt.get("generated_at"),
"age_hours": round(age_hours, 3) if age_hours is not None else None,
"stale": stale,
"http_status": http_status,
"capture_status": receipt.get("status"),
"url": capture_target.get("url") or page_metrics.get("final_url"),
"title": page_metrics.get("title"),
"visual_barrier_detected": bool(barrier_reason),
"visual_barrier_reason": barrier_reason,
"tile_file_count": int(file_summary.get("tile_file_count") or 0),
"missing_file_count": missing_file_count,
"rag_candidate_text": _candidate_text(
platform=platform,
manifest_id=manifest_id,
receipt=receipt,
file_summary=file_summary,
barrier_reason=barrier_reason,
),
"internal_rag_target": INTERNAL_RAG_TARGET,
"promotion_boundary": {
"direct_ai_insights_write_allowed": False,
"direct_price_write_allowed": False,
"requires_ocr_or_vlm_replay": True,
"requires_identity_matcher_replay": True,
"requires_promotion_gate": True,
"requires_embedding_signature_guard": True,
},
"next_machine_action": (
"run_ocr_vlm_replay_then_promotion_gate"
if eligible
else (
"run_platform_probe_or_use_structured_api"
if barrier_reason
else "refresh_pixelrag_visual_capture_receipt"
)
),
"file_summary": file_summary,
}
def _unique_receipt_candidates(
root: Path,
*,
platforms: tuple[str, ...] | list[str] | None,
limit: int,
) -> list[Path]:
seen: set[Path] = set()
candidates: list[Path] = []
selected_platforms = [
str(platform or "").strip().lower()
for platform in (platforms or [])
if str(platform or "").strip()
]
if selected_platforms:
for platform in selected_platforms:
for path in _receipt_candidates(root, platform=platform):
resolved = path.resolve()
if resolved not in seen:
seen.add(resolved)
candidates.append(path)
else:
for path in _receipt_candidates(root):
resolved = path.resolve()
if resolved not in seen:
seen.add(resolved)
candidates.append(path)
return sorted(candidates, key=lambda path: path.stat().st_mtime, reverse=True)[:limit]
def build_pixelrag_rag_candidate_replay_readback(
*,
artifact_root: str | Path | None = None,
platform: str | tuple[str, ...] | list[str] | None = None,
max_age_hours: int | None = None,
limit: int | None = None,
) -> dict[str, Any]:
"""Build a replayable RAG candidate readback from PixelRAG receipts."""
root = Path(artifact_root or DEFAULT_ARTIFACT_ROOT)
max_age = max(1, int(max_age_hours or DEFAULT_ARTIFACT_MAX_AGE_HOURS))
item_limit = max(1, min(int(limit or DEFAULT_LIMIT), 250))
if isinstance(platform, str):
platforms = (platform.strip().lower(),) if platform.strip() else None
else:
platforms = tuple(
str(item or "").strip().lower()
for item in (platform or [])
if str(item or "").strip()
) or None
now = datetime.now(timezone.utc)
candidates = _unique_receipt_candidates(root, platforms=platforms, limit=item_limit)
replay_candidates = [
_candidate_from_receipt_path(path, now=now, max_age_hours=max_age)
for path in candidates
]
eligible_count = sum(1 for item in replay_candidates if item["candidate_status"] == "eligible")
blocked_count = sum(1 for item in replay_candidates if item["candidate_status"] == "blocked")
invalid_count = sum(1 for item in replay_candidates if item["candidate_status"] == "invalid")
visual_barrier_count = sum(1 for item in replay_candidates if item["visual_barrier_detected"])
missing_file_count = sum(int(item.get("missing_file_count") or 0) for item in replay_candidates)
tile_file_count = sum(int(item.get("tile_file_count") or 0) for item in replay_candidates)
stale_count = sum(1 for item in replay_candidates if item.get("stale"))
if invalid_count and invalid_count == len(replay_candidates):
status = "critical"
elif eligible_count == 0 or blocked_count or invalid_count or visual_barrier_count:
status = "warning"
else:
status = "ok"
if not replay_candidates:
status = "warning"
summary = {
"receipt_count": len(replay_candidates),
"eligible_count": eligible_count,
"blocked_count": blocked_count,
"invalid_count": invalid_count,
"visual_barrier_count": visual_barrier_count,
"tile_file_count": tile_file_count,
"missing_file_count": missing_file_count,
"stale_count": stale_count,
"status_counts": _status_counts(replay_candidates),
"platforms": sorted({str(item.get("platform") or "unknown") for item in replay_candidates}),
}
return {
"success": invalid_count == 0,
"policy": POLICY,
"status": status,
"generated_at": now.isoformat(),
"artifact_root": str(root),
"max_age_hours": max_age,
"platform_filter": list(platforms or []),
"summary": summary,
"candidates": replay_candidates,
"replay_contract": {
"internal_rag_target": INTERNAL_RAG_TARGET,
"writes_database": False,
"writes_ai_insights": False,
"writes_price_tables": False,
"network_call": False,
"secret_read": False,
"promotion_gate_required_before_knowledge_write": True,
"ocr_or_vlm_required_before_field_extraction": True,
"blocked_pages_are_not_product_data": True,
},
"next_machine_action": (
"run_pixelrag_visual_capture_worker"
if not replay_candidates
else (
"fix_invalid_pixelrag_receipts"
if invalid_count and not eligible_count
else (
"run_platform_probe_or_use_structured_api"
if visual_barrier_count and not eligible_count
else (
"run_ocr_vlm_replay_then_promotion_gate"
if eligible_count
else "refresh_pixelrag_visual_capture_receipt"
)
)
)
),
"controlled_apply": {
"network_call": False,
"db_write": False,
"secret_read": False,
"production_price_write": False,
"artifact_write": False,
},
}
__all__ = [
"INTERNAL_RAG_TARGET",
"POLICY",
"build_pixelrag_rag_candidate_replay_readback",
]