cjc0013 commited on
Commit
2ff2a78
·
verified ·
1 Parent(s): 5d2cb97

Simplify drone map UI and link each point to exact public row data

Browse files
README.md CHANGED
@@ -13,8 +13,8 @@ python_version: 3.11
13
 
14
  # Drone Sightings Near Covered U.S. Military and Civilian Areas
15
 
16
- This private preview shows reported drone sightings from news stories over official covered military and civilian areas.
17
 
18
- Use the built-in time slider and play button in the map to watch red dots appear across time.
19
 
20
- This release tracks reported drone sightings and does not prove intent, threat, wrongdoing, or a verified security breach.
 
13
 
14
  # Drone Sightings Near Covered U.S. Military and Civilian Areas
15
 
16
+ This private preview maps reported drone sightings from news stories over official covered military and civilian areas.
17
 
18
+ Use the built-in time slider and play button in the map to watch red dots appear across time, then match the `event_id` on the map to the row data directly below it.
19
 
20
+ Each point is tied to one public event row and a linked source list. This release tracks reported drone sightings and does not prove intent, threat, wrongdoing, or a verified security breach.
dataset_bundle/public_release_manifest.json CHANGED
@@ -1,7 +1,7 @@
1
  {
2
  "public_version": "drone-sightings-slice-2026-04-v1-smoke",
3
  "title": "Drone Sightings Near Covered U.S. Military and Civilian Areas",
4
- "release_date": "2026-04-20T17:54:30.455865+00:00",
5
  "source_run_name": "drone_sightings_smoke_20260420",
6
  "slice_description": "A small, review-oriented slice of U.S. news-reported drone sightings mapped against official covered-area registries.",
7
  "source_window": {
 
1
  {
2
  "public_version": "drone-sightings-slice-2026-04-v1-smoke",
3
  "title": "Drone Sightings Near Covered U.S. Military and Civilian Areas",
4
+ "release_date": "2026-04-20T18:04:25.903466+00:00",
5
  "source_run_name": "drone_sightings_smoke_20260420",
6
  "slice_description": "A small, review-oriented slice of U.S. news-reported drone sightings mapped against official covered-area registries.",
7
  "source_window": {
public_copy.json CHANGED
@@ -1,5 +1,5 @@
1
  {
2
  "title": "Drone Sightings Near Covered U.S. Military and Civilian Areas",
3
  "dataset_bundle_prefix": "dataset_bundle",
4
- "landing_markdown": "# Drone Sightings Near Covered U.S. Military and Civilian Areas\n\nThis private preview shows reported drone sightings from news stories over official covered military and civilian areas.\n\nUse the built-in time slider and play button in the map to watch red dots appear across time.\n\nThis release tracks reported drone sightings and does not prove intent, threat, wrongdoing, or a verified security breach."
5
  }
 
1
  {
2
  "title": "Drone Sightings Near Covered U.S. Military and Civilian Areas",
3
  "dataset_bundle_prefix": "dataset_bundle",
4
+ "landing_markdown": "# Drone Sightings Near Covered U.S. Military and Civilian Areas\n\nThis private preview maps reported drone sightings from news stories over official covered military and civilian areas.\n\nUse the built-in time slider and play button in the map to watch red dots appear across time, then match the `event_id` on the map to the row data directly below it.\n\nEach point is tied to one public event row and a linked source list. This release tracks reported drone sightings and does not prove intent, threat, wrongdoing, or a verified security breach."
5
  }
public_space_app.py CHANGED
@@ -20,32 +20,36 @@ def _dataset_root(public_copy_path: Path) -> Path:
20
  def load_release_data(public_copy_path: Path) -> dict:
21
  dataset_root = _dataset_root(public_copy_path)
22
  events = pd.read_csv(dataset_root / "events.csv")
23
- areas = pd.read_csv(dataset_root / "monitored_areas.csv")
24
  event_sources = pd.read_csv(dataset_root / "event_sources.csv")
25
- daily_rollup = pd.read_csv(dataset_root / "daily_area_rollup.csv")
26
  manifest = json.loads((dataset_root / "public_release_manifest.json").read_text(encoding="utf-8"))
27
  return {
28
  "events": events.fillna(""),
29
- "areas": areas.fillna(""),
30
  "event_sources": event_sources.fillna(""),
31
- "daily_rollup": daily_rollup.fillna(""),
32
  "manifest": manifest,
33
  }
34
 
35
 
36
- def _filter_events(events: pd.DataFrame, area_type: str, civ_mil_class: str, state: str, confidence: str, review_status: str) -> pd.DataFrame:
37
- filtered = events.copy()
38
- if area_type != "all":
39
- filtered = filtered[filtered["area_type"] == area_type]
40
- if civ_mil_class != "all":
41
- filtered = filtered[filtered["civ_mil_class"] == civ_mil_class]
42
- if state != "all":
43
- filtered = filtered[filtered["state"] == state]
44
- if confidence != "all":
45
- filtered = filtered[filtered["confidence_label"] == confidence]
46
- if review_status != "all":
47
- filtered = filtered[filtered["review_status"] == review_status]
48
- return filtered
 
 
 
 
 
 
 
 
49
 
50
 
51
  def _build_map(events: pd.DataFrame):
@@ -61,14 +65,15 @@ def _build_map(events: pd.DataFrame):
61
  color_discrete_sequence=["#d62728"],
62
  hover_name="headline",
63
  hover_data={
 
64
  "area_name": True,
65
  "state": True,
66
  "event_date": True,
67
  "source_count": True,
68
  "confidence_label": True,
 
69
  "lat": False,
70
  "lon": False,
71
- "event_id": True,
72
  },
73
  custom_data=["event_id"],
74
  animation_frame="animation_frame",
@@ -100,7 +105,7 @@ def _build_map(events: pd.DataFrame):
100
 
101
  def _detail_markdown(event_sources: pd.DataFrame, events: pd.DataFrame, event_id: str) -> str:
102
  if not event_id:
103
- return "Select a table row or click a point to inspect supporting sources."
104
  matched = events[events["event_id"] == event_id]
105
  if matched.empty:
106
  return "No event details available."
@@ -109,17 +114,33 @@ def _detail_markdown(event_sources: pd.DataFrame, events: pd.DataFrame, event_id
109
  lines = [
110
  f"### {event_row['headline']}",
111
  "",
112
- f"- Date: `{event_row['event_date']}` (`{event_row['date_quality']}`)",
113
- f"- Area: `{event_row['area_name']}` ({event_row['area_type']}, {event_row['civ_mil_class']})",
114
- f"- State: `{event_row['state']}`",
115
- f"- Supporting stories: `{len(sources)}`",
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  "",
117
  event_row.get("summary", ""),
118
  "",
119
- "#### Supporting URLs",
120
  ]
121
  for _, source_row in sources.iterrows():
122
- lines.append(f"- [{source_row['publisher']} | {source_row['title']}]({source_row['canonical_url']})")
 
 
 
123
  return "\n".join(lines)
124
 
125
 
@@ -129,42 +150,24 @@ def build_app(public_copy_path: str | Path):
129
  data = load_release_data(public_copy_path)
130
  events = data["events"]
131
  event_sources = data["event_sources"]
132
- states = ["all"] + sorted(str(value) for value in events["state"].astype(str).unique() if str(value))
133
- area_types = ["all"] + sorted(str(value) for value in events["area_type"].astype(str).unique() if str(value))
134
- classes = ["all"] + sorted(str(value) for value in events["civ_mil_class"].astype(str).unique() if str(value))
135
- confidences = ["all"] + sorted(str(value) for value in events["confidence_label"].astype(str).unique() if str(value))
136
- review_statuses = ["all"] + sorted(str(value) for value in events["review_status"].astype(str).unique() if str(value))
137
-
138
- def render(area_type: str, civ_mil_class: str, state: str, confidence: str, review_status: str):
139
- filtered = _filter_events(events, area_type, civ_mil_class, state, confidence, review_status)
140
- table = filtered[["event_id", "event_date", "area_name", "area_type", "state", "confidence_label", "source_count", "headline"]].head(250)
141
- return _build_map(filtered), table
142
 
143
  with gr.Blocks(title=payload["title"]) as app:
144
  gr.Markdown(payload["landing_markdown"])
145
- with gr.Row():
146
- area_type = gr.Dropdown(choices=area_types, value="all", label="Area Type")
147
- civ_mil_class = gr.Dropdown(choices=classes, value="all", label="Military / Civilian")
148
- state = gr.Dropdown(choices=states, value="all", label="State")
149
- confidence = gr.Dropdown(choices=confidences, value="all", label="Confidence")
150
- review_status = gr.Dropdown(choices=review_statuses, value="all", label="Review Status")
151
  plot = gr.Plot(label="Drone sightings over time")
152
- table = gr.Dataframe(label="Filtered events", interactive=False)
153
- detail = gr.Markdown("Select a table row or click a point to inspect supporting sources.")
 
 
154
 
155
  def _table_detail(evt: gr.SelectData):
156
  if not evt or evt.index is None:
157
- return "Select a table row or click a point to inspect supporting sources."
158
  row_index = evt.index[0] if isinstance(evt.index, (list, tuple)) else evt.index
159
- filtered = _filter_events(events, area_type.value, civ_mil_class.value, state.value, confidence.value, review_status.value)
160
- filtered = filtered.reset_index(drop=True)
161
- if row_index >= len(filtered):
162
  return "No event details available."
163
- return _detail_markdown(event_sources, events, str(filtered.iloc[row_index]["event_id"]))
164
 
165
- inputs = [area_type, civ_mil_class, state, confidence, review_status]
166
- for component in inputs:
167
- component.change(render, inputs=inputs, outputs=[plot, table])
168
  table.select(_table_detail, outputs=detail)
169
- app.load(render, inputs=inputs, outputs=[plot, table])
170
  return app
 
20
  def load_release_data(public_copy_path: Path) -> dict:
21
  dataset_root = _dataset_root(public_copy_path)
22
  events = pd.read_csv(dataset_root / "events.csv")
 
23
  event_sources = pd.read_csv(dataset_root / "event_sources.csv")
 
24
  manifest = json.loads((dataset_root / "public_release_manifest.json").read_text(encoding="utf-8"))
25
  return {
26
  "events": events.fillna(""),
 
27
  "event_sources": event_sources.fillna(""),
 
28
  "manifest": manifest,
29
  }
30
 
31
 
32
+ def _point_table(events: pd.DataFrame) -> pd.DataFrame:
33
+ ordered = events.copy()
34
+ ordered["event_date"] = ordered["event_date"].astype(str)
35
+ ordered = ordered.sort_values(["event_date", "event_id", "headline"], ascending=[True, True, True]).reset_index(drop=True)
36
+ return ordered[
37
+ [
38
+ "event_id",
39
+ "event_date",
40
+ "headline",
41
+ "area_name",
42
+ "area_type",
43
+ "civ_mil_class",
44
+ "state",
45
+ "lat",
46
+ "lon",
47
+ "source_count",
48
+ "confidence_label",
49
+ "review_status",
50
+ "primary_source_url",
51
+ ]
52
+ ]
53
 
54
 
55
  def _build_map(events: pd.DataFrame):
 
65
  color_discrete_sequence=["#d62728"],
66
  hover_name="headline",
67
  hover_data={
68
+ "event_id": True,
69
  "area_name": True,
70
  "state": True,
71
  "event_date": True,
72
  "source_count": True,
73
  "confidence_label": True,
74
+ "primary_source_url": True,
75
  "lat": False,
76
  "lon": False,
 
77
  },
78
  custom_data=["event_id"],
79
  animation_frame="animation_frame",
 
105
 
106
  def _detail_markdown(event_sources: pd.DataFrame, events: pd.DataFrame, event_id: str) -> str:
107
  if not event_id:
108
+ return "Select a row in the point-data table to inspect the exact public data and source URLs used for that plotted point."
109
  matched = events[events["event_id"] == event_id]
110
  if matched.empty:
111
  return "No event details available."
 
114
  lines = [
115
  f"### {event_row['headline']}",
116
  "",
117
+ "This is the exact public row used to draw the point on the map.",
118
+ "",
119
+ f"- `event_id`: `{event_row['event_id']}`",
120
+ f"- `event_date`: `{event_row['event_date']}`",
121
+ f"- `date_quality`: `{event_row['date_quality']}`",
122
+ f"- `area_name`: `{event_row['area_name']}`",
123
+ f"- `area_type`: `{event_row['area_type']}`",
124
+ f"- `civ_mil_class`: `{event_row['civ_mil_class']}`",
125
+ f"- `state`: `{event_row['state']}`",
126
+ f"- `lat`: `{event_row['lat']}`",
127
+ f"- `lon`: `{event_row['lon']}`",
128
+ f"- `source_count`: `{event_row['source_count']}`",
129
+ f"- `confidence_label`: `{event_row['confidence_label']}`",
130
+ f"- `review_status`: `{event_row['review_status']}`",
131
+ f"- `primary_source_url`: {event_row['primary_source_url']}",
132
+ "",
133
+ "#### Summary",
134
  "",
135
  event_row.get("summary", ""),
136
  "",
137
+ "#### Source stories behind this point",
138
  ]
139
  for _, source_row in sources.iterrows():
140
+ lines.append(
141
+ f"- [{source_row['publisher']} | {source_row['title']}]({source_row['canonical_url']})"
142
+ f" | `published_at={source_row['published_at']}`"
143
+ )
144
  return "\n".join(lines)
145
 
146
 
 
150
  data = load_release_data(public_copy_path)
151
  events = data["events"]
152
  event_sources = data["event_sources"]
153
+ point_table = _point_table(events)
 
 
 
 
 
 
 
 
 
154
 
155
  with gr.Blocks(title=payload["title"]) as app:
156
  gr.Markdown(payload["landing_markdown"])
 
 
 
 
 
 
157
  plot = gr.Plot(label="Drone sightings over time")
158
+ gr.Markdown("## Point data")
159
+ gr.Markdown("Every row below is one plotted point. The `event_id` shown on hover in the map matches the `event_id` in the table and detail panel.")
160
+ table = gr.Dataframe(label="Point data used to draw the map", interactive=False)
161
+ detail = gr.Markdown("Select a row in the point-data table to inspect the exact public data and source URLs used for that plotted point.")
162
 
163
  def _table_detail(evt: gr.SelectData):
164
  if not evt or evt.index is None:
165
+ return "Select a row in the point-data table to inspect the exact public data and source URLs used for that plotted point."
166
  row_index = evt.index[0] if isinstance(evt.index, (list, tuple)) else evt.index
167
+ if row_index >= len(point_table):
 
 
168
  return "No event details available."
169
+ return _detail_markdown(event_sources, events, str(point_table.iloc[row_index]["event_id"]))
170
 
 
 
 
171
  table.select(_table_detail, outputs=detail)
172
+ app.load(lambda: (_build_map(events), point_table), outputs=[plot, table])
173
  return app