← Files ClaraARCHIVED FILE

modules/reporting-engine/scripts/dataset_intake.py

14.3 KB · Oct 3, 2026 · 06:30 UTC

↓ Download file

"""Profile one dataset snapshot and create or reuse reviewed business semantics.

This helper deliberately performs no semantic classification. It prepares the
profile and authoring context that a model or human reviews, or it validates and
attaches an explicitly supplied semantic layer for the same dataset contract.
"""

from __future__ import annotations

# Direct CLI calls must select dependencies before importing workflow modules.
if __name__ == "__main__":
    import runpy as _runpy
    from pathlib import Path as _Path

    for _parent in _Path(__file__).resolve().parents:
        _launcher = _parent / "scripts" / "self_relaunch.py"
        if not _launcher.is_file():
            _launcher = _parent / "clara" / "scripts" / "self_relaunch.py"
        if _launcher.is_file():
            _runpy.run_path(str(_launcher))["ensure_running_in_managed_venv"](__file__)
            break
    else:
        # Standalone components retain their host's dependency setup.
        if any(
            (_p / "components.json").is_file()
            for _p in _Path(__file__).resolve().parents
        ):
            raise SystemExit(
                "Managed Python launcher is missing; rebuild the plugin package."
            )

import argparse
import importlib.util
import json
import logging
import re
from pathlib import Path
from types import ModuleType
from typing import Any, Sequence

__all__ = ["main", "run_dataset_intake"]

LOGGER = logging.getLogger(__name__)
REPORTING_ENGINE_ROOT = Path(__file__).resolve().parents[1]
DEFAULT_MANIFEST = REPORTING_ENGINE_ROOT / "catalog" / "selection_manifest.json"
DATASET_CONTRACT_ID_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$")

PROFILE_FILENAME = "dataset_profile.json"
DRAFT_LAYER_FILENAME = "semantic_layer.draft.json"
AUTHORING_CONTEXT_FILENAME = "semantic_authoring_context.json"
ATTACHMENT_FILENAME = "snapshot_attachment.json"
RECEIPT_FILENAME = "dataset_intake.json"


def _load_script(module_name: str, filename: str) -> ModuleType:
    module_path = Path(__file__).resolve().parent / filename
    spec = importlib.util.spec_from_file_location(module_name, module_path)
    if spec is None or spec.loader is None:
        raise RuntimeError(f"Could not load Reporting Engine helper: {module_path}")
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module


def _load_json(path: Path) -> dict[str, Any]:
    payload = json.loads(path.read_text(encoding="utf-8"))
    if not isinstance(payload, dict):
        raise ValueError(f"Expected a JSON object in {path}.")
    return payload


def _write_json(path: Path, payload: dict[str, Any]) -> None:
    path.write_text(
        json.dumps(payload, indent=2, ensure_ascii=False) + "\n",
        encoding="utf-8",
    )


def _prepare_output_dir(output_dir: Path, filenames: Sequence[str]) -> Path:
    resolved = output_dir.expanduser().resolve()
    if resolved.exists() and not resolved.is_dir():
        raise ValueError(f"Output path is not a directory: {resolved}")
    resolved.mkdir(parents=True, exist_ok=True)
    existing = [
        resolved / filename for filename in filenames if (resolved / filename).exists()
    ]
    if existing:
        paths = ", ".join(str(path) for path in existing)
        raise FileExistsError(
            f"Dataset intake will not overwrite existing artifacts: {paths}"
        )
    return resolved


def _artifact_paths(output_dir: Path, *, reuse_existing_layer: bool) -> dict[str, Path]:
    paths = {
        "dataset_profile": output_dir / PROFILE_FILENAME,
        "semantic_authoring_context": output_dir / AUTHORING_CONTEXT_FILENAME,
        "dataset_intake": output_dir / RECEIPT_FILENAME,
    }
    if reuse_existing_layer:
        paths["snapshot_attachment"] = output_dir / ATTACHMENT_FILENAME
    else:
        paths["semantic_layer_draft"] = output_dir / DRAFT_LAYER_FILENAME
    return paths


def _mapped_role_availability(
    layer: dict[str, Any], attachment: dict[str, Any]
) -> dict[str, str]:
    compatibility = attachment.get("compatibility") or {}
    concept_results = {
        str(result.get("concept_id")): result
        for result in compatibility.get("concept_results") or []
        if isinstance(result, dict) and result.get("concept_id")
    }
    availability: dict[str, str] = {}
    mappings = layer.get("business_metric_mappings") or {}
    for role, raw_mapping in mappings.items():
        if not isinstance(raw_mapping, dict) or raw_mapping.get("state") != "mapped":
            continue
        metric_id = str(raw_mapping.get("metric_id") or "")
        availability[str(role)] = str(
            (concept_results.get(metric_id) or {}).get("status") or "missing"
        )
    return availability


def _receipt_artifacts(paths: dict[str, Path]) -> dict[str, str]:
    return {name: str(path) for name, path in paths.items()}


def run_dataset_intake(
    dataset_path: Path,
    *,
    dataset_contract_id: str,
    output_dir: Path,
    semantic_layer_path: Path | None = None,
    sheet_name: str | None = None,
    csv_options: dict[str, Any] | None = None,
) -> dict[str, Any]:
    """Prepare first-upload semantic review or reuse one reviewed contract."""

    contract_id = dataset_contract_id.strip()
    if not DATASET_CONTRACT_ID_PATTERN.fullmatch(contract_id):
        raise ValueError(
            "dataset_contract_id must start with a letter or number and contain "
            "only letters, numbers, dots, underscores, or hyphens."
        )

    dataset = dataset_path.expanduser().resolve()
    if not dataset.is_file():
        raise FileNotFoundError(f"Dataset does not exist: {dataset}")
    existing_layer_path = (
        semantic_layer_path.expanduser().resolve()
        if semantic_layer_path is not None
        else None
    )
    if existing_layer_path is not None and not existing_layer_path.is_file():
        raise FileNotFoundError(f"Semantic layer does not exist: {existing_layer_path}")

    reuse_existing_layer = existing_layer_path is not None
    filenames = [
        PROFILE_FILENAME,
        AUTHORING_CONTEXT_FILENAME,
        RECEIPT_FILENAME,
        ATTACHMENT_FILENAME if reuse_existing_layer else DRAFT_LAYER_FILENAME,
    ]
    resolved_output_dir = _prepare_output_dir(output_dir, filenames)
    paths = _artifact_paths(
        resolved_output_dir,
        reuse_existing_layer=reuse_existing_layer,
    )

    profiler = _load_script(
        "reporting_engine_dataset_intake_profiler", "profile_dataset.py"
    )
    semantic = _load_script(
        "reporting_engine_dataset_intake_semantics", "semantic_layer.py"
    )
    manifest = _load_json(DEFAULT_MANIFEST)
    profile = profiler.profile_dataset(
        dataset,
        dataset_id=contract_id,
        sheet_name=sheet_name,
        csv_options=csv_options,
    )
    _write_json(paths["dataset_profile"], profile)

    if existing_layer_path is None:
        layer = semantic.build_semantic_layer_scaffold(
            profile,
            dataset_contract_id=contract_id,
            profile_locator=str(paths["dataset_profile"]),
        )
        context = semantic.build_authoring_context(
            profile,
            manifest,
            semantic_layer=layer,
        )
        mapping_summary = semantic.summarize_business_metric_mappings(layer)
        receipt = {
            "schema_version": "0.1",
            "status": "review_required",
            "mode": "first_upload",
            "reason": "source_backed_semantic_review_required",
            "dataset_contract_id": contract_id,
            "semantic_layer_dataset_contract_id": None,
            "business_metric_mappings": mapping_summary,
            "artifacts": _receipt_artifacts(paths),
            "next_action": (
                "Review the dataset and available business evidence, then author "
                "Sales, Discount, and COGS as mapped, absent, ambiguous, or unknown "
                "in the persistent semantic layer."
            ),
            "boundary": (
                "The intake profiler and scaffold do not identify business meaning "
                "from headers or values."
            ),
        }
        _write_json(paths["semantic_layer_draft"], layer)
        _write_json(paths["semantic_authoring_context"], context)
        _write_json(paths["dataset_intake"], receipt)
        return receipt

    layer = _load_json(existing_layer_path)
    raw_layer_contract = layer.get("dataset_contract")
    layer_contract = raw_layer_contract if isinstance(raw_layer_contract, dict) else {}
    layer_contract_id = layer_contract.get("dataset_contract_id")
    if layer_contract_id != contract_id:
        receipt = {
            "schema_version": "0.1",
            "status": "rejected",
            "mode": "existing_semantic_layer",
            "reason": "dataset_contract_id_mismatch",
            "dataset_contract_id": contract_id,
            "semantic_layer_dataset_contract_id": layer_contract_id,
            "business_metric_mappings": (
                semantic.summarize_business_metric_mappings(layer)
            ),
            "artifacts": _receipt_artifacts(
                {
                    "dataset_profile": paths["dataset_profile"],
                    "dataset_intake": paths["dataset_intake"],
                }
            ),
            "next_action": (
                "Supply the reviewed semantic layer for this exact dataset contract; "
                "schema similarity cannot establish dataset identity."
            ),
            "boundary": ("The supplied semantic layer was not attached or replaced."),
        }
        _write_json(paths["dataset_intake"], receipt)
        return receipt

    validation = semantic.validate_semantic_layer(layer, profile, manifest)
    attachment = semantic.build_snapshot_attachment(layer, profile)
    attachment["semantic_contract_validation"] = {
        "status": validation["status"],
        "semantic_readiness": validation["semantic_readiness"],
        "errors": validation["errors"],
    }
    if validation["status"] != "contract_valid":
        attachment["attachment_status"] = "rejected"
    context = semantic.build_authoring_context(
        profile,
        manifest,
        semantic_layer=layer,
    )
    mapping_summary = semantic.summarize_business_metric_mappings(layer)
    mapped_role_availability = _mapped_role_availability(layer, attachment)
    unavailable_mapped_roles = sorted(
        role
        for role, status in mapped_role_availability.items()
        if status != "compatible"
    )

    review = layer.get("review")
    review_status = review.get("status") if isinstance(review, dict) else None
    if attachment["attachment_status"] != "attached":
        status = "rejected"
        reason = "semantic_contract_or_snapshot_rejected"
    elif (
        review_status in {"model_reviewed", "human_reviewed"}
        and mapping_summary["status"] == "resolved"
        and not unavailable_mapped_roles
    ):
        status = "mapping_reused"
        reason = "reviewed_business_metric_mapping_reused"
    else:
        status = "review_required"
        reason = (
            "mapped_metric_unavailable_in_snapshot"
            if unavailable_mapped_roles
            else "business_metric_mapping_review_required"
        )

    receipt = {
        "schema_version": "0.1",
        "status": status,
        "mode": "existing_semantic_layer",
        "reason": reason,
        "dataset_contract_id": contract_id,
        "semantic_layer_dataset_contract_id": layer_contract_id,
        "semantic_layer_id": layer.get("semantic_layer_id"),
        "semantic_version": layer.get("semantic_version"),
        "semantic_review_status": review_status,
        "semantic_contract_status": validation["status"],
        "semantic_readiness": validation["semantic_readiness"],
        "snapshot_attachment_status": attachment["attachment_status"],
        "snapshot_compatibility_status": (
            (attachment.get("compatibility") or {}).get("status")
        ),
        "business_metric_mappings": mapping_summary,
        "mapped_role_snapshot_availability": mapped_role_availability,
        "unavailable_mapped_roles": unavailable_mapped_roles,
        "artifacts": _receipt_artifacts(paths),
        "next_action": (
            "Use the attached reviewed mapping for this snapshot."
            if status == "mapping_reused"
            else (
                "Resolve the reported semantic or snapshot incompatibility; do not "
                "replace the persistent layer automatically."
                if status == "rejected"
                else (
                    "Review unresolved or unavailable Sales, Discount, and COGS "
                    "roles before using them."
                )
            )
        ),
        "boundary": (
            "Reuse preserves the supplied semantic version and never rewrites the "
            "persistent semantic layer."
        ),
    }
    _write_json(paths["snapshot_attachment"], attachment)
    _write_json(paths["semantic_authoring_context"], context)
    _write_json(paths["dataset_intake"], receipt)
    return receipt


def _parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("dataset", type=Path)
    parser.add_argument("--dataset-contract-id", required=True)
    parser.add_argument("--output-dir", type=Path, required=True)
    parser.add_argument("--semantic-layer", type=Path)
    parser.add_argument("--sheet-name")
    parser.add_argument(
        "--csv-options-json", help="Explicit CSV parser settings as a JSON object."
    )
    return parser


def main(argv: Sequence[str] | None = None) -> int:
    """Run dataset intake and return non-zero only when intake is rejected."""

    args = _parser().parse_args(argv)
    try:
        receipt = run_dataset_intake(
            args.dataset,
            dataset_contract_id=args.dataset_contract_id,
            output_dir=args.output_dir,
            semantic_layer_path=args.semantic_layer,
            sheet_name=args.sheet_name,
            csv_options=(
                json.loads(args.csv_options_json) if args.csv_options_json else None
            ),
        )
    except (FileNotFoundError, FileExistsError, OSError, ValueError) as error:
        LOGGER.error("%s", error)
        return 1
    LOGGER.info("%s", (args.output_dir / RECEIPT_FILENAME).expanduser().resolve())
    return 1 if receipt["status"] == "rejected" else 0


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

SHA-256: b712213f606b9aa7dd4f5e0aeb1551f0a64f62accc84f01c6b53959e9922154c