The Gemini review — every finding, and what we did about it

Run overnight 2026-08-04 · five rounds of back-and-forth · Gemini 3.1 Pro reviewing, Claude verifying and deciding · every claim below was reproduced on the live server before it was accepted, and every fix was tested after it was made.

The short version

Gemini raised 19 findings across five rounds. 14 were real and are fixed. 3 were wrong and are rejected with the evidence. 2 were mine — Gemini caught an error in my own reasoning and a self-inflicted mess, and both are fixed too.

The most expensive one: the appraisal engine was pricing a four-door building's expenses as if it had one door, printing a 3.90% cap rate where 3.27% is honest — about a 19% error in implied value, on a number real buyers and sellers read.

Nothing here changed what the site is meant to do. Two changes touch the shared backend that the live Neuman site also uses; both were regression-tested against the live site and it stayed up.

1 · Findings confirmed and fixed

fixed The appraisal cap rate was priced as a one-door building

Finding: market_cap_rate() computed each comparable sale's expenses with a hardcoded 1 unit, while the rent it compared against covered the whole building.

Reproduced and measured against the live expense module, on a four-door comp:

units=1 insurance $ 2,500 NOI $46,748 cap 3.90% units=4 insurance $10,000 NOI $39,248 cap 3.27%

Root cause: gross rent is rent-per-sqft × total sqft — the whole building — but insurance scales per door and the repairs floor is per door. One door's costs against four doors' rent inflates NOI, which inflates the extracted market cap rate.

Then Gemini caught my fix. I first fell back to 1 when the door count was unknown and called that "conservative". Gemini said that is backwards — it is the flattering guess, not the safe one. It was right, and worse than either of us assumed: I measured 294 of 400 multifamily comps carry no door count at all, so the fallback was the common path. Now a sale the MLS filed as income property with no provable door count is dropped from the sample and counted as skipped, the same as any other sale that cannot carry its own evidence.

Tested: rebuilt a real report end-to-end — 467 NE 1 AVE, Deerfield Beach, a genuine 4-unit — income approach still stated, cap rate extracted from 21 comparable sales, median 3.66%.

fixed A sale needing a 40% adjustment could enter the grid as "clean"

Each adjustment line is capped at 15% of the sale price so one bad MLS field cannot swing the value. But the "is this really comparable" test read the capped total — so a sale that genuinely needed a huge adjustment showed a small one and passed. The test now reads the raw uncapped sum; the capped figure is still what displays.

fixed Lead forms were dropping the property

Finding (Gemini, round 5): nobody had tested whether leads actually deliver.

I posted a real showing request and read it back out of the store. The lead arrived — but address, listingId, neighborhood and price were silently thrown away, because the handler whitelisted only name, phone, email, note and kind. A showing request was reaching the team with no property on it.

Fixed and re-tested — the same post now stores:

"address": "467 NE 1 AVE, Deerfield Beach" "listingId": "A11-TEST" "neighborhood": "Deerfield" "price": "1200000"

Forms that call the field message or summary instead of note are now aliased too, and anything else a form sends is kept rather than dropped.

fixed Password reset told strangers who has an account

Confirmed live: the API answered "No account found with that email" — so anyone could test addresses and map the customer list. Both real and unknown emails now get the identical answer. Verified on this site and the live one; login and session endpoints regression-tested; live site stayed up throughout. Reset-email failures are now logged instead of silently swallowed, and the rate-limit table evicts aged-out entries instead of wiping every visitor's counter at once.

fixed The preview was feeding the real advertising pixel

The Meta Pixel loaded on preview pages and pulled the real pixel id from the shared config, so review clicks landed in production ad analytics. It is now gated to the production hostname — dark on the preview, and it wakes up by itself at cutover with nothing to remember.

fixed 30 dead links the button guard could not see

The existing guard clicks tool buttons but skips the tool pages themselves. I wrote a second guard that crawls every local link on every page against the filesystem. First run found 30 dead links. I curl-tested ten distinct targets: nine were real live 404s/premium.html, /analyze.html, /find, /push.html, /area.html, /motivated.html, /migration_map.html, /re_sold_rented.html, /datamined_login.html. The tenth, /listing?key=, redirected into a directory that returns 403.

All ten now retarget to the real local page, wired into the link fixer so future copies inherit it. Current state: all local links resolve, 192 pages checked.

fixed No Equal Housing notice on 84 pages

Only the hand-built pages carried it; the copied tool pages had none. It is now injected by the one script that already walks every page and runs inside both copy scripts — so copies get it forever, not just today. Live on the tool pages.

fixed MLS listing data shown with no attribution

Gemini, round 5: neither of us had checked IDX compliance. Confirmed: only one page carried the "Listing data © MIAMI Association of REALTORS®, deemed reliable but not guaranteed" line, while six other pages render the same feed. Now injected on every page that pulls listing-level MLS data. Verified live.

fixed No plan for the cutover

The site replaces a live brokerage site with existing search rankings. There was no redirect plan. Built three things:

FileWhat it does
cutover_redirects.pyGenerates 108 verified 301s for old URLs that moved. It refuses to write the file if any target does not exist — it failed twice and made me map seven more pages by hand.
cutover_noindex.pyTurns search engines on: 174 pages plus 13 generator scripts, so noindex cannot sneak back on the next rebuild. Dry-run unless you pass --do.
CUTOVER.mdThe ordered runbook — backup, switch, redirects, reset-email domain, search engines on, rollback path.

fixed A regex that could have eaten a page

The nav swapper's pattern could run past the end of its own <nav> element and swap the wrong list. One live page has the shape that triggers it. Nothing was broken yet; it is bounded now, and re-running the swapper reports "applied to 0 pages" — proof it is idempotent.

2 · Findings not accepted

Gemini reviewed these rejections and agreed with all three.

rejected "The rate limiter trusts a spoofable header"

Evidence: the backend binds 127.0.0.1:25062 only — verified with ss -tlnp — and every nginx location does proxy_set_header X-Real-IP $remote_addr;, which overwrites whatever the client sent. There is no path from the public internet that reaches the app with an attacker-chosen IP. The other half of that finding — a global counter wipe — was real, and is fixed.

rejected "The outlier check skips 3-comp grids — change > to ≥"

Evidence: it would change nothing. The inner test needs len(near) ≥ 3 AND len(near) < len(kept); with exactly three comps kept that is "at least 3 and fewer than 3" — unsatisfiable either way. It also matches appraisal practice: you cannot drop a comparable when you are already at the minimum of three.

rejected "This line will crash on a missing key"

Evidence: I read all three return paths of the function that builds those records. Every one of them sets the key. No live crash path.

3 · What Gemini caught in my own work

  1. My "conservative" fallback was the flattering one. Covered above — it changed the fix from "guess one door" to "drop the sale and say so".
  2. I made a mess and it caught the smell. Editing VPS files through the Windows drive rewrote every line ending in three files: a 19-line change appeared as 2,086 lines, which then tripped the repository's secret scanner against six pre-existing hardcoded keys. Restored the line endings on all five affected files, re-checked syntax, restarted both services, and confirmed the real change is 19 lines. Written into the notes so it does not happen again.

4 · Verified after the changes

CheckResult
Tool-button guard (signed out)120 buttons across 206 pages — every one opens what it promises
Every local link on every pageall resolve, 192 pages checked
Redirect map108 lines, 0 unmapped, every target verified to exist
All 9 doors + home, live over HTTPS200 each
Login / forgot / reset pages200 each; wrong password still rejected cleanly
Lead submission, end to endposted and read back, with the property attached
Appraisal engine, end to endreal 4-unit report rebuilt, income approach intact
Both backend services after restartactive; live Neuman site 200 throughout

5 · Still open — nothing hidden

This is not a statement that the site is ready to launch. It is ready for you to walk it. The cutover runbook exists and its steps are testable, but no one has done a rehearsal, and the signed-in half of the site has never been swept by a guard.

6 · Where the work landed

RepositoryCommits
/opt/apps/prestige-neumanpixel gate · Equal Housing · MLS attribution · bounded nav regex · check_all_links.py · 10 link retargets · cutover kit
/opt/apps/bk-fl-calcreset enumeration closed · email failures logged · rate-limit eviction · lead property context
/opt/apps/reminedcap-rate door count · unknown-door comps dropped · over-adjusted judged on raw figures