#!/usr/bin/env python3 """Build the first agent-facing intake packet for a 1C user task.""" from __future__ import annotations import argparse import base64 import json import sys from pathlib import Path from typing import Any sys.path.insert(0, str(Path(__file__).resolve().parent)) from route_1c_question import route_question # noqa: E402 ROOT = Path(__file__).resolve().parents[1] DEFAULT_INDEX = ROOT / "reports" / "1c-sql" / "upo" / "unified-object-route-index.json" def decode_arg(value: str | None, encoded: str | None) -> str | None: if encoded: return base64.b64decode(encoded).decode("utf-8") return value def fact_summary(fact_checks: list[dict[str, Any]]) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: confirmed = [] unresolved = [] for check in fact_checks: result = check.get("result") or {} row = { "path": check.get("path"), "status": check.get("status"), "exists": result.get("exists") if result else None, "confidence": result.get("confidence"), "reason": result.get("reason"), "object": result.get("object"), "match": result.get("match"), } if result.get("exists") is True: confirmed.append(row) else: unresolved.append(row) return confirmed, unresolved def next_commands(route: dict[str, Any], *, index: Path, view: str) -> list[dict[str, Any]]: commands = [] decision = route.get("decision") or {} if decision.get("needs_docs_rag"): commands.append( { "tool": "docs_rag", "purpose": "official_documentation_context", "api": "/api/rag/query", "payload": { "question": route.get("question"), "source_type": decision.get("safe_rag_scope") or "official_1c_docs", "limit": 5, }, } ) for path in route.get("fact_paths") or []: commands.append( { "tool": "fact_resolver", "purpose": "current_configuration_fact", "command": f"python scripts/resolve_1c_fact.py --index {index} --path --view {view}", "api": "/api/1c/fact", "payload": { "source_kind": "route_index", "source_path": str(index), "path": path, "view": view, }, } ) if decision.get("needs_current_config") and not route.get("fact_paths"): commands.append( { "tool": "task_evidence", "purpose": "discover_objects_and_relevant_metadata", "command": f"python scripts/build_1c_task_evidence.py --index {index} --text --view {view}", } ) return commands def build_intake(text: str, *, index: Path, view: str) -> dict[str, Any]: route = route_question(text, index_path=index, view=view) confirmed, unresolved = fact_summary(route.get("fact_checks") or []) decision = route.get("decision") or {} code_allowed = not unresolved and bool(confirmed or not decision.get("needs_current_config")) if decision.get("needs_current_config") and not confirmed and not route.get("fact_paths"): code_allowed = False return { "schema": "onec_agent_intake.v1", "task": {"text": text}, "index": str(index), "view": view, "route": route, "source_policy": { "allowed_for_current_facts": ["route_index", "metadata_snapshot_explicit_current", "1c_agent_current"], "allowed_for_documentation": ["official_1c_docs"], "examples_are_current_facts": False, "blocked_as_current_fact_sources": ["metadata.example", "synthetic-example", "rag_examples", "old_exports"], }, "facts": { "confirmed": confirmed, "unresolved": unresolved, "confirmed_count": len(confirmed), "unresolved_count": len(unresolved), }, "answer_policy": { "code_generation_allowed": code_allowed, "must_check_current_config_before_code": bool(decision.get("current_config_required_before_code")), "must_not_use_examples_as_facts": True, "safe_rag_scope": decision.get("safe_rag_scope"), }, "next_commands": next_commands(route, index=index, view=view), } def main() -> int: parser = argparse.ArgumentParser(description="Build first agent intake packet for a 1C task.") parser.add_argument("--text") parser.add_argument("--text-b64") parser.add_argument("--index", type=Path, default=DEFAULT_INDEX) parser.add_argument("--view", choices=["effective", "base"], default="effective") parser.add_argument("--output", type=Path) args = parser.parse_args() text = decode_arg(args.text, args.text_b64) if not text: raise SystemExit("Use --text or --text-b64.") result = build_intake(text, index=args.index, view=args.view) output = json.dumps(result, ensure_ascii=False, indent=2) + "\n" if args.output: args.output.parent.mkdir(parents=True, exist_ok=True) args.output.write_text(output, encoding="utf-8") print(json.dumps({"output": str(args.output), "route": result["route"]["decision"]["route"], "code_allowed": result["answer_policy"]["code_generation_allowed"]}, ensure_ascii=False)) else: print(output) return 0 if __name__ == "__main__": raise SystemExit(main())