| from __future__ import annotations
|
|
|
| import html
|
| import os
|
| from functools import lru_cache
|
| from pathlib import Path
|
|
|
| import gradio as gr
|
| import pandas as pd
|
|
|
|
|
| ROOT = Path(__file__).parent
|
| DATA = ROOT / "data"
|
|
|
|
|
| @lru_cache(maxsize=1)
|
| def metrics():
|
| import json
|
|
|
| return json.loads((DATA / "summary_metrics.json").read_text(encoding="utf-8"))
|
|
|
|
|
| @lru_cache(maxsize=1)
|
| def places():
|
| return pd.read_csv(DATA / "place_exposure.csv")
|
|
|
|
|
| @lru_cache(maxsize=1)
|
| def missing_geocodes():
|
| return pd.read_csv(DATA / "missing_geocode_examples.csv")
|
|
|
|
|
| @lru_cache(maxsize=1)
|
| def public_infrastructure_sites():
|
| path = DATA / "public_infrastructure_sites.csv"
|
| if not path.exists():
|
| return pd.DataFrame()
|
| return pd.read_csv(path)
|
|
|
|
|
| @lru_cache(maxsize=1)
|
| def events():
|
| return pd.read_csv(DATA / "event_explanatory_features.csv.gz")
|
|
|
|
|
| def state_choices():
|
| state_values = sorted(str(value) for value in places()["state"].dropna().unique())
|
| return ["All"] + state_values
|
|
|
|
|
| def bucket_choices():
|
| values = sorted(str(value) for value in missing_geocodes()["reason_bucket"].dropna().unique())
|
| return ["All"] + values
|
|
|
|
|
| def population_bins():
|
| return ["All", "lt_10k", "10k_50k", "50k_250k", "250k_1m", "gte_1m", "unknown_population"]
|
|
|
|
|
| def score_cards():
|
| m = metrics()
|
| assessment = m["assessment"]
|
| population = m["population_summary"]
|
| airport = m["airport_summary"]
|
| missing = m["missing_geocode_summary"]
|
| return f"""
|
| <div class="score-grid">
|
| <div class="score-card"><div class="label">Population signal</div><div class="score">{assessment["population_explanation_strength_score"]}/100</div><div class="small">log-pop/report correlation {population["log_population_log_event_count_pearson"]}</div></div>
|
| <div class="score-card"><div class="label">Airport confounder</div><div class="score">{assessment["airport_confounder_strength_score"]}/100</div><div class="small">event median {airport["event_nearest_major_airport_distance_median_miles"]} mi</div></div>
|
| <div class="score-card"><div class="label">Nuclear non-support</div><div class="score">{assessment["nuclear_non_support_strength_score"]}/100</div><div class="small">matched controls beat nuclear in the primary test</div></div>
|
| <div class="score-card"><div class="label">Missing geocodes</div><div class="score">{missing["missing_geocode_rows"]}</div><div class="small">{missing["missing_geocode_share"]:.1%} of U.S. rows</div></div>
|
| </div>
|
| """
|
|
|
|
|
| def fmt_int(value):
|
| try:
|
| return f"{int(float(value)):,}"
|
| except (TypeError, ValueError):
|
| return str(value)
|
|
|
|
|
| def fmt_float(value, digits=2):
|
| try:
|
| return f"{float(value):,.{digits}f}"
|
| except (TypeError, ValueError):
|
| return str(value)
|
|
|
|
|
| def pct(value, digits=1):
|
| try:
|
| return f"{float(value) * 100:.{digits}f}%"
|
| except (TypeError, ValueError):
|
| return str(value)
|
|
|
|
|
| def bar_row(label, value, max_value, color_class, note=""):
|
| width = 0 if max_value == 0 else max(2, min(100, (float(value) / float(max_value)) * 100))
|
| safe_label = html.escape(str(label))
|
| safe_note = html.escape(str(note))
|
| return f"""
|
| <div class="bar-row" role="img" aria-label="{safe_label}: {fmt_float(value, 2)} {safe_note}">
|
| <div class="bar-label"><span>{safe_label}</span><strong>{fmt_float(value, 2)}</strong></div>
|
| <div class="bar-track"><div class="bar-fill {color_class}" style="width:{width:.1f}%"></div></div>
|
| <div class="bar-note">{safe_note}</div>
|
| </div>
|
| """
|
|
|
|
|
| def share_bar(label, value, color_class, note=""):
|
| safe_label = html.escape(str(label))
|
| safe_note = html.escape(str(note))
|
| try:
|
| width = max(2, min(100, float(value) * 100))
|
| except (TypeError, ValueError):
|
| width = 0
|
| return f"""
|
| <div class="share-row" role="img" aria-label="{safe_label}: {pct(value)} {safe_note}">
|
| <div class="share-label"><span>{safe_label}</span><strong>{pct(value)}</strong></div>
|
| <div class="bar-track"><div class="bar-fill {color_class}" style="width:{width:.1f}%"></div></div>
|
| <div class="bar-note">{safe_note}</div>
|
| </div>
|
| """
|
|
|
|
|
| def visual_story_html():
|
| m = metrics()
|
| assessment = m["assessment"]
|
| stats = m["nuclear_statistical_tests"]
|
| primary = stats["tests_by_radius"]["50"]
|
| population = m["population_summary"]
|
| airport = m["airport_summary"]
|
| missing = m["missing_geocode_summary"]
|
|
|
| nuclear_mean = float(primary["nuclear_mean_reports_per_site"])
|
| control_mean = float(primary["control_mean_reports_per_site"])
|
| comparison_max = max(nuclear_mean, control_mean)
|
| nuclear_bars = (
|
| bar_row("Nuclear power-plant sites", nuclear_mean, comparison_max, "fill-nuclear", "mean reports per site within 50 miles")
|
| + bar_row("Matched non-nuclear power controls", control_mean, comparison_max, "fill-control", "mean reports per site within 50 miles")
|
| )
|
|
|
| bin_labels = {
|
| "lt_10k": "Places under 10k people",
|
| "10k_50k": "10k to 50k people",
|
| "50k_250k": "50k to 250k people",
|
| "250k_1m": "250k to 1M people",
|
| "gte_1m": "1M+ people",
|
| "unknown_population": "Unknown population",
|
| }
|
| population_bins = population["population_bin_event_distribution"]
|
| pop_rows = []
|
| for key in ["lt_10k", "10k_50k", "50k_250k", "250k_1m", "gte_1m", "unknown_population"]:
|
| value = population_bins.get(key, {}).get("event_share", 0)
|
| pop_rows.append(share_bar(bin_labels[key], value, "fill-population", f"{fmt_int(population_bins.get(key, {}).get('event_count', 0))} rows"))
|
| population_bars = "".join(pop_rows)
|
|
|
| airport_rows = []
|
| for radius in (10, 25, 50):
|
| event_key = f"event_share_within_{radius}_miles_major_airport"
|
| pop_key = f"population_weighted_share_within_{radius}_miles_major_airport"
|
| airport_rows.append(f"""
|
| <div class="paired-block">
|
| <div class="paired-title">Within {radius} miles of a major scheduled airport</div>
|
| {share_bar("Public report rows", airport[event_key], "fill-airport", "event-place centroids")}
|
| {share_bar("Population-weighted places", airport[pop_key], "fill-baseline", "baseline geography")}
|
| </div>
|
| """)
|
| airport_bars = "".join(airport_rows)
|
|
|
| missing_max = max(missing["reason_bucket_counts"].values()) if missing.get("reason_bucket_counts") else 1
|
| missing_bars = "".join(
|
| bar_row(label.replace("_", " ").title(), count, missing_max, "fill-gap", "rows needing recovery")
|
| for label, count in missing.get("reason_bucket_counts", {}).items()
|
| )
|
|
|
| return f"""
|
| <section class="hero-panel">
|
| <div class="eyebrow">Public-source evidence surface</div>
|
| <h1>The nuclear-specific UAP proximity claim does not hold in this dataset</h1>
|
| <p class="lede">We tested public geocoded report rows against nuclear power-plant sites and matched non-nuclear power-plant controls. The stronger visible pattern is reporting geography: where people live, report, and see the sky.</p>
|
| <div class="claim-path" aria-label="Question, test, result">
|
| <div><span>Question</span><strong>Do reports cluster near nuclear plants?</strong></div>
|
| <div><span>Control</span><strong>Compare with similar non-nuclear power sites</strong></div>
|
| <div><span>Result</span><strong>No nuclear-specific lift observed</strong></div>
|
| </div>
|
| </section>
|
|
|
| <section class="visual-grid">
|
| <article class="viz-card wide">
|
| <h2>Matched controls are the comparison anchor</h2>
|
| <p>The 50-mile test compares nuclear power-plant sites with matched non-nuclear power-plant controls. If the nuclear claim were visible here, the nuclear bar would be clearly higher.</p>
|
| {nuclear_bars}
|
| <div class="takeaway">Nuclear/control ratio: <strong>{primary["nuclear_to_control_mean_ratio"]}</strong>. One-sided p-value for nuclear greater than controls: <strong>{primary["p_value_one_sided_nuclear_greater"]}</strong>.</div>
|
| </article>
|
|
|
| <article class="viz-card">
|
| <h2>Population is the first pattern to notice</h2>
|
| <p>Report rows are not evenly distributed across places. Place population and report count move together on a log scale.</p>
|
| <div class="big-number">{population["log_population_log_event_count_pearson"]}</div>
|
| <div class="big-number-label">log population vs. log report-count correlation</div>
|
| <div class="takeaway">{fmt_int(population["matched_population_event_count"])} of {fmt_int(population["event_count"])} rows matched to Census population places.</div>
|
| </article>
|
|
|
| <article class="viz-card">
|
| <h2>Airport proximity mostly follows people</h2>
|
| <p>Major airports are close to many reports, but they are also close to much of the U.S. population.</p>
|
| <div class="big-number">{airport["event_nearest_major_airport_distance_median_miles"]} mi</div>
|
| <div class="big-number-label">median report-row distance to a major scheduled airport</div>
|
| <div class="takeaway">Population-weighted Census-place median: <strong>{airport["population_weighted_nearest_major_airport_distance_median_miles"]} mi</strong>.</div>
|
| </article>
|
|
|
| <article class="viz-card wide">
|
| <h2>Reports span place sizes, with large places carrying much of the volume</h2>
|
| <p>These bars show the share of report rows by Census-place population bin. The table tabs remain available for exact rows.</p>
|
| {population_bars}
|
| </article>
|
|
|
| <article class="viz-card wide">
|
| <h2>Airport proximity should be read against the population baseline</h2>
|
| <p>If public report rows and the population baseline are close, airport proximity is a confounder rather than a standalone explanation.</p>
|
| {airport_bars}
|
| </article>
|
|
|
| <article class="viz-card wide">
|
| <h2>Uncertainty is visible, not hidden</h2>
|
| <p>{fmt_int(missing["missing_geocode_rows"])} U.S. rows did not resolve to Census place centroids. They are excluded from spatial tests until recovered by a public geocoder path.</p>
|
| {missing_bars}
|
| </article>
|
| </section>
|
|
|
| <section class="claim-box">
|
| <h2>How to read the evidence</h2>
|
| <div class="claim-grid">
|
| <div><strong>Supported</strong><span>Population/reporting geography is a strong observable factor.</span></div>
|
| <div><strong>Confounder</strong><span>Airport proximity is measurable, but close to the population baseline.</span></div>
|
| <div><strong>Not supported here</strong><span>A nuclear-specific proximity lift after matched power-plant controls.</span></div>
|
| <div><strong>Still outside scope</strong><span>Classified activity, exact witness GPS, and case-level truth claims.</span></div>
|
| </div>
|
| </section>
|
| """
|
|
|
|
|
| def overview_markdown():
|
| m = metrics()
|
| assessment = m["assessment"]
|
| stats = m["nuclear_statistical_tests"]
|
| primary = stats["tests_by_radius"]["50"]
|
| can_claim = "\n".join(f"- {item}" for item in assessment["what_we_can_claim"])
|
| cannot_claim = "\n".join(f"- {item}" for item in assessment["what_we_cannot_claim"])
|
| return f"""
|
| # Nuclear UAP Evidence Surface
|
|
|
| This is a public-source, reduced analytical dataset for one question: do public UFO/UAP report rows cluster around nuclear power plants after ordinary controls?
|
|
|
| **Finding:** {assessment["plain_english_assessment"]}
|
|
|
| Primary 50-mile test:
|
|
|
| - Public geocoded report rows: `{stats["uap_event_count"]:,}`
|
| - Nuclear sites: `{stats["nuclear_site_count"]}`
|
| - Matched non-nuclear controls: `{stats["control_site_count"]}`
|
| - Nuclear/control mean ratio: `{primary["nuclear_to_control_mean_ratio"]}`
|
| - One-sided p-value for nuclear greater than controls: `{primary["p_value_one_sided_nuclear_greater"]}`
|
|
|
| ## What it is
|
|
|
| - A public-source evidence surface with hashes and receipts.
|
| - A reduced table of place/date/shape/geocode/proximity features.
|
| - A way to inspect population, airport, and nuclear-site proximity factors.
|
|
|
| ## What it is not
|
|
|
| - It is not proof that any report is true or false.
|
| - It is not a claim that aircraft explain every report.
|
| - It is not exact sighting GPS; rows use public Census place centroids.
|
| - It is not a test of classified weapons-site activity.
|
|
|
| ## What we can claim
|
|
|
| {can_claim}
|
|
|
| ## What we cannot claim
|
|
|
| {cannot_claim}
|
| """
|
|
|
|
|
| def public_infrastructure_html():
|
| layer_stats = metrics().get("public_infrastructure_layers") or {}
|
| reports = layer_stats.get("layer_reports") or {}
|
| if not reports:
|
| return ""
|
| cards = []
|
| labels = {
|
| "public_military_installation": "Public military installations",
|
| "public_nuclear_security_enterprise": "Public NNSA / nuclear-security-enterprise sites",
|
| "public_strategic_delivery_installation": "Public strategic-delivery subset",
|
| }
|
| for key in [
|
| "public_military_installation",
|
| "public_nuclear_security_enterprise",
|
| "public_strategic_delivery_installation",
|
| ]:
|
| report = reports.get(key) or {}
|
| if not report:
|
| continue
|
| near_50 = (report.get("near_any_counts") or {}).get("50", 0)
|
| share_50 = report.get("event_share_within_50_miles", 0)
|
| cards.append(
|
| f"""
|
| <div class="infra-card">
|
| <div class="infra-label">{html.escape(labels.get(key, key))}</div>
|
| <div class="infra-sites">{fmt_int(report.get("site_count", 0))} sites</div>
|
| <div class="infra-detail">{fmt_int(near_50)} public report rows within 50 miles ({pct(share_50)})</div>
|
| </div>
|
| """
|
| )
|
| can_claim = "".join(f"<li>{html.escape(str(item))}</li>" for item in layer_stats.get("what_we_can_claim", []))
|
| cannot_claim = "".join(f"<li>{html.escape(str(item))}</li>" for item in layer_stats.get("what_we_cannot_claim", []))
|
| return f"""
|
| <section class="infra-panel">
|
| <div class="eyebrow">Added public infrastructure layer</div>
|
| <h2>Military and nuclear-security infrastructure are descriptive context, not a causation test</h2>
|
| <p>This layer adds official NTAD military installations and public NNSA/nuclear-security-enterprise anchors, including an NNSS public coordinate receipt. It helps people inspect proximity without implying classified activity, weapons storage, or attraction.</p>
|
| <div class="infra-grid">{''.join(cards)}</div>
|
| <div class="infra-claim-grid">
|
| <div><strong>What this layer can say</strong><ul>{can_claim}</ul></div>
|
| <div><strong>What this layer cannot say</strong><ul>{cannot_claim}</ul></div>
|
| </div>
|
| </section>
|
| """
|
|
|
|
|
| def filter_places(state, min_events, sort_by):
|
| df = places().copy()
|
| if state and state != "All":
|
| df = df[df["state"] == state]
|
| df = df[df["event_count"].fillna(0) >= int(min_events)]
|
| sort_col = "event_count" if sort_by == "Report count" else "events_per_100k_population"
|
| df = df.sort_values(sort_col, ascending=False)
|
| cols = [
|
| "place_name",
|
| "state",
|
| "population",
|
| "event_count",
|
| "events_per_100k_population",
|
| "nearest_major_airport_name",
|
| "nearest_major_airport_distance_miles",
|
| ]
|
| return df[cols].head(200)
|
|
|
|
|
| def filter_events(state, pop_bin, airport_radius, sample_rows):
|
| df = events().copy()
|
| if state and state != "All":
|
| df = df[df["state"] == state]
|
| if pop_bin and pop_bin != "All":
|
| df = df[df["population_bin"] == pop_bin]
|
| if airport_radius == "Within 10 miles":
|
| df = df[df["within_10_miles_major_airport"] == "yes"]
|
| elif airport_radius == "Within 25 miles":
|
| df = df[df["within_25_miles_major_airport"] == "yes"]
|
| elif airport_radius == "Beyond 50 miles":
|
| df = df[df["within_50_miles_major_airport"] == "no"]
|
| cols = [
|
| "event_date",
|
| "city",
|
| "state",
|
| "shape",
|
| "population_bin",
|
| "nearest_major_airport_name",
|
| "nearest_major_airport_distance_miles",
|
| "nearest_nuclear_distance_miles",
|
| "nearest_control_distance_miles",
|
| ]
|
| return df[cols].head(int(sample_rows))
|
|
|
|
|
| def filter_missing(bucket):
|
| df = missing_geocodes().copy()
|
| if bucket and bucket != "All":
|
| df = df[df["reason_bucket"] == bucket]
|
| return df.head(200)
|
|
|
|
|
| def filter_public_infrastructure(layer):
|
| df = public_infrastructure_sites().copy()
|
| if df.empty:
|
| return df
|
| if layer and layer != "All":
|
| df = df[df["site_layer"] == layer]
|
| cols = [
|
| "site_name",
|
| "site_layer",
|
| "site_kind",
|
| "state",
|
| "coordinate_basis",
|
| "source_name",
|
| "source_confidence",
|
| "public_role",
|
| ]
|
| return df[[col for col in cols if col in df.columns]].head(300)
|
|
|
|
|
| def download_files():
|
| files = [
|
| DATA / "event_explanatory_features.csv.gz",
|
| DATA / "place_exposure.csv",
|
| DATA / "missing_geocode_examples.csv",
|
| DATA / "nuclear_sites.csv",
|
| DATA / "matched_controls.csv",
|
| DATA / "site_proximity_summary.csv",
|
| DATA / "public_infrastructure_sites.csv",
|
| DATA / "public_infrastructure_site_summary.csv",
|
| DATA / "event_public_infrastructure_proximity.csv.gz",
|
| DATA / "statistical_tests.json",
|
| DATA / "summary_metrics.json",
|
| DATA / "source_receipts_combined.csv",
|
| DATA / "artifact_manifest.json",
|
| ]
|
| return [str(path) for path in files if path.exists()]
|
|
|
|
|
| css = """
|
| html,
|
| body,
|
| #root,
|
| .app,
|
| gradio-app {
|
| background: #f3f6fa !important;
|
| color: #18232f !important;
|
| }
|
| html body p,
|
| html body a,
|
| html body [role="link"] {
|
| color: #294860 !important;
|
| }
|
| .gradio-container {
|
| max-width: 1180px !important;
|
| margin-left: auto !important;
|
| margin-right: auto !important;
|
| color-scheme: light;
|
| background: #f3f6fa !important;
|
| color: #18232f !important;
|
| --body-background-fill: #f3f6fa;
|
| --body-text-color: #18232f;
|
| --block-background-fill: #ffffff;
|
| --block-border-color: #d7dde8;
|
| --block-label-text-color: #24324a;
|
| --input-background-fill: #ffffff;
|
| --input-border-color: #c8d2de;
|
| --button-secondary-background-fill: #ffffff;
|
| --button-secondary-text-color: #18232f;
|
| }
|
| .gradio-container,
|
| .gradio-container h1,
|
| .gradio-container h2,
|
| .gradio-container h3,
|
| .gradio-container h4,
|
| .gradio-container p,
|
| .gradio-container li,
|
| .gradio-container label,
|
| .gradio-container td,
|
| .gradio-container th,
|
| .gradio-container input,
|
| .gradio-container textarea,
|
| .gradio-container select {
|
| color: #18232f !important;
|
| }
|
| .gradio-container input,
|
| .gradio-container textarea,
|
| .gradio-container select,
|
| .gradio-container [data-testid="dropdown"],
|
| .gradio-container [data-testid="dropdown"] *,
|
| .gradio-container .dropdown,
|
| .gradio-container .dropdown *,
|
| .gradio-container .select-wrap,
|
| .gradio-container .select-wrap *,
|
| .gradio-container .wrap-inner,
|
| .gradio-container .wrap-inner *,
|
| .gradio-container .secondary-wrap,
|
| .gradio-container .secondary-wrap *,
|
| .gradio-container [role="listbox"],
|
| .gradio-container [role="listbox"] *,
|
| .gradio-container [role="option"],
|
| .gradio-container [role="option"] {
|
| color: #18232f !important;
|
| background-color: #ffffff !important;
|
| opacity: 1 !important;
|
| }
|
| .gradio-container [role="option"]:hover,
|
| .gradio-container [role="option"][aria-selected="true"],
|
| .gradio-container .option:hover,
|
| .gradio-container .item:hover {
|
| color: #0f2533 !important;
|
| background-color: #e7f1f7 !important;
|
| }
|
| .gradio-container .prose,
|
| .gradio-container .markdown,
|
| .gradio-container [data-testid="markdown"],
|
| .gradio-container [data-testid="block-label"],
|
| .gradio-container .dataframe,
|
| .gradio-container .wrap {
|
| color: #18232f !important;
|
| background: transparent;
|
| }
|
| .gradio-container button,
|
| .gradio-container [role="tab"],
|
| .gradio-container [role="button"] {
|
| color: #18232f !important;
|
| }
|
| .gradio-container a,
|
| .gradio-container footer,
|
| .gradio-container footer *,
|
| .gradio-container [data-testid="built-with"],
|
| .gradio-container [data-testid="built-with"] * {
|
| color: #294860 !important;
|
| }
|
| .gradio-container footer {
|
| background: #f3f6fa !important;
|
| }
|
| .gradio-container table,
|
| .gradio-container thead,
|
| .gradio-container tbody,
|
| .gradio-container tr,
|
| .gradio-container td,
|
| .gradio-container th {
|
| background-color: #ffffff !important;
|
| }
|
| .gradio-container code {
|
| color: #18232f !important;
|
| background: #eef2f6 !important;
|
| border: 1px solid #d7dde8;
|
| }
|
| .hero-panel { border: 1px solid #d7dde8; border-radius: 6px; padding: 24px; background: #f7fbfd; margin-bottom: 18px; }
|
| .eyebrow { color: #48606f; font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; }
|
| .hero-panel h1 { font-size: 32px; line-height: 1.14; margin: 8px 0 10px; color: #18232f; }
|
| .lede { font-size: 17px; max-width: 860px; color: #344554; }
|
| .claim-path { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 10px; margin-top: 18px; }
|
| .claim-path div { border-left: 4px solid #12719e; background: white; padding: 12px; }
|
| .claim-path span { display: block; color: #5b6975; font-size: 13px; font-weight: 700; }
|
| .claim-path strong { display: block; margin-top: 4px; color: #1d2b36; }
|
| .score-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 12px; margin: 16px 0; }
|
| .score-card, .viz-card, .claim-box { border: 1px solid #d7dde8; border-radius: 6px; padding: 16px; background: #fff; }
|
| .label { font-weight: 700; color: #24324a; }
|
| .score { font-size: 30px; font-weight: 800; margin: 6px 0; }
|
| .small, .bar-note, .big-number-label { font-size: 13px; color: #536070; }
|
| .visual-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
|
| .viz-card h2, .claim-box h2 { font-size: 18px; margin: 0 0 8px; color: #18232f; }
|
| .viz-card p { color: #415160; margin-top: 0; }
|
| .wide { grid-column: 1 / -1; }
|
| .bar-row, .share-row { margin: 12px 0; }
|
| .bar-label, .share-label { display: flex; justify-content: space-between; gap: 12px; font-size: 14px; margin-bottom: 4px; }
|
| .bar-track { height: 14px; background: #eceff3; border-radius: 4px; overflow: hidden; }
|
| .bar-fill { height: 100%; border-radius: 4px; }
|
| .fill-nuclear { background: #12719e; }
|
| .fill-control { background: #ca5800; }
|
| .fill-population { background: #408941; }
|
| .fill-airport { background: #af1f6b; }
|
| .fill-baseline { background: #696969; }
|
| .fill-gap { background: #e88e2d; }
|
| .takeaway { margin-top: 12px; padding: 10px 12px; background: #f4f6f8; border-left: 4px solid #696969; color: #25313c; }
|
| .big-number { font-size: 42px; font-weight: 850; color: #12719e; line-height: 1; margin-top: 12px; }
|
| .paired-block { margin: 14px 0 18px; }
|
| .paired-title { font-weight: 750; margin-bottom: 8px; color: #25313c; }
|
| .claim-box { margin-top: 16px; background: #fbfcff; }
|
| .claim-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 10px; }
|
| .claim-grid div { background: white; border: 1px solid #d7dde8; border-radius: 6px; padding: 12px; }
|
| .claim-grid strong { display: block; color: #18232f; margin-bottom: 5px; }
|
| .claim-grid span { color: #40515f; }
|
| .infra-panel { border: 1px solid #d7dde8; border-radius: 6px; padding: 18px; background: #fff; margin: 16px 0; }
|
| .infra-panel h2 { font-size: 20px; margin: 6px 0 8px; color: #18232f; }
|
| .infra-panel p { color: #40515f; margin-top: 0; }
|
| .infra-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 10px; margin: 14px 0; }
|
| .infra-card { border: 1px solid #d7dde8; border-radius: 6px; padding: 12px; background: #f7fbfd; }
|
| .infra-label { color: #425466; font-weight: 750; font-size: 13px; }
|
| .infra-sites { color: #12719e; font-size: 28px; font-weight: 850; margin-top: 4px; }
|
| .infra-detail { color: #25313c; font-size: 13px; }
|
| .infra-claim-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 10px; }
|
| .infra-claim-grid div { background: #fbfcff; border: 1px solid #d7dde8; border-radius: 6px; padding: 12px; }
|
| .infra-claim-grid ul { margin: 8px 0 0; padding-left: 18px; color: #40515f; }
|
| @media (prefers-color-scheme: dark) {
|
| .gradio-container {
|
| background: #f3f6fa !important;
|
| color: #18232f !important;
|
| }
|
| .hero-panel, .score-card, .viz-card, .claim-box, .claim-grid div {
|
| background-color: #ffffff !important;
|
| color: #18232f !important;
|
| }
|
| }
|
| @media (max-width: 760px) { .visual-grid { grid-template-columns: 1fr; } .hero-panel h1 { font-size: 26px; } }
|
| """
|
|
|
|
|
| with gr.Blocks(css=css, title="Nuclear UAP Evidence Surface", theme=gr.themes.Soft()) as demo:
|
| gr.HTML(visual_story_html())
|
| gr.HTML(public_infrastructure_html())
|
| gr.HTML(score_cards())
|
| with gr.Tabs():
|
| with gr.Tab("Claim Boundaries"):
|
| gr.Markdown(overview_markdown())
|
| with gr.Tab("Explore Places"):
|
| with gr.Row():
|
| place_state = gr.Dropdown(choices=state_choices(), value="All", label="State")
|
| min_events = gr.Slider(0, 100, value=10, step=1, label="Minimum report rows")
|
| place_sort = gr.Radio(["Report count", "Per-capita rate"], value="Report count", label="Sort")
|
| place_table = gr.Dataframe(value=filter_places("All", 10, "Report count"), interactive=False, wrap=True)
|
| for control in (place_state, min_events, place_sort):
|
| control.change(filter_places, [place_state, min_events, place_sort], place_table)
|
| with gr.Tab("Explore Event Rows"):
|
| with gr.Row():
|
| event_state = gr.Dropdown(choices=state_choices(), value="All", label="State")
|
| event_bin = gr.Dropdown(choices=population_bins(), value="All", label="Population bin")
|
| airport_filter = gr.Radio(["Any", "Within 10 miles", "Within 25 miles", "Beyond 50 miles"], value="Any", label="Major airport proximity")
|
| sample_rows = gr.Slider(25, 500, value=100, step=25, label="Rows")
|
| event_table = gr.Dataframe(value=filter_events("All", "All", "Any", 100), interactive=False, wrap=True)
|
| for control in (event_state, event_bin, airport_filter, sample_rows):
|
| control.change(filter_events, [event_state, event_bin, airport_filter, sample_rows], event_table)
|
| with gr.Tab("Missing Geocodes"):
|
| bucket = gr.Dropdown(choices=bucket_choices(), value="All", label="Gap bucket")
|
| missing_table = gr.Dataframe(value=filter_missing("All"), interactive=False, wrap=True)
|
| bucket.change(filter_missing, bucket, missing_table)
|
| with gr.Tab("Military / NNSA Layer"):
|
| infra_layer = gr.Dropdown(
|
| choices=[
|
| "All",
|
| "public_military_installation",
|
| "public_nuclear_security_enterprise",
|
| ],
|
| value="All",
|
| label="Public infrastructure layer",
|
| )
|
| infra_table = gr.Dataframe(value=filter_public_infrastructure("All"), interactive=False, wrap=True)
|
| infra_layer.change(filter_public_infrastructure, infra_layer, infra_table)
|
| with gr.Tab("Downloads & Receipts"):
|
| gr.Markdown("Download the reduced analytical tables, source receipts, hashes, and manifest. These exports do not include raw witness summaries.")
|
| gr.File(value=download_files(), file_count="multiple", label="Dataset files")
|
|
|
|
|
| if __name__ == "__main__":
|
| server_port = int(os.environ.get("GRADIO_SERVER_PORT", "7860"))
|
| server_name = os.environ.get("GRADIO_SERVER_NAME") or None
|
| demo.launch(server_name=server_name, server_port=server_port)
|
|
|