162 lines
7.2 KiB
Python
162 lines
7.2 KiB
Python
#!/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
|
||
|
||
|
||
KNOWN_ENUM_VALUES: dict[str, dict[str, str]] = {
|
||
"Вид": {
|
||
"1": "Поле надписи",
|
||
"2": "Поле ввода",
|
||
"4": "Страница",
|
||
"5": "Группа",
|
||
"9": "Командная панель",
|
||
"12": "Расширенная подсказка",
|
||
"31": "Кнопка командной панели",
|
||
"48": "Поле формы",
|
||
"55": "Динамический список",
|
||
"73": "Таблица формы",
|
||
},
|
||
"ПоложениеЗаголовка": {"0": "Авто", "1": "Верх", "2": "Нет"},
|
||
"ПоложениеВКоманднойПанели": {"0": "Авто", "1": "В командной панели", "2": "В дополнительном подменю"},
|
||
"Отображение": {"3": "Авто"},
|
||
"ЦветФона": {"3": "Авто"},
|
||
"ЦветТекста": {"3": "Авто"},
|
||
"ЦветРамки": {"3": "Авто"},
|
||
}
|
||
|
||
PROPERTY_ALIASES = {
|
||
"group": "Группа",
|
||
"id": "Идентификатор",
|
||
"name": "Имя",
|
||
"view": "Вид",
|
||
"title": "Заголовок",
|
||
"command_bar_location": "ПоложениеВКоманднойПанели",
|
||
}
|
||
|
||
|
||
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 property_key(entry: dict[str, Any]) -> tuple[str, str, str, str]:
|
||
prop = entry.get("property") if isinstance(entry.get("property"), dict) else {}
|
||
target = entry.get("requested_target") if isinstance(entry.get("requested_target"), dict) else {}
|
||
raw_name = str(prop.get("semantic_name") or prop.get("canonical_property") or prop.get("property") or "")
|
||
name = PROPERTY_ALIASES.get(raw_name, raw_name)
|
||
marker = str(target.get("marker") or "")
|
||
index = str(prop.get("parameter_index") if prop.get("parameter_index") is not None else "")
|
||
value_type = str(prop.get("value_type") or "")
|
||
return name, marker, index, value_type
|
||
|
||
|
||
def risk_class(name: str, value_type: str) -> str:
|
||
normalized = PROPERTY_ALIASES.get(name, name)
|
||
if normalized in {"Идентификатор", "Имя"} or "маркер" in normalized.casefold():
|
||
return "manual_only_identity_or_marker"
|
||
if name == "Вид":
|
||
return "structural_type_no_generic_write"
|
||
if normalized == "Группа":
|
||
return "reference_or_container_rule_required"
|
||
if normalized in KNOWN_ENUM_VALUES:
|
||
return "allowed_values_known_needs_smoke_rule"
|
||
if value_type in {"enum_atom", "bool_or_enum_atom", "color_or_enum_atom"}:
|
||
return "allowed_values_unknown"
|
||
return "scalar_semantics_unknown"
|
||
|
||
|
||
def main() -> int:
|
||
parser = argparse.ArgumentParser(description="Build enum/scalar learning registry from 1C write matrix gaps.")
|
||
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 enum registry JSON path.")
|
||
parser.add_argument("--sample-limit", type=int, default=8, help="Examples per enum/scalar group.")
|
||
args = parser.parse_args()
|
||
|
||
report = json.loads(args.matrix_report.read_text(encoding="utf-8"))
|
||
groups: dict[tuple[str, str, str, str], dict[str, Any]] = {}
|
||
for entry in load_entries(report):
|
||
prop = entry.get("property") if isinstance(entry.get("property"), dict) else {}
|
||
if entry.get("can_smoke"):
|
||
continue
|
||
if entry.get("reason") not in {"value_type_not_smoke_safe", "identity_or_binding_property"}:
|
||
continue
|
||
value_type = str(prop.get("value_type") or "")
|
||
if value_type not in {"enum_atom", "bool_or_enum_atom", "color_or_enum_atom", "integer_atom", "scalar"}:
|
||
continue
|
||
key = property_key(entry)
|
||
name, marker, index, _ = key
|
||
row = groups.setdefault(
|
||
key,
|
||
{
|
||
"property": name,
|
||
"marker": marker or None,
|
||
"parameter_index": index or None,
|
||
"value_type": value_type,
|
||
"risk": risk_class(name, value_type),
|
||
"observed_values": Counter(),
|
||
"reasons": Counter(),
|
||
"sections": Counter(),
|
||
"examples": [],
|
||
},
|
||
)
|
||
target = 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 {}
|
||
old = "" if prop.get("old") is None else str(prop.get("old"))
|
||
row["observed_values"][old] += 1
|
||
row["reasons"][str(entry.get("reason") or "")] += 1
|
||
row["sections"][str(effective.get("section") or "")] += 1
|
||
if len(row["examples"]) < args.sample_limit:
|
||
row["examples"].append(
|
||
{
|
||
"target": target.get("name") or target.get("path"),
|
||
"requested_section": target.get("section"),
|
||
"effective_section": effective.get("section"),
|
||
"presentation": prop.get("presentation"),
|
||
"semantic_name": prop.get("semantic_name"),
|
||
"old": old,
|
||
"write_path": prop.get("write_path"),
|
||
"reason": entry.get("reason"),
|
||
}
|
||
)
|
||
|
||
properties = []
|
||
for row in groups.values():
|
||
known = KNOWN_ENUM_VALUES.get(str(row.get("property") or ""))
|
||
properties.append(
|
||
{
|
||
**{key: value for key, value in row.items() if key not in {"observed_values", "reasons", "sections"}},
|
||
"observed_values": dict(row["observed_values"].most_common()),
|
||
"known_values": known,
|
||
"reasons": dict(row["reasons"]),
|
||
"sections": dict(row["sections"]),
|
||
"counts": {"entries": sum(row["observed_values"].values()), "observed_values": len(row["observed_values"])},
|
||
}
|
||
)
|
||
properties.sort(key=lambda item: (-int(item["counts"]["entries"]), str(item.get("property")), str(item.get("marker")), str(item.get("parameter_index"))))
|
||
|
||
result = {
|
||
"schema": "onec_form_write_enum_registry.v1",
|
||
"status": "ok",
|
||
"source_report": str(args.matrix_report),
|
||
"counts": {
|
||
"groups": len(properties),
|
||
"entries": sum(int(item["counts"]["entries"]) for item in properties),
|
||
"by_risk": dict(Counter(str(item.get("risk")) for item in properties)),
|
||
},
|
||
"properties": properties,
|
||
}
|
||
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())
|