cjc0013's picture
Add public infrastructure layer and dropdown contrast fix
396ecfd verified
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)