Files
llm/scripts/compare_1c_sql_xml_guids.py
T

154 lines
6.4 KiB
Python

#!/usr/bin/env python3
"""Compare SQL DBNames GUIDs with a 1C XML GUID index.
This report is a bridge between SQL storage-role records and XML metadata
objects. It keeps DBNames storage roles unchanged and only attaches XML facts
when the same GUID is present in the XML dump.
"""
from __future__ import annotations
import argparse
import json
from collections import Counter, defaultdict
from pathlib import Path
from typing import Any
def load_json(path: Path) -> dict[str, Any]:
return json.loads(path.read_text(encoding="utf-8"))
def group_dbnames(dbnames_report: dict[str, Any]) -> dict[str, Any]:
grouped: dict[str, Any] = defaultdict(lambda: {"records": [], "roles": Counter(), "sources": Counter()})
for source in dbnames_report.get("dbnames") or []:
file_name = source.get("file_name") or ""
for record in source.get("records") or []:
if record.get("status") != "parsed":
continue
guid = str(record.get("guid") or "").lower()
if not guid:
continue
item = grouped[guid]
compact = {
"source_file": file_name,
"storage_role": record.get("storage_role"),
"sql_number": record.get("sql_number"),
"index": record.get("index"),
}
item["records"].append(compact)
item["roles"][compact["storage_role"]] += 1
item["sources"][file_name] += 1
result = {}
for guid, item in grouped.items():
result[guid] = {
"records": sorted(item["records"], key=lambda row: (row["source_file"], str(row["storage_role"]), row["sql_number"])),
"storage_roles": dict(sorted(item["roles"].items())),
"source_files": dict(sorted(item["sources"].items())),
}
return dict(sorted(result.items()))
def main() -> int:
parser = argparse.ArgumentParser(description="Compare SQL DBNames GUIDs with XML GUID index.")
parser.add_argument("--dbnames", type=Path, required=True)
parser.add_argument("--xml-index", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--max-objects", type=int, default=1000)
args = parser.parse_args()
dbnames = load_json(args.dbnames)
xml_index = load_json(args.xml_index)
sql_by_guid = group_dbnames(dbnames)
xml_map = xml_index.get("guid_map") or {}
matches = []
top_object_matches = []
occurrence_only_matches = []
unmatched_sql = []
matched_role_counts: Counter[str] = Counter()
top_object_matched_role_counts: Counter[str] = Counter()
occurrence_only_matched_role_counts: Counter[str] = Counter()
unmatched_role_counts: Counter[str] = Counter()
xml_kind_counts: Counter[str] = Counter()
for guid, sql_item in sql_by_guid.items():
xml_item = xml_map.get(guid)
roles = sql_item["storage_roles"]
if not xml_item:
unmatched_sql.append({"guid": guid, "storage_roles": roles, "records": sql_item["records"][:20]})
unmatched_role_counts.update(roles)
continue
top_objects = xml_item.get("top_objects") or []
for role, count in roles.items():
matched_role_counts[role] += count
if top_objects:
top_object_matched_role_counts[role] += count
else:
occurrence_only_matched_role_counts[role] += count
for top_object in top_objects:
xml_kind_counts[top_object.get("xml_kind") or "<unknown>"] += 1
match_item = {
"guid": guid,
"storage_roles": roles,
"source_files": sql_item["source_files"],
"xml_top_objects": top_objects,
"xml_occurrences": xml_item.get("occurrences") or [],
"records": sql_item["records"][:50],
}
matches.append(match_item)
if top_objects:
top_object_matches.append(match_item)
else:
occurrence_only_matches.append(match_item)
report = {
"schema": "onec_sql_xml_guid_compare.v1",
"dbnames": str(args.dbnames),
"xml_index": str(args.xml_index),
"sql_guid_count": len(sql_by_guid),
"xml_guid_count": len(xml_map),
"matched_guid_count": len(matches),
"top_object_matched_guid_count": len(top_object_matches),
"occurrence_only_matched_guid_count": len(occurrence_only_matches),
"unmatched_sql_guid_count": len(unmatched_sql),
"matched_guids": [item["guid"] for item in matches],
"top_object_matched_guids": [item["guid"] for item in top_object_matches],
"occurrence_only_matched_guids": [item["guid"] for item in occurrence_only_matches],
"unmatched_sql_guids": [item["guid"] for item in unmatched_sql],
"matched_storage_role_counts": dict(sorted(matched_role_counts.items(), key=lambda item: (-item[1], item[0]))),
"top_object_matched_storage_role_counts": dict(
sorted(top_object_matched_role_counts.items(), key=lambda item: (-item[1], item[0]))
),
"occurrence_only_matched_storage_role_counts": dict(
sorted(occurrence_only_matched_role_counts.items(), key=lambda item: (-item[1], item[0]))
),
"unmatched_storage_role_counts": dict(sorted(unmatched_role_counts.items(), key=lambda item: (-item[1], item[0]))),
"matched_xml_kind_counts": dict(sorted(xml_kind_counts.items(), key=lambda item: (-item[1], item[0]))),
"matches": matches[: args.max_objects],
"top_object_matches": top_object_matches[: args.max_objects],
"occurrence_only_matches": occurrence_only_matches[: args.max_objects],
"unmatched_sql": unmatched_sql[: args.max_objects],
}
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(
json.dumps(
{
"output": str(args.output),
"sql_guids": len(sql_by_guid),
"xml_guids": len(xml_map),
"matched": len(matches),
"top_object_matched": len(top_object_matches),
"occurrence_only_matched": len(occurrence_only_matches),
"unmatched_sql": len(unmatched_sql),
},
ensure_ascii=False,
)
)
return 0
if __name__ == "__main__":
raise SystemExit(main())