Initial SQL-only 1C adapter baseline
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
FIELDS = ("left", "right", "top", "bottom")
|
||||
|
||||
|
||||
def read_json(path: Path) -> dict[str, Any]:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def as_int(value: Any) -> int | None:
|
||||
try:
|
||||
return int(value)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def probe_ranges(payload: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
probe = payload.get("probe") if isinstance(payload.get("probe"), dict) else payload
|
||||
ranges = probe.get("named_ranges") or probe.get("named_range_candidates") or []
|
||||
return [item for item in ranges if isinstance(item, dict)]
|
||||
|
||||
|
||||
def candidate_indexes(raw_scalars: list[Any], expected_one_based: int) -> list[int]:
|
||||
result = []
|
||||
for index, value in enumerate(raw_scalars):
|
||||
if index < 2 or index > 5:
|
||||
continue
|
||||
parsed = as_int(value)
|
||||
if parsed is not None and parsed + 1 == expected_one_based:
|
||||
result.append(index)
|
||||
return result
|
||||
|
||||
|
||||
def analyze_range(item: dict[str, Any]) -> dict[str, Any] | None:
|
||||
range_info = item.get("range") if isinstance(item.get("range"), dict) else {}
|
||||
one_based = range_info.get("one_based") if isinstance(range_info.get("one_based"), dict) else {}
|
||||
raw_scalars = item.get("raw_scalars") if isinstance(item.get("raw_scalars"), list) else []
|
||||
if not one_based or not raw_scalars:
|
||||
return None
|
||||
field_candidates: dict[str, list[int]] = {}
|
||||
for field in FIELDS:
|
||||
expected = as_int(one_based.get(field))
|
||||
if expected is None:
|
||||
continue
|
||||
field_candidates[field] = candidate_indexes(raw_scalars, expected)
|
||||
unique_values = len({one_based.get(field) for field in FIELDS if one_based.get(field) is not None})
|
||||
return {
|
||||
"name": item.get("name"),
|
||||
"kind": item.get("kind"),
|
||||
"one_based": {field: one_based.get(field) for field in FIELDS if field in one_based},
|
||||
"raw_scalars": raw_scalars,
|
||||
"field_candidates": field_candidates,
|
||||
"distinct_coordinate_values": unique_values,
|
||||
}
|
||||
|
||||
|
||||
def aggregate_rules(samples: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
rules = []
|
||||
for field in FIELDS:
|
||||
sample_candidates = [set(sample.get("field_candidates", {}).get(field) or []) for sample in samples if sample.get("field_candidates", {}).get(field)]
|
||||
if not sample_candidates:
|
||||
continue
|
||||
intersection = set.intersection(*sample_candidates) if sample_candidates else set()
|
||||
all_distinct = all(int(sample.get("distinct_coordinate_values") or 0) >= 4 for sample in samples)
|
||||
confidence = "high" if len(intersection) == 1 and all_distinct else "medium" if intersection else "low"
|
||||
rules.append(
|
||||
{
|
||||
"target": f"moxel.named_range.{field}",
|
||||
"expression": "one_based = int(raw_scalar) + 1",
|
||||
"raw_scalar_indexes": sorted(intersection) if intersection else sorted(set.union(*sample_candidates)),
|
||||
"confidence": confidence,
|
||||
"evidence": {
|
||||
"samples": len(sample_candidates),
|
||||
"distinct_rectangular_samples": sum(1 for sample in samples if int(sample.get("distinct_coordinate_values") or 0) >= 4),
|
||||
},
|
||||
}
|
||||
)
|
||||
return rules
|
||||
|
||||
|
||||
def analyze(probes: list[dict[str, Any]], target_name: str | None = None) -> dict[str, Any]:
|
||||
samples = []
|
||||
for payload in probes:
|
||||
for item in probe_ranges(payload):
|
||||
if target_name and str(item.get("name") or "") != target_name:
|
||||
continue
|
||||
sample = analyze_range(item)
|
||||
if sample:
|
||||
samples.append(sample)
|
||||
return {
|
||||
"schema": "codex_1c_moxel_named_range_rule_analysis.v1",
|
||||
"target_name": target_name,
|
||||
"status": "ok",
|
||||
"samples": samples,
|
||||
"rules": aggregate_rules(samples),
|
||||
"counts": {
|
||||
"samples": len(samples),
|
||||
"rules": 0,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def render_markdown(payload: dict[str, Any]) -> str:
|
||||
payload["counts"]["rules"] = len(payload.get("rules") or [])
|
||||
lines = ["# 1C MOXCEL Named Range Rule Analysis", ""]
|
||||
lines.append(f"- Samples: `{payload.get('counts', {}).get('samples')}`")
|
||||
lines.append(f"- Rules: `{payload.get('counts', {}).get('rules')}`")
|
||||
lines.append("")
|
||||
lines.append("| Target | Confidence | Raw indexes | Samples |")
|
||||
lines.append("| --- | --- | --- | --- |")
|
||||
for rule in payload.get("rules") or []:
|
||||
evidence = rule.get("evidence") or {}
|
||||
lines.append(
|
||||
f"| `{rule.get('target')}` | `{rule.get('confidence')}` | "
|
||||
f"`{', '.join(map(str, rule.get('raw_scalar_indexes') or []))}` | `{evidence.get('samples')}` |"
|
||||
)
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Infer MOXCEL named range coordinate scalar indexes from probe snapshots.")
|
||||
parser.add_argument("--probe", action="append", required=True, help="Probe snapshot JSON. Repeatable.")
|
||||
parser.add_argument("--target-name", help="Optional named range to analyze.")
|
||||
parser.add_argument("--output-json", default="reports/1c-template-baselines/moxel-named-range-rules.json")
|
||||
parser.add_argument("--output-markdown", default="reports/1c-template-baselines/moxel-named-range-rules.md")
|
||||
args = parser.parse_args()
|
||||
|
||||
payload = analyze([read_json(Path(path)) for path in args.probe], args.target_name)
|
||||
payload["counts"]["rules"] = len(payload.get("rules") or [])
|
||||
json_path = Path(args.output_json)
|
||||
md_path = Path(args.output_markdown)
|
||||
json_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
md_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
json_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
md_path.write_text(render_markdown(payload), encoding="utf-8")
|
||||
print(json.dumps({"status": "ok", "json": str(json_path), "markdown": str(md_path), "counts": payload["counts"]}, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user