feat(dashboard): add forward "what's next" signal + fix upcoming floor

The daily-file bill queue is a backward snapshot, so "upcoming" could only
resurface the latest past day (the 08-31 snapshot read as today's queue) and
there was no "what is scheduled next." leginfo bill status/history are
backward-only, so the genuine future-dated signal is the chambers' "upcoming
events" calendars.

- api.py: floor the "upcoming" view at *today* (not the DB max date) when no
  as_of is pinned; surface next_legislative_day + scheduled_events (next 7
  days) on /bills. Add _today_iso / _next_legislative_day / _scheduled_events,
  all degrading to a recess marker / [] (never 5xx).
- relational.py: schema v2 adds the scheduled_events table +
  store_scheduled_events (idempotent per-day refresh), load_scheduled_events,
  next_legislative_day.
- parsers.py: parse_events_page + classify_event_kind for the chamber events
  calendar; skip empty-title rows (decompose the <time> before the fallback).
- ingest.py + cli.py: --store-events lane (cache-first, recess-safe) that
  parses the calendar HTML into scheduled_events.
- sources.py + config.yaml: register assembly_events / senate_events
  (kind: events, empty_is_ok: true).
- run_ingest.sh: run the events lane after the daily-file lane as non-fatal
  (skippable via CALI_SCRAPER_SKIP_EVENTS=1); daily-file exit still gates cron.
- frontend: "Next legislative day" line + "What's next · next 7 days" band
  with honest recess empty states.
- DASHBOARD.md: document the forward signal + known limitations (leginfo
  backward-only gap, recess empty state, 08-31 snapshot is analysis-only).
- tests: 5 new API tests + 18 parse-events tests; 170 pass, 92% coverage.
This commit is contained in:
kfj001 committed 2026-09-06 19:50:02 -07:00
1 parent 1919d43bf0
commit 03eeb6bb62
16 files changed
+941 -70

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
<!DOCTYPE html><html lang="en" dir="ltr"><head><meta charset="utf-8" /><noscript><style>form.antibot > * { display: none !important; } form.antibot noscript { display: block !important; }</style></noscript><script async src="https://www.googletagmanager.com/gtag/js?id=G-4D0PPGX2BH"></script><script>window.dataLayer = window.dataLayer || [];function gtag(){dataLayer.push(arguments)};gtag("js", new Date());gtag("set", "developer_id.dMDhkMT", true);gtag("config", "G-4D0PPGX2BH", {"groups":"default","cookie_domain":"assembly.ca.gov","page_placeholder":"PLACEHOLDER_page_location","link_attribution":true});</script><style id="gutenberg-palette">:root { .has-primary-color{ color:#255752 } .has-primary-background-color{ background-color:#255752 } .has-primary-border-color{ border-color:#255752 } .has-text-color{ color:#292B29 } .has-text-background-color{ background-color:#292B29 } .has-text-border-color{ border-color:#292B29 } .has-link-color{ color:#01579F } .has-link-background-color{ background-color:#01579F } .has-link-border-color{ border-color:#01579F } .has-highlight-color{ color:#595959 } .has-highlight-background-color{ background-color:#595959 } .has-highlight-border-color{ border-color:#595959 } .has-light-color{ color:#a30c1f } .has-light-background-color{ background-color:#a30c1f } .has-light-border-color{ border-color:#a30c1f } }</style><style id="gutenberg-gradients">:root { .has-primary-to-secondary-gradient-background{ background:linear-gradient(135deg, #CEBAFF 0%, #FFC884 100%) } .has-primary-to-contrast-gradient-background{ background:linear-gradient(135deg, #CEBAFF 0%, #3556D1 100%) } .has-secondary-to-light-gradient-background{ background:linear-gradient(to right bottom, #FFC884, #FFFFFF) } }</style><style id="gutenberg-font-sizes">:root { .has-small-font-size{ font-size:14px } .has-normal-font-size{ font-size:16px } .has-large-font-size{ font-size:18px } .has-extra-large-font-size{ font-size:20px } .has-extra-extra-large-font-size{ font-size:24px } }</style><style id="gutenberg-spacing-variables">:root { --wp--preset--spacing--10:8px; --wp--preset--spacing--20:16px; --wp--preset--spacing--30:32px; --wp--preset--spacing--40:64px; --wp--preset--spacing--50:128px; --wp--preset--spacing--60:172px; }</style><style>/* @see https://github.com/aFarkas/lazysizes#broken-image-symbol */.js img.lazyload:not([src]) { visibility: hidden; }/* @see https://github.com/aFarkas/lazysizes#automatically-setting-the-sizes-attribute */.js img.lazyloaded[data-sizes=auto] { display: block; width: 100%; }/* Transition effect. */.js .lazyload, .js .lazyloading { opacity: 0; }.js .lazyloaded { opacity: 1; -webkit-transition: opacity 2000ms; transition: opacity 2000ms; }</style><meta name="MobileOptimized" content="width" /><meta name="HandheldFriendly" content="true" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><link rel="icon" href="/themes/custom/asm_internet_theme/favicon.ico" type="image/png" /><title>Events | California State Assembly</title><link rel="preload" as="font" href="/themes/custom/asm_internet_theme/assets/fonts/InstrumentSans%5Bwdth%2Cwght%5D.woff2" type="font/woff2" crossorigin="anonymous"><link rel="preload" as="image" type="image/svg+xml" href="/themes/custom/asm_internet_theme/assets/images/Seal.svg"><link rel="preconnect" href="https://webapi.assembly.ca.gov"><link rel="dns-prefetch" href="https://webapi.assembly.ca.gov"><link rel="stylesheet" media="all" href="/sites/assembly.ca.gov/files/css/css_ejSlPE3p5AQVnknFBVL5QBhPgeUULpNo9LmKiD81JM0.css?delta=0&amp;language=en&amp;theme=asm_internet_theme&amp;include=eJx1iVEKwzAMxS4U5iMZN3trszpxiN2F3n5Q2NfojxCSeOXSAqMhODZU0Eslesn7SPI_V7VFNDlk5I2lF5YjLFvtigDd9OSnByot4kifgul08VHteSjSFOf37ConBv3kqtPG7l0ySG0t7QsR4EXh" /><link rel="stylesheet" media="all" href="/sites/assembly.ca.gov/files/css/css_iwL7Sf3hxDGV2hXPq5CgiQnir6U0Y8VHL7HgNPcA6o0.css?delta=1&amp;language=en&amp;theme=asm_internet_theme&amp;include=eJx1iVEKwzAMxS4U5iMZN3trszpxiN2F3n5Q2NfojxCSeOXSAqMhODZU0Eslesn7SPI_V7VFNDlk5I2lF5YjLFvtigDd9OSnByot4kifgul08VHteSjSFOf37ConBv3kqtPG7l0ySG0t7QsR4EXh" /></head><body class="path-events page-events-upcoming"> <a href="#main-content" class="visually-hidden focusable skip-link"> Skip to main content </a><div class="dialog-off-canvas-main-canvas" data-off-canvas-main-canvas><div class="layout-container"><div class="top_bar"><div data-drupal-messages-fallback class="hidden"></div></div> <header class="header"><div class="header__inner"><div class="header__primary"><div class="header__branding"><div class="logo"> <a class="logo__link" href="/"
> <img class="logo__image" src="/themes/custom/asm_internet_theme/assets/images/Seal.svg" alt="California State Assembly Home" /> <span class="logo--title"> California State <br> Assembly </span> </a></div></div><div class="header__menu"> <nav role="navigation" aria-labelledby="block-asm-internet-theme-main-menu-menu" id="block-asm-internet-theme-main-menu" class="nav--primary"><h2 class="visually-hidden" id="block-asm-internet-theme-main-menu-menu">Main navigation</h2><div class="nav-wrapper" > <nav id="main-nav" class="main-nav"><div id="mobile-menu--close" class="hide"> <svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> <g id="ICO - Close"> <g id="Vector"> <path fill-rule="evenodd" clip-rule="evenodd" d="M25.8839 6.11612C26.372 6.60427 26.372 7.39573 25.8839 7.88388L7.88388 25.8839C7.39573 26.372 6.60427 26.372 6.11612 25.8839C5.62796 25.3957 5.62796 24.6043 6.11612 24.1161L24.1161 6.11612C24.6043 5.62796 25.3957 5.62796 25.8839 6.11612Z" fill="#292B29"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M6.11612 6.11612C6.60427 5.62796 7.39573 5.62796 7.88388 6.11612L25.8839 24.1161C26.372 24.6043 26.372 25.3957 25.8839 25.8839C25.3957 26.372 24.6043 26.372 24.1161 25.8839L6.11612 7.88388C5.62796 7.39573 5.62796 6.60427 6.11612 6.11612Z" fill="#292B29"/> </g> </g> </svg></div><ul class="main-menu"><li class="main-menu__item main-menu__item--og-child main-menu__item--with-sub"> <a href="/assemblymembers" class="main-menu__link main-menu__link--og-child main-menu__link--with-sub" data-drupal-link-system-path="node/86244">Members</a> <span class="expand-sub"></span><ul class="main-menu main-menu--sub main-menu--sub-1"><li class="main-menu__item main-menu__item--sub main-menu__item--sub-1"> <a href="/assemblymembers" class="main-menu__link main-menu__link--sub main-menu__link--sub-1" data-drupal-link-system-path="node/86244">Member Roster</a></li><li class="main-menu__item main-menu__item--sub main-menu__item--sub-1"> <a href="/assemblymembers/leadership" title="Leadership" class="main-menu__link main-menu__link--sub main-menu__link--sub-1" data-drupal-link-system-path="node/8">Leadership</a></li><li class="main-menu__item main-menu__item--sub main-menu__item--sub-1"> <a href="/assemblymembers/member-seating-chart" class="main-menu__link main-menu__link--sub main-menu__link--sub-1" data-drupal-link-system-path="node/86246">Member Seating Chart</a></li><li class="main-menu__item main-menu__item--sub main-menu__item--sub-1"> <a href="/assemblymembers/find-my-rep" class="main-menu__link main-menu__link--sub main-menu__link--sub-1" data-drupal-link-system-path="assemblymembers/find-my-rep">Find Your Representative</a></li><li class="main-menu__item main-menu__item--sub main-menu__item--sub-1"> <a href="/assemblymembers/10th-o-street-offices" class="main-menu__link main-menu__link--sub main-menu__link--sub-1" data-drupal-link-system-path="node/86248">10th &amp; O Street Offices</a></li></ul></li><li class="main-menu__item main-menu__item--og-child main-menu__item--with-sub"> <a href="/committees" class="main-menu__link main-menu__link--og-child main-menu__link--with-sub" data-drupal-link-system-path="node/85503">Committees</a> <span class="expand-sub"></span><ul class="main-menu main-menu--sub main-menu--sub-1"><li class="main-menu__item main-menu__item--sub main-menu__item--sub-1"> <a href="/committees?sessionnumber=0" class="main-menu__link main-menu__link--sub main-menu__link--sub-1" data-drupal-link-query="{&quot;sessionnumber&quot;:&quot;0&quot;}" data-drupal-link-system-path="node/85503">Regular Session Committees</a></li><li class="main-menu__item main-menu__item--sub main-menu__item--sub-1"> <a href="/committees?sessionnumber=1" class="main-menu__link main-menu__link--sub main-menu__link--sub-1" data-drupal-link-query="{&quot;sessionnumber&quot;:&quot;1&quot;}" data-drupal-link-system-path="node/85503">First Extraordinary Committees</a></li></ul></li><li class="main-menu__item main-menu__item--og-child main-menu__item--with-sub"> <a href="/schedules-publications" title="Schedules &amp; Publications" class="main-menu__link main-menu__link--og-child main-menu__link--with-sub" data-drupal-link-system-path="node/5936">Schedules &amp; Publications</a> <span class="expand-sub"></span><ul class="main-menu main-menu--sub main-menu--sub-1"><li class="main-menu__item main-menu__item--sub main-menu__item--sub-1"> <a href="/schedules-publications" class="main-menu__link main-menu__link--sub main-menu__link--sub-1" data-drupal-link-system-path="node/5936">Overview</a></li><li class="main-menu__item main-menu__item--sub main-menu__item--sub-1"> <a href="/schedules-publications/daily-file" class="main-menu__link main-menu__link--sub main-menu__link--sub-1" data-drupal-link-system-path="node/86247">Daily File</a></li><li class="main-menu__item main-menu__item--sub main-menu__item--sub-1"> <a href="/schedules-publications/salaries-and-expenditures" title="Salaries &amp; ExpendLine truncated
> <img class="logo__image" src="/themes/custom/asm_internet_theme/assets/images/Seal.svg" alt="California State Assembly Home" /> <span class="logo--title"> California State <br> Assembly </span> </a></div></div></div></footer><div class="bottom_bar"><div class="bottom_bar__inner"><div id="block-asm-internet-theme-wascopyrightblock" class="block"><div class="block-container"><div><div class="copyright-container"><span class="copyright">Copyright © 2026 State of California</span></div></div></div></div><div id="block-asm-internet-theme-subfooter" class="block"><div class="block-container"><div><div class="copyright-container"><ul><li><a href="/sitemap">Site Map</a></li><li><a href="/accessibility">Accessibility</a></li><li><a href="/privacy-and-conditions-use-policy">Terms &amp; Privacy</a></li><li class="btn-vote"><a href="https://registertovote.ca.gov/">Register to Vote</a></li></ul></div></div></div></div></div></div></div></div><div class="bg-overlay"></div><script type="application/json" data-drupal-selector="drupal-settings-json">{"path":{"baseUrl":"\/","pathPrefix":"","currentPath":"events\/upcoming","currentPathIsAdmin":false,"isFront":false,"currentLanguage":"en"},"pluralDelimiter":"\u0003","suppressDeprecationErrors":true,"ajaxPageState":{"libraries":"eJx1kE1uxSAMhC9Ey2W6R5Mw4Vk1EGF4ET1981J1U6WbWcxnj39gOUSIzk2UgU-Wbh6JJSLkGqEOZ4WUzlbYQ38w00duGNrv0E_CHUlal_u4DClvmWXcwrqcm300zItmRoG_NOyKyXY2SHfMQ022-Tsm1ZrOg1Cgs8tq_q_hFF_Tv8QZ0dZHwC4Bo9e15l3Z6f_xnU3rzH6B0T2Fh_lL389_DaU7YOGo7dN2rPRak5RvonyN-w","theme":"asm_internet_theme","theme_token":null},"ajaxTrustedUrl":{"\/search":true},"google_analytics":{"account":"G-4D0PPGX2BH","trackOutbound":true,"trackMailto":true,"trackTel":true,"trackDownload":true,"trackDownloadExtensions":"7z|aac|arc|arj|asf|asx|avi|bin|csv|doc(x|m)?|dot(x|m)?|exe|flv|gif|gz|gzip|hqx|jar|jpe?g|js|mp(2|3|4|e?g)|mov(ie)?|msi|msp|pdf|phps|png|ppt(x|m)?|pot(x|m)?|pps(x|m)?|ppam|sld(x|m)?|thmx|qtm?|ra(m|r)?|sea|sit|tar|tgz|torrent|txt|wav|wma|wmv|wpd|xls(x|m|b)?|xlt(x|m)|xlam|xml|z|zip","trackDomainMode":1},"lazy":{"lazysizes":{"lazyClass":"lazyload","loadedClass":"lazyloaded","loadingClass":"lazyloading","preloadClass":"lazypreload","errorClass":"lazyerror","autosizesClass":"lazyautosizes","srcAttr":"data-src","srcsetAttr":"data-srcset","sizesAttr":"data-sizes","minSize":40,"customMedia":[],"init":true,"expFactor":1.5,"hFac":0.8,"loadMode":1,"loadHidden":true,"ricTimeout":0,"throttleDelay":125,"plugins":[]},"placeholderSrc":"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==","preferNative":false,"minified":true,"libraryPath":"\/libraries\/lazysizes"},"was_jwplayer":{"config":{"key":"yh5RZuPQQH3OaPRB\/a\/geG6f46hHX1+s7SszOHZIu3T0qSGJ","clean_source":0,"thumbnails":0},"defaults":{"volume":"","height":"","width":"100%","aspectratio":"16:9","poster":{"live":"https:\/\/www.assembly.ca.gov\/sites\/assembly.ca.gov\/files\/jwplayer\/walkthrough_cover_02_1.jpg","archive":"https:\/\/www.assembly.ca.gov\/sites\/assembly.ca.gov\/files\/jwplayer\/walkthrough_cover_02_2.jpg"}},"files":{"images":{"arrow_white":"https:\/\/www.assembly.ca.gov\/modules\/custom\/was_jwplayer\/css\/arrow-white.png"}}},"search_api_autocomplete":{"search":{"auto_submit":true}},"was_workspace":{"toggled_elements":".tabs__nav, .tabs, div:not(.view-offices-menu) .contextual, .edit-mode, .add-docs-form, .contextual, .view-display-id-block_class_session .views-field-edit-node, .unpublished-menu-item, #environment-indicator","edit_mode_key_toggle":"`","toggled_parent_elements":".node--unpublished.node--view-mode-teaser, .unpublished-menu-item","selector_items":[]},"user":{"uid":0,"permissionsHash":"06c739f6dcef12a85d882bf46cf3cf32d176c4d3b071849cbe28f89ad246822c"}}</script><script src="/sites/assembly.ca.gov/files/js/js_cJF17uhHYne82fqpJY-WNnbu2QR5m7D3XRgV96CJpmk.js?scope=footer&amp;delta=0&amp;language=en&amp;theme=asm_internet_theme&amp;include=eJx1jsEOwyAMQ3-IiZ_ZHXngtpECVJBOYl8_1t2m7uKDn-0EPYcE0bGIMvDJYt1jZUkIuSaow0xIMbZCC7Yx0ycuONSu0HfhimRIuWWW4xLWx7x_bxgnzUwCf2rYFYNtFsTcWus630SBDpPY_a_hFK_hP-I60eIWsEvAYTXWvCuN_o__BrePaYc"></script></body></html>