Initial SQL-only 1C adapter baseline
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from analyze_1c_template_xml_profiles import merge_ranges
|
||||
|
||||
|
||||
def rpc(adapter_url: str, method: str, payload: dict[str, Any]) -> dict[str, Any]:
|
||||
body = json.dumps({"method": method, "payload": payload}, ensure_ascii=False).encode("utf-8")
|
||||
req = urllib.request.Request(
|
||||
f"{adapter_url.rstrip('/')}/rpc",
|
||||
data=body,
|
||||
headers={"Content-Type": "application/json; charset=utf-8"},
|
||||
method="POST",
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=180) as resp:
|
||||
return json.loads(resp.read().decode("utf-8", errors="replace"))
|
||||
|
||||
|
||||
def runs(values: list[int]) -> list[dict[str, int]]:
|
||||
if not values:
|
||||
return []
|
||||
result: list[dict[str, int]] = []
|
||||
start = previous = values[0]
|
||||
for value in values[1:]:
|
||||
if value == previous + 1:
|
||||
previous = value
|
||||
continue
|
||||
result.append({"start": start, "end": previous, "length": previous - start + 1})
|
||||
start = previous = value
|
||||
result.append({"start": start, "end": previous, "length": previous - start + 1})
|
||||
return result
|
||||
|
||||
|
||||
def div32_values(numbers: list[Any]) -> list[int]:
|
||||
return [
|
||||
int(value) // 32
|
||||
for value in numbers
|
||||
if isinstance(value, int) and value > 0 and value <= 4096 and value % 32 == 0
|
||||
]
|
||||
|
||||
|
||||
def small_values(numbers: list[Any]) -> list[int]:
|
||||
return [int(value) for value in numbers if isinstance(value, int) and 2 <= value <= 128]
|
||||
|
||||
|
||||
def template_xml_path(root: Path, template: str) -> Path:
|
||||
return root / template / "Ext" / "Template.xml"
|
||||
|
||||
|
||||
def merge_block_candidate(adapter_url: str, base_id: str, owner_kind: str, owner_name: str, template: str) -> dict[str, Any]:
|
||||
data = rpc(
|
||||
adapter_url,
|
||||
"templates.read",
|
||||
{
|
||||
"base_id": base_id,
|
||||
"kind": owner_kind,
|
||||
"name": owner_name,
|
||||
"template": template,
|
||||
"sections": "merges",
|
||||
"refresh_cache": False,
|
||||
},
|
||||
)
|
||||
return (((data.get("templates") or [{}])[0].get("structure") or {}).get("merge_record_block_candidates") or [{}])[0]
|
||||
|
||||
|
||||
def merge_block_records(
|
||||
adapter_url: str,
|
||||
base_id: str,
|
||||
owner_kind: str,
|
||||
owner_name: str,
|
||||
template: str,
|
||||
candidate: dict[str, Any],
|
||||
) -> list[dict[str, Any]]:
|
||||
position = str(candidate.get("tree_position") or "$.0")
|
||||
try:
|
||||
start = int(position.split(".")[1]) + 1
|
||||
except (IndexError, ValueError):
|
||||
start = 0
|
||||
count = int(candidate.get("count") or 0)
|
||||
data = rpc(
|
||||
adapter_url,
|
||||
"templates.read",
|
||||
{
|
||||
"base_id": base_id,
|
||||
"kind": owner_kind,
|
||||
"name": owner_name,
|
||||
"template": template,
|
||||
"sections": "moxel_records",
|
||||
"max_moxel_records": count + 40,
|
||||
"moxel_record_start": start,
|
||||
"moxel_record_end": start + count + 35,
|
||||
"refresh_cache": False,
|
||||
},
|
||||
)
|
||||
diagnostics = ((data.get("templates") or [{}])[0].get("structure") or {}).get("moxel_record_diagnostics") or [{}]
|
||||
if isinstance(diagnostics, list):
|
||||
diagnostics = diagnostics[0] if diagnostics else {}
|
||||
return [record for record in diagnostics.get("top_level_records") or [] if isinstance(record, dict)][:count]
|
||||
|
||||
|
||||
def analyze_template(
|
||||
*,
|
||||
adapter_url: str,
|
||||
base_id: str,
|
||||
owner_kind: str,
|
||||
owner_name: str,
|
||||
template: str,
|
||||
xml_root: Path,
|
||||
) -> dict[str, Any]:
|
||||
xml_path = template_xml_path(xml_root, template)
|
||||
merges = merge_ranges(ET.parse(xml_path).getroot(), limit=500)
|
||||
xml_rows = sorted(set(int(item["row"]) for item in merges))
|
||||
xml_columns = sorted(set(int(item["column"]) for item in merges) | set(int(item["column"]) + int(item["width"]) - 1 for item in merges))
|
||||
candidate = merge_block_candidate(adapter_url, base_id, owner_kind, owner_name, template)
|
||||
records = merge_block_records(adapter_url, base_id, owner_kind, owner_name, template, candidate)
|
||||
by_value: dict[int, list[int]] = {}
|
||||
coordinate_records: list[dict[str, Any]] = []
|
||||
for index, record in enumerate(records, 1):
|
||||
numbers = record.get("numeric_items") or []
|
||||
for value in set(small_values(numbers)):
|
||||
by_value.setdefault(value, []).append(index)
|
||||
packed_columns = div32_values(numbers)
|
||||
if packed_columns:
|
||||
coordinate_records.append(
|
||||
{
|
||||
"index": index,
|
||||
"tree_position": record.get("tree_position"),
|
||||
"numeric_items": numbers,
|
||||
"div32": packed_columns,
|
||||
"small": small_values(numbers),
|
||||
}
|
||||
)
|
||||
value_summaries = [
|
||||
{
|
||||
"value": value,
|
||||
"count": len(indexes),
|
||||
"record_indexes": indexes[:30],
|
||||
"runs": runs(indexes),
|
||||
"matches_xml_row": value in xml_rows,
|
||||
"matches_xml_column_or_edge": value in xml_columns,
|
||||
}
|
||||
for value, indexes in sorted(by_value.items())
|
||||
]
|
||||
xml_row_hits = [
|
||||
{
|
||||
"row": row,
|
||||
"count": len(by_value.get(row) or []),
|
||||
"record_indexes": (by_value.get(row) or [])[:20],
|
||||
"runs": runs(by_value.get(row) or [])[:8],
|
||||
}
|
||||
for row in xml_rows
|
||||
if by_value.get(row)
|
||||
]
|
||||
return {
|
||||
"template": template,
|
||||
"xml_merge_count": len(merges),
|
||||
"sql_block_count": int(candidate.get("count") or 0),
|
||||
"tree_position": candidate.get("tree_position"),
|
||||
"xml_rows": xml_rows,
|
||||
"xml_row_runs": runs(xml_rows),
|
||||
"xml_columns_and_right_edges": xml_columns,
|
||||
"value_summaries": value_summaries,
|
||||
"xml_row_hits": xml_row_hits,
|
||||
"coordinate_records": coordinate_records[:120],
|
||||
"coordinate_record_runs": runs([item["index"] for item in coordinate_records]),
|
||||
}
|
||||
|
||||
|
||||
def render_markdown(payload: dict[str, Any]) -> str:
|
||||
lines = ["# MOXCEL merge row-band analysis", ""]
|
||||
for item in payload.get("items") or []:
|
||||
lines.append(f"## {item.get('template')}")
|
||||
lines.append("")
|
||||
lines.append(f"- XML merges: `{item.get('xml_merge_count')}`")
|
||||
lines.append(f"- SQL block count: `{item.get('sql_block_count')}` at `{item.get('tree_position')}`")
|
||||
lines.append(f"- XML row runs: `{item.get('xml_row_runs')}`")
|
||||
lines.append(f"- SQL coordinate-record runs: `{(item.get('coordinate_record_runs') or [])[:20]}`")
|
||||
lines.append("")
|
||||
lines.append("### XML Row Hits In SQL Small Scalars")
|
||||
lines.append("")
|
||||
lines.append("| Row | Count | Runs | First indexes |")
|
||||
lines.append("| ---: | ---: | --- | --- |")
|
||||
for hit in (item.get("xml_row_hits") or [])[:60]:
|
||||
lines.append(f"| {hit.get('row')} | {hit.get('count')} | `{hit.get('runs')}` | `{(hit.get('record_indexes') or [])[:12]}` |")
|
||||
lines.append("")
|
||||
lines.append("### Top Small Scalar Values")
|
||||
lines.append("")
|
||||
lines.append("| Value | Count | XML row | XML col/edge | Runs |")
|
||||
lines.append("| ---: | ---: | --- | --- | --- |")
|
||||
for value in sorted(item.get("value_summaries") or [], key=lambda row: (-int(row.get("count") or 0), int(row.get("value") or 0)))[:30]:
|
||||
lines.append(
|
||||
f"| {value.get('value')} | {value.get('count')} | `{value.get('matches_xml_row')}` | "
|
||||
f"`{value.get('matches_xml_column_or_edge')}` | `{(value.get('runs') or [])[:8]}` |"
|
||||
)
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Analyze SQL MOXCEL merge-block row/size scalar bands against XML merge rows.")
|
||||
parser.add_argument("--adapter-url", default="http://docker-gpu.cin.su:8011")
|
||||
parser.add_argument("--base-id", default="upo_test")
|
||||
parser.add_argument("--owner-kind", default="Document")
|
||||
parser.add_argument("--owner-name", default="АвансовыйОтчет")
|
||||
parser.add_argument(
|
||||
"--xml-root",
|
||||
default=r"Z:\codex\1C\XML\UPO\Структура базы 1с\Конфигурация\Documents\АвансовыйОтчет\Templates",
|
||||
)
|
||||
parser.add_argument("--template", action="append", required=True)
|
||||
parser.add_argument("--output-json", default="reports/1c-template-baselines/moxel-merge-row-band-analysis.json")
|
||||
parser.add_argument("--output-markdown", default="reports/1c-template-baselines/moxel-merge-row-band-analysis.md")
|
||||
args = parser.parse_args()
|
||||
|
||||
payload = {
|
||||
"schema": "codex_1c_moxel_merge_row_band_analysis.v1",
|
||||
"items": [
|
||||
analyze_template(
|
||||
adapter_url=args.adapter_url,
|
||||
base_id=args.base_id,
|
||||
owner_kind=args.owner_kind,
|
||||
owner_name=args.owner_name,
|
||||
template=template,
|
||||
xml_root=Path(args.xml_root),
|
||||
)
|
||||
for template in args.template
|
||||
],
|
||||
}
|
||||
Path(args.output_json).write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
Path(args.output_markdown).write_text(render_markdown(payload), encoding="utf-8")
|
||||
print(json.dumps({"status": "ok", "json": args.output_json, "markdown": args.output_markdown, "items": len(payload["items"])}, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user