#!/usr/bin/env python3
"""Build the APLD forecast-vs-actual ledger from primary-source manifest entries.

The extractor is intentionally deterministic: row specs reference source_id values in
apld_forecast_actual_manifest.csv, and every emitted row carries source URL, source
location, source snippet, support status, confidence, and caveat fields.
"""

from __future__ import annotations

import csv
import re
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import Iterable

BASE = Path(__file__).resolve().parent
MANIFEST = BASE / "apld_forecast_actual_manifest.csv"
OUT = BASE / "apld_forecast_actual_rows.csv"

REQUIRED_FIELDS = [
    "company",
    "period",
    "metric_name",
    "raw_value",
    "normalized_value",
    "unit",
    "value_qualifier",
    "ownership_basis",
    "forecast_or_actual",
    "is_derived",
    "source_url",
    "source_location",
    "source_snippet",
    "snippet_support_status",
    "confidence",
    "caveat",
]

OUTPUT_FIELDS = REQUIRED_FIELDS + ["source_id", "source_date", "campus", "target_or_snapshot_date"]


@dataclass(frozen=True)
class RowSpec:
    source_id: str
    period: str
    metric_name: str
    raw_value: str
    normalized_value: str
    unit: str
    value_qualifier: str
    ownership_basis: str
    forecast_or_actual: str
    is_derived: str
    source_location: str
    source_snippet: str
    snippet_support_status: str
    confidence: str
    caveat: str
    campus: str
    target_or_snapshot_date: str


def load_manifest(path: Path = MANIFEST) -> dict[str, dict[str, str]]:
    with path.open(newline="", encoding="utf-8") as f:
        rows = list(csv.DictReader(f))
    by_id: dict[str, dict[str, str]] = {}
    for row in rows:
        sid = row["source_id"]
        if sid in by_id:
            raise ValueError(f"duplicate manifest source_id: {sid}")
        if row.get("source_family") != "PRIMARY":
            raise ValueError(f"non-primary source in manifest: {sid}")
        if not row.get("source_url", "").startswith("https://"):
            raise ValueError(f"source_id {sid} does not have an https URL")
        by_id[sid] = row
    return by_id


def normalized_from_raw(raw_value: str, unit: str, snippet: str) -> float:
    raw = raw_value.replace(",", "").strip()
    if "+" in raw:
        return sum(float(part) for part in raw.split("+"))
    value = float(raw)
    if unit == "MW" and re.search(r"\bGW\b|gigawatt", snippet, flags=re.I):
        return value * 1000
    return value


def row_specs() -> Iterable[RowSpec]:
    yield RowSpec("APLD_SRC_01", "FY2025Q4", "contracted_critical_it_load", "250", "250", "MW", "approximately", "critical IT load; signed lease commitment; APLD company-level disclosure", "forecast", "false", "press release body", "entered into two approximately 15-year lease agreements ... deliver 250 megawatts (MW)", "partial", "high", "Signed lease forecast/commitment, not live operating capacity.", "Polaris Forge 1", "15-year lease term")
    yield RowSpec("APLD_SRC_01", "FY2025Q4", "optional_critical_it_load", "150", "150", "MW", "option", "critical IT load; customer option; APLD company-level disclosure", "forecast", "false", "press release body", "option to access an additional 150 MW of critical IT load", "exact", "high", "Customer option only at source date; exclude from contracted capacity until exercised/signed.", "Polaris Forge 1", "option period not disclosed")
    yield RowSpec("APLD_SRC_02", "FY2025Q4", "operating_hosting_capacity", "106+180", "286", "MW", "derived_sum", "data center hosting facilities; APLD company-level disclosure", "actual", "true", "Data Center Hosting Update", "106 MW facility ... and 180 MW facility ... operating at full capacity", "partial", "high", "Derived from two disclosed hosting facilities; not AI/HPC Polaris Forge capacity.", "Data Center Hosting", "2025-05-31")
    yield RowSpec("APLD_SRC_02", "FY2025Q4", "incremental_leased_capacity", "150", "150", "MW", "option_exercised_pending_final_lease", "critical IT load; option exercise pending lease finalization; APLD disclosure", "forecast", "false", "Recent Highlights", "CoreWeave exercised its option for an additional 150MW", "exact", "high", "Option was exercised but lease finalization was still pending in this source.", "Polaris Forge 1", "promptly after option exercise")
    yield RowSpec("APLD_SRC_02", "FY2025Q4", "planned_ready_for_service_capacity", "100", "100", "MW", "scheduled", "critical IT load; first HPC building timing; APLD disclosure", "forecast", "false", "HPC Hosting Update", "first 100 MW facility scheduled to be operational in Q4 2025", "exact", "high", "Forecast timing for first AI/HPC building; compare with later RFS actuals.", "Polaris Forge 1", "calendar Q4 2025")
    yield RowSpec("APLD_SRC_02", "FY2025Q4", "planned_ready_for_service_capacity", "150", "150", "MW", "scheduled", "critical IT load; second HPC building timing; APLD disclosure", "forecast", "false", "HPC Hosting Update", "second 150 MW facility scheduled to come online in mid-2026", "exact", "high", "Forecast timing for second AI/HPC building; not delivered at source date.", "Polaris Forge 1", "mid-2026")
    yield RowSpec("APLD_SRC_03", "FY2026Q1", "planned_campus_capacity", "280", "280", "MW", "designed", "campus design capacity; APLD disclosure", "forecast", "false", "press release body", "designed for 280MW ... initial capacity in 2026 and reaching full capacity in early 2027", "partial", "high", "Design capacity; later sources use 200 MW initial IT-load lease framing.", "Polaris Forge 2", "initial 2026; full early 2027")
    yield RowSpec("APLD_SRC_04", "FY2026Q1", "contracted_critical_it_load", "400", "400", "MW", "approximately", "critical IT load; signed leases; APLD disclosure", "actual", "false", "Recent Highlights and Management Commentary", "additional 150 megawatts ... bringing the full 400 MW ... under contract", "partial", "high", "Contracted capacity snapshot, not all operating capacity.", "Polaris Forge 1", "15-year lease terms")
    yield RowSpec("APLD_SRC_04", "FY2026Q1", "planned_initial_it_load", "200", "200", "MW", "initial", "IT load; initial phase; APLD disclosure", "forecast", "false", "Subsequent to quarter end", "approximately $3 billion, 300 MW IT load AI Factory campus ... initial 200 MW", "partial", "medium", "Source mixes 300 MW campus and 200 MW initial-phase language; row captures initial IT load.", "Polaris Forge 2", "calendar 2026 and 2027")
    yield RowSpec("APLD_SRC_04", "FY2026Q1", "possible_expansion_capacity", "1", "1000", "MW", "greater_than", "potential expansion; power-allocation dependent; APLD disclosure", "forecast", "false", "Management Commentary", "potential to expand significantly ... beyond 1 gigawatt", "partial", "medium", "Normalized GW to MW; optional/potential only and should not be counted as contracted or delivered.", "Polaris Forge 1", "2028-2030")
    yield RowSpec("APLD_SRC_05", "FY2026Q2", "ready_for_service_capacity", "50", "50", "MW", "exact", "critical IT load; RFS phase; APLD disclosure", "actual", "false", "press release headline/body", "Ready for Service (RFS) for the first phase (50 MW)", "exact", "high", "First 50 MW phase ready for service; partial completion of first 100 MW building.", "Polaris Forge 1", "2025-10-27")
    yield RowSpec("APLD_SRC_06", "FY2026Q2", "ready_for_service_capacity", "100", "100", "MW", "exact", "critical IT load; RFS building; APLD disclosure", "actual", "false", "press release headline/body", "Building 1 now operates at its full 100 MW critical IT load", "exact", "high", "Actual delivery against the FY2025Q4 forecast for a first 100 MW facility in Q4 2025.", "Polaris Forge 1", "2025-11-24")
    yield RowSpec("APLD_SRC_07", "FY2026Q2", "operating_hpc_capacity", "100", "100", "MW", "exact", "HPC hosting operations; APLD disclosure", "actual", "false", "HPC Hosting Update", "Operations commenced at our first HPC data center ... 100 MW of capacity", "partial", "high", "Confirms operations commenced during the quarter; same building as RFS row.", "Polaris Forge 1", "2025-11-30")
    yield RowSpec("APLD_SRC_07", "FY2026Q2", "under_construction_capacity", "150", "150", "MW", "exact", "HPC data center under construction; APLD disclosure", "actual", "false", "HPC Hosting Update", "second 150 MW HPC data center is under construction ... expected to come online in calendar 2026", "exact", "high", "Construction status is actual; commencement window remains forecast.", "Polaris Forge 1", "calendar 2026")
    yield RowSpec("APLD_SRC_07", "FY2026Q2", "planned_ready_for_service_capacity", "150", "150", "MW", "anticipated", "critical IT load; third facility timing; APLD disclosure", "forecast", "false", "HPC Hosting Update", "third 150 MW facility is anticipated in calendar 2027", "exact", "high", "Forecast for third contracted building; no operating delivery yet.", "Polaris Forge 1", "calendar 2027")
    yield RowSpec("APLD_SRC_07", "FY2026Q2", "contracted_critical_it_load", "200", "200", "MW", "approximately", "critical IT load; signed lease; APLD disclosure", "actual", "false", "HPC Hosting Update", "signed an approximately 15-year lease ... for 200 MW at Polaris Forge 2", "exact", "high", "Contracted 200 MW; initial/full capacity timing remains future.", "Polaris Forge 2", "calendar 2026 and early 2027")
    yield RowSpec("APLD_SRC_08", "FY2026Q3", "planned_utility_power", "430", "430", "MW", "initial", "utility power; campus electrical basis; APLD disclosure", "forecast", "false", "press release body", "support an initial 430 MW of total utility power", "exact", "high", "Utility power, not critical IT load; keep separate from IT MW.", "Delta Forge 1", "mid-2027")
    yield RowSpec("APLD_SRC_08", "FY2026Q3", "planned_critical_it_load", "300", "300", "MW", "up_to", "critical IT load; APLD disclosure", "forecast", "false", "press release body", "enabling up to 300 MW of critical IT load", "exact", "high", "Critical IT load forecast; not leased at January 2026 source date.", "Delta Forge 1", "mid-2027")
    yield RowSpec("APLD_SRC_09", "FY2026Q3", "operating_hosting_capacity", "106+180", "286", "MW", "derived_sum", "data center hosting facilities; APLD disclosure", "actual", "true", "Data Center Hosting Update", "106 MW facility ... and 180 MW facility ... were operating at full capacity", "partial", "high", "Derived from two hosting facilities; crypto/data-center hosting actual, not AI/HPC Polaris Forge.", "Data Center Hosting", "2026-02-28")
    yield RowSpec("APLD_SRC_09", "FY2026Q3", "under_construction_capacity", "150+150", "300", "MW", "derived_sum", "critical IT load; next two buildings under construction; APLD disclosure", "actual", "true", "Recent Highlights", "advancing construction of the next two 150 MW buildings", "partial", "high", "Derived from two 150 MW buildings; still not operating.", "Polaris Forge 1", "calendar 2026 and 2027")
    yield RowSpec("APLD_SRC_09", "FY2026Q3", "construction_pipeline_capacity", "200", "200", "MW", "exact", "critical IT load; construction/pipeline snapshot; APLD disclosure", "actual", "false", "HPC Hosting Update", "200 MW data center campus ... Initial capacity is anticipated in calendar 2026", "partial", "high", "Construction/pipeline snapshot; full capacity target remains future.", "Polaris Forge 2", "calendar 2026 and early 2027")
    yield RowSpec("APLD_SRC_10", "FY2026Q3", "contracted_critical_it_load", "300", "300", "MW", "exact", "critical IT load; signed lease; APLD disclosure", "actual", "false", "press release body", "lease ... covers 300 megawatts (MW) of critical IT load", "exact", "high", "Lease signed after January groundbreaking; delivery remains future.", "Delta Forge 1", "mid-2027")
    yield RowSpec("APLD_SRC_10", "FY2026Q3", "contracted_revenue", "7.5", "7.5", "USD_billions", "approximately", "contracted value; lease economics; APLD disclosure", "actual", "false", "press release body", "approximately $7.5 billion in total contracted value", "exact", "high", "Contract value, not revenue recognized or capex.", "Delta Forge 1", "15-year lease term")
    yield RowSpec("APLD_SRC_11", "FY2026Q4", "contracted_critical_it_load", "300", "300", "MW", "exact", "critical IT load; signed lease; APLD disclosure", "actual", "false", "press release body", "300 MW of critical IT load", "exact", "high", "New fourth-campus lease; future delivery risk remains.", "Polaris Forge 3", "15-year lease term")
    yield RowSpec("APLD_SRC_11", "FY2026Q4", "total_contracted_capacity", "1", "1000", "MW", "greater_than", "portfolio contracted critical IT load; APLD disclosure", "actual", "false", "press release title/body", "Surpassing 1 GW of Contracted Capacity", "partial", "high", "Raw source uses GW wording; normalized to MW. Contracted capacity, not delivered operating capacity.", "Portfolio", "portfolio current snapshot")


def build_rows() -> list[dict[str, str]]:
    manifest = load_manifest()
    out: list[dict[str, str]] = []
    for spec in row_specs():
        src = manifest.get(spec.source_id)
        if src is None:
            raise ValueError(f"row references missing source_id {spec.source_id}")
        row = asdict(spec)
        row["company"] = "APLD"
        row["source_url"] = src["source_url"]
        row["source_date"] = src["source_date"]
        expected = normalized_from_raw(row["raw_value"], row["unit"], row["source_snippet"])
        if abs(float(row["normalized_value"]) - expected) > 0.001:
            raise ValueError(f"bad normalization for {spec.source_id} {spec.metric_name}: {row['normalized_value']} vs {expected}")
        out.append({field: row.get(field, "") for field in OUTPUT_FIELDS})
    return out


def main() -> None:
    rows = build_rows()
    with OUT.open("w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=OUTPUT_FIELDS)
        writer.writeheader()
        writer.writerows(rows)
    print(f"wrote {len(rows)} APLD forecast-vs-actual rows to {OUT}")


if __name__ == "__main__":
    main()
