algo/applications/collatz/collatz_k_scripts/collatz_verifier_minimal.py
Nicolas Cantu 14ed1de36b Pipeline Collatz aligné sur commandes.md et reprise après interruption
**Motivations:**
- Implémenter le workflow complet de démonstration Collatz (commandes.md)
- Permettre la reprise après interruption au palier D20

**Evolutions:**
- Scripts 01-12 et run-full-workflow alignés sur commandes.md sections 1-10
- collatz_recover_noyau.py : recréation de noyau_post_D20 à partir du CSV candidats
- Option --resume-from D20 dans collatz_k_pipeline pour reprendre sans recalculer D18-D19-F15
- Détection automatique : si candidats_D20 existe sans noyau_post_D20, récupération puis poursuite
- Filtres --cible=critique et --modulo dans collatz_fusion_pipeline
- ROOT par défaut = collatz_k_scripts (plus data/source vide)

**Pages affectées:**
- .gitignore (__pycache__, out/)
- applications/collatz/collatz_k_scripts/*.py
- applications/collatz/scripts/*.sh
- applications/collatz/scripts/README.md
2026-03-02 02:49:23 +01:00

107 lines
3.2 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
collatz_verifier_minimal.py
Verifies a Collatz certificate JSON structure.
Loads certificate, validates that clauses have required fields.
Outputs simple MD: OK or list of errors.
Usage: python collatz_verifier_minimal.py --certificat JSON_PATH --output MD_PATH
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
REQUIRED_CLAUSE_FIELDS = frozenset({"word", "k", "s", "mod", "residue", "a", "B", "N0"})
def load_certificate(path: str) -> dict[str, Any]:
"""Load and parse JSON certificate."""
p = Path(path)
if not p.exists():
raise FileNotFoundError(f"Certificate not found: {path}")
text = p.read_text(encoding="utf-8")
return json.loads(text)
def get_clauses(data: dict[str, Any]) -> list[dict[str, Any]]:
"""Extract clauses from certificate (support 'closed' or 'clauses').
Returns empty list if not found."""
if "closed" in data:
return data["closed"]
if "clauses" in data:
return data["clauses"]
return []
def validate_clause(clause: Any, index: int) -> list[str]:
"""Validate a single clause. Returns list of error messages."""
errors: list[str] = []
if isinstance(clause, (int, float)):
return []
if not isinstance(clause, dict):
errors.append(f"Clause {index}: not a dict (got {type(clause).__name__})")
return errors
for field in REQUIRED_CLAUSE_FIELDS:
if field not in clause:
errors.append(f"Clause {index}: missing required field '{field}'")
if isinstance(clause.get("word"), str):
for c in clause["word"]:
if c not in ("0", "1"):
errors.append(f"Clause {index}: word must be binary string")
break
return errors
def validate_certificate(data: dict[str, Any]) -> list[str]:
"""Validate certificate structure."""
errors: list[str] = []
clauses = get_clauses(data)
if not clauses:
errors.append("Certificate has no 'closed' or 'clauses' array")
return errors
if isinstance(clauses[0], (int, float)):
return []
for i, c in enumerate(clauses):
errors.extend(validate_clause(c, i))
return errors
def run(cert_path: str, output_path: str) -> None:
"""Run verification and write output."""
errors: list[str] = []
try:
data = load_certificate(cert_path)
errors = validate_certificate(data)
except FileNotFoundError as e:
errors.append(str(e))
except json.JSONDecodeError as e:
errors.append(f"Invalid JSON: {e}")
if errors:
content = "# Verification failed\n\n" + "\n".join(f"- {e}" for e in errors)
else:
content = "# Verification OK\n\n"
Path(output_path).write_text(content, encoding="utf-8")
def main() -> None:
parser = argparse.ArgumentParser(description="Verify Collatz certificate structure")
parser.add_argument("--certificat", required=True, help="Path to certificate JSON")
parser.add_argument("--output", required=True, help="Path to output MD file")
args = parser.parse_args()
run(args.certificat, args.output)
if __name__ == "__main__":
main()