from __future__ import annotations import argparse import json from concurrent.futures import ThreadPoolExecutor, as_completed from pathlib import Path from typing import Any from urllib import request def rpc(adapter_url: str, method: str, payload: dict[str, Any], *, timeout: int = 240) -> dict[str, Any]: body = json.dumps({"method": method, "payload": payload}, ensure_ascii=False).encode("utf-8") req = request.Request( f"{adapter_url.rstrip('/')}/rpc", data=body, headers={"Content-Type": "application/json; charset=utf-8"}, method="POST", ) with request.urlopen(req, timeout=timeout) as resp: return json.loads(resp.read().decode("utf-8", errors="replace")) def read_inventory(path: Path) -> dict[str, Any]: return json.loads(path.read_text(encoding="utf-8")) def structure_summary(structure: dict[str, Any], sample_limit: int) -> dict[str, Any]: cells = structure.get("cells") or [] parameters = structure.get("parameters") or [] cell_parameters = structure.get("cell_parameters") or [] cell_text_identifiers = structure.get("cell_text_identifiers") or [] cell_coordinate_hints = structure.get("cell_coordinate_hints") or [] named_areas = structure.get("named_areas") or [] named_ranges = structure.get("named_range_candidates") or [] styles = structure.get("cell_style_candidates") or [] column_widths = structure.get("column_widths") or [] format_table = structure.get("format_table") or [] font_table = structure.get("font_table") or [] cell_format_links = structure.get("cell_format_links") or [] merged_ranges = structure.get("merged_ranges") or [] merged_range_candidates = structure.get("merged_range_candidates") or [] merge_record_block_candidates = structure.get("merge_record_block_candidates") or [] merge_count_hints = structure.get("merge_count_hints") or [] style_coordinate_hints = [ item for item in styles if isinstance(item, dict) and isinstance(item.get("coordinate_hints"), dict) ] diagnostics = structure.get("diagnostics") or [] top_level = structure.get("moxel_record_diagnostics") or {} record_summary = top_level.get("top_level_record_summary") if isinstance(top_level, dict) else {} counts = { **(structure.get("counts") or {}), "cells": int((structure.get("counts") or {}).get("cells") or len(cells)), "parameters": int((structure.get("counts") or {}).get("parameters") or len(parameters)), "cell_parameters": int((structure.get("counts") or {}).get("cell_parameters") or len(cell_parameters)), "cell_text_identifiers": int((structure.get("counts") or {}).get("cell_text_identifiers") or len(cell_text_identifiers)), "cell_coordinate_hints": int((structure.get("counts") or {}).get("cell_coordinate_hints") or len(cell_coordinate_hints)), "named_areas": int((structure.get("counts") or {}).get("named_areas") or len(named_areas)), "named_range_candidates": int((structure.get("counts") or {}).get("named_range_candidates") or len(named_ranges)), "cell_style_candidates": int((structure.get("counts") or {}).get("cell_style_candidates") or len(styles)), "cell_style_coordinate_hints": int((structure.get("counts") or {}).get("cell_style_coordinate_hints") or len(style_coordinate_hints)), "column_widths": int((structure.get("counts") or {}).get("column_widths") or len(column_widths)), "format_table": int((structure.get("counts") or {}).get("format_table") or len(format_table)), "font_table": int((structure.get("counts") or {}).get("font_table") or len(font_table)), "cell_format_links": int((structure.get("counts") or {}).get("cell_format_links") or len(cell_format_links)), "merged_ranges": int((structure.get("counts") or {}).get("merged_ranges") or len(merged_ranges)), "merged_range_candidates": int((structure.get("counts") or {}).get("merged_range_candidates") or len(merged_range_candidates)), "merge_record_block_candidates": int((structure.get("counts") or {}).get("merge_record_block_candidates") or len(merge_record_block_candidates)), "merge_count_hints": int((structure.get("counts") or {}).get("merge_count_hints") or len(merge_count_hints)), } return { "format": structure.get("format"), "dimensions": structure.get("dimensions"), "capacity_dimensions": structure.get("capacity_dimensions"), "used_dimensions": structure.get("used_dimensions"), "format_dimensions": structure.get("format_dimensions"), "capabilities": structure.get("capabilities") or {}, "counts": counts, "sample_texts": [str(item.get("text") or "") for item in cells[:sample_limit] if isinstance(item, dict) and item.get("text")], "sample_parameters": [str(item.get("name") or "") for item in parameters[:sample_limit] if isinstance(item, dict) and item.get("name")], "sample_cell_parameters": [ { "name": item.get("name"), "row": item.get("row"), "column": item.get("column"), "cell_text": item.get("cell_text"), } for item in cell_parameters[:sample_limit] if isinstance(item, dict) ], "sample_text_identifiers": [ { "name": item.get("name"), "row": item.get("row"), "column": item.get("column"), "parameter": item.get("parameter"), } for item in cell_text_identifiers[:sample_limit] if isinstance(item, dict) ], "sample_cell_coordinate_hints": [ { "text": item.get("text"), "one_based": item.get("one_based"), "confidence": item.get("confidence"), "evidence": item.get("evidence"), } for item in cell_coordinate_hints[:sample_limit] if isinstance(item, dict) ], "sample_named_areas": [item.get("name") for item in named_areas[:sample_limit] if isinstance(item, dict) and item.get("name")], "sample_named_ranges": [item.get("name") for item in named_ranges[:sample_limit] if isinstance(item, dict) and item.get("name")], "sample_style_texts": [str(item.get("text") or "") for item in styles[:sample_limit] if isinstance(item, dict) and item.get("text")], "sample_style_coordinate_hints": [ { "text": item.get("text"), "tree_position": item.get("tree_position"), "coordinate_hints": item.get("coordinate_hints"), } for item in style_coordinate_hints[:sample_limit] if isinstance(item, dict) ], "sample_column_widths": [ { "column": item.get("column"), "width": item.get("width"), } for item in column_widths[:sample_limit] if isinstance(item, dict) ], "sample_format_table": [ { "format_index": item.get("format_index"), "font_index": item.get("font_index"), "width": item.get("width"), "horizontal_alignment": item.get("horizontal_alignment"), "vertical_alignment": item.get("vertical_alignment"), "text_placement": item.get("text_placement"), } for item in format_table[:sample_limit] if isinstance(item, dict) ], "sample_font_table": [ { "font_index": item.get("font_index"), "face_name": item.get("face_name"), "height": item.get("height"), "weight": item.get("weight"), "bold": item.get("bold"), } for item in font_table[:sample_limit] if isinstance(item, dict) ], "sample_cell_format_links": [ { "row": item.get("row"), "column": item.get("column"), "format_index": item.get("format_index"), "format": item.get("format"), "text": item.get("text"), } for item in cell_format_links[:sample_limit] if isinstance(item, dict) ], "cell_format_link_stats": structure.get("cell_format_link_stats") or {}, "sample_merged_ranges": [ { "range": item.get("range"), "source": item.get("source"), } for item in merged_ranges[:sample_limit] if isinstance(item, dict) ], "sample_merged_range_candidates": [ { "name": item.get("name"), "range": item.get("range"), "confidence": item.get("confidence"), } for item in merged_range_candidates[:sample_limit] if isinstance(item, dict) ], "sample_merge_record_block_candidates": [ { "count": item.get("count"), "tree_position": item.get("tree_position"), "record_window": item.get("record_window"), "confidence": item.get("confidence"), "evidence": item.get("evidence"), "sample_records": item.get("sample_records"), } for item in merge_record_block_candidates[:sample_limit] if isinstance(item, dict) ], "sample_merge_count_hints": [ { "count": item.get("count"), "tree_position": item.get("tree_position"), "confidence": item.get("confidence"), "evidence": item.get("evidence"), } for item in merge_count_hints[:sample_limit] if isinstance(item, dict) ], "diagnostics": diagnostics[:sample_limit] if isinstance(diagnostics, list) else [], "top_level_record_summary": record_summary or None, } def profile_template( adapter_url: str, base_id: str, owner: dict[str, Any], template: dict[str, Any], timeout_seconds: int, sample_limit: int, refresh_cache: bool, ) -> dict[str, Any]: result = rpc( adapter_url, "templates.read", { "base_id": base_id, "guid": owner.get("guid"), "kind": owner.get("kind"), "name": owner.get("name"), "template": template.get("name"), "sections": "summary,merged_ranges,merges,coordinate_hints,cells,parameters,named_areas,formats,diagnostics", "refresh_cache": refresh_cache, "max_cells": sample_limit, "max_merged": sample_limit, "max_parameters": sample_limit, "max_named_areas": sample_limit, "timeout_seconds": timeout_seconds, }, timeout=max(240, timeout_seconds + 60), ) templates = result.get("templates") or [] if not templates: return { "owner": owner, "template": {"name": template.get("name"), "guid": template.get("guid"), "synonym": template.get("synonym")}, "status": "not_found", } details = templates[0] or {} payload_parts = [part for part in (details.get("parts") or []) if isinstance(part, dict) and (part.get("features") or {}).get("tabular_document")] part = payload_parts[0] if payload_parts else {} structure = (part.get("structure") or {}) if isinstance(part, dict) else {} return { "owner": owner, "template": { "guid": details.get("guid"), "name": details.get("name"), "synonym": details.get("synonym"), "format": details.get("format"), "platform_type": details.get("platform_type"), }, "status": details.get("status"), "features": details.get("features") or {}, "template_type_candidates": details.get("template_type_candidates") or [], "profile": structure_summary(structure, sample_limit), } def render_markdown(payload: dict[str, Any]) -> str: def dimension_text(value: dict[str, Any]) -> str: if not isinstance(value, dict) or not value: return "" rows = value.get("rows") if value.get("rows") is not None else "-" columns = value.get("columns") if value.get("columns") is not None else "-" return f"{rows}x{columns}" lines: list[str] = [] lines.append("# 1C tabular template profiles") lines.append("") lines.append(f"- Base: `{payload.get('base_id')}`") lines.append(f"- Templates profiled: `{payload.get('counts', {}).get('templates_profiled')}`") lines.append(f"- Objects covered: `{payload.get('counts', {}).get('objects_with_templates')}`") lines.append("") lines.append("| Owner | Template | Capacity | Used | Cells | Cell params | Text ids | Coord hints | Style hints | Merges | Merge blocks | Merge count hints | Formats | Named areas |") lines.append("| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |") for item in payload.get("items") or []: owner = item.get("owner") or {} template = item.get("template") or {} profile = item.get("profile") or {} dimensions = profile.get("capacity_dimensions") or profile.get("dimensions") or {} used_dimensions = profile.get("used_dimensions") or {} format_dimensions = profile.get("format_dimensions") or {} counts = profile.get("counts") or {} dim = dimension_text(dimensions) used = dimension_text(used_dimensions) formats = ( dimension_text(format_dimensions) if format_dimensions else counts.get("cell_style_candidates") ) lines.append( f"| `{owner.get('kind')}.{owner.get('name')}` | `{template.get('name')}` | `{dim}` | `{used}` | " f"`{counts.get('cells')}` | `{counts.get('cell_parameters')}` | `{counts.get('cell_text_identifiers')}` | " f"`{counts.get('cell_coordinate_hints')}` | " f"`{counts.get('cell_style_coordinate_hints')}` | " f"`{counts.get('merged_ranges')}` | `{counts.get('merge_record_block_candidates')}` | " f"`{counts.get('merge_count_hints')}` | `{formats}` | " f"`{counts.get('named_areas')}` |" ) return "\n".join(lines) + "\n" def main() -> int: parser = argparse.ArgumentParser(description="Build compact structural profiles for all inventoried tabular 1C templates.") parser.add_argument( "--inventory-json", default=str(Path("Z:/codex/LLM/reports/1c-template-baselines/upo_test_configuration_tabular_templates.json")), ) parser.add_argument("--adapter-url", default="http://docker-gpu.cin.su:8011") parser.add_argument("--base-id", default="upo_test") parser.add_argument("--workers", type=int, default=8) parser.add_argument("--timeout-seconds", type=int, default=120) parser.add_argument("--sample-limit", type=int, default=10) parser.add_argument("--refresh-cache", action="store_true") parser.add_argument( "--output-json", default=str(Path("Z:/codex/LLM/reports/1c-template-baselines/upo_test_tabular_template_profiles.json")), ) parser.add_argument( "--output-markdown", default=str(Path("Z:/codex/LLM/reports/1c-template-baselines/upo_test_tabular_template_profiles.md")), ) args = parser.parse_args() inventory = read_inventory(Path(args.inventory_json)) work_items: list[tuple[dict[str, Any], dict[str, Any]]] = [] for item in inventory.get("items") or []: owner = item.get("object") or {} for template in item.get("templates") or []: work_items.append((owner, template)) results: list[dict[str, Any]] = [] with ThreadPoolExecutor(max_workers=max(1, int(args.workers or 1))) as pool: future_map = { pool.submit( profile_template, args.adapter_url, args.base_id, owner, template, int(args.timeout_seconds or 120), int(args.sample_limit or 10), bool(args.refresh_cache), ): (owner, template) for owner, template in work_items } for future in as_completed(future_map): results.append(future.result()) results.sort(key=lambda item: ((item.get("owner") or {}).get("name") or "", (item.get("template") or {}).get("name") or "")) payload = { "schema": "codex_1c_tabular_template_profiles.v1", "inventory_json": args.inventory_json, "adapter_url": args.adapter_url, "base_id": args.base_id, "items": results, "counts": { "templates_profiled": len(results), "objects_with_templates": len({((item.get("owner") or {}).get("guid") or (item.get("owner") or {}).get("name")) for item in results}), }, } json_path = Path(args.output_json) md_path = Path(args.output_markdown) json_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8") md_path.write_text(render_markdown(payload), encoding="utf-8") print( json.dumps( { "status": "ok", "json": str(json_path), "markdown": str(md_path), "counts": payload["counts"], }, ensure_ascii=False, indent=2, ) ) return 0 if __name__ == "__main__": raise SystemExit(main())