ramanna commited on
Commit
0984ab4
Β·
verified Β·
1 Parent(s): 15a2842

Upload streamlit_app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. streamlit_app.py +174 -2
streamlit_app.py CHANGED
@@ -566,7 +566,7 @@ if not st.session_state.get("_hf_downloaded"):
566
  # ── Data loading ───────────────────────────────────────────────────────────
567
  from utils.data_loader import (
568
  load_bills, load_summaries, load_suggested_questions,
569
- load_reports, load_newsletters, get_last_updated,
570
  get_summary, get_suggested_questions, get_report,
571
  CHANGES_DIR,
572
  )
@@ -575,6 +575,7 @@ df = load_bills()
575
  summaries = load_summaries()
576
  questions_cache = load_suggested_questions()
577
  reports_cache = load_reports()
 
578
 
579
 
580
  if df.empty:
@@ -751,7 +752,7 @@ with st.sidebar:
751
  st.button("βœ• Clear all filters", on_click=_clear_filters, width="stretch")
752
 
753
  # ── Tabs ───────────────────────────────────────────────────────────────────
754
- tab_bills, tab_insights, tab_ai, tab_updates = st.tabs(["Bills", "Insights", "AI Tools", "Newsletter"])
755
 
756
  # ══════════════════════════════════════════════════════════════════════════
757
  # TAB 1 β€” BILLS
@@ -883,6 +884,177 @@ with tab_bills:
883
  st.download_button("⬇ CSV", tbl.to_csv(index=False), "bills.csv", "text/csv")
884
 
885
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
886
  # ══════════════════════════════════════════════════════════════════════════
887
  # TAB 2 β€” INSIGHTS (Charts)
888
  # ══════════════════════════════════════════════════════════════════════════
 
566
  # ── Data loading ───────────────────────────────────────────────────────────
567
  from utils.data_loader import (
568
  load_bills, load_summaries, load_suggested_questions,
569
+ load_reports, load_newsletters, load_calendar, get_last_updated,
570
  get_summary, get_suggested_questions, get_report,
571
  CHANGES_DIR,
572
  )
 
575
  summaries = load_summaries()
576
  questions_cache = load_suggested_questions()
577
  reports_cache = load_reports()
578
+ calendar_events = load_calendar()
579
 
580
 
581
  if df.empty:
 
752
  st.button("βœ• Clear all filters", on_click=_clear_filters, width="stretch")
753
 
754
  # ── Tabs ───────────────────────────────────────────────────────────────────
755
+ tab_bills, tab_calendar, tab_insights, tab_ai, tab_updates = st.tabs(["Bills", "Calendar", "Insights", "AI Tools", "Newsletter"])
756
 
757
  # ══════════════════════════════════════════════════════════════════════════
758
  # TAB 1 β€” BILLS
 
884
  st.download_button("⬇ CSV", tbl.to_csv(index=False), "bills.csv", "text/csv")
885
 
886
 
887
+ # ══════════════════════════════════════════════════════════════════════════
888
+ # TAB β€” CALENDAR (Bills to Watch)
889
+ # ══════════════════════════════════════════════════════════════════════════
890
+ with tab_calendar:
891
+ import calendar as _pycal
892
+ from collections import defaultdict
893
+
894
+ _cal_colors = {
895
+ "hearing_scheduled": "#E8A317",
896
+ "committee_referral": "#5B8DEF",
897
+ "committee_passed": "#50C878",
898
+ "floor_vote": "#CF4520",
899
+ "sent_to_governor": "#8B6914",
900
+ }
901
+ _cal_dots = {
902
+ "hearing_scheduled": "●",
903
+ "committee_referral": "●",
904
+ "committee_passed": "●",
905
+ "floor_vote": "●",
906
+ "sent_to_governor": "●",
907
+ }
908
+
909
+ if calendar_events:
910
+ # Filter by milestone type
911
+ all_types = sorted(set(e.get("event_label", e.get("event_type", "")) for e in calendar_events))
912
+ selected_types = st.multiselect(
913
+ "Filter by milestone type", all_types, default=all_types, key="cal_type_filter"
914
+ )
915
+ cal_filtered = [e for e in calendar_events
916
+ if e.get("event_label", e.get("event_type", "")) in selected_types]
917
+
918
+ if cal_filtered:
919
+ # Determine which month to show (most recent event)
920
+ latest_date = cal_filtered[0].get("event_date", "")
921
+ if latest_date:
922
+ cal_year = int(latest_date[:4])
923
+ cal_month = int(latest_date[5:7])
924
+ else:
925
+ cal_year = datetime.now().year
926
+ cal_month = datetime.now().month
927
+
928
+ # Month navigation
929
+ if "cal_month_offset" not in st.session_state:
930
+ st.session_state.cal_month_offset = 0
931
+
932
+ prev_col, title_col, next_col = st.columns([1, 5, 1])
933
+ with prev_col:
934
+ if st.button("← Prev", key="cal_prev", use_container_width=True):
935
+ st.session_state.cal_month_offset -= 1
936
+ st.rerun()
937
+ with next_col:
938
+ if st.button("Next β†’", key="cal_next", use_container_width=True):
939
+ st.session_state.cal_month_offset += 1
940
+ st.rerun()
941
+
942
+ # Apply offset
943
+ display_month = cal_month + st.session_state.cal_month_offset
944
+ display_year = cal_year
945
+ while display_month < 1:
946
+ display_month += 12
947
+ display_year -= 1
948
+ while display_month > 12:
949
+ display_month -= 12
950
+ display_year += 1
951
+
952
+ with title_col:
953
+ st.markdown(
954
+ f"<h3 style='text-align:center;margin:0;padding:0.3rem 0;'>"
955
+ f"{_pycal.month_name[display_month]} {display_year}</h3>",
956
+ unsafe_allow_html=True,
957
+ )
958
+
959
+ # Group events by date
960
+ events_by_date = defaultdict(list)
961
+ for e in cal_filtered:
962
+ events_by_date[e.get("event_date", "")].append(e)
963
+
964
+ # Build HTML calendar grid
965
+ cal_obj = _pycal.Calendar(firstweekday=6) # Sunday first
966
+ month_days = cal_obj.monthdayscalendar(display_year, display_month)
967
+
968
+ cal_html = '<table style="width:100%;border-collapse:collapse;font-family:Inter,sans-serif;table-layout:fixed;">'
969
+ # Header row
970
+ cal_html += '<tr>'
971
+ for day_name in ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]:
972
+ cal_html += f'<th style="padding:8px 4px;text-align:center;font-size:0.8rem;color:#64748B;border-bottom:2px solid #E2E8F0;">{day_name}</th>'
973
+ cal_html += '</tr>'
974
+
975
+ today_str = datetime.now().strftime("%Y-%m-%d")
976
+
977
+ for week in month_days:
978
+ cal_html += '<tr>'
979
+ for day in week:
980
+ if day == 0:
981
+ cal_html += '<td style="padding:4px;border:1px solid #F1F5F9;min-height:80px;height:80px;vertical-align:top;background:#FAFAFA;"></td>'
982
+ else:
983
+ date_str = f"{display_year}-{display_month:02d}-{day:02d}"
984
+ is_today = date_str == today_str
985
+ day_events = events_by_date.get(date_str, [])
986
+
987
+ bg = "#FFF9E6" if day_events else "#FFFFFF"
988
+ border = "2px solid #CFB991" if is_today else "1px solid #F1F5F9"
989
+ day_num_style = "font-weight:700;color:#CFB991;" if is_today else "color:#0F172A;"
990
+
991
+ cal_html += f'<td style="padding:4px 6px;border:{border};min-height:80px;height:80px;vertical-align:top;background:{bg};">'
992
+ cal_html += f'<div style="font-size:0.75rem;{day_num_style}">{day}</div>'
993
+
994
+ for ev in day_events[:3]:
995
+ color = _cal_colors.get(ev.get("event_type", ""), "#CFB991")
996
+ label = f"{ev.get('state', '')} {ev.get('bill_number', '')}"
997
+ url = ev.get("bill_url", "")
998
+ milestone = ev.get("event_label", "")
999
+ if url:
1000
+ cal_html += (
1001
+ f'<a href="{url}" target="_blank" style="display:block;font-size:0.65rem;'
1002
+ f'padding:1px 3px;margin:1px 0;border-radius:3px;background:{color};'
1003
+ f'color:#fff;text-decoration:none;overflow:hidden;white-space:nowrap;'
1004
+ f'text-overflow:ellipsis;" title="{milestone}: {ev.get("event_description", "")}">'
1005
+ f'{label}</a>'
1006
+ )
1007
+ else:
1008
+ cal_html += (
1009
+ f'<div style="font-size:0.65rem;padding:1px 3px;margin:1px 0;'
1010
+ f'border-radius:3px;background:{color};color:#fff;overflow:hidden;'
1011
+ f'white-space:nowrap;text-overflow:ellipsis;" '
1012
+ f'title="{milestone}: {ev.get("event_description", "")}">'
1013
+ f'{label}</div>'
1014
+ )
1015
+ if len(day_events) > 3:
1016
+ cal_html += f'<div style="font-size:0.6rem;color:#64748B;">+{len(day_events) - 3} more</div>'
1017
+
1018
+ cal_html += '</td>'
1019
+ cal_html += '</tr>'
1020
+
1021
+ cal_html += '</table>'
1022
+
1023
+ # Legend
1024
+ legend = '<div style="display:flex;gap:1rem;flex-wrap:wrap;margin-top:0.75rem;margin-bottom:0.5rem;">'
1025
+ for etype, color in _cal_colors.items():
1026
+ label = etype.replace("_", " ").title()
1027
+ legend += (
1028
+ f'<span style="font-size:0.75rem;color:#64748B;">'
1029
+ f'<span style="display:inline-block;width:10px;height:10px;border-radius:2px;'
1030
+ f'background:{color};margin-right:4px;vertical-align:middle;"></span>{label}</span>'
1031
+ )
1032
+ legend += '</div>'
1033
+
1034
+ st.markdown(cal_html + legend, unsafe_allow_html=True)
1035
+
1036
+ # Event list below calendar
1037
+ st.markdown("---")
1038
+ for e in cal_filtered:
1039
+ color = _cal_colors.get(e.get("event_type", ""), "#CFB991")
1040
+ url = e.get("bill_url", "")
1041
+ link_html = f' β€” <a href="{url}" target="_blank" style="color:#8B6914;">View on LegiScan</a>' if url else ""
1042
+ st.markdown(
1043
+ f'<div style="padding:0.4rem 0;border-bottom:1px solid #F1F5F9;">'
1044
+ f'<span style="display:inline-block;width:8px;height:8px;border-radius:2px;'
1045
+ f'background:{color};margin-right:6px;vertical-align:middle;"></span>'
1046
+ f'<strong>{e.get("event_date", "")}</strong> &nbsp; '
1047
+ f'<span style="color:#0F172A;font-weight:600;">{e.get("state", "")} {e.get("bill_number", "")}</span> &nbsp; '
1048
+ f'<span style="color:#64748B;">{e.get("event_description", "")}</span>'
1049
+ f'{link_html}</div>',
1050
+ unsafe_allow_html=True,
1051
+ )
1052
+ else:
1053
+ st.info("No events match the selected filters.")
1054
+ else:
1055
+ st.info("No recent legislative milestones available. Calendar data will populate after the next pipeline run.")
1056
+
1057
+
1058
  # ══════════════════════════════════════════════════════════════════════════
1059
  # TAB 2 β€” INSIGHTS (Charts)
1060
  # ══════════════════════════════════════════════════════════════════════════