#!/usr/bin/env python3
"""Build website-ready final results and chart data from validated public files."""
from __future__ import annotations

import csv
import hashlib
import json
from collections import Counter
from pathlib import Path

ROOT = Path(__file__).resolve().parents[2]
OUT = ROOT / "research-output"


def load(name):
    return json.loads((OUT / name).read_text(encoding="utf-8"))


def csv_rows(name):
    with (OUT / name).open(newline="", encoding="utf-8") as stream:
        return list(csv.DictReader(stream))


def estimate(measure_id, label, source, item, status, direction, interpretation, evidence=None):
    return {
        "measure_id": measure_id,
        "label": label,
        "source": source,
        "status": status,
        "estimate": item.get("estimate"),
        "n": item.get("n", item.get("n_current", 0)),
        "ci95": item.get("ci95", [None, None]),
        "interval_method": item.get("method", "not_available"),
        "direction_convention": direction,
        "interpretation": interpretation,
        "evidence_rows": evidence or [],
    }


def main():
    manual = load("university-manual-results.json")
    validation = load("university-validation-results.json")
    auto = load("automated-screening-results.json")
    leadership = load("leadership.json")
    trend = load("leadership-exploratory-results.json")
    articles = csv_rows("articles.csv")
    manual_rows = {r["article_id"]: r for r in csv_rows("university-manual-audit.csv")}
    by_id = {r["article_id"]: r for r in articles}

    manual_specs = [
        ("manual_scrutiny_gap", "Administration minus challenger skepticism", "admin_minus_challenger_scrutiny", "positive = more scrutiny of administration", "Contrary to the deference hypothesis inside the audit set."),
        ("manual_tone_gap", "Administration minus challenger reporter tone", "admin_minus_challenger_tone", "positive = more favorable treatment of administration", "Negative: administration was treated more unfavorably inside the audit set, contrary to the hypothesis."),
        ("manual_headline_adverse_gap", "Administration minus challenger adverse-headline rate", "admin_minus_challenger_adverse_headline", "positive = more adverse framing of administration", "Positive: administration more often received adverse headline framing in comparable audit rows."),
        ("manual_lede_adverse_gap", "Administration minus challenger adverse-lede rate", "admin_minus_challenger_adverse_lede", "positive = more adverse framing of administration", "Positive: administration more often received adverse lede framing in comparable audit rows."),
        ("manual_assertion_test_gap", "Official minus challenger assertion testing", "official_minus_challenger_assertion_tested", "positive = official assertions tested more often", "Contrary direction, but intercoder reliability for assertion-testing flags was below 0.60; interpret cautiously."),
        ("manual_first_quote_gap", "Administration minus challenger first-quote probability", "admin_minus_challenger_first_quote", "positive = administration quoted first more often", "Interval crosses zero; the audit did not establish quotation priority."),
    ]
    comparisons = [
        estimate(mid, label, "230-article diversity-oriented manual audit", manual["paired_estimands"][key], "audit_sample_not_population", direction, interp)
        for mid, label, key, direction, interp in manual_specs
    ]
    auto_specs = [
        ("auto_scrutiny_gap", "Automated administration-minus-challenger scrutiny", "paired_scrutiny_gap", "negative = automated screen consistent with administrative deference"),
        ("auto_framing_gap", "Automated administration-minus-challenger marked framing", "paired_reporter_framing_gap", "negative = automated screen consistent with administrative deference"),
        ("auto_first_quote_gap", "Automated administration-minus-challenger first quote", "first_quote_probability_gap", "positive = official first-quote priority"),
        ("auto_quote_share_gap", "Automated administration-minus-challenger quote-word share", "direct_quote_word_share_gap", "positive = larger administration share"),
    ]
    for mid, label, key, direction in auto_specs:
        item = auto["primary_estimands"][key]
        comparisons.append(estimate(mid, label, "complete-census automated screen", item, "suppressed_failed_manual_validation", direction, "Screening value only. None of 19 automated semantic validation measures reached Cohen's kappa 0.60."))

    counter_ids = [
        "spec-14778f2a1d85994c", "spec-f90359a994a158f5", "spec-0fd272e6c455f4a0",
        "spec-765839142daaa7ac", "spec-9a48483e03b2f087", "spec-fd77c077803c4629",
    ]
    counterexamples = []
    for aid in counter_ids:
        if aid not in by_id or aid not in manual_rows:
            continue
        counterexamples.append({
            "article_id": aid,
            "title": by_id[aid]["title"],
            "date": by_id[aid]["date"],
            "url": by_id[aid]["url"],
            "why_it_matters": manual_rows[aid]["manual_notes"],
            "evidence_excerpt": manual_rows[aid]["evidence_excerpt"],
        })

    year_counts = Counter(r["date"][:4] for r in articles)
    period_counts = Counter(r["leadership_period"] for r in articles)
    topic_breakdowns = auto["missingness_breakdowns"]["by_subdesk_primary_topic"]
    validation_values = [v["kappa"] for v in validation["statistics"].values() if v["kappa"] is not None]
    results = {
        "study": {
            "title": "Columbia Daily Spectator University News coverage audit",
            "version": "2.0-authorized-university-census",
            "status": "complete_census_metadata_semantic_population_results_suppressed",
            "main_hypothesis_assessment": "not_established; diversity-oriented manual evidence runs predominantly contrary; automated semantic evidence is mixed and unvalidated",
            "leadership_trend_assessment": "not_supported; no manual measure and only one of four suppressed automated screens moved monotonically toward pro-administration treatment",
            "preregistration_sha256": hashlib.sha256((OUT / "preregistration.md").read_bytes()).hexdigest(),
            "university_addendum_sha256": hashlib.sha256((OUT / "preregistration-university-news-addendum.md").read_bytes()).hexdigest(),
        },
        "cutoff": {"start_date": "2023-10-01", "end_date": "2026-08-10", "latest_observed_article_date": "2026-08-05", "collection_completed_utc": "2026-08-10T19:04:00Z"},
        "corpus": {
            "coverage_status": "complete_reconciled_university_news_census",
            "articles_n": 1527,
            "successful_full_text_private_n": 1527,
            "failed_n": 0,
            "excluded_other_sections": ["opinion", "city_news", "sports", "spectrum", "other_non_university_news"],
            "subdesk_counts": {"administration": 671, "academics": 266, "student_life": 590},
            "manual_audit_n": 230,
            "manual_audit_share": 230 / 1527,
            "manual_paired_actor_n": manual["paired_actor_n"],
            "private_corpus_sha256": auto["study"]["private_corpus_sha256"],
            "public_articles_sha256": hashlib.sha256((OUT / "articles.csv").read_bytes()).hexdigest(),
            "article_counts_by_year": dict(year_counts),
        },
        "validation": {
            "independent_overlap_n": 50,
            "manual_semantic_threshold": 0.60,
            "automated_semantic_measures_tested": len(validation["statistics"]),
            "automated_semantic_measures_passing": sum(validation["population_semantic_gate"].values()),
            "automated_kappa_range": [min(validation_values), max(validation_values)],
            "result": "all automated semantic population findings suppressed",
            "intercoder_file": "university-manual-intercoder-reliability.csv",
            "automation_file": "university-automation-validation.csv",
        },
        "headline_findings": [c for c in comparisons if c["measure_id"] in {"manual_headline_adverse_gap", "manual_lede_adverse_gap", "auto_framing_gap"}],
        "actor_comparisons": comparisons,
        "article_level_manual_audit_rates": manual["article_level_rates"],
        "topic_breakdowns": topic_breakdowns,
        "time_period_breakdowns": {
            "article_counts_by_leadership_period": dict(period_counts),
            "exploratory_stage_results": trend["trend_tests"],
            "claim_assessment": "The requested progressive-worsening claim is not supported. No manual outcome follows the predicted monotonic sequence; only the unvalidated quote-word-share screen does.",
        },
        "leadership_periods": leadership["periods"],
        "counterexamples": counterexamples,
        "limitations": [
            "The 230-article manual set deliberately maximizes screen diversity; it is not self-weighting and its rates do not estimate all 1,527 articles.",
            "None of 19 automated semantic measures reached Cohen's kappa 0.60 against adjudicated manual coding; all semantic census estimates are screening-only.",
            "Strict automated framing reverses sign under the broad actor dictionary, and category-specific directions differ.",
            "Manual assertion-testing flags had sub-threshold intercoder reliability even after adjudication supplied a final row.",
            "Quotation-word share, attribution verbs, verified unique-source counts, and category-specific challenger coding lack full manual ground truth.",
            "The 150th leadership period is right-censored, period topic mixes differ, and no evidence links named editors causally to article decisions.",
            "The corpus scores the latest visible page version at cutoff and cannot detect silent edits or fully assess image consent/harm.",
        ],
        "evidence_links": {
            "articles": "articles.csv",
            "article_coding": "article-coding.csv",
            "actor_rows": "actor-level-analysis.csv",
            "manual_audit": "university-manual-audit.csv",
            "claim_ledger": "claim-ledger.csv",
            "leadership": "leadership-timeline.csv",
            "leadership_exploratory": "leadership-exploratory-results.json",
            "methods": "methodology.md",
            "parser_repair": "corpus-parser-repair-log.md",
        },
    }
    (OUT / "results.json").write_text(json.dumps(results, indent=2) + "\n", encoding="utf-8")

    chart_data = {
        "cutoff": results["cutoff"],
        "charts": [
            {
                "chart_id": "corpus_by_subdesk", "type": "bar", "status": "descriptive_census",
                "series": [{"label": key, "value": value, "n": 1527} for key, value in results["corpus"]["subdesk_counts"].items()],
                "evidence": "articles.csv",
            },
            {
                "chart_id": "manual_paired_actor_gaps", "type": "interval_dot", "status": "audit_sample_not_population",
                "series": [{"label": c["label"], "value": c["estimate"], "lower": c["ci95"][0], "upper": c["ci95"][1], "n": c["n"], "measure_id": c["measure_id"]} for c in comparisons if c["status"] == "audit_sample_not_population"],
                "note": manual["caution"], "evidence": "university-manual-audit.csv",
            },
            {
                "chart_id": "automation_validation_kappa", "type": "bar", "status": "validation",
                "threshold": 0.60,
                "series": [{"label": key, "value": item["kappa"], "n": item["n"]} for key, item in validation["statistics"].items()],
                "evidence": "university-automation-validation.csv",
            },
            {
                "chart_id": "leadership_trend_manual", "type": "multi_line", "status": "post_request_exploratory_nonrepresentative",
                "stages": trend["sequence"],
                "series": [{"label": key, "values": list(item["stage_estimates"].values()), "expected_pro_admin_direction": item["pro_admin_expected_direction"], "monotonic_support": item["monotonic_in_expected_direction"]} for key, item in trend["trend_tests"]["manual_diversity_sample"].items()],
                "evidence": "leadership-exploratory-results.json",
            },
            {
                "chart_id": "leadership_trend_automated_screen", "type": "multi_line", "status": "suppressed_unvalidated_screening",
                "stages": trend["sequence"],
                "series": [{"label": key, "values": list(item["stage_estimates"].values()), "expected_pro_admin_direction": item["pro_admin_expected_direction"], "monotonic_support": item["monotonic_in_expected_direction"]} for key, item in trend["trend_tests"]["automated_screening"].items()],
                "evidence": "leadership-exploratory-results.json",
            },
        ],
    }
    (OUT / "chart-data.json").write_text(json.dumps(chart_data, indent=2) + "\n", encoding="utf-8")
    print(json.dumps({"comparisons": len(comparisons), "counterexamples": len(counterexamples), "charts": len(chart_data["charts"])}, indent=2))


if __name__ == "__main__":
    main()
