Initial SQL-only 1C adapter baseline
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Resolve composite primitive/reference value groups from a 1C SQL read result."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
SUFFIX_KIND = {
|
||||
"_TYPE": "type_marker",
|
||||
"_S": "string",
|
||||
"_N": "number",
|
||||
"_L": "boolean",
|
||||
"_T": "datetime",
|
||||
"_RRRef": "reference_id",
|
||||
"_RTRef": "reference_type",
|
||||
}
|
||||
|
||||
|
||||
def load_json(path: Path) -> dict[str, Any]:
|
||||
return json.loads(path.read_text(encoding="utf-8-sig"))
|
||||
|
||||
|
||||
def value_hex(value: Any) -> str | None:
|
||||
if isinstance(value, dict) and value.get("kind") == "binary":
|
||||
return value.get("hex")
|
||||
return None
|
||||
|
||||
|
||||
def is_zero_binary(value: Any) -> bool:
|
||||
hex_value = value_hex(value)
|
||||
return bool(hex_value) and set(hex_value) <= {"0"}
|
||||
|
||||
|
||||
def is_present(value: Any) -> bool:
|
||||
if value is None:
|
||||
return False
|
||||
if isinstance(value, str):
|
||||
return value != ""
|
||||
if isinstance(value, dict) and value.get("kind") == "binary":
|
||||
return not is_zero_binary(value)
|
||||
return True
|
||||
|
||||
|
||||
def column_suffix(column: str) -> str | None:
|
||||
for suffix in sorted(SUFFIX_KIND, key=len, reverse=True):
|
||||
if column.endswith(suffix):
|
||||
return suffix
|
||||
return None
|
||||
|
||||
|
||||
def group_row(row: dict[str, Any]) -> dict[str, list[dict[str, Any]]]:
|
||||
groups: dict[str, list[dict[str, Any]]] = defaultdict(list)
|
||||
for cell_key, cell in row.items():
|
||||
column = str(cell.get("column") or "")
|
||||
suffix = column_suffix(column)
|
||||
if not suffix:
|
||||
continue
|
||||
metadata_path = str(cell.get("metadata_path") or cell_key)
|
||||
groups[metadata_path].append({"cell_key": cell_key, "suffix": suffix, "cell": cell})
|
||||
return groups
|
||||
|
||||
|
||||
def reference_types(types: list[str]) -> list[str]:
|
||||
return [item for item in types if item.startswith("cfg:") and "Ref." in item]
|
||||
|
||||
|
||||
def primitive_types(types: list[str]) -> list[str]:
|
||||
return [item for item in types if item.startswith("xs:")]
|
||||
|
||||
|
||||
def selected_branch(parts: dict[str, dict[str, Any]], types: list[str]) -> dict[str, Any]:
|
||||
primitive_candidates = []
|
||||
for suffix in ("_S", "_N", "_L", "_T"):
|
||||
part = parts.get(suffix)
|
||||
if part and is_present(part["cell"].get("value")):
|
||||
primitive_candidates.append(
|
||||
{
|
||||
"suffix": suffix,
|
||||
"kind": SUFFIX_KIND[suffix],
|
||||
"value": part["cell"].get("value"),
|
||||
}
|
||||
)
|
||||
rrref = parts.get("_RRRef")
|
||||
rtref = parts.get("_RTRef")
|
||||
if rrref and is_present(rrref["cell"].get("value")):
|
||||
refs = reference_types(types)
|
||||
return {
|
||||
"branch": "reference",
|
||||
"reference_id": rrref["cell"].get("value"),
|
||||
"reference_type_marker": rtref["cell"].get("value") if rtref else None,
|
||||
"candidate_reference_types": refs,
|
||||
"selection_evidence": "RRRef is non-zero; RTRef chooses target when present, otherwise value_type must contain a single reference type",
|
||||
}
|
||||
if primitive_candidates:
|
||||
return {
|
||||
"branch": "primitive",
|
||||
"primitive_values": primitive_candidates,
|
||||
"candidate_primitive_types": primitive_types(types),
|
||||
"selection_evidence": "primitive physical value column is present/non-empty",
|
||||
}
|
||||
return {
|
||||
"branch": "empty_or_unknown",
|
||||
"candidate_primitive_types": primitive_types(types),
|
||||
"candidate_reference_types": reference_types(types),
|
||||
"selection_evidence": "no non-empty primitive or reference value column observed",
|
||||
}
|
||||
|
||||
|
||||
def collect_scope(rows: list[dict[str, Any]], *, scope: str, table_part_name: str | None, table_part_table: str | None) -> list[dict[str, Any]]:
|
||||
result = []
|
||||
for row_index, row in enumerate(rows):
|
||||
for metadata_path, members in group_row(row).items():
|
||||
if len(members) < 2:
|
||||
continue
|
||||
parts = {member["suffix"]: member for member in members}
|
||||
if "_TYPE" not in parts:
|
||||
continue
|
||||
first = members[0]["cell"]
|
||||
value_type = first.get("value_type") or {}
|
||||
types = list(value_type.get("types") or [])
|
||||
if len(types) < 2:
|
||||
continue
|
||||
result.append(
|
||||
{
|
||||
"scope": scope,
|
||||
"table_part_name": table_part_name,
|
||||
"table_part_table": table_part_table,
|
||||
"row_index": row_index,
|
||||
"metadata_path": metadata_path,
|
||||
"metadata_name": first.get("metadata_name"),
|
||||
"metadata_uuid": first.get("metadata_uuid"),
|
||||
"value_types": types,
|
||||
"type_marker": parts["_TYPE"]["cell"].get("value"),
|
||||
"physical_parts": {
|
||||
suffix: {
|
||||
"kind": SUFFIX_KIND[suffix],
|
||||
"column": part["cell"].get("column"),
|
||||
"cell_key": part["cell_key"],
|
||||
"value": part["cell"].get("value"),
|
||||
}
|
||||
for suffix, part in sorted(parts.items())
|
||||
},
|
||||
"selected": selected_branch(parts, types),
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Resolve composite primitive/reference SQL values.")
|
||||
parser.add_argument("--read-result", type=Path, required=True)
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
read_result = load_json(args.read_result)
|
||||
composites = []
|
||||
composites.extend(
|
||||
collect_scope(
|
||||
read_result.get("main", {}).get("rows") or [],
|
||||
scope="main",
|
||||
table_part_name=None,
|
||||
table_part_table=read_result.get("main", {}).get("table"),
|
||||
)
|
||||
)
|
||||
for part in read_result.get("table_parts") or []:
|
||||
composites.extend(
|
||||
collect_scope(
|
||||
part.get("rows") or [],
|
||||
scope="table_part",
|
||||
table_part_name=part.get("name"),
|
||||
table_part_table=part.get("table"),
|
||||
)
|
||||
)
|
||||
|
||||
summary = defaultdict(int)
|
||||
for item in composites:
|
||||
summary[item["selected"]["branch"]] += 1
|
||||
result = {
|
||||
"schema": "onec_sql_composite_value_resolution.v1",
|
||||
"read_result_path": str(args.read_result),
|
||||
"source": {"kind": read_result.get("kind"), "identity": read_result.get("identity")},
|
||||
"summary": {
|
||||
"composite_values": len(composites),
|
||||
"by_branch": dict(sorted(summary.items())),
|
||||
},
|
||||
"composites": composites,
|
||||
}
|
||||
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({"output": str(args.output), "summary": result["summary"]}, ensure_ascii=False))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user