danielhjerresen commited on
Commit
885791a
·
verified ·
1 Parent(s): 0e6f4ae

Update streamlit_app.py

Browse files
Files changed (1) hide show
  1. streamlit_app.py +92 -17
streamlit_app.py CHANGED
@@ -19,6 +19,16 @@ API_BASE_URL = os.getenv(
19
  )
20
 
21
 
 
 
 
 
 
 
 
 
 
 
22
  @st.cache_data(ttl=300)
23
  def load_classified_articles() -> pd.DataFrame:
24
  try:
@@ -35,8 +45,31 @@ def load_classified_articles() -> pd.DataFrame:
35
  if df.empty:
36
  return df
37
 
38
- df["published_at"] = pd.to_datetime(df.get("published_at"), errors="coerce", utc=True)
39
- df["classified_at"] = pd.to_datetime(df.get("classified_at"), errors="coerce", utc=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
  df["published_date"] = df["published_at"].dt.date
42
  df["published_day"] = df["published_at"].dt.strftime("%Y-%m-%d")
@@ -68,7 +101,7 @@ def load_daily_summary() -> dict:
68
  def normalize_summary_payload(summary: dict) -> dict:
69
  normalized = dict(summary)
70
 
71
- nested_summary = summary.get("summary_json") or summary.get("top_stories")
72
 
73
  if isinstance(nested_summary, str):
74
  try:
@@ -78,7 +111,7 @@ def normalize_summary_payload(summary: dict) -> dict:
78
  except Exception:
79
  pass
80
 
81
- elif isinstance(nested_summary, dict) and "top_stories" in nested_summary:
82
  normalized.update(nested_summary)
83
 
84
  normalized["executive_summary"] = (
@@ -108,8 +141,8 @@ def normalize_summary_payload(summary: dict) -> dict:
108
  def apply_filters(df: pd.DataFrame) -> pd.DataFrame:
109
  st.sidebar.header("Filters")
110
 
111
- label_options = sorted(df["label"].dropna().unique().tolist()) if "label" in df else []
112
- source_options = sorted(df["source"].dropna().unique().tolist()) if "source" in df else []
113
 
114
  default_labels = [
115
  label
@@ -129,8 +162,8 @@ def apply_filters(df: pd.DataFrame) -> pd.DataFrame:
129
  default=[],
130
  )
131
 
132
- min_date = df["published_date"].min() if "published_date" in df and not df.empty else None
133
- max_date = df["published_date"].max() if "published_date" in df and not df.empty else None
134
 
135
  date_range = None
136
  if min_date and max_date:
@@ -185,8 +218,8 @@ def render_metrics(df: pd.DataFrame, filtered_df: pd.DataFrame) -> None:
185
 
186
  c1.metric("Articles", len(df))
187
  c2.metric("Shown", len(filtered_df))
188
- c3.metric("Sources", df["source"].nunique() if "source" in df else 0)
189
- c4.metric("Categories", df["label"].nunique() if "label" in df else 0)
190
 
191
 
192
  def render_bullet_list(items: list[str], empty_message: str) -> None:
@@ -198,6 +231,30 @@ def render_bullet_list(items: list[str], empty_message: str) -> None:
198
  st.markdown(f"- {item}")
199
 
200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  def render_daily_summary(summary: dict) -> None:
202
  st.subheader("Daily AI Summary")
203
 
@@ -257,7 +314,11 @@ def render_daily_summary(summary: dict) -> None:
257
  article_id = story.get("article_id")
258
 
259
  if pd.notnull(published_at):
260
- published_at = pd.to_datetime(published_at, errors="coerce", utc=True)
 
 
 
 
261
 
262
  if pd.notnull(published_at):
263
  published_at = published_at.strftime("%Y-%m-%d %H:%M UTC")
@@ -285,7 +346,7 @@ def render_daily_summary(summary: dict) -> None:
285
  st.write(decision_relevance)
286
 
287
  if url:
288
- st.markdown(f"[Open article]({url})")
289
 
290
  if article_id:
291
  st.caption(f"Article ID: {article_id}")
@@ -316,9 +377,15 @@ def render_article_browser(df: pd.DataFrame) -> None:
316
  elif sort_option == "Oldest first":
317
  display_df = display_df.sort_values("published_at", ascending=True)
318
  elif sort_option == "Action category":
319
- display_df = display_df.sort_values(["label", "published_at"], ascending=[True, False])
 
 
 
320
  elif sort_option == "Source":
321
- display_df = display_df.sort_values(["source", "published_at"], ascending=[True, False])
 
 
 
322
 
323
  max_rows = st.slider("Number of articles to display", 5, 100, 20)
324
  display_df = display_df.head(max_rows)
@@ -345,7 +412,7 @@ def render_article_browser(df: pd.DataFrame) -> None:
345
 
346
  url = row.get("url")
347
  if pd.notnull(url) and str(url).strip():
348
- st.markdown(f"[Open article]({url})")
349
 
350
  st.markdown("**More details**")
351
 
@@ -365,11 +432,18 @@ def main() -> None:
365
  "with filters for action categories, dates, sources, and search terms."
366
  )
367
 
 
 
 
 
368
  df = load_classified_articles()
369
  summary = load_daily_summary()
370
 
371
  if df.empty:
372
- st.warning("No classified articles found yet. Check whether the API is live and returning data.")
 
 
 
373
  return
374
 
375
  section = st.segmented_control(
@@ -379,7 +453,8 @@ def main() -> None:
379
  )
380
 
381
  if section == "Daily Summary":
382
- render_metrics(df, df)
 
383
  render_daily_summary(summary)
384
 
385
  elif section == "Articles":
 
19
  )
20
 
21
 
22
+ def ensure_columns(df: pd.DataFrame, columns: list[str]) -> pd.DataFrame:
23
+ df = df.copy()
24
+
25
+ for column in columns:
26
+ if column not in df.columns:
27
+ df[column] = None
28
+
29
+ return df
30
+
31
+
32
  @st.cache_data(ttl=300)
33
  def load_classified_articles() -> pd.DataFrame:
34
  try:
 
45
  if df.empty:
46
  return df
47
 
48
+ df = ensure_columns(
49
+ df,
50
+ [
51
+ "article_id",
52
+ "title",
53
+ "description",
54
+ "source",
55
+ "label",
56
+ "raw_label",
57
+ "url",
58
+ "published_at",
59
+ "classified_at",
60
+ ],
61
+ )
62
+
63
+ df["published_at"] = pd.to_datetime(
64
+ df["published_at"],
65
+ errors="coerce",
66
+ utc=True,
67
+ )
68
+ df["classified_at"] = pd.to_datetime(
69
+ df["classified_at"],
70
+ errors="coerce",
71
+ utc=True,
72
+ )
73
 
74
  df["published_date"] = df["published_at"].dt.date
75
  df["published_day"] = df["published_at"].dt.strftime("%Y-%m-%d")
 
101
  def normalize_summary_payload(summary: dict) -> dict:
102
  normalized = dict(summary)
103
 
104
+ nested_summary = summary.get("summary_json")
105
 
106
  if isinstance(nested_summary, str):
107
  try:
 
111
  except Exception:
112
  pass
113
 
114
+ elif isinstance(nested_summary, dict):
115
  normalized.update(nested_summary)
116
 
117
  normalized["executive_summary"] = (
 
141
  def apply_filters(df: pd.DataFrame) -> pd.DataFrame:
142
  st.sidebar.header("Filters")
143
 
144
+ label_options = sorted(df["label"].dropna().unique().tolist())
145
+ source_options = sorted(df["source"].dropna().unique().tolist())
146
 
147
  default_labels = [
148
  label
 
162
  default=[],
163
  )
164
 
165
+ min_date = df["published_date"].min() if not df.empty else None
166
+ max_date = df["published_date"].max() if not df.empty else None
167
 
168
  date_range = None
169
  if min_date and max_date:
 
218
 
219
  c1.metric("Articles", len(df))
220
  c2.metric("Shown", len(filtered_df))
221
+ c3.metric("Sources", df["source"].nunique())
222
+ c4.metric("Categories", df["label"].nunique())
223
 
224
 
225
  def render_bullet_list(items: list[str], empty_message: str) -> None:
 
231
  st.markdown(f"- {item}")
232
 
233
 
234
+ def render_daily_summary_source_basis(
235
+ df: pd.DataFrame,
236
+ summary: dict,
237
+ ) -> pd.DataFrame:
238
+ summary_date = summary.get("summary_date")
239
+
240
+ if summary_date and "published_day" in df:
241
+ summary_df = df[df["published_day"] == summary_date]
242
+ else:
243
+ summary_df = df
244
+
245
+ if summary_date:
246
+ st.caption(
247
+ f"Summary is based on {len(summary_df)} classified articles "
248
+ f"published on {summary_date}."
249
+ )
250
+ else:
251
+ st.caption(
252
+ f"Summary is based on {len(summary_df)} classified articles."
253
+ )
254
+
255
+ return summary_df
256
+
257
+
258
  def render_daily_summary(summary: dict) -> None:
259
  st.subheader("Daily AI Summary")
260
 
 
314
  article_id = story.get("article_id")
315
 
316
  if pd.notnull(published_at):
317
+ published_at = pd.to_datetime(
318
+ published_at,
319
+ errors="coerce",
320
+ utc=True,
321
+ )
322
 
323
  if pd.notnull(published_at):
324
  published_at = published_at.strftime("%Y-%m-%d %H:%M UTC")
 
346
  st.write(decision_relevance)
347
 
348
  if url:
349
+ st.link_button("Open article", url)
350
 
351
  if article_id:
352
  st.caption(f"Article ID: {article_id}")
 
377
  elif sort_option == "Oldest first":
378
  display_df = display_df.sort_values("published_at", ascending=True)
379
  elif sort_option == "Action category":
380
+ display_df = display_df.sort_values(
381
+ ["label", "published_at"],
382
+ ascending=[True, False],
383
+ )
384
  elif sort_option == "Source":
385
+ display_df = display_df.sort_values(
386
+ ["source", "published_at"],
387
+ ascending=[True, False],
388
+ )
389
 
390
  max_rows = st.slider("Number of articles to display", 5, 100, 20)
391
  display_df = display_df.head(max_rows)
 
412
 
413
  url = row.get("url")
414
  if pd.notnull(url) and str(url).strip():
415
+ st.link_button("Open article", str(url))
416
 
417
  st.markdown("**More details**")
418
 
 
432
  "with filters for action categories, dates, sources, and search terms."
433
  )
434
 
435
+ if st.sidebar.button("Refresh data"):
436
+ st.cache_data.clear()
437
+ st.rerun()
438
+
439
  df = load_classified_articles()
440
  summary = load_daily_summary()
441
 
442
  if df.empty:
443
+ st.warning(
444
+ "No classified articles found yet. "
445
+ "Check whether the API is live and returning data."
446
+ )
447
  return
448
 
449
  section = st.segmented_control(
 
453
  )
454
 
455
  if section == "Daily Summary":
456
+ summary_df = render_daily_summary_source_basis(df, summary)
457
+ render_metrics(df, summary_df)
458
  render_daily_summary(summary)
459
 
460
  elif section == "Articles":