Initial SQL-only 1C adapter baseline
This commit is contained in:
@@ -0,0 +1,383 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build a compact read-only evidence bundle for a 1C development 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 get_1c_form_context import build_context as build_form_context # noqa: E402
|
||||
from get_1c_module import build_module_result # noqa: E402
|
||||
from get_1c_object_metadata import build_object_metadata # noqa: E402
|
||||
from plan_1c_task_context import build_plan # noqa: E402
|
||||
from resolve_1c_object import load_json # noqa: E402
|
||||
|
||||
|
||||
def decode_arg(value: str | None, encoded: str | None) -> str | None:
|
||||
if encoded:
|
||||
return base64.b64decode(encoded).decode("utf-8")
|
||||
return value
|
||||
|
||||
|
||||
def collect_target_reads(investigation: dict[str, Any]) -> tuple[list[str], list[str]]:
|
||||
forms = []
|
||||
modules = []
|
||||
for read in investigation.get("recommended_reads") or []:
|
||||
form = read.get("form")
|
||||
module = read.get("module")
|
||||
if form and form not in forms:
|
||||
forms.append(form)
|
||||
if module and module not in modules:
|
||||
modules.append(module)
|
||||
for search in investigation.get("searches") or []:
|
||||
for match in search.get("matches") or []:
|
||||
form = match.get("form")
|
||||
if form and form not in forms:
|
||||
forms.append(form)
|
||||
area = match.get("area")
|
||||
module = match.get("name")
|
||||
if area in {"module", "module.code"} and module and module not in modules:
|
||||
modules.append(module)
|
||||
return forms, modules
|
||||
|
||||
|
||||
def compact_metadata(metadata: dict[str, Any], *, max_attributes: int) -> dict[str, Any]:
|
||||
attrs = metadata.get("attributes") or []
|
||||
sections = metadata.get("tabular_sections") or []
|
||||
return {
|
||||
"schema": metadata.get("schema"),
|
||||
"view": metadata.get("view"),
|
||||
"object": metadata.get("object"),
|
||||
"attributes": attrs[:max_attributes],
|
||||
"attributes_total": len(attrs),
|
||||
"attributes_truncated": len(attrs) > max_attributes,
|
||||
"tabular_sections": sections,
|
||||
"counts": metadata.get("counts"),
|
||||
}
|
||||
|
||||
|
||||
def compact_form_context(context: dict[str, Any], *, max_items: int, max_attributes: int, max_commands: int) -> dict[str, Any]:
|
||||
forms = []
|
||||
for form in context.get("forms") or []:
|
||||
copy = {key: form.get(key) for key in ("name", "synonym", "uuid", "origin", "effective_action", "meta_xml_path", "form_xml_path", "module_path", "extension_overlays") if form.get(key) not in (None, [], "")}
|
||||
structure = form.get("structure") or {}
|
||||
if structure:
|
||||
copy["structure"] = {
|
||||
"origin": structure.get("origin"),
|
||||
"form_xml_path": structure.get("form_xml_path"),
|
||||
"events": structure.get("events") or [],
|
||||
"items": (structure.get("items") or [])[:max_items],
|
||||
"attributes": (structure.get("attributes") or [])[:max_attributes],
|
||||
"commands": (structure.get("commands") or [])[:max_commands],
|
||||
"counts": structure.get("counts"),
|
||||
}
|
||||
overlays = []
|
||||
for overlay in form.get("extension_overlays") or []:
|
||||
overlay_copy = {key: overlay.get(key) for key in ("name", "synonym", "uuid", "origin", "effective_action", "meta_xml_path", "form_xml_path", "module_path") if overlay.get(key) not in (None, [], "")}
|
||||
structure = overlay.get("structure") or {}
|
||||
if structure:
|
||||
overlay_copy["structure"] = {
|
||||
"origin": structure.get("origin"),
|
||||
"form_xml_path": structure.get("form_xml_path"),
|
||||
"events": structure.get("events") or [],
|
||||
"items": (structure.get("items") or [])[:max_items],
|
||||
"attributes": (structure.get("attributes") or [])[:max_attributes],
|
||||
"commands": (structure.get("commands") or [])[:max_commands],
|
||||
"counts": structure.get("counts"),
|
||||
}
|
||||
overlays.append(overlay_copy)
|
||||
if overlays:
|
||||
copy["extension_overlays"] = overlays
|
||||
forms.append(copy)
|
||||
return {
|
||||
"schema": context.get("schema"),
|
||||
"view": context.get("view"),
|
||||
"object": context.get("object"),
|
||||
"query": context.get("query"),
|
||||
"forms": forms,
|
||||
"counts": context.get("counts"),
|
||||
}
|
||||
|
||||
|
||||
def compact_module_result(result: dict[str, Any]) -> dict[str, Any]:
|
||||
return {
|
||||
"schema": result.get("schema"),
|
||||
"view": result.get("view"),
|
||||
"object": result.get("object"),
|
||||
"query": result.get("query"),
|
||||
"modules": result.get("modules") or [],
|
||||
"counts": result.get("counts"),
|
||||
}
|
||||
|
||||
|
||||
def read_lines(path: str) -> list[str]:
|
||||
file_path = Path(path)
|
||||
try:
|
||||
return file_path.read_text(encoding="utf-8-sig").splitlines()
|
||||
except UnicodeDecodeError:
|
||||
return file_path.read_text(encoding="cp1251", errors="replace").splitlines()
|
||||
|
||||
|
||||
def code_snippet(path: str, line: int, *, radius: int, max_chars: int) -> dict[str, Any] | None:
|
||||
file_path = Path(path)
|
||||
if not file_path.is_file() or line <= 0:
|
||||
return None
|
||||
lines = read_lines(path)
|
||||
start = max(1, line - radius)
|
||||
end = min(len(lines), line + radius)
|
||||
text = "\n".join(lines[start - 1 : end])
|
||||
truncated = len(text) > max_chars
|
||||
return {
|
||||
"path": path,
|
||||
"line_start": start,
|
||||
"line_end": end,
|
||||
"focus_line": line,
|
||||
"text": text[:max_chars],
|
||||
"truncated": truncated,
|
||||
"char_count": len(text),
|
||||
}
|
||||
|
||||
|
||||
def collect_code_snippets(searches: list[dict[str, Any]], *, radius: int, max_chars: int, limit: int) -> list[dict[str, Any]]:
|
||||
snippets = []
|
||||
seen = set()
|
||||
for search in searches:
|
||||
for match in search.get("matches") or []:
|
||||
if match.get("area") != "module.code":
|
||||
continue
|
||||
evidence = match.get("evidence") or {}
|
||||
path = evidence.get("path")
|
||||
line = evidence.get("line")
|
||||
if not path or not line:
|
||||
continue
|
||||
key = (path, line)
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
snippet = code_snippet(str(path), int(line), radius=radius, max_chars=max_chars)
|
||||
if not snippet:
|
||||
continue
|
||||
snippet.update(
|
||||
{
|
||||
"search_text": search.get("text"),
|
||||
"module": match.get("name"),
|
||||
"origin": match.get("origin"),
|
||||
"effective_action": match.get("effective_action"),
|
||||
}
|
||||
)
|
||||
snippets.append(snippet)
|
||||
if len(snippets) >= limit:
|
||||
return snippets
|
||||
return snippets
|
||||
|
||||
|
||||
def evidence_for_investigation(
|
||||
index: dict[str, Any],
|
||||
investigation: dict[str, Any],
|
||||
*,
|
||||
view: str,
|
||||
max_attributes: int,
|
||||
max_form_items: int,
|
||||
max_form_attributes: int,
|
||||
max_form_commands: int,
|
||||
max_module_chars: int,
|
||||
code_snippet_radius: int,
|
||||
max_code_snippet_chars: int,
|
||||
max_code_snippets: int,
|
||||
max_forms: int,
|
||||
max_modules: int,
|
||||
) -> dict[str, Any]:
|
||||
candidate = investigation.get("candidate") or {}
|
||||
kind = candidate.get("kind")
|
||||
name = candidate.get("name")
|
||||
metadata = build_object_metadata(index, kind=kind, name=name, view=view, extension=None, include_storage=False)
|
||||
forms, modules = collect_target_reads(investigation)
|
||||
form_contexts = []
|
||||
for form_name in forms[:max_forms]:
|
||||
form_contexts.append(
|
||||
compact_form_context(
|
||||
build_form_context(index, kind=kind, name=name, form=form_name, view=view, extension=None, max_items=max_form_items),
|
||||
max_items=max_form_items,
|
||||
max_attributes=max_form_attributes,
|
||||
max_commands=max_form_commands,
|
||||
)
|
||||
)
|
||||
module_contexts = []
|
||||
for module_name in modules[:max_modules]:
|
||||
module_contexts.append(
|
||||
compact_module_result(
|
||||
build_module_result(
|
||||
index,
|
||||
kind=kind,
|
||||
name=name,
|
||||
module_name=module_name,
|
||||
view=view,
|
||||
extension=None,
|
||||
max_chars=max_module_chars,
|
||||
routine=None,
|
||||
)
|
||||
)
|
||||
)
|
||||
snippets = collect_code_snippets(
|
||||
investigation.get("searches") or [],
|
||||
radius=code_snippet_radius,
|
||||
max_chars=max_code_snippet_chars,
|
||||
limit=max_code_snippets,
|
||||
)
|
||||
return {
|
||||
"candidate": candidate,
|
||||
"brief": investigation.get("brief"),
|
||||
"searches": investigation.get("searches") or [],
|
||||
"metadata": compact_metadata(metadata, max_attributes=max_attributes),
|
||||
"forms": form_contexts,
|
||||
"modules": module_contexts,
|
||||
"code_snippets": snippets,
|
||||
"recommended_reads": investigation.get("recommended_reads") or [],
|
||||
"counts": {
|
||||
"forms_materialized": len(form_contexts),
|
||||
"modules_materialized": len(module_contexts),
|
||||
"code_snippets": len(snippets),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def build_evidence(
|
||||
index: dict[str, Any],
|
||||
*,
|
||||
text: str,
|
||||
view: str,
|
||||
max_objects: int,
|
||||
max_terms: int,
|
||||
max_matches: int,
|
||||
max_attributes: int,
|
||||
max_form_items: int,
|
||||
max_form_attributes: int,
|
||||
max_form_commands: int,
|
||||
max_module_chars: int,
|
||||
code_snippet_radius: int,
|
||||
max_code_snippet_chars: int,
|
||||
max_code_snippets: int,
|
||||
max_forms: int,
|
||||
max_modules: int,
|
||||
) -> dict[str, Any]:
|
||||
plan = build_plan(index, text=text, view=view, max_objects=max_objects, max_terms=max_terms, max_matches=max_matches)
|
||||
investigations = []
|
||||
for investigation in (plan.get("investigations") or [])[:max_objects]:
|
||||
investigations.append(
|
||||
evidence_for_investigation(
|
||||
index,
|
||||
investigation,
|
||||
view=view,
|
||||
max_attributes=max_attributes,
|
||||
max_form_items=max_form_items,
|
||||
max_form_attributes=max_form_attributes,
|
||||
max_form_commands=max_form_commands,
|
||||
max_module_chars=max_module_chars,
|
||||
code_snippet_radius=code_snippet_radius,
|
||||
max_code_snippet_chars=max_code_snippet_chars,
|
||||
max_code_snippets=max_code_snippets,
|
||||
max_forms=max_forms,
|
||||
max_modules=max_modules,
|
||||
)
|
||||
)
|
||||
return {
|
||||
"schema": "onec_task_evidence_bundle.v1",
|
||||
"view": view,
|
||||
"task": {"text": text},
|
||||
"plan": {
|
||||
"schema": plan.get("schema"),
|
||||
"object_candidates": plan.get("object_candidates") or [],
|
||||
"search_terms": plan.get("search_terms") or [],
|
||||
"safety": plan.get("safety"),
|
||||
"counts": plan.get("counts"),
|
||||
},
|
||||
"investigations": investigations,
|
||||
"limits": {
|
||||
"max_objects": max_objects,
|
||||
"max_terms": max_terms,
|
||||
"max_matches": max_matches,
|
||||
"max_attributes": max_attributes,
|
||||
"max_form_items": max_form_items,
|
||||
"max_form_attributes": max_form_attributes,
|
||||
"max_form_commands": max_form_commands,
|
||||
"max_module_chars": max_module_chars,
|
||||
"code_snippet_radius": code_snippet_radius,
|
||||
"max_code_snippet_chars": max_code_snippet_chars,
|
||||
"max_code_snippets": max_code_snippets,
|
||||
"max_forms": max_forms,
|
||||
"max_modules": max_modules,
|
||||
},
|
||||
"safety": {
|
||||
"mode": "read_only",
|
||||
"write_status": "blocked_until_write_gates",
|
||||
"write_contract": "docs/1c-write-path-safety.md",
|
||||
},
|
||||
"counts": {
|
||||
"investigations": len(investigations),
|
||||
"forms_materialized": sum(item.get("counts", {}).get("forms_materialized", 0) for item in investigations),
|
||||
"modules_materialized": sum(item.get("counts", {}).get("modules_materialized", 0) for item in investigations),
|
||||
"code_snippets": sum(item.get("counts", {}).get("code_snippets", 0) for item in investigations),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Build read-only 1C task evidence bundle.")
|
||||
parser.add_argument("--index", type=Path, required=True)
|
||||
parser.add_argument("--text")
|
||||
parser.add_argument("--text-b64")
|
||||
parser.add_argument("--view", choices=["effective", "base"], default="effective")
|
||||
parser.add_argument("--max-objects", type=int, default=2)
|
||||
parser.add_argument("--max-terms", type=int, default=8)
|
||||
parser.add_argument("--max-matches", type=int, default=8)
|
||||
parser.add_argument("--max-attributes", type=int, default=120)
|
||||
parser.add_argument("--max-form-items", type=int, default=250)
|
||||
parser.add_argument("--max-form-attributes", type=int, default=120)
|
||||
parser.add_argument("--max-form-commands", type=int, default=80)
|
||||
parser.add_argument("--max-module-chars", type=int, default=12000)
|
||||
parser.add_argument("--code-snippet-radius", type=int, default=8)
|
||||
parser.add_argument("--max-code-snippet-chars", type=int, default=8000)
|
||||
parser.add_argument("--max-code-snippets", type=int, default=20)
|
||||
parser.add_argument("--max-forms", type=int, default=3)
|
||||
parser.add_argument("--max-modules", type=int, default=4)
|
||||
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_evidence(
|
||||
load_json(args.index),
|
||||
text=text,
|
||||
view=args.view,
|
||||
max_objects=args.max_objects,
|
||||
max_terms=args.max_terms,
|
||||
max_matches=args.max_matches,
|
||||
max_attributes=args.max_attributes,
|
||||
max_form_items=args.max_form_items,
|
||||
max_form_attributes=args.max_form_attributes,
|
||||
max_form_commands=args.max_form_commands,
|
||||
max_module_chars=args.max_module_chars,
|
||||
code_snippet_radius=args.code_snippet_radius,
|
||||
max_code_snippet_chars=args.max_code_snippet_chars,
|
||||
max_code_snippets=args.max_code_snippets,
|
||||
max_forms=args.max_forms,
|
||||
max_modules=args.max_modules,
|
||||
)
|
||||
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), "counts": result["counts"], "view": result["view"]}, ensure_ascii=False))
|
||||
else:
|
||||
print(output)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user