Live testing of all 7 languages (en/es/zh/vi/ht/ar/tl against the
Chicago Drexel test address) showed every language correctly populated
the advisor's insurance + mitigation arrays (confirming the v0.15.3
Tier-1 fix), AND advisor.tldr rendered fluent prose in every language.
But risk.plain_verdict came back EMPTY for zh and ar — the two
non-Latin-script languages. Other risk-agent fields (risk_score,
risk_level, fema_gap_explanation) populated normally.
Root cause: the field's instruction said "Second person, plain English,
~10th-grade reading level" — when the system-prompt language directive
says "write everything in Mandarin/Arabic", the model gets two
contradictory signals about what language to use for THIS particular
field and either skips it or returns an empty string. The Latin-script
languages were robust to the contradiction; the non-Latin ones weren't.
Fix:
- Drop "plain English" from the plain_verdict instruction — replace
with explicit "Write the VALUE in the user's ch…
v0.15.3: make non-English dossiers actually work end-to-end
Hand-testing the Spanish ("es") dossier on a Chicago address surfaced
two distinct failures the original "translate everything" prompt
directive caused:
(1) Empty advisor sections. Gemma 4 in Spanish mode helpfully
translated the technical enum values inside the JSON it returned —
"nfip_standard" became "nfip_estandar", bucket "drainage" became
"drenaje". The strict post-parse filter in advisor_agent.py then
silently dropped every insurance_recommendations entry (none matched
a catalog product_id) and the bucket-grouped action renderer
collapsed all mitigation_actions into the "other" bucket (or the
model emitted an empty array, same result). The dossier rendered
with "The advisor agent did not return insurance recommendations
for this address" and only the forced-passthrough city resources
in the action plan. Functionally broken Spanish.
(2) Mixed-language UI chrome. The model-generated prose translated
correctly (verdict, NRI narrative, vision findings, action descriptions
— all Sp…
Caught when a chip pointed to 1 World Way (Clifton A. Moore Admin
Building at LAX) and FlutIQ still produced residential-flavored
"For this property…" insurance copy, a homeowner action plan, and a
before-you-sign checklist — none of which make sense for a commercial
property. Root cause: property type wasn't surfaced anywhere; advisor
was hardcoded residential.
Fix (Option A — honest residential-only mode, on-brand with FlutIQ's
homeowner mission):
geocoder.py — classify Nominatim hits as residential or commercial
using class/type/name. Defaults to residential unless
the signal is confident; Census fallback assumes
residential (it only resolves TIGER street addresses).
orchestrator — pass property_type through into the dossier payload.
advisor — early-return for commercial: skip the LLM call entirely
and return a graceful note explaining FlutIQ is built
for residential and pointing to …
Adds a local-inference path (FlutIQ Edge via Ollama / Gemma 4 e4b)
alongside the existing cloud path (FlutIQ Cloud via OpenRouter / Gemma
4 31B). Same FastAPI server, same agents, same frontend; INFERENCE_BACKEND
env var picks the LLM endpoint. Verified end-to-end on an M4 Pro 24 GB:
/api/assess for a Chicago address completes in ~190s with all 9 agents
finishing on gemma4:e4b. Frontend re-skins itself based on data-variant
so judges can tell the modes apart at a glance (blue Cloud / green Edge).
Directly addresses the hackathon brief's "local frontier intelligence"
and "privacy is non-negotiable" framing.
Reframes the dossier as an expert briefing for someone about to live,
buy, or rent at the property — value is synthesis and time, not novel
prediction:
- risk_agent gains a plain_verdict field (3-5 sentences, second-person,
~10th-grade reading: bottom line first, then most important reason,
then trend direction)
- advisor_agent gains before_you_move_in[] (3-5 due-dilige…
v0.14: regional_risk_agent — FEMA NRI multi-hazard at county level
Broadens FlutIQ from flood-only to multi-hazard. Every county in the US
now gets a calibrated risk profile across 18 hazards (wildfire, hurricane,
tornado, earthquake, drought, heat wave, etc.) at the neighborhood level
from FEMA's National Risk Index — the canonical source.
The win that matters: NRI's data was previously gated behind FEMA's
click-through CSV download. Found the exposed ArcGIS REST endpoint
behind RAPT (Resilience Analysis and Planning Tool):
https://services.arcgis.com/XG15cJAlne2vxtgt/arcgis/rest/services/
National_Risk_Index_Counties/FeatureServer/0/query
Free, no auth, point queries, 467 fields per county, full national
coverage. Verified live across 6 geographies with very different
hazard fingerprints:
Chicago: Cold wave + Winter weather + Tornado + Inland flood
LA: Earthquake + Wildfire + Inland flood (Community
Resilience: Very Low — interesting demo signal)
Houston: Hurricane + Tornado + Inland flood + Lightning
Miami:…
v0.13: Tier-1 multi-city expansion — Chicago + NYC + SF + LA + Austin
Refactors the Chicago-only local_agent into a city-aware system driven
by a registry. Each registry entry encodes the city's Socrata dataset
IDs, field names, flood-related category codes, and a context blurb
that gets injected into the local-agent prompt so Gemma 4 reasons
about each city's actual sewer system (combined vs separated, dominant
flood mode, local hazard pattern).
Honest per-city availability — what each city's dossier actually shows:
Chicago: full — 311 (WIB/SFL) + permits with reported_cost
(e.g. $80M new medical office at 4822 S Cottage Grove)
NYC: 311 — 391 sewer-flood reports per 350 5th Ave query.
Permits intentionally deferred: DOB datasets are
fragmented (legacy is stale, modern lacks dates,
active is filtered to currently-open jobs).
SF: full — 941 sewer reports + 75 permits at $366M (Mission
test point: '595 basement reports, increa…
v0.12: building permits as flood-risk leading indicator (Chicago)
The local agent now pulls TWO Socrata signals in parallel and asks
Gemma 4 to interpret them together: 311 flood reports (the historical
symptom) AND building permits (the leading indicator of impervious-
surface change). The combined-sewer system that handles stormwater
doesn't get upgraded when neighborhoods densify, so each new permit
shifts more load onto the same shared pipes.
The killer narrative this unlocks, verified live for 4521 S Drexel Blvd:
311 reports today: 0 (clean — historically stable)
Building permits: 89 within 1km / 3y, $189M total, including a
$80M new medical office and a $7.2M renovation
of a 15-story building literally next door.
Gemma 4 compound_signal: 'Clean 311 history combined with heavy
recent densification suggests a rising risk profile; the lack of
current reports may be a lagging indicator while the infrastructure
is being stressed by new development.'
That's leading-vs-lagging-indicator reasoning…
v0.11: dedicated satellite-analysis agent with bbox detection
The satellite image is now its own first-class agent (parallel to
the streetview agent) instead of being passed through to the risk
analyst as a raw image with no dedicated extraction. Each vision
agent owns its own image fetch, runs a Gemma 4 vision call with
a domain-specific prompt, and returns structured findings + per-
indicator bounding boxes. The risk analyst still gets both raw
images for cross-modal reasoning.
Why this is a real upgrade over v0.10:
v0.10 sent the satellite to the risk analyst as a raw image
with a generic 'use this for impervious surface reasoning'
hint. The model would mention it in passing.
v0.11 has a dedicated satellite agent that produces structured
output BEFORE the risk synthesis: impervious_estimate_pct,
building_footprint_pct, catchment_assessment, vegetation_buffer,
drainage_features_visible, plus 5+ categorized indicators
(impervious / drainage / water_body / vegetation / building /
other) with bounding boxes. The risk analyst the…
v0.10: 3-image multimodal — satellite + wider-area + street view in one Gemma 4 call
Risk analyst now reasons across THREE visual perspectives plus all
6 data sources in a single inference, exercising six Gemma 4
capabilities at once (vision + interleaved multimodal + reasoning
+ structured JSON + long context + graceful per-image fallback).
Pipeline:
Mapbox satellite (zoom 17, ~150m across) →
Mapbox outdoors (zoom 15, ~600m across) →
Google Street View (heading-aimed at the building) →
+ 6 data agents → risk analyst (Gemma 4 reasoning=on)
Verified end-to-end against 4521 S Drexel Blvd, Chicago. The model
named all three views in its trace ('From the satellite I can see…',
'In the street view…') and produced a visual_corroboration that
explicitly cross-referenced views vs data:
'The satellite view confirms a high percentage of impervious surface
due to the large asphalt parking lot, increasing runoff. The street
view corroborates the presence of basement-level windows, which
create a direct vulnerability to water ingress that is not reflected
…
v0.9: multimodal risk analyst — vision + reasoning + 6 data sources in one Gemma 4 call
The risk analyst now receives the Street View photo as an image_url
content part alongside the text data from every other agent, with
reasoning mode enabled. Single Gemma 4 inference pass composes:
vision + reasoning + interleaved multimodal + structured JSON +
long context.
Per Gemma 4 best practice the image goes BEFORE the text in the
user message content array.
Concretely, the chain-of-thought trace now reads like a single mind
reasoning across both modalities, e.g. for 4521 S Drexel Blvd:
'I can see basement-level windows (partially obscured by the
fence). This is a critical vulnerability for surface water
ingress. The driveway is paved (impervious) and appears to slope
slightly toward the building/parking area, which could channel
water toward the foundation.'
…then cross-references with the FEMA zone, 311 count, weather
forecast, and AEP math. The model is examining the patient, not
reading the nurse's notes.
Backend:
- risk_agent.py accepts streetview_image_d…
v0.8: Gemma 4 bounding-box detection on Street View
The streetview agent now returns a 2D bounding box per identified
indicator using Gemma 4's native object-detection capability. The
frontend overlays the boxes on the actual image as an SVG so users
can see exactly which pixels Gemma 4 saw.
Smoke-tested against Drexel Blvd Chicago: returns
- basement-level windows [moderate] box_2d=[400, 205, 458, 242]
- sloping driveway [low] box_2d=[455, 395, 580, 552]
both in the Gemini-convention yxyx 0-1000 normalized format.
Backend (streetview_agent.py + scripts/smoke_test_bbox.py):
- System prompt now describes the bounding-box convention
explicitly (yxyx, normalized 0-1000, top-left origin) — verified
via smoke test that Gemma 4 follows the explicit instruction
reliably while being unreliable about self-describing format.
- User prompt schema adds box_2d field per indicator entry.
- Post-parse validator drops box_2d if it isn't a valid 4-tuple of
numbers in [0, 1000] with ymin<ymax and xmin<xmax. Indicator
itself i…
The single deepest Gemma 4 capability we hadn't been using. Adds a
6th data agent that fetches a Google Street View image at the
property and asks Gemma 4 (vision) to identify visible flood-risk
indicators: basement-level windows, ground-floor HVAC, drainage
infrastructure, evidence of past water damage, elevation vs street.
Backend:
- New backend/app/tools/streetview.py — Google Street View Static
API client. Metadata-first pattern (free, doesn't count against
quota): walks radii None→50m→200m→500m looking for a real
panorama before fetching the image. Computes a bearing from the
panorama's actual location back to the geocoded address so the
camera aims AT the building (without this, default heading shows
the road, not the property — verified with a Drexel Blvd test
case where unaimed heading produced property_visible=false but
the aimed shot found a real impermeable-paving indicator).
Image returned as a data: URL ready to inline into the vision
request.
- New ba…
Adds a language dropdown in the chrome bar that controls the language
of all user-facing copy in the dossier. Hits the Digital Equity &
Inclusivity track, and is the strongest single demo moment for the
mission framing — a Pilsen homeowner getting their flood-risk briefing
in Spanish without having to translate jargon.
Backend:
- New backend/app/data/languages.py — registry of 7 supported codes
(en, es, zh, vi, ht, ar, tl) covering ~80% of non-English-speaking
households in US flood-prone metros (USCB ACS 2022). normalize()
collapses 'es-MX' → 'es' and unknown codes → 'en'.
- prompt_directive(code) emits the language directive injected into
the risk + advisor agent system prompts. Critical bilingual rule:
user-facing copy translates, but legal product names (NFIP flood
insurance, sewer/water-backup endorsement), URLs, phone numbers,
dollar amounts, and JSON keys stay English. Otherwise the homeowner
can't search/say the right thing to actually buy the product.
- Assess…
The frontend already used "FlutIQ" in the chrome wordmark and the
repo name has always been Gemma4Good_FlutIQ; the rest of the code
(backend app title, system prompts, smoke-test headers, skill
metadata, env-var name) had drifted into "FloodIQ" from the
original spec. Standardizing on FlutIQ — "Flut" is German for
flood/tide.
What changed:
- backend/app: APP_NAME, USER_AGENT, app title, agent + prompt
module docstrings
- backend/scripts: smoke-test User-Agent + display headers
- backend/README.md: HF Spaces frontmatter
- SKILL.md: skill name in YAML frontmatter (floodiq → flutiq)
- index.html: window.FLOODIQ_API → window.FLUTIQ_API
- floodiq-skill.tar → flutiq-skill.tar (git mv)
- placeholder domain: floodiq.pages.dev → flutiq.pages.dev
- new STATUS.md — working snapshot of architecture, fixed bugs,
known limitations, and what's left before submission
Intentionally NOT renamed:
- FLOODIQ_BACKEND_SPEC.md — historical build spec, referenced in
earlier commit messages; renaming wo…
advisor: stop inventing insurance products — drive from a verified catalog
The advisor was generating insurance recommendations directly from
Gemma 4's training data, which produced two real inaccuracies in the
Chicago dossier:
- "NFIP Preferred Risk Policy $400-600/yr" — that product was retired
in October 2021 when Risk Rating 2.0 launched; there is no longer
a separate PRP tier.
- "Chicago DWM 312-747-7030" — wrong number. The real DWM line is
311 or 312-744-7000. (This came from the original spec; I propagated
it without verifying.)
For a tool whose mission is reducing the scariness of insurance,
making up products and phone numbers is the opposite of helpful.
Architectural fix:
- New backend/app/data/insurance_catalog.py — hand-curated list of
real, verified flood insurance products (sewer endorsement, NFIP
post-RR2.0, private flood via Neptune/Wright, parametric via
FloodFlash US/Arbol) and city-specific resources (Chicago: MWRD
rain barrels at correct $21.96, CNT RainReady, real DWM phone).
- Advisor agent now passes the catalog into…
backend: full 7-agent pipeline working end-to-end
Adds the four remaining data agents (local/weather/news/archive),
the risk-analyst agent (Gemma 4 reasoning mode), and the advisor
agent. Orchestrator runs the 5 data agents in parallel, then risk,
then advisor, streaming agent_update SSE events along the way and
emitting the compiled dossier on complete.
Verified against 4521 S Drexel Blvd, Chicago — full assessment in
~30s, all 7 agents return structured JSON, and the risk agent's
reasoning_trace shows actual step-by-step math:
P = 1 - (1 - 0.005)^30
P = 1 - (0.995)^30
0.995^30 ≈ 0.860
P ≈ 0.14 (14%)
It also unit-converted the precipitation forecast (1.02 mm/hr ≈
0.04 in/hr) and compared to the 0.67 in/hr Chicago combined-sewer
overwhelm threshold from the system prompt before concluding low
near-term risk. Advisor wrote recs at 5th-grade level as instructed.
Spec deviations:
- archive_agent uses GDELT 24-month timespan as a Storm Events DB
proxy. NCEI Storm Events has no clean public JSON API (CSV bulk
per year only)…
Stands up the FastAPI backend with one agent fully wired through the
SSE pipe so the architecture is proven before we fan out the rest:
POST /api/assess → geocode → FEMA NFHL → Gemma 4 interpret
→ agent_update events → complete{dossier}
Verified end-to-end against two cases:
- Houston Allen Pkwy (Zone AE) → is_sfha=true, requires_insurance=true
- Chicago S Drexel (Zone X) → gap_warning generated unprompted,
surfacing the urban-sewer-backup blind spot that is the whole pitch
Smoke-tested Gemma 4 on OpenRouter free tier (scripts/smoke_test.py):
basic completion, reasoning mode, and OpenAI-format tool calls all
pass on google/gemma-4-31b-it:free.
Spec bugs fixed while building:
- llm/client.extract_reasoning read message.reasoning_details[i].content;
the actual key is .text. Also falls back to top-level .reasoning string.
- tools/fema requested VERSION_ID, which does not exist on NFHL layer 28.
ArcGIS returned HTTP 200 with an {"error": ...} b…