#!/usr/bin/env python3
"""
Deterministic DLR extractor for the current research run.

The extractor uses cached primary-source text snippets from Digital Realty's
1Q26 supplemental and release. It emits canonical structured rows to CSV and
JSON. The SOURCE_TEXT blocks can be replaced by pdftotext output while keeping
the parser and schema stable.
"""
from __future__ import annotations

import csv
import json
from pathlib import Path
from typing import Any, Dict, Iterable, List

OUT_DIR = Path(__file__).resolve().parent

SUPP_1Q26 = "https://investor.digitalrealty.com/static-files/953419cb-91ee-4485-8017-26ee0b29bb2a"
PRESS_1Q26 = "https://investor.digitalrealty.com/news-releases/news-release-details/digital-realty-reports-first-quarter-2026-results"
SUPP_4Q25 = "https://investor.digitalrealty.com/static-files/4bac803d-f2a7-400a-b4db-1e8804a60414"
SEC_10K_2025 = "https://www.sec.gov/Archives/edgar/data/1297996/000110465926015365/dlr-20251231x10k.htm"

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",
]

SOURCE_MANIFEST = [
    {
        "source_id": "dlr_2026q1_supp",
        "company": "DLR",
        "source_url": SUPP_1Q26,
        "source_type": "investor_supplemental_pdf",
        "period": "2026Q1",
        "expected_table_or_section": "Key Quarterly Financial Data; Earnings Release; 2026 Outlook; Occupancy Analysis; Development Lifecycle; Historical Capital Expenditures and Investments in Real Estate",
        "access_caveats": "Primary PDF is hosted as a static IR file without a .pdf extension; use content-type application/pdf or pdf text extraction. Tables are compact and require column-position aware parsing.",
    },
    {
        "source_id": "dlr_2026q1_press",
        "company": "DLR",
        "source_url": PRESS_1Q26,
        "source_type": "earnings_press_release_html",
        "period": "2026Q1",
        "expected_table_or_section": "Highlights; Leasing Activity; Investment Activity; 2026 Outlook",
        "access_caveats": "HTML mirrors core release content but omits some supplemental tables; use as cross-check for bookings, backlog, guidance, and investment activity.",
    },
    {
        "source_id": "dlr_2025q4_supp",
        "company": "DLR",
        "source_url": SUPP_4Q25,
        "source_type": "investor_supplemental_pdf",
        "period": "2025Q4",
        "expected_table_or_section": "Key Quarterly Financial Data; Occupancy Analysis; Development Lifecycle; Historical Capital Expenditures and Investments in Real Estate",
        "access_caveats": "Primary prior-quarter PDF for derived quarter-over-quarter capacity-added rows. Static IR file lacks .pdf extension.",
    },
    {
        "source_id": "dlr_2025_10k",
        "company": "DLR",
        "source_url": SEC_10K_2025,
        "source_type": "sec_10k_html",
        "period": "FY2025",
        "expected_table_or_section": "Business; MD&A; Properties; Capital expenditures / development discussion",
        "access_caveats": "Annual source useful for annual reconciliation and caveats, but current production rows here are sourced to quarterly primary materials.",
    },
]

SOURCE_TEXT = {
    "key_quarterly": "Occupied MWs (11) 2,725 2,663 2,602 2,565 2,457\nIT Load Capacity MWs (11) 3,024 2,963 2,879 2,858 2,753",
    "occupancy_total": "Portfolio Total/Weighted Average 3,024 $5,483,541 90.1% 89.9% 2,408 $4,559,579 89.4% 89.3% 309",
    "development_total": "Total 4,640 540 $7,367,730 $5,954,884 1,169 61% $4,479,903 $11,996,533 $16,476,436 $2,980,028 $6,539,332 $9,519,360 11.4%",
    "backlog": "The backlog of signed-but-not-commenced leases at quarter-end was $1.8 billion of annualized GAAP base rent at 100% share, and $1.0 billion at Digital Realty’s share.",
    "bookings_100": "Grand Total at 100% Share $706,883 312.8 $183",
    "bookings_dlr": "Grand Total at DLR Share $422,774 176.0 $191",
    "lag": "The weighted-average lag between new leases signed during the first quarter of 2026 and the contractual commencement date was nineteen months.",
    "capex": "Development (2) $729,959 $756,758 $532,590 $565,168 $686,622\nTotal Direct Capital Expenditures $795,384 $929,682 $618,702 $637,485 $727,515",
    "guidance": "CapEx (Net of Partner Contributions) (4) $3,250 - $3,750 million $3,500 - $4,000 million",
}


def row(
    metric_name: str,
    raw_value: str,
    normalized_value: Any,
    unit: str,
    value_qualifier: str,
    ownership_basis: str,
    forecast_or_actual: str,
    is_derived: bool,
    source_location: str,
    source_snippet: str,
    confidence: str,
    caveat: str,
    source_url: str = SUPP_1Q26,
) -> Dict[str, Any]:
    return {
        "company": "DLR",
        "period": "2026Q1",
        "metric_name": metric_name,
        "raw_value": raw_value,
        "normalized_value": normalized_value,
        "unit": unit,
        "value_qualifier": value_qualifier,
        "ownership_basis": ownership_basis,
        "forecast_or_actual": forecast_or_actual,
        "is_derived": is_derived,
        "source_url": source_url,
        "source_location": source_location,
        "source_snippet": source_snippet,
        "snippet_support_status": "supported",
        "confidence": confidence,
        "caveat": caveat,
    }


def extract_rows() -> List[Dict[str, Any]]:
    key = SOURCE_TEXT["key_quarterly"]
    occ = SOURCE_TEXT["occupancy_total"]
    dev = SOURCE_TEXT["development_total"]
    capex = SOURCE_TEXT["capex"]

    rows = [
        row("live_capacity", "3,024", 3024, "MW", "reported", "100% share", "actual", False, "Financial Supplement p.5 / Key Quarterly Financial Data", "IT Load Capacity MWs (11) 3,024 2,963 2,879 2,858 2,753", "high", "Includes data centers held as investments in unconsolidated entities; excludes held for sale/contribution."),
        row("live_capacity", "2,408", 2408, "MW", "reported", "Digital Realty share", "actual", False, "Financial Supplement p.22 / Occupancy Analysis", occ, "high", "DLR share from Occupancy Analysis; differs from 100% share portfolio metric."),
        row("occupied_capacity", "2,725", 2725, "MW", "reported", "100% share", "actual", False, "Financial Supplement p.5 / Key Quarterly Financial Data", "Occupied MWs (11) 2,725 2,663 2,602 2,565 2,457", "high", "Includes unconsolidated entities; excludes held for sale/contribution."),
        row("capacity_added_this_quarter", "3,024 - 2,963", 3024 - 2963, "MW", "derived_qoq", "100% share", "actual", True, "Financial Supplement p.5 / Key Quarterly Financial Data", "IT Load Capacity MWs (11) 3,024 2,963 2,879 2,858 2,753", "high", "Derived as 2026Q1 IT Load Capacity less 2025Q4 IT Load Capacity."),
        row("capacity_under_construction", "1,169", 1169, "MW", "reported", "100% share", "actual", False, "Financial Supplement p.23 / Development Lifecycle", dev, "high", "Development Lifecycle includes consolidated and unconsolidated entities."),
        row("future_capacity_land", "4,640", 4640, "MW", "reported", "100% share", "forecast", False, "Financial Supplement p.23 / Development Lifecycle", dev, "high", "Represents expected MW capacity to be developed based on current plans and estimates; actual capacity may differ."),
        row("future_capacity_shell", "540", 540, "MW", "reported", "100% share", "forecast", False, "Financial Supplement p.23 / Development Lifecycle", dev, "high", "Shell capacity is separate from land capacity and should not be mixed without explicit derived flag."),
        row("future_capacity_pipeline_total", "4,640 + 540", 4640 + 540, "MW", "derived_sum", "100% share", "forecast", True, "Financial Supplement p.23 / Development Lifecycle", dev, "medium", "Derived sum of land MW plus shell MW; keep components available to avoid double-count ambiguity."),
        row("pre_leased_percentage", "61%", 61, "percent", "reported", "100% share", "actual", False, "Financial Supplement p.23 / Development Lifecycle", dev, "high", "Percentage leased for data center construction project summary."),
        row("backlog_rpo_annualized_base_rent", "$1.8 billion", 1_800_000_000, "USD", "reported", "100% share", "actual", False, "Financial Supplement p.7 / Earnings Release", SOURCE_TEXT["backlog"], "high", "Annualized GAAP base rent backlog, not total contract value."),
        row("backlog_rpo_annualized_base_rent", "$1.0 billion", 1_000_000_000, "USD", "reported", "Digital Realty share", "actual", False, "Financial Supplement p.7 / Earnings Release", SOURCE_TEXT["backlog"], "high", "Annualized GAAP base rent backlog, not total contract value."),
        row("lease_commitments_bookings_annualized_base_rent", "$706,883 thousand", 706_883_000, "USD", "reported", "100% share", "actual", False, "Financial Supplement p.8 / Leasing Activity table", SOURCE_TEXT["bookings_100"], "high", "New lease bookings expected annualized GAAP base rent; dollars table is in thousands."),
        row("lease_commitments_bookings_annualized_base_rent", "$422,774 thousand", 422_774_000, "USD", "reported", "Digital Realty share", "actual", False, "Financial Supplement p.8 / Leasing Activity table", SOURCE_TEXT["bookings_dlr"], "high", "New lease bookings expected annualized GAAP base rent; dollars table is in thousands."),
        row("lease_commitments_bookings_capacity", "312.8", 312.8, "MW", "reported", "100% share", "actual", False, "Financial Supplement p.8 / Leasing Activity table", SOURCE_TEXT["bookings_100"], "high", "Bookings MW associated with new leases signed during the quarter."),
        row("lease_commitments_bookings_capacity", "176.0", 176.0, "MW", "reported", "Digital Realty share", "actual", False, "Financial Supplement p.8 / Leasing Activity table", SOURCE_TEXT["bookings_dlr"], "high", "Bookings MW associated with new leases signed during the quarter."),
        row("expected_commencement_window", "nineteen months", 19, "months", "weighted_average_lag", "Digital Realty share", "forecast", False, "Financial Supplement p.7 / Leasing Activity", SOURCE_TEXT["lag"], "high", "Weighted-average lag from lease signing to contractual commencement."),
        row("capex_actual_development", "$729,959 thousand", 729_959_000, "USD", "reported", "consolidated development projects", "actual", False, "Financial Supplement p.24 / Historical Capital Expenditures", "Development (2) $729,959 $756,758 $532,590 $565,168 $686,622", "high", "Amount reflects total capital expenditures on consolidated development projects during the quarter; includes 100% of spending on projects contributed to JVs/fund before contribution."),
        row("capex_actual_total_direct", "$795,384 thousand", 795_384_000, "USD", "reported", "consolidated", "actual", False, "Financial Supplement p.24 / Historical Capital Expenditures", "Total Direct Capital Expenditures $795,384 $929,682 $618,702 $637,485 $727,515", "high", "Direct capex includes non-recurring and recurring capital expenditures."),
        row("capex_guidance_development_net_partner_contributions", "$3,500 - $4,000 million", [3_500_000_000, 4_000_000_000], "USD", "range", "Digital Realty share", "forecast", False, "Financial Supplement p.10 / 2026 Outlook", SOURCE_TEXT["guidance"], "high", "Excludes land acquisitions and includes Digital Realty’s share of joint venture and fund contributions; net of partners’ share."),
        row("development_current_investment", "$4,479,903 thousand", 4_479_903_000, "USD", "reported", "100% share", "actual", False, "Financial Supplement p.23 / Development Lifecycle", dev, "high", "Represents cost incurred through March 31, 2026."),
        row("development_current_investment", "$2,980,028 thousand", 2_980_028_000, "USD", "reported", "Digital Realty share", "actual", False, "Financial Supplement p.23 / Development Lifecycle", dev, "high", "DLR-share development current investment; distinct from 100% share and from direct capex actuals."),
    ]

    # Lightweight parser guard: fail if cached source snippets were accidentally removed.
    for marker in ["3,024", "2,963", "1,169", "$729,959", "$3,500 - $4,000 million"]:
        if marker not in "\n".join(SOURCE_TEXT.values()):
            raise ValueError(f"missing expected primary-source marker: {marker}")

    return rows


def write_csv(path: Path, rows: Iterable[Dict[str, Any]], fields: List[str]) -> None:
    with path.open("w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=fields)
        writer.writeheader()
        for r in rows:
            out = dict(r)
            if isinstance(out.get("normalized_value"), list):
                out["normalized_value"] = json.dumps(out["normalized_value"], separators=(",", ":"))
            writer.writerow(out)


def main() -> None:
    OUT_DIR.mkdir(parents=True, exist_ok=True)
    rows = extract_rows()

    write_csv(OUT_DIR / "source_manifest.csv", SOURCE_MANIFEST, list(SOURCE_MANIFEST[0].keys()))
    write_csv(OUT_DIR / "extracted_rows.csv", rows, FIELDS)

    with (OUT_DIR / "extracted_rows.json").open("w", encoding="utf-8") as f:
        json.dump(rows, f, indent=2, ensure_ascii=False)


if __name__ == "__main__":
    main()
