Initial SQL-only 1C adapter baseline
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from urllib import request
|
||||
|
||||
|
||||
def rpc(adapter_url: str, method: str, payload: dict[str, Any]) -> dict[str, Any]:
|
||||
body = json.dumps({"method": method, "payload": payload}, ensure_ascii=False).encode("utf-8")
|
||||
req = request.Request(
|
||||
f"{adapter_url.rstrip('/')}/rpc",
|
||||
data=body,
|
||||
headers={"Content-Type": "application/json; charset=utf-8"},
|
||||
method="POST",
|
||||
)
|
||||
with request.urlopen(req, timeout=240) as resp:
|
||||
return json.loads(resp.read().decode("utf-8", errors="replace"))
|
||||
|
||||
|
||||
def latest_configcas_rows(adapter_url: str, base_id: str, limit: int) -> list[dict[str, Any]]:
|
||||
result = rpc(
|
||||
adapter_url,
|
||||
"query.run",
|
||||
{
|
||||
"base_id": base_id,
|
||||
"diagnostic": True,
|
||||
"query": f"SELECT TOP {int(limit)} FileName, DATALENGTH(BinaryData) AS Bytes, PartNo, Creation, Modified FROM ConfigCAS ORDER BY Modified DESC",
|
||||
"timeout_seconds": 120,
|
||||
},
|
||||
)
|
||||
return result.get("rows") or []
|
||||
|
||||
|
||||
def template_summary(adapter_url: str, base_id: str, file_name: str) -> dict[str, Any]:
|
||||
result = rpc(
|
||||
adapter_url,
|
||||
"templates.map",
|
||||
{
|
||||
"base_id": base_id,
|
||||
"table": "ConfigCAS",
|
||||
"file_name": file_name,
|
||||
"view": "summary",
|
||||
"sections": "cells,styles,named_areas,named_ranges,diagnostics",
|
||||
"max_cells": 200,
|
||||
"max_areas": 100,
|
||||
"timeout_seconds": 120,
|
||||
},
|
||||
)
|
||||
templates = result.get("templates") or []
|
||||
if not templates:
|
||||
return {}
|
||||
return (templates[0] or {}).get("structure") or {}
|
||||
|
||||
|
||||
def compact_signature(item: dict[str, Any]) -> dict[str, Any]:
|
||||
data: dict[str, Any] = {}
|
||||
for key in (
|
||||
"name",
|
||||
"kind",
|
||||
"text",
|
||||
"cell_id",
|
||||
"tree_position",
|
||||
"range",
|
||||
"range_candidate",
|
||||
"next_moxel_record",
|
||||
):
|
||||
if key in item:
|
||||
data[key] = item.get(key)
|
||||
style = item.get("style_evidence")
|
||||
if isinstance(style, dict):
|
||||
compact_style: dict[str, Any] = {}
|
||||
for key in ("immediate_preceding_values", "last_7_preceding_values"):
|
||||
if key in style:
|
||||
compact_style[key] = style.get(key)
|
||||
if compact_style:
|
||||
data["style_evidence"] = compact_style
|
||||
return data
|
||||
|
||||
|
||||
def build_history(
|
||||
adapter_url: str,
|
||||
base_id: str,
|
||||
limit: int,
|
||||
track_name: str | None,
|
||||
track_text: str | None,
|
||||
) -> list[dict[str, Any]]:
|
||||
rows = latest_configcas_rows(adapter_url, base_id, limit)
|
||||
history: list[dict[str, Any]] = []
|
||||
for row in rows:
|
||||
file_name = str(row.get("FileName") or "")
|
||||
byte_count = int(row.get("Bytes") or 0)
|
||||
if not file_name or byte_count <= 0 or byte_count > 5000:
|
||||
continue
|
||||
structure = template_summary(adapter_url, base_id, file_name)
|
||||
if str(structure.get("format") or "") != "MOXCEL":
|
||||
continue
|
||||
item: dict[str, Any] = {
|
||||
"file_name": file_name,
|
||||
"bytes": byte_count,
|
||||
"modified": row.get("Modified"),
|
||||
"counts": structure.get("counts") or {},
|
||||
}
|
||||
if track_name:
|
||||
matches = []
|
||||
for candidate in structure.get("named_range_candidates") or []:
|
||||
if str((candidate or {}).get("name") or "") == track_name:
|
||||
matches.append(compact_signature(candidate))
|
||||
item["named_range_matches"] = matches
|
||||
if track_text:
|
||||
matches = []
|
||||
for candidate in structure.get("cell_style_candidates") or []:
|
||||
if str((candidate or {}).get("text") or "") == track_text:
|
||||
matches.append(compact_signature(candidate))
|
||||
item["text_matches"] = matches
|
||||
history.append(item)
|
||||
return history
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Track recent MOXCEL template signatures from live ConfigCAS.")
|
||||
parser.add_argument("--adapter-url", default="http://docker-gpu.cin.su:8011")
|
||||
parser.add_argument("--base-id", default="upo_test")
|
||||
parser.add_argument("--limit", type=int, default=12)
|
||||
parser.add_argument("--track-name", default="R7C2_TEST")
|
||||
parser.add_argument("--track-text", default="Ячейка 7 - 2")
|
||||
parser.add_argument("--output", help="Optional JSON output path.")
|
||||
args = parser.parse_args()
|
||||
|
||||
history = build_history(args.adapter_url, args.base_id, args.limit, args.track_name, args.track_text)
|
||||
payload = {
|
||||
"schema": "codex_1c_template_history.v1",
|
||||
"adapter_url": args.adapter_url,
|
||||
"base_id": args.base_id,
|
||||
"track_name": args.track_name,
|
||||
"track_text": args.track_text,
|
||||
"items": history,
|
||||
"counts": {"items": len(history)},
|
||||
}
|
||||
rendered = json.dumps(payload, ensure_ascii=False, indent=2)
|
||||
if args.output:
|
||||
Path(args.output).write_text(rendered, encoding="utf-8")
|
||||
print(rendered)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user