Reconcile a week of local work with origin's iCloud CalDAV fix (PR #39) - #40
Merged
Conversation
.planning/ is already gitignored; ROADMAP.md was a historical tracked exception. Untracking it so it stops showing as locally-modified — consistent with commit_docs: false, same as every other planning doc. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same reasoning as ROADMAP.md: .planning/ has been gitignored for a while, but 40 files predating that rule stayed tracked (MILESTONES.md, RETROSPECTIVE.md, v3.0 milestone docs, and SUMMARY.md files from phases 13-39). Untracking brings them in line with commit_docs: false and the rest of .planning/ — files remain on disk, only removed from the git index. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Create docs/demo/index.html: semantic single-page shell (hero, map, readout, HA sensor card, calendar) with all 26 DOM contract ids for app.js - Pin Leaflet 1.9.4 CSS/JS via unpkg CDN with verified SRI + crossorigin (T-41-03) - Wire styles.css and deferred app.js (app.js created in 41-04) - Add docs/.nojekyll so GitHub Pages serves files without Jekyll (Pitfall 6) - No inline handlers or business logic; behavior lives in 41-04
- reprojection bounds (EPSG:2263 -> WGS84, [lon,lat] NYC bbox) - weekly-pattern-not-absolute-datetime guard - mock-sensor attribute keys subset of real HA sensor keys - dataset completeness + status across schedule_found/resolution_failed/no_match
- Implement UI-SPEC palette (--bg/--surface/--border/--fg/--muted/--accent/ --warning/--positive) and 8-point spacing scale as CSS custom properties - Four type roles (Display/Heading/Body/Label-mono), two weights, system-ui + mono - Explicit #map min-height 320/360/480px (mobile/tablet/desktop) so Leaflet never collapses to 0px (Pitfall 7) - Responsive: single-column default, two-column ~60/40 sticky panel at >=1024px - State chips (positive/warning/neutral) and accent reserved to CTA, active profile, next-move calendar cell, and links only - Calendar highlight fade-in + scale + ring-pulse, all disabled under prefers-reduced-motion (static highlight only) - >=44px touch targets on profile/mode/copy buttons
- reproject_wkt_to_wgs84: EPSG:2263 -> WGS84 [lon,lat] GeoJSON order - build_sensor_shapes: mock next_move + resolved_street, subset of real HA keys - build_point_entry: explicit dict build, weekly pattern (no absolute datetime) - dump_point: per-point fail-soft over 5 resolver exceptions - main: DEMO_POINTS -> demo.json + demo-segments.geojson; never serializes token
…ndar) - Leaflet map wiring: OSM tiles, keyboard-focusable accent pins, segment overlay, invalidateSize (Pitfall 7) - Loads committed demo.json/demo-segments.geojson via response.json() only; reveals #error-state on fetch/parse failure - selectPoint resolves a block; renderReadout + renderHaCard render ALL dataset text via textContent/DOM node creation (T-41-05, no innerHTML/eval) - computeNextMove mirrors find_next_window: 8-day lookahead, America/New_York wall clock, JS Sun=0 -> Python Mon=0 conversion - renderCalendar highlights the client-recomputed next-move day (is-next / is-today), CSS-driven animation honors prefers-reduced-motion - Profile radiogroup re-renders all surfaces with no network call - Demo/full mode toggle; FULL_RESOLVER_ENDPOINT null by default -> inert full-resolver branch with graceful not-configured message - copy-YAML via navigator.clipboard
- docs/demo/data/demo.json: 7 hand-picked NYC points resolved via resolve_asp against the local spatial index + live SODA (generation_date 2026-07-28) - docs/demo/data/demo-segments.geojson: matched segment geometry reprojected EPSG:2263 -> WGS84, NYC-bounded FeatureCollection - Weekly schedule pattern stored (no absolute next-move date); mock HA sensor shapes are a subset of real sensor attribute keys - Non-schedule sensor states present (NoSegmentFoundError / resolution_failed) - No NYC app token/secret serialized (T-41-01 grep clean) Deviation: Prospect Pl (40.677629,-73.968527) resolves to NORTH, not South as the plan asserted. Verified via authoritative NYC curb calibration (segment 39223: center_offset_c=-3.04 ft, calibrated=True, curb_width 33.1 ft); point signed_offset=+9.16 ft, firmly North of the calibrated centre. The plan's 'South' ground truth is a factual error; no calibrated index flips it.
- New .github/workflows/pages.yml with build + deploy jobs - Runs on GitHub-hosted ubuntu-latest (Pages deploy needs GitHub runners) - Publishes committed docs/ tree as-is; no in-CI precompute (Pitfall 3) - Least-privilege permissions (pages: write, id-token: write, contents: read) - Pinned actions: checkout@v4, configure-pages@v5, upload-pages-artifact@v3, deploy-pages@v4
- Add Live Demo section + Table-of-Contents entry - Explain the static docs/demo/ page (map click -> rule, next move, HA sensors, calendar) - Note the committed dated snapshot (2026-07-28) and that it is not live - Give the exact regenerate command and index build/download options - Document local http.server run and GitHub Pages hosting paths
…w weekly data Code review found 6 of 7 demo points hard-failed (NoSegmentFoundError/ resolution_failed), including the "Car B" profile toggle target — leaving only Prospect Pl functional. Replaced the speculative coordinates with verified-resolving real NYC points across 4 boroughs (Williamsburg, Astoria, Bronx Grand Concourse, Staten Island), plus a genuine currently-active cleaning window (Oriental Blvd) found via targeted probing. outside_coverage is kept as an intentional failure (by design, outside NYC bounds). Also fixes build_point_entry() to populate weekly/summary for the ASPActiveNow status (previously only ScheduleFound was handled, so any active-now point would have rendered an empty calendar), and adds a build-time check that every DEMO_PROFILES target actually resolved before writing output — the root-cause enabler that let the broken data ship silently in the first place. A genuine "no_asp" (confirmed no restrictions) point was not found despite ~2,900 probes across 4 methods, including bypassing the confidence gate entirely — this looks structural (the live SODA endpoint may not return NoASPSigns in practice) rather than a sampling gap. Documented as a known limitation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…w sensors build_sensor_shapes()/_cleaning_day_names() had the same ScheduleFound-only gap as the previous build_point_entry() fix (0629ee0) — the mock HA sensor's cleaning_days and schedule_summary attributes were silently dropped for any point in the ASPActiveNow status, even though weekly/summary were correctly populated at the top level. Extends both to also handle ASPActiveNow. Adds test_asp_active_now_populates_weekly, a regression test for both gaps. Also renames the oriental_blvd demo point key: the underlying live cleaning window closed between generation runs and its committed status flipped to schedule_found, so "_active_now" in the key name was no longer accurate — an expected consequence of asp_active_now being a point-in-time status baked into a dated snapshot, not a bug (the weekly-pattern-derived calendar display recomputes correctly client-side regardless of which status label was frozen at generation time). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- SRI-pinned Leaflet 1.9.4 CSS+JS copied verbatim from docs/demo - coverage-QA hero copy (not the demo's sales hero, D-15) - #map region + always-visible labeled #legend (four tiers, hue + text label, D-16/Prohibition 3) - four AND-composable filters: borough, tier, SODA level (0/1/2/3, L4 folded into L3 per D-18), street search (R4/D-07) - #export-geojson control (R5), #no-results (R4) and #error-state (R2) regions, #data-freshness footer node (D-17) - no date/sign_type filter (D-08); docs/demo/ untouched (R6)
- RED scaffold for the coverage-dataset dumper's deterministic core - test_grouping_key_and_side_derivation: bearing-derived sides + normalize_to_soda canonical group key - test_tier_boundary_partition: half-open [0,1] partition into high/medium/low/unresolved + confidence_for_level - fails with ModuleNotFoundError until scripts.build_coverage_dataset lands (GREEN in task 2)
- reuse docs/demo design tokens, reset/base, and typography roles verbatim (D-15 polish parity) - add red->green tier color scale: high=--positive, medium teal, low=--warning, unresolved red #e5484d (D-09) - document per-tier marker radius convention (unresolved largest -> high smallest) as 42-04's third colorblind channel - style always-visible labeled legend, four-filter panel (44px touch targets), export button, no-results + error states - dark theme retained; docs/demo/styles.css untouched (R6)
- derive_segment_sides: bearing-derived {N,S}/{E,W} candidate curbs (D-02, geometry not has_asp flags)
- group_key: (normalize_to_soda(street), side) canonical dedup key (D-01)
- confidence_for_level + CONFIDENCE_BY_LEVEL: SODA level -> confidence (D-18, 1/2/3/0 -> .90/.66/.40/.00)
- tier_for_confidence + TIER_BOUNDS: one documented half-open [0,1] partition (0.33->low, 1.0->high)
- reproject_wkt_to_wgs84, segment_midpoint_wgs84 (single-midpoint, Pitfall 5), _borough_name, lazy _load_segments
- main() stub raises NotImplementedError (SODA resolve pipeline lands in 42-02); no compute_confidence/resolve_asp import (Pitfall 2)
…oring + render - Add docs/explorer/app.js plain-ES controller (use strict, textContent-only) - tierForConfidence mirrors 42-01 half-open TIER_BOUNDS (0.33/0.50/0.75 boundaries) - colorForTier (hue) + radiusForTier (colorblind size channel, unresolved largest) - initMap uses preferCanvas:true + one shared L.canvas() renderer, citywide view - loadDataset shows visible error-state on failure (never a blank map, R2) - Stamp generation_date freshness; export tierForConfidence/colorForTier/radiusForTier
…tate - Add buildPopup(point) returning a DOM node (textContent/createElement only) - Shows street + cross streets, confidence + shared tier label, SODA level, schedule summary + weekly cleaning times (schedule states only) - Explicit 'unresolved'/'no ASP sign' copy for no-match states; never a confirmed-clear reading for a coverage gap (Prohibition 2 / T-42-04) - Street View + FreeNYC link-outs, both target=_blank rel=noopener (T-42-06); NYC DOT link dropped (no stable URL, D-05) - Wire buildPopup into renderMarkers via lazy bindPopup; export buildPopup
…no-match R1 tests - test_query_count_is_grouped: one SODA fetch per distinct (street,side) group - test_every_segment_has_entry: exactly one entry per input segment_id - test_zero_record_group_no_match: empty group yields explicit no_match/lv0 entry - add in-memory _seg fixture + call-recording _StubClient (no network, no token)
- Add applyFilters(): AND logic over borough/tier/SODA-level/street-search on the plain points array; redraws the filtered subset onto the shared canvas layer group; toggles the #no-results state at zero results (R4) - Tier filter reuses the shared tierForConfidence (single rule with coloring) - Street search is case-insensitive substring on full_street_name (st) - Add pure buildFeatureCollection (features:[] when empty) + exportGeoJSON that returns the FC and, in-browser, downloads via Blob (application/geo+json) (R5) - Wire filter events + export button; initial render via applyFilters - Export applyFilters/buildFeatureCollection/exportGeoJSON for node tests
- resolve_group: one broad build_on_street_query/fetch_signs per (street,side) group, fail-soft to [] on error (Pitfall 4) - cross_streets_match wraps signs._cross_streets_match (variant+swap+empty guard) - resolve_side assigns soda_level by match precision (0/1/2/3) and runs the materialize_cached_records -> compute_schedule pipeline (no resolve_asp) - build_coverage dedups to one query per distinct group, picks worst-case side per segment (D-13), emits one entry per segment (no omission) - build_segment_entry emits the canonical compact schema keys
Correctness: demo page no longer mislabels an actively-running ASP window as next week; unparseable-sign blocks no longer masquerade as high-confidence matches; a representative-street selection bug that could silently no-match whole street groups; a fail-soft try/except gap that could abort the whole build; a missing borocode no longer serializes as the literal string "None"; README snapshot date synced to the committed demo.json. Cleanup/perf: hasSchedule/DAY_ABBR/attribute-row-builder unified into docs/common.js instead of duplicated between demo and explorer; name_variants no longer recomputed per side; build_demo_dataset reuses the resolver's already-loaded SpatialIndex instead of re-parsing segments.json; the semaphore-bounded-gather idiom factored into dataset_common.bounded_gather so both dumpers share one implementation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
Fixes 12 verified findings from an xhigh-effort review: demo sensors now gate location/schedule attributes behind the same isinstance check the real HA integration uses, cross_streets no longer garbles on empty street names, all_unparseable status copy and confidence no longer misrepresent parse failures as clean/absent records, computeNextMove() derives "active now" from the visitor's live clock instead of a frozen build-time flag, the --segments dumper override reuses the shared loader, the README quickstart command no longer 404s, and duplicated CSS/JS between demo and explorer moved into common.css/common.js. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
Mislabeled sensor states (all_unparseable, resolution_failed) fixed in docs/demo/app.js; --points override in build_demo_dataset.py no longer broken by a hardcoded self-check; shared CSS deduplicated into common.css; and build_coverage_dataset.py's confidence/street-key duplication removed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
… weekly pattern - Add required weekly_schedule: WeeklySchedule field to ASPActiveNow, mirroring ScheduleFound - Populate it from merged_schedule in compute_schedule() (same source as summary) - Update every ASPActiveNow constructor (production + test fixtures) to pass it - Re-vendor custom_components copy via sync_vendored.py - Add regression test proving the full weekly schedule survives on ASPActiveNow Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
- build_demo_dataset: build_point_entry + _cleaning_day_names now iterate the full weekly_schedule.windows for ASPActiveNow (was single active_window) - build_coverage_dataset: _summary_and_weekly iterates all windows for ASPActiveNow - Correct test_asp_active_now_populates_weekly to assert Monday AND Thursday - Add coverage regression test proving multi-day ASPActiveNow serialization Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
- soda_level 2 was structurally unreachable: _exact_cross_match compared already-normalized forms, so a record matched via the normalized group index always passed the exact check too. Compare raw literal forms instead so abbreviation-only matches correctly fall through to level 2. - build_coverage_dataset.py's segment loader ignored GPS2ASP_INDEX_DIR, unlike the demo dumper's SpatialIndex path — could silently read a different/stale index. dataset_common.py now honors the env var. - The "never omit a segment" self-check compared against the already-filtered segment count, so it could never catch a segment the loader itself silently dropped. Compare against the raw pre-filter count. - Duplicate "key"/"profile" values in a hand-edited --points file were silently collapsed by dict construction, dropping data with no warning. Fail loud instead. - The demo page mislabeled transient build-time failures (SODAAPIError, IncompleteResultsError, IndexNotFoundError) as "outside coverage area". - Stale README snapshot date, stale HTML comment on SODA level 3 confidence, hardcoded marker colors duplicating CSS custom properties, and a redundant normalize_to_soda recomputation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
get_segment_geometry_wkt() existed in src/gps2asp/resolver/spatial_index.py but the vendored mirror under custom_components/asp_parking/gps2asp/ had drifted behind it. Caught by scripts/sync_vendored.py while re-vendoring the schedule/ changes for the ASPActiveNow fix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
sensor.py now reads ASPActiveNow.weekly_schedule directly instead of hard-coding the single active day, matching the demo/coverage dumpers. Also fixes a validation gap in build_demo_dataset.py's points loader, a falsy-zero bug in the explorer's marker-radius parsing, a redundant double-parse of segments.json in build_coverage_dataset.py, and collapses three now-identical ScheduleFound/ASPActiveNow branches. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
Exercises the real ASPNextMoveTimeSensor.extra_state_attributes property (not the test-local reimplementation) via the vendored schedule classes, so it fails if sensor.py regresses to hard-coding cleaning_days to only the in-progress active_window day for ASPActiveNow. Verified the new test fails against the pre-fix sensor.py and passes against the fix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
…Now test mirror Vendored dataset_common.py was missing _load_raw_segments and load_segment_records_with_raw_count, failing the vendor-guard CI check. The test-local sensor_extra_attributes() mirror still hardcoded ASPActiveNow cleaning_days to one day instead of reading the full weekly_schedule like the fixed production sensor.py. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
…-limit self-check count resolve_group's caller previously picked one arbitrary representative raw spelling per (street, side) group and never replaced it once it became non-canonical, so a sibling segment's own raw CSCL spelling could be permanently skipped from SODA queries — silently zeroing out an entire group's coverage when SODA stores the untried spelling. Now every distinct raw spelling observed in a group is collected and queried (deduped by resolve_group). main()'s --limit self-check compared the expected segment count against the raw pre-filter total, but build_coverage() slices --limit off the already-filtered segments dict — causing false "refusing to write a lossy dataset" aborts whenever any raw records were filtered out. Now compares against the filtered count when --limit is set. Also removes load_segment_records() and count_raw_segment_records() from dataset_common.py (and its vendored custom_components mirror) — both dead code superseded by load_segment_records_with_raw_count. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
- docs/demo/app.js: fix NYC midnight day-rollover in nycNowParts() when hour="24" pairs with the ending calendar day; share one now/baseMs snapshot between computeNextMove and renderCalendar to remove the intra-render "today" race. - scripts/build_coverage_dataset.py: resolve_group() now gathers per-variant SODA fetches concurrently instead of awaiting them sequentially. - custom_components/asp_parking/sensor.py + tests/test_ha_integration.py: sort cleaning_days via ASPDay's own IntEnum ordinal instead of a hand-maintained day-name list duplicated across prod and test code. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
Code review flagged query_count = 0 in resolve_group() as dead code, unconditionally overwritten by query_count = len(variants) with no branch in between. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
…stants Resolve xhigh code-review findings: promote BOROUGH_NAMES and SIDE_LABELS to dataset_common.py as the single source of truth (coordinator.py and sensor.py now import instead of hand-copying), and anchor build_coverage_dataset.py's TIER_BOUNDS low-tier bound to the resolver's DEFAULT_CONFIDENCE_THRESHOLD instead of a hardcoded 0.33 literal. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
…ABELS SpatialIndex.resolve_index_dir() is now the single source for the env-var/package-default precedence, reused by dataset_common's _default_segments_path() instead of a hand-copied duplicate. Test file now imports the shared gps2asp.dataset_common.SIDE_LABELS instead of a manually-synced mirror. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
… cross-match, resync vendored mirror _exact_cross_match() only stripped leading/trailing whitespace, so raw cross-street spellings differing by internal double-spacing (e.g. "W THAMES ST") were misclassified as soda_level 2 instead of the correct exact-match level 1, understating confidence on the coverage map. Also re-ran scripts/sync_vendored.py to pick up the resolve_index_dir() refactor from 2a8cda4, which had left the custom_components mirror stale and failing the vendor-guard CI check. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
…ntime-only labels, dedupe whitespace-collapse - ruff format the 8 files left unformatted after prior dedupe commits, fixing the required ruff+mypy CI job - split BOROUGH_NAMES/SIDE_LABELS/borough_name into a new dataset_labels module (both src and vendored mirror) so coordinator.py/sensor.py no longer trigger a dead pyproj Transformer construction at HA startup just to reach two dict constants - factor collapse_whitespace_upper() out of normalize_to_soda() and reuse it in build_coverage_dataset._exact_cross_match() instead of re-deriving the same whitespace-collapse expression four times inline Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
…t dataset_labels import Vendored custom_components/asp_parking mirror was missing the collapse_whitespace_upper() extraction applied to src, reintroducing a src/vendored divergence. test_ha_integration.py imported SIDE_LABELS from the heavy dataset_common module instead of the lightweight dataset_labels module split out to avoid pulling in pyproj/asyncio. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
Truthiness check dropped cross_streets whenever from_street/to_street resolved to an empty (but non-None) string, unlike the is-not-None check used for the same fields elsewhere in build_sensor_shapes(). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
…ion, drop dead soda_level clamp boroughName() coerced a null borocode to the literal string "null" instead of falling through cleanly. resolve_side()'s soda_level clamp was dead code since soda_level is always >=1 whenever filtered is non-empty. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
…t-derived JS state Prevent drift in duplicated/mirrored logic flagged by code review: sync docs/demo/app.js's transient-failure status list from demo.json (mirroring the existing TIER_BOUNDS pattern) instead of hardcoding it; factor the weekday-conversion formula into one shared helper; extract cleaning-day derivation into gps2asp.dataset_labels.cleaning_day_names, shared by sensor.py, build_demo_dataset.py, and the test mirror; promote signs._normalize_street to a public normalize_street; and memoize build_coverage_dataset.py's per-segment street-name normalization. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
Critical: docs/explorer/app.js had a stray */ inside a block comment (--tier-*/--marker-r-*) that closed it early, turning the following prose into invalid JS and breaking the entire file's parse. Also add console.error + HTTP-status-aware error messages to both webapps' dataset-load failure paths, which previously swallowed the underlying fetch/parse error with no diagnostic trail. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
- CONFIDENCE_BY_LEVEL's level-2 value (0.66) never fell in TIER_BOUNDS' "low" band, making the explorer's "Low — fuzzy or fallback match" filter structurally unreachable; level-2 matches always showed as "medium" instead. Retuned to 0.40. - resolve_group()'s per-variant SODA fetch was an unbounded asyncio.gather nested inside the group-level bounded_gather, letting a multi-variant group multiply in-flight SODA requests past the documented _GROUP_CONCURRENCY=10 cap. Added a shared semaphore around the actual per-request fetch. - docs/demo/app.js's transient_failure_statuses override used a `.length > 0` guard that would silently ignore an authoritative but legitimately-empty array from demo.json, contradicting its own documented "dataset is the source of truth" invariant. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
Prevents src/gps2asp <-> custom_components vendor drift before it reaches CI's vendor-guard workflow or code review, instead of only catching it after the fact. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
…solve findings Adds targeted ruff rules for the two recurring xhigh code-review bug categories identified in git history: swallowed exceptions (BLE001, TRY400) and dead code (RUF059). Fixes the 17 findings surfaced: - logger.error -> logger.exception at 5 sites that were dropping tracebacks - narrowed a test's blind except to the specific exception it already documented expecting (SODAAPIError), so an unrelated bug can no longer hide behind it - justified # noqa: BLE001 on 6 sites where broad-catch is deliberate (cleanup paths, best-effort scripts, test stubs) and already logged - dropped 2 unused unpacked test variables - formatted dataset_labels.py (pre-existing drift, unrelated to the above) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
Resolves xhigh code-review finding: generate_random_fixtures.py re-implemented a second, divergent urllib-based HTTP client against the same NYC GeoSearch endpoint that geocode_fixtures.py already covers via httpx. Now calls geocode_fixtures.geocode_address() directly, picking up its borough validation for free. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
Reconciles a week of local-only work (2026-07-27 to 08-04) with origin's PR #39, which had squash-merged an earlier snapshot of the same docs/demo, docs/explorer, and build-script work plus a genuinely new iCloud cross-host calendar-sharding fix in caldav_sync.py (3.3.0-rc1 -> rc3). Conflict resolution: kept local's versions of the 11 add/add files (docs/demo/*, docs/explorer/*, scripts/build_coverage_dataset.py, scripts/build_demo_dataset.py, and their tests) plus README.md, since local is a later, refactored continuation of the exact same feature work origin's snapshot predates (e.g. docs/common.css/common.js exist locally as the target of a design-token dedup that origin's styles.css still has inline). caldav_sync.py, manifest.json, and tests/test_caldav_sync.py auto-merged cleanly with no local changes to that region, so origin's CalDAV fix applied without conflict. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
… review build_coverage_dataset.py caught blind Exception per SODA variant query, collapsing a systemic outage (missing/invalid NYC_OPEN_DATA_APP_TOKEN, rate-limiting) into the same empty result as a genuine no-coverage street, so main() could silently write an all-"unresolved" coverage.json with exit 0. Narrow the catch to SODAAPIError/IncompleteResultsError, track failures alongside query_count, and abort the build if every issued query errored. generate_random_fixtures.py wrote whatever generate_fixtures() returned unconditionally, so a geocoder outage could silently overwrite a committed fixture file with an empty one. Add the same guard geocode_fixtures.py already has for this failure mode. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B79Ri837rLoorjzjoybpT9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A week of local work (2026-07-27 → 2026-08-04, 63 commits) reconciled with
origin/main's PR #39 (iCloud CalDAV cross-host fix, merged 2026-08-01).Phase 41 — Static demo page: offline dataset dumper, dark-theme Leaflet demo page (map + HA card + next-move calendar), GitHub Pages publish workflow, committed demo dataset snapshot.
Phase 42 — Coverage explorer: deterministic coverage-dataset dumper (grouped SODA resolve, worst-case-side pick), canvas-map explorer page with tier coloring, filters, GeoJSON export, and a committed whole-index
coverage.json.Ongoing hardening: a long tail of
xhighcode-review fix rounds across the demo/explorer/coverage-dataset/sensor code — deduping vendored constants (BOROUGH_NAMES,SIDE_LABELS,TIER_BOUNDS), fixing tier misclassification and SODA fallback bugs, preserving fullweekly_scheduleonASPActiveNow, enabling stricter ruff rules (BLE001/TRY400/RUF059), and today's fix dedupinggenerate_random_fixtures.py's geocoding client againstgeocode_fixtures.py.Merge reconciliation
origin/main's PR #39 had squash-merged an earlier (2026-08-01) snapshot of this same docs/demo, docs/explorer, and build-script work, plus a genuinely new fix: iCloud's per-account calendar host sharding brokeprincipal.calendar(cal_url=...)'s local URL-join validation, fixed here with aprincipal.calendars()fallback matched by URL path (custom_components/asp_parking/caldav_sync.py, version bump 3.3.0-rc1 → rc3).Conflict resolution kept this branch's versions of the 11 add/add-conflicting files (
docs/demo/*,docs/explorer/*, both build scripts and their tests) plusREADME.md, since they're a later, refactored continuation of the exact same feature work origin's snapshot predates (e.g.docs/common.css/docs/common.jsexist here as the target of a design-token dedup origin'sstyles.cssstill has inline).caldav_sync.py,manifest.json, andtests/test_caldav_sync.pyauto-merged cleanly with no conflicting local changes, so origin's CalDAV fix applied without any manual resolution.Verification
ruff checkpasses on all touched Python filesgrepfor the URL-join fallback and3.3.0-rc3)py_compilepasses on modified/merged Python entry points.venv/bin/pytestbefore merge