Initial SQL-only 1C adapter baseline
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
"""Mechanical section summaries for Config brace trees.
|
||||
|
||||
This module intentionally does not name sections as attributes, tabular
|
||||
sections, forms, etc. It only reports paths, shapes, strings, and GUIDs so a
|
||||
higher-level validator can attach semantics using XML or other evidence.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import asdict, dataclass
|
||||
from typing import Any
|
||||
|
||||
from .payload import GUID_RE, collect_strings, scalar
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SectionSummary:
|
||||
path: str
|
||||
node_type: str
|
||||
list_len: int | None
|
||||
first_scalars: list[str]
|
||||
string_count_sampled: int
|
||||
guid_count_sampled: int
|
||||
strings_sample: list[str]
|
||||
guids_sample: list[str]
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return asdict(self)
|
||||
|
||||
|
||||
def _children(node: Any) -> list[Any]:
|
||||
if isinstance(node, dict) and node.get("type") in {"list", "sequence"}:
|
||||
return node.get("items") or []
|
||||
return []
|
||||
|
||||
|
||||
def _atoms(node: Any, limit: int) -> list[str]:
|
||||
values: list[str] = []
|
||||
|
||||
def walk(value: Any) -> None:
|
||||
if len(values) >= limit:
|
||||
return
|
||||
if isinstance(value, dict) and value.get("type") in {"atom", "string"}:
|
||||
text = scalar(value)
|
||||
if text:
|
||||
values.append(text)
|
||||
return
|
||||
for child in _children(value):
|
||||
walk(child)
|
||||
|
||||
walk(node)
|
||||
return values
|
||||
|
||||
|
||||
def summarize_section(node: Any, path: str, *, limit: int = 200) -> SectionSummary:
|
||||
children = _children(node)
|
||||
atoms = _atoms(node, limit)
|
||||
strings = collect_strings(node, limit=limit)
|
||||
guids = sorted(set(value.lower() for value in atoms if GUID_RE.fullmatch(value)))
|
||||
return SectionSummary(
|
||||
path=path,
|
||||
node_type=node.get("type") if isinstance(node, dict) else type(node).__name__,
|
||||
list_len=len(children) if children else None,
|
||||
first_scalars=[scalar(child) for child in children[:12]],
|
||||
string_count_sampled=len(strings),
|
||||
guid_count_sampled=len(guids),
|
||||
strings_sample=strings[:50],
|
||||
guids_sample=guids[:50],
|
||||
)
|
||||
|
||||
|
||||
def summarize_sections(tree: Any, *, max_depth: int = 2, limit: int = 200) -> list[SectionSummary]:
|
||||
summaries: list[SectionSummary] = []
|
||||
|
||||
def walk(node: Any, path: list[int], depth: int) -> None:
|
||||
if depth > max_depth:
|
||||
return
|
||||
if path:
|
||||
summaries.append(summarize_section(node, ".".join(str(part) for part in path), limit=limit))
|
||||
for index, child in enumerate(_children(node)):
|
||||
walk(child, [*path, index], depth + 1)
|
||||
|
||||
walk(tree, [], 0)
|
||||
return summaries
|
||||
Reference in New Issue
Block a user