Initial SQL-only 1C adapter baseline
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from collections import Counter, defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
def load_entries(report: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
matrix = report.get("matrix") if isinstance(report.get("matrix"), dict) else {}
|
||||
entries = matrix.get("entries") if isinstance(matrix.get("entries"), list) else []
|
||||
return [entry for entry in entries if isinstance(entry, dict)]
|
||||
|
||||
|
||||
def gap_row(entry: dict[str, Any]) -> dict[str, Any]:
|
||||
requested = entry.get("requested_target") if isinstance(entry.get("requested_target"), dict) else {}
|
||||
effective = entry.get("effective_target") if isinstance(entry.get("effective_target"), dict) else {}
|
||||
source = entry.get("effective_source") if isinstance(entry.get("effective_source"), dict) else {}
|
||||
prop = entry.get("property") if isinstance(entry.get("property"), dict) else {}
|
||||
probe = entry.get("codec_probe") if isinstance(entry.get("codec_probe"), dict) else {}
|
||||
probe_node = probe.get("node") if isinstance(probe.get("node"), dict) else {}
|
||||
return {
|
||||
"reason": entry.get("reason") or "unknown",
|
||||
"requested_section": requested.get("section"),
|
||||
"requested_name": requested.get("name"),
|
||||
"requested_path": requested.get("path"),
|
||||
"effective_section": effective.get("section"),
|
||||
"effective_name": effective.get("name"),
|
||||
"effective_path": effective.get("path"),
|
||||
"source_kind": source.get("kind") or "local",
|
||||
"property": prop.get("semantic_name") or prop.get("canonical_property") or prop.get("property"),
|
||||
"canonical_property": prop.get("canonical_property") or prop.get("property"),
|
||||
"presentation": prop.get("presentation"),
|
||||
"semantic_name": prop.get("semantic_name"),
|
||||
"semantic_group": prop.get("semantic_group"),
|
||||
"semantic_source": prop.get("semantic_source"),
|
||||
"parameter_index": prop.get("parameter_index"),
|
||||
"value_type": prop.get("value_type"),
|
||||
"old": prop.get("old"),
|
||||
"read_path": prop.get("read_path"),
|
||||
"write_path": prop.get("write_path"),
|
||||
"verification": prop.get("verification"),
|
||||
"codec_probe": probe or None,
|
||||
"codec_probe_node_type": probe_node.get("type") or probe.get("error") if probe else None,
|
||||
}
|
||||
|
||||
|
||||
def classify_action(reason: str, prop: str | None, value_type: str | None) -> str:
|
||||
if reason == "identity_or_binding_property":
|
||||
return "manual_only_identity_or_binding"
|
||||
if reason == "empty_local_string_requires_codec_probe":
|
||||
return "add_empty_composite_string_codec_probe"
|
||||
if reason == "composite_node_requires_semantic_rule":
|
||||
return "learn_composite_node_semantics"
|
||||
if reason == "value_type_not_smoke_safe":
|
||||
if value_type in {"enum_atom", "bool_or_enum_atom", "color_or_enum_atom"}:
|
||||
return "learn_allowed_enum_values"
|
||||
if prop in {"group"}:
|
||||
return "learn_reference_or_container_write_rule"
|
||||
return "classify_scalar_semantics"
|
||||
return "inspect"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Analyze not-smoked entries from a 1C saved-state write matrix report.")
|
||||
parser.add_argument("--matrix-report", type=Path, required=True, help="Report produced by scripts/smoke_1c_write_matrix.py.")
|
||||
parser.add_argument("--output", type=Path, required=True, help="Output JSON gap report.")
|
||||
parser.add_argument("--sample-limit", type=int, default=12, help="Samples per reason/action.")
|
||||
args = parser.parse_args()
|
||||
|
||||
report = json.loads(args.matrix_report.read_text(encoding="utf-8"))
|
||||
gaps = []
|
||||
for entry in load_entries(report):
|
||||
if entry.get("can_smoke"):
|
||||
continue
|
||||
row = gap_row(entry)
|
||||
row["next_action"] = classify_action(str(row.get("reason") or ""), row.get("property"), row.get("value_type"))
|
||||
gaps.append(row)
|
||||
|
||||
by_reason = Counter(row["reason"] for row in gaps)
|
||||
by_action = Counter(row["next_action"] for row in gaps)
|
||||
by_section = Counter(row["effective_section"] for row in gaps)
|
||||
by_property = Counter(row["property"] for row in gaps)
|
||||
by_probe_node_type = Counter(row.get("codec_probe_node_type") for row in gaps if row.get("codec_probe_node_type"))
|
||||
samples_by_reason: dict[str, list[dict[str, Any]]] = defaultdict(list)
|
||||
samples_by_action: dict[str, list[dict[str, Any]]] = defaultdict(list)
|
||||
shape_summary: dict[str, dict[str, Any]] = defaultdict(lambda: {"count": 0, "properties": Counter(), "sections": Counter(), "samples": []})
|
||||
for row in gaps:
|
||||
reason = str(row["reason"])
|
||||
action = str(row["next_action"])
|
||||
probe = row.get("codec_probe") if isinstance(row.get("codec_probe"), dict) else {}
|
||||
node = probe.get("node") if isinstance(probe.get("node"), dict) else {}
|
||||
children = node.get("children") if isinstance(node.get("children"), list) else []
|
||||
if node:
|
||||
shape = str(node.get("type") or "unknown") + "|" + ",".join(str((child or {}).get("type")) for child in children[:12])
|
||||
shape_row = shape_summary[shape]
|
||||
shape_row["count"] = int(shape_row["count"]) + 1
|
||||
shape_row["properties"][row.get("property")] += 1
|
||||
shape_row["sections"][row.get("effective_section")] += 1
|
||||
if len(shape_row["samples"]) < args.sample_limit:
|
||||
shape_row["samples"].append(
|
||||
{
|
||||
"target": row.get("requested_name") or row.get("requested_path"),
|
||||
"section": row.get("effective_section"),
|
||||
"property": row.get("presentation") or row.get("property"),
|
||||
"semantic_name": row.get("semantic_name"),
|
||||
"semantic_group": row.get("semantic_group"),
|
||||
"old": row.get("old"),
|
||||
"write_path": row.get("write_path"),
|
||||
}
|
||||
)
|
||||
sample = {
|
||||
"target": row.get("requested_name") or row.get("requested_path"),
|
||||
"section": row.get("effective_section"),
|
||||
"property": row.get("presentation") or row.get("property"),
|
||||
"semantic_name": row.get("semantic_name"),
|
||||
"semantic_group": row.get("semantic_group"),
|
||||
"value_type": row.get("value_type"),
|
||||
"old": row.get("old"),
|
||||
"write_path": row.get("write_path"),
|
||||
}
|
||||
if len(samples_by_reason[reason]) < args.sample_limit:
|
||||
samples_by_reason[reason].append(sample)
|
||||
if len(samples_by_action[action]) < args.sample_limit:
|
||||
samples_by_action[action].append(sample)
|
||||
|
||||
result = {
|
||||
"schema": "onec_form_write_matrix_gap_analysis.v1",
|
||||
"status": "ok",
|
||||
"source_report": str(args.matrix_report),
|
||||
"counts": {
|
||||
"gaps": len(gaps),
|
||||
"by_reason": dict(sorted(by_reason.items())),
|
||||
"by_next_action": dict(sorted(by_action.items())),
|
||||
"by_effective_section": dict(sorted(by_section.items())),
|
||||
"by_codec_probe_node_type": dict(sorted(by_probe_node_type.items())),
|
||||
"top_properties": by_property.most_common(40),
|
||||
},
|
||||
"samples_by_reason": dict(samples_by_reason),
|
||||
"samples_by_next_action": dict(samples_by_action),
|
||||
"codec_probe_shapes": [
|
||||
{
|
||||
"shape": shape,
|
||||
"count": row["count"],
|
||||
"properties": row["properties"].most_common(20),
|
||||
"sections": dict(row["sections"]),
|
||||
"samples": row["samples"],
|
||||
}
|
||||
for shape, row in sorted(shape_summary.items(), key=lambda item: int(item[1]["count"]), reverse=True)
|
||||
],
|
||||
"gaps": gaps,
|
||||
}
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.output.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
print(json.dumps({"schema": result["schema"], "status": "ok", "counts": result["counts"], "path": str(args.output)}, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user