Initial SQL-only 1C adapter baseline
This commit is contained in:
@@ -0,0 +1,265 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SCRIPTS = ROOT / "scripts"
|
||||
if str(SCRIPTS) not in sys.path:
|
||||
sys.path.insert(0, str(SCRIPTS))
|
||||
|
||||
import capture_1c_template_probe as probe # noqa: E402
|
||||
|
||||
|
||||
def read_json(path: Path) -> dict[str, Any]:
|
||||
return json.loads(path.read_text(encoding="utf-8")) if path.exists() else {}
|
||||
|
||||
|
||||
def write_json(path: Path, payload: dict[str, Any]) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
|
||||
def snapshot_identity(snapshot: dict[str, Any]) -> dict[str, Any]:
|
||||
return {
|
||||
"file_name": snapshot.get("file_name"),
|
||||
"modified": snapshot.get("modified"),
|
||||
"bytes": snapshot.get("bytes"),
|
||||
}
|
||||
|
||||
|
||||
def capture_snapshot(
|
||||
*,
|
||||
adapter_url: str,
|
||||
base_id: str,
|
||||
output_dir: Path,
|
||||
label: str,
|
||||
scan_limit: int,
|
||||
max_cells: int,
|
||||
file_name: str | None = None,
|
||||
compare_to: Path | None = None,
|
||||
) -> tuple[Path, Path, dict[str, Any]]:
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
selected_file, row, structure = probe.choose_latest_moxel(adapter_url, base_id, scan_limit, max_cells, file_name)
|
||||
snapshot = {
|
||||
"schema": "codex_1c_template_probe_snapshot.v1",
|
||||
"captured_at": datetime.now(UTC).isoformat(timespec="seconds").replace("+00:00", "Z"),
|
||||
"adapter_url": adapter_url,
|
||||
"base_id": base_id,
|
||||
"file_name": selected_file,
|
||||
"modified": row.get("Modified"),
|
||||
"bytes": row.get("Bytes"),
|
||||
"label": label,
|
||||
"probe": probe.build_snapshot(structure),
|
||||
}
|
||||
diff: dict[str, Any] | None = None
|
||||
if compare_to and compare_to.exists():
|
||||
previous = read_json(compare_to)
|
||||
diff = probe.build_diff(previous.get("probe") or {}, snapshot.get("probe") or {})
|
||||
snapshot["diff"] = {"compare_to": str(compare_to), "summary": diff.get("summary") or {}}
|
||||
|
||||
timestamp = datetime.now(UTC).strftime("%Y%m%dT%H%M%SZ")
|
||||
stem = f"{base_id}_{label}_{timestamp}_{selected_file[:8]}"
|
||||
json_path = output_dir / f"{stem}.json"
|
||||
md_path = output_dir / f"{stem}.md"
|
||||
write_json(json_path, snapshot)
|
||||
md_path.write_text(probe.render_markdown(snapshot, diff, compare_to if compare_to and compare_to.exists() else None), encoding="utf-8")
|
||||
return json_path, md_path, snapshot
|
||||
|
||||
|
||||
def append_manifest_experiment(
|
||||
manifest_path: Path,
|
||||
*,
|
||||
property_name: str,
|
||||
operation: str,
|
||||
target_text: str | None,
|
||||
target_name: str | None,
|
||||
before_path: Path,
|
||||
after_path: Path,
|
||||
) -> dict[str, Any]:
|
||||
manifest = read_json(manifest_path)
|
||||
if not manifest:
|
||||
manifest = {"schema": "codex_1c_moxel_property_experiment_manifest.v1", "experiments": []}
|
||||
manifest.setdefault("schema", "codex_1c_moxel_property_experiment_manifest.v1")
|
||||
manifest.setdefault("experiments", [])
|
||||
base_dir = manifest_path.parent
|
||||
experiment = {
|
||||
"property": property_name,
|
||||
"operation": operation,
|
||||
"target_text": target_text,
|
||||
"target_name": target_name,
|
||||
"before": str(before_path.resolve().relative_to(base_dir.resolve())) if before_path.resolve().is_relative_to(base_dir.resolve()) else str(before_path),
|
||||
"after": str(after_path.resolve().relative_to(base_dir.resolve())) if after_path.resolve().is_relative_to(base_dir.resolve()) else str(after_path),
|
||||
}
|
||||
manifest["experiments"].append(experiment)
|
||||
write_json(manifest_path, manifest)
|
||||
return experiment
|
||||
|
||||
|
||||
def pipeline_command(after_path: Path, manifest_path: Path) -> list[str]:
|
||||
return [
|
||||
sys.executable,
|
||||
"scripts/run_1c_moxel_discovery_pipeline.py",
|
||||
"--probe",
|
||||
str(after_path),
|
||||
"--property-manifest",
|
||||
str(manifest_path),
|
||||
]
|
||||
|
||||
|
||||
def wait_for_new_moxel(
|
||||
*,
|
||||
adapter_url: str,
|
||||
base_id: str,
|
||||
baseline: dict[str, Any],
|
||||
scan_limit: int,
|
||||
max_cells: int,
|
||||
poll_seconds: float,
|
||||
timeout_seconds: float,
|
||||
) -> tuple[str, dict[str, Any], dict[str, Any]] | None:
|
||||
started = time.monotonic()
|
||||
baseline_identity = snapshot_identity(baseline)
|
||||
while time.monotonic() - started <= timeout_seconds:
|
||||
file_name, row, structure = probe.choose_latest_moxel(adapter_url, base_id, scan_limit, max_cells, None)
|
||||
current_identity = {
|
||||
"file_name": file_name,
|
||||
"modified": row.get("Modified"),
|
||||
"bytes": row.get("Bytes"),
|
||||
}
|
||||
if current_identity != baseline_identity:
|
||||
return file_name, row, structure
|
||||
time.sleep(max(0.2, poll_seconds))
|
||||
return None
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Capture a before/after MOXCEL one-property experiment around a manual 1C save.")
|
||||
parser.add_argument("--adapter-url", default="http://docker-gpu.cin.su:8011")
|
||||
parser.add_argument("--base-id", default="upo_test")
|
||||
parser.add_argument("--property", required=True, help="Property label, for example ВертикальноеПоложение.")
|
||||
parser.add_argument("--operation", default="manual_one_property_save")
|
||||
parser.add_argument("--target-text", default="Ячейка 7 - 2")
|
||||
parser.add_argument("--target-name", default="R7C2_TEST")
|
||||
parser.add_argument("--output-dir", default="reports/1c-template-probes")
|
||||
parser.add_argument("--manifest", default="reports/1c-template-baselines/Primer3_moxel_property_experiments.manifest.json")
|
||||
parser.add_argument("--scan-limit", type=int, default=30)
|
||||
parser.add_argument("--max-cells", type=int, default=800)
|
||||
parser.add_argument("--poll-seconds", type=float, default=5.0)
|
||||
parser.add_argument("--timeout-seconds", type=float, default=600.0)
|
||||
parser.add_argument("--baseline", help="Existing before snapshot JSON. If omitted, capture one before waiting.")
|
||||
parser.add_argument("--after-file-name", help="Explicit after ConfigCAS file name. Skips waiting.")
|
||||
parser.add_argument("--run-pipeline-after", action="store_true", help="Run the MOXCEL discovery pipeline after appending the experiment.")
|
||||
args = parser.parse_args()
|
||||
|
||||
output_dir = Path(args.output_dir)
|
||||
manifest_path = Path(args.manifest)
|
||||
if args.baseline:
|
||||
before_path = Path(args.baseline)
|
||||
before_snapshot = read_json(before_path)
|
||||
else:
|
||||
before_path, _, before_snapshot = capture_snapshot(
|
||||
adapter_url=args.adapter_url,
|
||||
base_id=args.base_id,
|
||||
output_dir=output_dir,
|
||||
label=f"{args.property}_before",
|
||||
scan_limit=args.scan_limit,
|
||||
max_cells=args.max_cells,
|
||||
)
|
||||
|
||||
if args.after_file_name:
|
||||
after_path, after_md, after_snapshot = capture_snapshot(
|
||||
adapter_url=args.adapter_url,
|
||||
base_id=args.base_id,
|
||||
output_dir=output_dir,
|
||||
label=f"{args.property}_after",
|
||||
scan_limit=args.scan_limit,
|
||||
max_cells=args.max_cells,
|
||||
file_name=args.after_file_name,
|
||||
compare_to=before_path,
|
||||
)
|
||||
else:
|
||||
found = wait_for_new_moxel(
|
||||
adapter_url=args.adapter_url,
|
||||
base_id=args.base_id,
|
||||
baseline=before_snapshot,
|
||||
scan_limit=args.scan_limit,
|
||||
max_cells=args.max_cells,
|
||||
poll_seconds=args.poll_seconds,
|
||||
timeout_seconds=args.timeout_seconds,
|
||||
)
|
||||
if not found:
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"status": "no_change",
|
||||
"before": str(before_path),
|
||||
"message": f"No new MOXCEL payload appeared within {args.timeout_seconds:.0f} seconds.",
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
)
|
||||
return 2
|
||||
file_name, _, _ = found
|
||||
after_path, after_md, after_snapshot = capture_snapshot(
|
||||
adapter_url=args.adapter_url,
|
||||
base_id=args.base_id,
|
||||
output_dir=output_dir,
|
||||
label=f"{args.property}_after",
|
||||
scan_limit=args.scan_limit,
|
||||
max_cells=args.max_cells,
|
||||
file_name=file_name,
|
||||
compare_to=before_path,
|
||||
)
|
||||
|
||||
experiment = append_manifest_experiment(
|
||||
manifest_path,
|
||||
property_name=args.property,
|
||||
operation=args.operation,
|
||||
target_text=args.target_text,
|
||||
target_name=args.target_name,
|
||||
before_path=before_path,
|
||||
after_path=after_path,
|
||||
)
|
||||
pipeline: dict[str, Any] | None = None
|
||||
if args.run_pipeline_after:
|
||||
command = pipeline_command(after_path, manifest_path)
|
||||
result = subprocess.run(command, cwd=ROOT, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
|
||||
pipeline = {
|
||||
"command": command,
|
||||
"status": "ok" if result.returncode == 0 else "failed",
|
||||
"returncode": result.returncode,
|
||||
"stdout": result.stdout.strip(),
|
||||
"stderr": result.stderr.strip(),
|
||||
}
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"status": "ok",
|
||||
"before": str(before_path),
|
||||
"after": str(after_path),
|
||||
"after_markdown": str(after_md),
|
||||
"manifest": str(manifest_path),
|
||||
"experiment": experiment,
|
||||
"before_identity": snapshot_identity(before_snapshot),
|
||||
"after_identity": snapshot_identity(after_snapshot),
|
||||
**({"pipeline": pipeline} if pipeline else {}),
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
)
|
||||
if pipeline and pipeline.get("returncode") != 0:
|
||||
return int(pipeline.get("returncode") or 1)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user