# NestGuard

**Point it at the room. Know what could hurt them.**

A photo of a room — a bathroom, a stairwell, a hallway — goes in. Back comes a scored,
prioritized fall-hazard report: numbered tags pinned right on the photo where each
issue is, a risk stamp (CLEAR / MINOR / CAUTION / URGENT), and a cost-ranked fix list.

## Why this exists

Falls are the leading cause of injury for adults 65+. **1 in 4 older adults falls
every year** — about 14 million falls, ~3 million ER visits, and ~1 million
hospitalizations annually, costing the U.S. healthcare system $50B+/year.
([CDC](https://www.cdc.gov/falls/data-research/facts-stats/index.html))

Almost nobody actually walks a parent's or grandparent's house room-by-room against
a real safety checklist — not because they don't care, but because nobody hands
them the checklist. NestGuard is that checklist, applied instantly to a photo.

## Architecture — same pattern as before, on purpose

A deterministic scoring engine (`hazards.js`) computes every number. The LLM
(Gemini) never invents hazard categories or scores anything — it only maps what
it sees in a photo onto a **fixed taxonomy taken directly from the CDC's "Check
for Safety" checklist**, then explains the already-computed result in plain

### Two reliability mechanisms worth knowing about

1. **Exhaustive checklist, not free enumeration.** The model is forced to explicitly
   rule in/out all ~19 taxonomy hazards on every scan, rather than freely "listing
   what it sees" — the latter reliably under-reports (LLMs tend to find the single
   most salient issue and stop). See `worker.js`'s `ALL_CHECKS`/`SCAN_SYSTEM`.
2. **Scene-precondition gating, enforced in code, not just prompted.** Some hazards
   only make sense given a scene fact (e.g. "dark path to bathroom" requires a
   bathroom actually visible; "no nightlights visible" is meaningless on a daylight
   photo). The model reports scene facts (`sceneContext`) once per photo; `worker.js`'s
   `meetsPrecondition()` — not the LLM — has final say on whether a hazard counts.
   Same "deterministic engine decides" philosophy as the fixed-taxonomy hallucination
   guard, just applied one level deeper.

### Regression-testing changes: `eval/run-eval.js`

Prompt/schema changes should be checked against a labeled test set, not eyeballed
one photo at a time — that doesn't scale and won't catch regressions elsewhere.

```bash
cd eval
node run-eval.js                # 1 run per labeled image
node run-eval.js --repeats=3    # also surfaces call-to-call LLM inconsistency
```

Add more real test photos to `eval/images/` and label them in `eval/labels.json`
(`mustFlag` / `mustNotFlag` / `optional`, the last for genuinely ambiguous calls a
human labeler wouldn't be confident about either) as you find them. This is the
actual answer to "we can't manually re-check every photo" — the harness is the
thing that scales, not another one-off prompt patch.
English. Any hazard/room the model returns that isn't in the fixed list is
silently discarded by the scoring engine (see `scoreHazards`'s taxonomy lookup) —
a hallucination guard, not just a formatting nicety.

**Important honesty note:** CDC's "Check for Safety" checklist is yes/no —
it doesn't have an official point-based score. The 0–100-ish risk score and
CLEAR/MINOR/CAUTION/URGENT bands in `hazards.js` are **our own heuristic**,
built on top of CDC's real hazard categories, not a CDC-endorsed formula. The UI
and this README say so — don't let the pitch imply otherwise.

## Sources

- CDC "Check for Safety: A Home Fall Prevention Checklist for Older Adults" —
  public domain, STEADI initiative, originally published 1999:
  [PDF](https://www.cdc.gov/steadi/pdf/steadi-brochure-checkforsafety-508.pdf)
- Falls statistics: [CDC Facts About Falls](https://www.cdc.gov/falls/data-research/facts-stats/index.html)
- Fix-cost ballparks in `hazards.js` are typical U.S. retail estimates (grab bars,
  nightlights, non-slip strips, motion-sensor lights) — approximate, editable.
- Medicare Advantage plans sometimes cover up to ~$500/year for grab bars/shower
  seats as an OTC benefit; Medicaid waivers cover home safety modifications in many
  states — real, verifiable growth angle for "connect flagged hazards to a benefit
  you may already qualify for," not a fabricated claim.

**This is an educational screening tool, not a professional home safety
assessment.** Say so in the pitch. For anyone at elevated fall risk, the real next
step is an in-person occupational therapy home visit.

## Deploy (100% free tier)

```
Browser (Cloudflare Pages, static)
  index.html / style.css / app.js / hazards.js
        |
        v
Cloudflare Worker (free tier, 100k req/day)
  worker/worker.js — holds API keys as secrets
        |                        |
        v                        v
  Google Gemini API        You.com Research API
  (gemini-2.5-flash,       (optional — "verify against
   vision + JSON schema)    live CDC guidance" button)
```

### 1. Get a Gemini API key (required)
[aistudio.google.com/apikey](https://aistudio.google.com/apikey) — no card needed.

> **Known issue, confirmed 2026-07-24: a brand-new project's free tier can be
> capped at just 20 `gemini-2.5-flash` requests PER DAY**, not the 250+/day
> figures quoted in most 2026 docs/blog posts. Confirmed directly from Google's
> own error payload (not a guess): `"quotaId": "GenerateRequestsPerDayPerProjectPerModel-FreeTier",
> "quotaValue": "20"`. The `retryDelay` field in the error (~30s) is misleading —
> it does NOT mean the quota clears in 30 seconds; this is a genuine daily cap,
> confirmed by retrying after the delay and still getting rejected.
>
> **Fix:** in Google Cloud Console, check whether this project has a billing
> account linked. Projects without one get this much stricter free quota;
> linking a billing account (still free at this usage level, no charges) is
> what raises it to the normal free-tier limits. Do this well before a live
> demo — don't discover a 20-request daily cap mid-pitch. Test it by running
> the eval harness (`eval/run-eval.js`) the day before and confirming it
> doesn't 429 partway through.

### 2. Deploy the Worker
```bash
cd worker
npx wrangler login
npx wrangler deploy
npx wrangler secret put GEMINI_API_KEY
npx wrangler secret put YDC_API_KEY   # optional
```

### 3. Point the frontend at your Worker
Set `WORKER_URL` at the top of `app.js` to the URL wrangler prints.

### 4. Deploy the static site
```bash
cd ..
npx wrangler pages deploy . --project-name=nestguard
```

## How to realistically test this

You don't need access to an actual elderly relative's home to build real
confidence in the scan quality before recording a demo. Do this in order:

**Phase 1 — scoring engine only, no API key needed (already done in this repo):**
Run `node hazards.js`-style unit checks (see the test used during development) with
hand-built hazard lists to confirm point totals and CLEAR/MINOR/CAUTION/URGENT
bands make sense, and that hazards outside the fixed taxonomy get silently dropped.

**Phase 2 — once you have a Gemini key, test the vision scan against a deliberate
set of real photos, covering both hits and misses:**
1. **Your own home**, 5–8 photos: a genuinely tidy hallway (should score low/clear —
   checks for false positives), a bathroom without a visible grab bar, a staircase
   with something left on a step or no handrail, a dim hallway at night, a kitchen
   with items on a high shelf.
2. **A deliberate "trick" photo** — something with no room in it at all (a laptop,
   a pet, your hand) — confirm it returns an empty hazard list instead of
   hallucinating a room.
3. **Free stock photos** for variety beyond your own house — search Unsplash or
   Pexels for terms like "cluttered staircase," "bathroom interior," "dim hallway"
   to test against homes that aren't yours.
4. Check specifically for: **false positives** (a hazard claimed that isn't
   there), **false negatives** (an obvious hazard missed), and **graceful
   handling** of blurry/dark/irrelevant photos.

**Phase 3 (optional, strengthens the pitch but not required):** if you have any
willing family member's house available, a couple of real photos from an actual
lived-in home (not staged) is the strongest possible validation — even without
filming their reaction, testing on real photos still improves your confidence in
what you're demoing.

## What's intentionally out of scope for the demo

- Single-photo hazard flagging only — no measurement claims (e.g. no "this ramp
  is at an 8° slope"), since a 2D photo without a reference object genuinely can't
  support that. The model is prompted to only report what's visibly evident.
- Federal/general guidance only — no attempt to model state-specific building
  codes or insurance-specific rules.
- The photo-pin coordinates are approximate (normalized 0–1 position from the
  model) — if a hazard has no usable position, it still appears in the checklist,
  just without a pin on the photo.
