Creative ideas + design notes
Commits with substantial prose (≥120 chars) — the rationale behind each move.
cbc32be · 2026-09-12 · fix(la-data): adaptive page-size backoff for oversized-geometry gateway 502s (TK-11500)
Zoning stalled at 75% (43,957 of 58,953 rows) because the NavigateLA gateway 502s
on a GEOMETRY page whose response exceeds ~30s to serialize. Root cause (pre-measured
by claude-run-10955, re-confirmed live 2026-09-12): the OBJECTID 42000-56000 band
carries ~22KB of geometry per feature, so a 2000-row page there is ~44MB. It is a
fixed OBJECTID band, not a fixed depth, so neither offset- nor oid-cursor paging
avoids it — the constraint is response SIZE.
Fix: arcgisPages now halves resultRecordCount on a gateway size-failure and retries
the SAME cursor position (down to a floor, LA_ADAPTIVE_MIN_PAGE default 50), then
grows back x2 on success so the rest of the layer still pages fast (AIMD). General to
both offset and oid modes. fetchArcgis gains an httpTries knob so the descent fails
fast while shrinking and spends the full retry budget only at the floor — a page that
502s even at the floor is a real outage and is rethrown (fail-loud; the aggregate
run's allowFailure exemption still catches a genuinely stuck source). Only geometry
pages shrink; a non-geometry transient is a real upstream outage and is never masked.
Proven three ways: negative test test/arcgis-adaptive-page.test.mjs (red on the pre-fix
baseline, green with the fix; covers offset backoff, all-band completion, non-geometry
fail-loud, oid mode); full existing suite unchanged; and a bounded read-only live run
against the real OBJECTID>42000 band — real 502 at 2000 caught, halved to 1000,
completed 2200 rows.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkjvkxHU1B2a7gfVQFKzxX
1879ff0 · 2026-09-12 · chore: v0.2.4 (session close)
Version bump only — reflects the TK-10955 assessor_parcels allowFailure fix
(93a7ac5) committed earlier this session. No lint issues found (no linter
configured in this repo); reviewed the diff manually, no refactor needed —
it mirrors the existing gis_zoning allowFailure pattern exactly.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MffyeUFB1BstaK2xS7ufYd
93a7ac5 · 2026-09-12 · fix(la-data): tolerate assessor_parcels' audited page-1 400 (TK-10955)
FeatureServer has twice returned the same generic HTTP 400 ("Cannot perform
query. Invalid query parameters.") on page 1 before any row is fetched
(2026-09-10, 2026-09-12), always ~55.3s after the request to within 50ms
across both occurrences -- consistent with an upstream execution-time
ceiling under load, not a bad query. Manual replay of the byte-identical
runtime query succeeded live both times. Table is upsert-only (conflict on
ain+roll_year, never deleted) and the OID-cursor rescans the full current
roll year every run, so a skipped day just leaves yesterday's 2.4M-row
snapshot in place one extra day -- no data loss, no corruption.
Added an allowFailure exemption scoped to the exact audited message,
mirroring gis_zoning's existing precedent exactly: a genuinely different
assessor_parcels error still fails the aggregate run loud, and a direct
(non-aggregate) run stays strict. Extended test/run-policy.test.mjs with a
positive control, a negative control, and a direct-run-stays-strict check.
Verified: 4/4 suites pass (18 checks); live smoke test (--max=200) confirms
normal ingestion is unaffected; full 'cli.js all' aggregate re-run today
exits 0 via the real classifyRunResults against real SOURCES (gis_zoning's
known 502 tolerated, assessor_parcels succeeded outright this run).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MffyeUFB1BstaK2xS7ufYd
28fcd49 · 2026-09-11 · fix(la-data): don't cache a failed catalog fetch; report probe errors honestly
Two defects surfaced while running the shipped resolver past a second model. Codex
was spend-capped (HTTP 429) and Kimi's key is expired, so the lens was a local 8B
model whose output was mostly a restatement of the questions — but two of its
prompts were worth answering properly, and one was a real bug.
REAL BUG — fetchCatalog memoised FAILURES. `Promise.resolve(...).catch(() => null)`
cached a null for the life of the process, so one transient blip while resolving the
FIRST drifted source silently downgraded every later source in the same run to the
window-scan path. Not a correctness hole (the window path also demands full proof)
but needlessly narrower. Failures are now forgotten so the next source retries.
Proven by check 11 plus a negative control showing the old shape called the catalog
once where the new one calls it twice.
MESSAGE HONESTY — probeLayer swallowed every error into `null`, which is
indistinguishable from "that layer isn't there". It still fails loud either way
(never ingests), but the error text could mislead a human into reading a flaky
upstream as "the layer genuinely moved away". Probe errors are now counted and the
failure message says "(N probe(s) ERRORED — absence here is not proof of absence)".
Analysed and NOT changed: "what if the catalog and the per-layer plane are BOTH
stale?" A consistently-old upstream is self-consistent — our pinned (new) id
mismatches, the old catalog names the old id, the old per-layer plane re-proves it,
and we repoint to the old id, which in that world IS our layer. Correct. The
dangerous case is SKEW between the two planes, which is exactly what the re-proof
step catches and what check 3 covers. The 'unverifiable keeps a pin but cannot MOVE'
asymmetry is likewise deliberate, not a false-green: it never lets weak evidence
move us onto a new layer, it only declines to break a pin that already matches.
5/5 suites (11 auto-repoint checks). Live re-verified: 9/9 clean on the pinned ids,
9/9 auto-repoint from stale ids. TK-10955. $0 local.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CocwcUw6gFt3tr4CZJmra6
482ed58 · 2026-09-11 · feat(la-data): verified auto-repoint for drifting NavigateLA layer ids
Steve's call (2026-09-11, DTD option B/D). NavigateLA layer ids are positional, so
LA inserting one layer renumbers everything below it. That happened on 09-05 and
again on 09-11 ("Special Event Permits" at position 52, 452 -> 453 layers). The
fail-loud-only posture ratified on 09-10 was right at the time but, at that
recurrence rate, means a roughly weekly outage where GIS layers sit stale until a
human repoints. This reversal is deliberate and the new fact is the recurrence.
New policy: the pinned id is a HINT. On a name mismatch the adapter FINDS the layer
by exact name and PROVES it before reading a single row; it still fails loud on
anything it cannot prove.
resolveLayerIdentity(src) — two discovery paths, proof required either way:
1. service catalog (one cached GET) — covers any drift distance
2. bounded +/-N window scan of per-layer endpoints — the fallback for a STALE
catalog, which was MEASURED on 09-11: it served a 452-layer body (old index)
while per-layer endpoints already served 453, so resolving off the catalog
ALONE would have returned the WRONG layer. A catalog hit is never trusted on
its own; it is always re-proved on /MapServer/<id>?f=json, the plane /query
follows.
Proof required to MOVE: exact normalized name + type 'Feature Layer' + a field
fingerprint (the source's oidField present, and at least one nameField when it
declares any). That closes codex's stated risk — a replacement dataset reusing the
exact name — and the documented layer-73 trap (same name, Group Layer). Measured
9/9 live before enforcing, so it cannot false-alarm today.
Fails loud, never guesses: a rename, zero matches, MORE THAN ONE proved match (the
window is scanned whole, so ambiguity is detected rather than masked), drift beyond
the window with no catalog, or right-name-wrong-schema on the pinned id (which
never triggers a search — a replaced dataset needs a human). Deliberate asymmetry:
metadata with no field list keeps a WORKING pin but is never enough to MOVE.
LIVE PROOF, not just mocks: replaying the pre-fix ids against the real MapServer
auto-repoints 9/9 to the correct layer via the catalog; with the catalog forced
unavailable, the window scan recovers 419->420 and correctly fails loud on a drift
of 419. Had this shipped a day earlier, today's 04:30 run would have self-healed.
test/auto-repoint.test.mjs — 10 checks, 8 of them NEGATIVE controls (Group Layer,
wrong schema, ambiguity, rename, schema drift, kill switch, unpinned passthrough).
Check 9 earned its keep immediately: it caught that LA_NO_AUTO_REPOINT and
LA_REPOINT_WINDOW were module-load consts, so the kill switch silently did nothing.
Both now read at call time.
Also closes a coverage regression the contrarian red-team found in 314f1dc: the old
check 6 caught two sources sharing one layer id only as a side effect of asserting
exact ids, and dropping it traded a static catch for a runtime-only one. Asserted
directly now (check 7b), with a negative control.
4/4 suites pass. TK-10955. $0 local (free public ArcGIS GETs).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CocwcUw6gFt3tr4CZJmra6
314f1dc · 2026-09-11 · fix(la-data): repoint 9 NavigateLA layers after a SECOND upstream +1 insertion
LA inserted the Feature Layer "Special Event Permits" at position 52 of the
NavigateLA MapServer (layer count 452 -> 453), shifting every layer at id >= 52
by +1 — the same mechanism as 2026-09-05, six days later.
This time the expectName guard from 88e40a8 caught it: the 2026-09-11 04:30 run
failed all 8 pinned NavigateLA sources loud at preflight and ingested NOTHING,
so there was no corruption to clean up. la_gis_features was verified to still
hold exactly the correct upstream counts. The exit=1 was a TRUE POSITIVE.
Repointed +1 and verified 9/9 on BOTH planes that matter — exact upstream layer
NAME and historical ROW COUNT: zoning 72->73, council_districts 419->420,
neighborhood_councils 440->441, hpoz 76->77, community_plan_areas 415->416,
fault_zones 120->121, liquefaction 125->126, flood 255->256, fire_vhfhsz
359->360. Live: 9/9 preflight pass, 9/9 negative control (the now-stale ids are
all rejected). Re-ingest of the 8 affected sources: 8/8 exit 0 with exactly the
historical counts, content spot-checked semantically (real council members, real
community plan areas, real HPOZ districts) — not just matching counts.
test/layer-identity.test.mjs check 6 rewritten. It asserted specific numeric ids,
which is a restatement of config: it proves nothing about correctness and turns
every legitimate repoint into a test edit. It now asserts the durable invariants —
every NavigateLA-served source is ARMED (pins an expectName, or the guard is a
no-op for it) and no two sources expect the same upstream name. Both are proven
to go red under injected faults.
Documented the trap that nearly shaped the fix wrongly: /MapServer?f=json is
cacheable and was observed serving a STALE body (452 layers, old index) while the
per-layer endpoints already served the new one. Resolving a layer by name off the
catalog alone would have returned the WRONG id. The authoritative plane is
/MapServer/<id>?f=json, because that is what /query follows.
TK-10955. $0 local (free public ArcGIS GETs + local Postgres).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CocwcUw6gFt3tr4CZJmra6
f6042cd · 2026-09-10 · fix(la-data): scope allowFailure to the audited failure, not the source name
Contrarian review (TK-10955) found that the identity guard shipped in 88e40a8 was
silently neutered for the one source that carries an exemption. classifyRunResults()
tolerated ANY failure on a source whose allowFailure.scope matched, keyed purely on the
source NAME and never on the cause. So a future NavigateLA re-index that drifted zoning's
layer 72 would throw "ArcGIS layer identity drift" -> land in the tolerated bucket ->
exit 0, and the cron exit code (the one alarm that actually caught the 2026-09-05
incident) would never fire for the exact failure class the guard exists to make loud.
- allowFailure gains an optional `match` array; a failure is tolerated only if its error
text contains one of the audited signatures. Omitting `match` keeps prior behaviour.
- gis_zoning pins match to its audited gateway degradation in BOTH error shapes:
the HTTP-level form ("HTTP 502 after 5 tries") and the ArcGIS body-level form
("ArcGIS error: {"code":502,...}").
- test/run-policy.test.mjs: the audited 502 still exits 0; identity drift exits 1;
asserted against the live SOURCES config, not just a mock.
This is closer to the ratified 2026-08-30 DTD intent ("exit 1 only for unexpected
failures") than the previous blanket-tolerate: a genuine 400 on gis_zoning was also
being swallowed and now correctly exits 1.
TK-10955
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019LHiEXMR2FCqRqUmoK4kQY
88e40a8 · 2026-09-10 · fix(la-data): repoint 9 NavigateLA layers after upstream +1 re-index; pin layers by NAME
On 2026-09-05 LA inserted a layer into the NavigateLA MapServer and every layer id
shifted +1. Sources pinned bare numeric ids, so all 9 GIS layers silently re-pointed
at a different dataset. Only 2 failed loudly (gis_fault_zones hit a Group Layer,
gis_flood hit a layer with no FLD_AR_ID) — those 400s are what has been failing the
nightly run's exit code since 09-05. The other 7 landed on queryable layers and
ingested a stranger's rows under our labels for 6 days with no error at all
(council_district 15->3, neighborhood_council 99->26, hpoz 35->7843,
community_plan_area 36->7, liquefaction 474->14181, fire_vhfhsz 14->247).
- repoint 71->72, 75->76, 119->120, 124->125, 254->255, 358->359, 414->415,
418->419, 439->440; verified read-only that all 6 countable layers return their
exact historical row counts and that 120/255/72 carry the fields our mappers need.
- add assertLayerIdentity(): every gisSrc now pins the expected upstream layer NAME
and the adapter verifies it before paging, so the next re-index fails LOUD instead
of silently ingesting the wrong layer. An ArcGIS layer id is a positional index,
not an identifier — this is the second id-drift incident (cf. 559875d).
- test/layer-identity.test.mjs: zero-network/zero-DB proof, incl. that all 9 sources
are pinned to the post-re-index ids.
Does not touch the 2026-08-30 allowFailure exit policy (ece3ea5): run-policy and
arcgis-retry suites still pass.
TK-10955
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019LHiEXMR2FCqRqUmoK4kQY
a8295e4 · 2026-08-26 · arcgis: retry transient body-level errors (499/CONT_0001,429,5xx) so one flaky source can't poison the nightly batch exit code (TK-10851)
ArcGIS tunnels errors as HTTP 200 with an {error:{code}} body, so http.js's
status-based retry never saw them and cli.js:76-77 failed the whole 'all' run's
exit code on a single transient 499. Add payload-aware fetchArcgis retry with
backoff; bare 499 (Token Required) stays fail-fast, only 499+CONT_0001 retries.
+ zero-dep regression test (transient-retry / 400-fail-fast / auth-499-fail-fast).
e6f0356 · 2026-08-12 · Viewer: CD/ZIP quick-jump control on the Map view
- On-map 'Jump to' control (top-left, clear of the zoom control) with CD + ZIP
selects. Picking one sets the filter and the existing fitBounds flies the map
to that area; clearing removes it. Options carry live counts and are drawn from
a stable unfiltered facet list (allFacets, fetched once at boot) so every CD/ZIP
is always offered even when a filter is active. Composes with deep-links + saved
views via the shared state.filters.
Author: Steve Abrams
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
48e81f3 · 2026-08-12 · Viewer: per-column filter inputs in the List header
- A sticky filter row under the column headers: each filterable column gets an
input wired to the matching server filter param (address->q contains, zip/cd/
work_type/building_type/status/era->exact with facet-fed <datalist> autocomplete,
issued->since date, project_value->min_value). Reuses state.filters + qs(), so it
composes with facets, deep-links, and saved views for free.
- Focus + caret preserved across the reload-triggered re-render; address input
stays in sync with the top search box; inputs only appear for visible columns.
Author: Steve Abrams
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
bcf4edb · 2026-08-12 · Viewer: saved views — named snapshots of the full view state
- 📑 Views popover: name + save the current filters/sort/view/color/columns/density
as a preset, apply any saved view in one click, delete with ✕. Persists to
localStorage. Since syncURL() already serializes the whole view state to the
querystring, a saved view is just a named snapshot of that query.
- Refactored boot into reusable hydrateFromQuery(strict) + syncControls(), shared
by page load (persisted-pref fallback) and saved-view apply (exact snapshot).
Author: Steve Abrams
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
d7a05ae · 2026-08-12 · Viewer: column show/hide menu, / search shortcut, density+columns in deep-link
- ▦ Columns popover (List view): checkbox per column to show/hide, show-all +
reset actions, always keeps >=1 column visible; persists to localStorage.
- Keyboard: '/' focuses the search box, Esc closes the modal/popover; placeholder
hints the shortcut.
- Deep-link now also encodes density (d=) and the visible column set+order
(cols=) so a shared URL restores the exact table layout. Verified round-trip.
Author: Steve Abrams
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
57d3d5a · 2026-08-12 · Viewer: loading spinner + empty-state + fetch-error overlay
- Loading spinner overlays the results area during every fresh fetch (valuable:
the unfiltered lead_score sort over 1.58M rows takes a couple seconds).
- Empty state when a filter set returns 0 rows: clear message + a Clear-all-filters
button (wired to reset) instead of a blank table.
- Network-error state shows the failure inline instead of a silent blank view.
Author: Steve Abrams
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
d5d5fec · 2026-08-12 · Viewer: CSV export of current filtered view + fix blank Issued date column
- ⬇ CSV button exports the current filtered+sorted permits as CSV (client-side,
pages /api/permits at 200/req up to a 5,000-row cap, UTF-8 BOM, quoted escaping,
filter-named file). Surfaces an explicit notice when the filtered total exceeds
the cap (no silent truncation).
- Fix real bug: API sends the date as issue_date but List/Grid column key is
'issued' -> the Issued date rendered blank everywhere. Normalize issued from
issue_date on load (List, Grid, and CSV). Verified: column now shows the date.
Author: Steve Abrams
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
c9ccae5 · 2026-08-12 · Unified property drill-down: address -> property + trade structure + NAMED CREW + contacts
Folded getCrew() into property.js (permit-crew.js now exports it, CLI guarded). One
command: node scripts/property.js "<address>" -> parcel + permit/trade structure +
timeline + full named crew (GC/subs/architect/engineer w/ license, phone, website).
--no-crew skips the LADBS scrape. $0 (cached LADBS GET + local CSLB join).
b291216 · 2026-08-12 · permit-crew c-fix (Cody gate): era-scope the crew + dates + entity decode + no-match flag
Cody: merging 2018+ permits presented stale-era tradespeople as current crew. Now
anchors to the latest building permit, includes only ~18mo era (Woodlake correctly
drops 2 older permits), shows each permit date, flags GC-appears-as-sub cross-role,
distinguishes 'no CSLB match' from not-tried, decodes HTML entities. DTD final: SHIP.
1cd9469 · 2026-08-12 · Named build crew (scripts/permit-crew.js) — the linkage layer: subs on any build
Breakthrough (la-research-agent): LADBS PcisPermitDetail is a PUBLIC single GET with
id1/id2/id3 (permit# split on -), not permitNumber. Chain: property -> all permits on
parcel -> GET each detail -> Contractor/Architect/Engineer + CSLB license -> match to
enriched cslb_raw (website/phone). GC on building permit + SUBS on electrical/plumbing/
mech permits = full named crew. Proven: 7143 Tampa named 7-firm crew w/ phones. $0.
Cached in permit_crew. DTD-decided, la-research-discovered, Cody-gated next.
fa030eb · 2026-08-12 · Property drill-down (scripts/property.js): full permit/trade crew-structure per parcel
Assembles every permit on a parcel grouped by trade (GC building + electrical/plumbing/
HVAC sub-permits = the crew) + parcel data + timeline. Answers 'who worked on this build'
as trade structure; contractor names are the LADBS permit-detail scrape layer (next).
012231a · 2026-08-12 · enrich: domain-guessing pipeline (scripts/enrich-domain.js) — the real max-it unlock
DDG rate-limits by IP so parallel search self-blocks. Domain-guessing hits each
contractor's OWN candidate domains (different hosts = no shared rate limit) + local
model verifies the page belongs to them. Test: 5/10 real sites, $0, fully parallel.
14a832a · 2026-08-12 · enrich: don't lose DDG-rate-limited rows (mark ddg-blocked, 45s cooldown, throw on 403/429)
Max-it fan-out over-pressured DuckDuckGo (403s). Bug: errors stamped enriched_at =
rate-limited contractors marked 'no site' (data loss). Now: 403/429 throws ddg-blocked,
worker cools 45s, degraded rows reset for clean retry. Gentler concurrency.
c5f4234 · 2026-08-12 · Contractor web-enrichment — $0 local (free DDG search + local Ollama verifier)
NO paid API (Steve: Exa $2400 = hard no). Free DuckDuckGo HTML search finds candidate
URLs; local qwen2.5 verifies + extracts official website/email/linkedin (rejects
directories), matched to the CSLB name/city/phone. Writes website/email/linkedin/
enrich_* fields on cslb_raw. Resumable (enriched_at stamp), prioritized by hottest
recent-permit ZIPs. Test: 4/5 real sites found, $0.
804a15e · 2026-08-12 · yoloforever c8 FIX (Cody): status brief market = permit volume + median (not outlier-inflated sum)
Cody c8: the Market section re-imported the sum(valuation) outlier problem killed in
c2/c7 ($493M incl. the $375M unverified permit, unflagged). Now ranks hottest district
by permit VOLUME (outlier-immune) + shows MEDIAN value. Consistent with the digest.
e1a34c1 · 2026-08-12 · yoloforever c8: consolidated platform status brief (scripts/status-brief.js)
One dated internal brief tying the 8 cycles together: data footprint (16.6M rows),
canary verdict (reads its own heartbeat), top-5 deals, hottest market + contractor
supply. Reads each component's own output (truthful); missing artifacts show as gaps.
1d08e30 · 2026-08-12 · viewer: make filters+sort shareable in the URL (deep-link/bookmark any filtered view)
- syncURL() mirrors filters + sort + view + color into the URL on every render (history.replaceState, no history spam)
- boot IIFE now restores era/work_type/building_type/status/cd/zip/q/min_value/since + sort/dir from the URL (previously only view+color)
- verified: applying facets writes ?work_type=Bldg-New&cd=5&sort=...; cold-loading that URL restores active facets, filtered count (9,224), sort, and rows
d115714 · 2026-08-12 · yoloforever c7 FIX (Cody): honest digest — verified-only headline + district table
Cody c7 kill-shot: district table silently summed the flagged $375M into CD9's total
(#1) while the deals list flagged it — $700M headline was 2x inflated. Now: headline
splits verified ($278M) vs flagged/unverified ($422M); district table sorts by verified
value + marks outlier rows (CD11 Palisades now correctly #1; CD9's single-outlier row
demoted). Added stale-feed guard. Publishable.
02d9b82 · 2026-08-12 · yoloforever c7: RE-news deals-digest markdown generator (scripts/deals-digest.js)
Reads the latest deals-feed JSON (single source of truth) and renders publishable
markdown: top deals w/ council member + parcel value + map links + ⚠verify flags,
by-council-district table, source disclaimer. Presentation layer only.
4aad896 · 2026-08-12 · yoloforever c6 FIX (Cody): drop invalid per-ZIP ratio, add valid county supply/demand + fix trade map
Cody c6: permits_per_contractor by ZIP was a residential-ness artifact (wealthy ZIPs
have few contractor BUSINESS addresses, not undersupply) — a caveat can't rescue a
'90272 underserved' headline that's false. Dropped it. Section 2 now county-level
supply vs permit demand by trade (Gen Building 2.7, Electrical 8.4, Plumbing 9.4
permits/contractor/yr — no geo proxy). Trade-code map fixed (norm dash-stripped).
e534fb9 · 2026-08-12 · yoloforever c6: contractor-market intelligence (scripts/contractor-intel.js)
First use of the 243k CSLB dataset. Joins active LA-County contractors (by trade
classification) to permit activity: supply by trade (23,250 General Building etc.)
+ per-ZIP demand-vs-local-supply (Palisades 90272 = 33.5 permits/contractor, fire-
rebuild straining local supply). Honesty caveat labeled: contractor ZIP=business
address, supply proxy not work-location. INTERNAL (not public site).
00f8def · 2026-08-12 · yoloforever c5 FIX (Cody): gate teardown bonus on valuation>=$500k
Cody c5: a $250k permit on a $6.9M parcel jumping to 98 rewarded parcel wealth, not
a teardown (that's a garage, not a rebuild). Added valuation>=$500k to the teardown
CASE. Now: $250k/$6.9M example 98->90 (tier only); real $2.16M rebuild keeps +->100.
192 true teardowns flagged; 0 penalized. v2 defensible.
48faabe · 2026-08-12 · yoloforever c5: permit_lead_score_v2 — parcel-signal teardown/redevelopment ranking
NEW function (live permit_lead_score untouched). v2 = v1 + up to 15pts parcel signal
(cap 100, no rescale): teardown (new build on valuable old parcel) + assessed tier.
Validated: 0 permits penalized (no-parcel = v1), 20,045 lifted, max +15. Surfaces
high-end redevelopment v1 misses (e.g. $250k permit on $6.9M 1922 parcel: 83->98).
60f8851 · 2026-08-12 · yoloforever c4 FIX (Cody): deals feed ranks by lead_score, dedups address, flags outliers
Cody c4 (verified): raw-max-valuation surfaced LAX 3x + a $375M 'addition'. Now:
(1) DISTINCT ON (address,zip) dedups same-project permits; (2) ORDER BY lead_score
then valuation = newsworthiness not brute dollars (ground-up dev now leads, airport
gone); (3) valuation_outlier flag (>$100M or >150x parcel assessed) tags data
artifacts for editorial verify. Now publishable as RE-news leads.
909b171 · 2026-08-12 · yoloforever c4: newsworthy permit deals feed (scripts/deals-feed.js)
Surfaces top high-value permits (last 90d, >=$1M, issued) as human-readable
headlines + structured JSON feed for RENTV/RE-news synergy. Enriched w/ parcel
value + council-member name. Fixed date formatting + 'built 0'. New local surface.
0377c1a · 2026-08-11 · yoloforever c3 FIX (Cody): green-able canary + real CSLB load + time-anchored bleed
Cody c3 (all verified): (1) permanently-FAIL canary = anti-canary -> demote KNOWN_BROKEN
sources (gis_zoning, documented) to WARNING so verdict can be OK/WARN; a NEW error still
FAILs. (2) bleed floor was run-count anchored (walks up, misses slow decline) -> time-anchored
to ~30d. (3) CSLB state was a planted timestamp that lies -> scripts/cslb-load.sh writes
real state on actual load. Verdict now WARN (was perpetual FAIL); green = signal again.
f794850 · 2026-08-11 · yoloforever c3: close Cody-c1 canary gaps — CSLB tracking + rolling-window bleed detection
(1) CSLB now recorded in la_ingest_state (cslb_master) so the source-staleness check
covers it (was the untracked blind spot). (2) Rolling history (canary-history.jsonl)
+ BLEED status: flags a slow net decline vs earliest-observed count that the ratchet
floor misses. Verified: no false-bleed on stable data; verdict still correctly FAILs
on gis_zoning's real 502.
b3681eb · 2026-08-11 · yoloforever c2 FIX (Cody): median_value + explicit scope label + CD-join diagnostic
Cody: total_value is right-skewed vanity without a central figure. Added median
(reveals CD11 $8.05B total vs $9k median = a few mega-projects). Scope label now
states building-only/current-dataset (excludes 1.17M historical+trade). Join
diagnostic prints parsed/matched (15/15) so a blank member column can't ship silently.
f57ce2c · 2026-08-11 · yoloforever c2: per-CD + per-ZIP permit-activity rollups (scripts/rollups.js)
Aggregates permits x lead_score joined to gis council-district NAMES; permits,
issued, new-builds, last-90d, total/avg value, avg lead score per CD + top-60 ZIPs.
CSV exports. New local analytics surface (not the live viewer). $0 local.
3687729 · 2026-08-11 · yoloforever c1 FIX (Cody gate): canary reads la_ingest_state for per-source staleness+errors
Count-only was false comfort (idempotent upserts keep counts flat while a feed
goes stale/errors). Now checks all 21 sources' last_run age + last_status — verdict
FAIL correctly surfaces gis_zoning stuck in HTTP 502. Covers the 5 count-only tables.
9a4905f · 2026-08-11 · yoloforever c1: LA-data freshness+rowcount canary (baseline-aware, read-only)
Flags row-count regressions vs a ratcheting baseline + stale feeds (max-age per
table). Report-only; CNCP/George alerting is a gated follow-up. Also: parcel tails
now 100% complete (12,099,614 = full 12.1M, all 5 roll years, OID-cursor fix).
cf20498 · 2026-08-11 · Compliance: pull board.html (monetization deck) off public surface + add robots.txt
vp-compliance flagged the public no-auth board.html resale/lead-gen deck as HIGH
(self-documented commercial + owner-targeting intent on a free-viewer surface).
Moved to viewer/private/ (preserved, not served). robots.txt disallows /api/ + deck.
69f49ff · 2026-08-11 · Viewer: swap CARTO basemap -> OSM tiles (ToS/copyright) + OSM attribution
CARTO CDN basemap is not licensed for public non-customer use; OSM standard
tiles are attribution-only. Disclaimer/sources footer already present.
1efa08a · 2026-08-11 · viewer: mirror compliance footer onto table.html + board.html
All three viewer entry points now carry source attribution, as-is warranty
disclaimer, and no-endorsement notice. board.html uses a self-contained
inline-styled footer (different flex layout, no --muted var).
6f64d55 · 2026-08-11 · viewer: add source-attribution + as-is/no-endorsement compliance footer
Data reuse verified: LA County eGIS Terms grant copy/publish/distribute +
commercial use; LA City permits are untagged-public open data. Footer keeps
any external viewer compliant (attribution, as-is warranty disclaimer, no
County/City endorsement).
ff21d5c · 2026-08-11 · Viewer: List/Grid/Map views + color-ID + shareable ?view=&color= deep links
Map view (Leaflet) plots the current filtered set color-coded by lead score/
status/value/work-type; grid = color-bordered cards; list = the sortable table.
One colorFor() model drives all three. ?view=map&color=project_value deep-links.
4862c20 · 2026-08-11 · GIS land use via hosted LADCP FeatureServer (56,901 features, reliable)
Replaces the flaky NavigateLA gateway for land use. Zoning stays partial (75%,
gateway-capped); complete LA City zoning is fragmented across ~15 per-community
Zone_Builder_Database services (documented follow-up).
0ccaea8 · 2026-08-11 · GIS: parcel polygons via hosted FeatureServer (421,684 w/ geometry); no-order offset for gateway layers
NavigateLA MapServer gateway 502s/500s on bulk geometry past ~offset 44k;
parcels repointed to hosted Assessor_Parcels_Land_2025 FeatureServer (reliable).
zoning partial (43,957), landuse blocked — need hosted-FeatureServer equivalents.
cc12805 · 2026-08-11 · GIS layers (NavigateLA ArcGIS) + CSLB contractor loader
ArcGIS adapter: OBJECTID-cursor pagination + geometry support. GIS schema
(la_parcel_geom, la_gis_features). Loaded zoning/land-use/council districts/
neighborhood councils/HPOZ/community plan areas + fire/flood/fault/liquefaction
hazard layers. CSLB free-portal ASP.NET session downloader -> 243,555 CA
contractors in cslb_raw. Assessor switched to OID-cursor (fixes deep-paging 400s).
d9ab84f · 2026-08-11 · Promote merged card+lead-score viewer to index.html; preserve table as table.html
- index.html: card viewer (facet rail, search/sort/density, detail modal) + permit_lead_score Rank badge on every card, 'Rank (high->low)' sort, 'Lead score N/100' in modal; header nav Cards/Table/Board
- table.html: previous sortable/movable-column table viewer, preserved + reachable from nav
- cards.html: removed (promoted into index.html)
Steve-directed (TK-10450). Verified in Chrome: / = cards w/ ranks, /table.html = table, /board.html = DTD board
f47742c · 2026-08-11 · Viewer: sortable+movable-column table + permit_lead_score ranking (default sort)
Every column server-sortable (whitelisted sort=<col>&dir); drag-to-reorder columns
persisted to localStorage; lead_score (0-100) ranking as default sort. Row-density
slider, faceted filter rail with live counts, detail modal w/ parcel enrichment +
Google Maps + raw JSON. On port 9814.
bbdafa5 · 2026-08-11 · LA Building Permits viewer frontend: faceted rail, search/sort/density, detail modal
- public/index.html: professional teal/neutral data-tool UI over /api/permits + /api/facets
- Left collapsible facet rail (Era/Work Type/Building Type/Status/CD/ZIP) with live counts, click-to-filter, active teal badge; active-facet options stay switchable (refetch minus own filter)
- Top bar: debounced address search, live count, sort (newest/value/oldest/address), density slider -> --cols; sort+density persist in localStorage
- Cards: address, work/building badges, formatted value, issue date + status pill, CD/era, assessed value/year built/sqft when present
- Detail modal (draggable+resizable, persisted): full permit + parcel enrichment, Google Maps link, clickable filter values, raw-JSON toggle
- Load-more pagination (no infinite scroll); stale-response seq guards on facets+permits
- server.js: existing viewer server (was untracked)