← Files VeraARCHIVED FILE

modules/bandi-agevolazioni/scripts/initialize_case.py

9.94 KB · Oct 4, 2026 · 12:28 UTC

↓ Download file

"""Initialize a bounded Bandi e agevolazioni Studio Archive run."""

from __future__ import annotations

import argparse
import logging
from pathlib import Path

from case_core import (
    PLUGIN_NAME,
    case_lock,
    iso_now,
    load_json_object,
    load_running_context,
    relative_run_path,
    safe_identifier,
    validate_iso_date,
    write_private_json,
)

__all__ = ["initialize_case", "main"]

LOGGER = logging.getLogger(__name__)


def initialize_case(
    output_dir: Path,
    *,
    client_engagement: Path,
    reference_date: str,
    client_reference: str,
    language: str = "it",
) -> dict[str, Path]:
    """Create empty drafts without choosing any legal or semantic conclusion."""

    client_reference = safe_identifier(client_reference, field="client_reference")
    reference_date = validate_iso_date(reference_date, field="reference_date")
    language = str(language or "").strip().lower()
    if not 2 <= len(language) <= 12 or not language.replace("-", "").isalpha():
        raise ValueError("language must be a short language tag")
    context = load_running_context(client_engagement, output_dir=output_dir)
    run_id = safe_identifier(context["run_id"], field="run_id")
    output_dir = output_dir.resolve()
    files = {
        "case_intake": output_dir / "case_intake.json",
        "sources": output_dir / "source_register.json",
        "workbench": output_dir / "application_workbench.json",
        "intelligence": output_dir / "intelligence_register.json",
        "reviews": output_dir / "review_log.json",
        "run_state": output_dir / "run_state.json",
    }
    with case_lock(output_dir):
        if files["run_state"].exists():
            missing = [path.name for path in files.values() if not path.exists()]
            if missing:
                raise ValueError(
                    "initialized case is incomplete; missing: " + ", ".join(missing)
                )
            state = load_json_object(files["run_state"])
            intake = load_json_object(files["case_intake"])
            if state.get("plugin") != PLUGIN_NAME or state.get("run_id") != run_id:
                raise ValueError("existing run_state belongs to another plugin run")
            if (
                intake.get("reference_date") != reference_date
                or intake.get("client_reference") != client_reference
                or state.get("language") != language
            ):
                raise ValueError("existing initialization parameters do not match")
            return files
        # run_state is committed last. Existing draft files therefore represent
        # an interrupted initialization and are safely replaced, making retry
        # idempotent without overwriting a completed run.
        for label, path in files.items():
            if label == "run_state" or not path.exists():
                continue
            payload = load_json_object(path)
            collections = {
                "sources": ("sources", "source_set_revision"),
                "workbench": (
                    "requirements",
                    "facts",
                    "assessments",
                    "document_checklist",
                    "expenses",
                    "form_fields",
                    "narratives",
                    "consistency_checks",
                    "issues",
                ),
                "reviews": ("events",),
                "intelligence": ("runs",),
            }
            if payload.get("plugin") != PLUGIN_NAME or payload.get("run_id") != run_id:
                raise ValueError(f"partial {label} belongs to another plugin run")
            if label == "case_intake":
                material_values = (
                    payload.get("professional_question"),
                    *(
                        payload.get(section, {}).get(field)
                        for section, field in (
                            ("application", "title"),
                            ("application", "issuing_authority"),
                            ("application", "procedure_id"),
                            ("applicant", "legal_name"),
                            ("applicant", "tax_code"),
                            ("applicant", "vat_number"),
                            ("project", "title"),
                            ("project", "summary"),
                            ("project", "requested_amount"),
                        )
                        if isinstance(payload.get(section), dict)
                    ),
                )
                if any(value not in (None, "") for value in material_values):
                    raise ValueError(
                        "run_state is missing but intake contains case work; "
                        "manual recovery is required"
                    )
            if label in collections and any(
                payload.get(key) not in ([], 0) for key in collections[label]
            ):
                raise ValueError(
                    "run_state is missing but existing artifacts contain case work; "
                    "manual recovery is required"
                )
            if label == "workbench" and payload.get("case_summary") not in (None, ""):
                raise ValueError(
                    "run_state is missing but workbench contains case work; "
                    "manual recovery is required"
                )
        created_at = iso_now()
        write_private_json(
            files["case_intake"],
            {
                "schema_version": "1.0",
                "plugin": PLUGIN_NAME,
                "run_id": run_id,
                "reference_date": reference_date,
                "client_reference": client_reference,
                "application": {
                    "title": "",
                    "issuing_authority": "",
                    "procedure_id": "",
                    "submission_deadline": None,
                    "status": "unknown",
                },
                "applicant": {
                    "legal_name": "",
                    "tax_code": "",
                    "vat_number": "",
                    "confirmation_status": "unknown",
                },
                "project": {
                    "title": "",
                    "summary": "",
                    "requested_amount": None,
                    "currency": None,
                    "confirmation_status": "unknown",
                },
                "professional_question": "",
            },
        )
        write_private_json(
            files["sources"],
            {
                "schema_version": "1.0",
                "plugin": PLUGIN_NAME,
                "run_id": run_id,
                "source_set_revision": 0,
                "sources": [],
            },
        )
        write_private_json(
            files["workbench"],
            {
                "schema_version": "1.2",
                "plugin": PLUGIN_NAME,
                "run_id": run_id,
                "case_summary": "",
                "requirements": [],
                "facts": [],
                "assessments": [],
                "document_checklist": [],
                "expenses": [],
                "form_fields": [],
                "narratives": [],
                "consistency_checks": [],
                "issues": [],
                "authority_simulation": {
                    "status": "not_run",
                    "reviewer_perspective": "",
                    "overall_outcome": "not_run",
                    "checks": [],
                },
                "dossier": {
                    "disposition": "review_required",
                    "ready_to_file": False,
                    "limitations": [
                        "Bozza per revisione professionale; Vera non firma e non invia la domanda."
                    ],
                },
            },
        )
        write_private_json(
            files["intelligence"],
            {
                "schema_version": "1.0",
                "contract_version": "bandi-intelligence-v2",
                "plugin": PLUGIN_NAME,
                "run_id": run_id,
                "runs": [],
            },
        )
        write_private_json(
            files["reviews"],
            {
                "schema_version": "1.1",
                "plugin": PLUGIN_NAME,
                "run_id": run_id,
                "events": [],
            },
        )
        write_private_json(
            files["run_state"],
            {
                "schema_version": "1.0",
                "plugin": PLUGIN_NAME,
                "workflow": PLUGIN_NAME,
                "run_id": run_id,
                "created_at": created_at,
                "updated_at": created_at,
                "language": language,
                "phase": "intake",
                "status": "needs_review",
                "source_set_revision": 0,
                "output_dir": relative_run_path(output_dir, context),
                "ready_to_file": False,
                "portal_actions_performed": False,
                "signature_actions_performed": False,
                "submission_actions_performed": False,
            },
        )
    return files


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--output-dir", required=True, type=Path)
    parser.add_argument("--client-engagement", required=True, type=Path)
    parser.add_argument("--reference-date", required=True)
    parser.add_argument("--client-reference", required=True)
    parser.add_argument("--language", default="it")
    args = parser.parse_args(argv)
    paths = initialize_case(
        args.output_dir,
        client_engagement=args.client_engagement,
        reference_date=args.reference_date,
        client_reference=args.client_reference,
        language=args.language,
    )
    for label, path in paths.items():
        LOGGER.info("%s: %s", label, path)
    return 0


if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO, format="%(message)s")
    raise SystemExit(main())

SHA-256: 86b95366a38e76349689ace1a49398bfa31c3bc6cf67e05b9977fa9337ee7264