/ 01 — Audit

The architecture is sound. The wiring inside it is not yet honest.

v1 runs — FastAPI, a LangGraph workflow, SQLAlchemy memory. But two nodes fetch real data then discard it, so the LLM fabricates the rest. This page is the audit of what exists, the verdict, and the v2 that makes fabrication impossible by construction.

/ 02 — Runtime flow

Request pipeline

flowchart TD
    LM(["landmark"]):::ok --> ST["POST /chat
JWT-authenticated request"]:::ok ST --> WF["execute_chat_workflow()
LangGraph"]:::ok WF --> N1["N1 · GET_LOCATION_AND_LOCALITY
Mapbox geocode → lon/lat + localities · cached"]:::ok N1 -.parallel.-> N2["N2 · FIND_NEARBY_SCHOOLS
Foursquare ICSE · fetched, then printed and discarded · returns {}"]:::bad N1 -.parallel.-> N3["N3 · FIND_NEARBY_PLACES
Foursquare metro · fetched, then printed and discarded · returns {}"]:::bad N1 --> N4["N4 · GET_AQI
Open-Meteo for target coords · cached"]:::ok N4 --> N5["N5 · RANK_BY_AQI
Open-Meteo per locality → sorted ascending"]:::ok N5 -. edge commented out .-> N6["N6 · GET_HOUSE_RENTALS
Apify housing.com · signature mismatch · disabled"]:::dead N2 --> LLM N3 --> LLM N5 --> LLM["LLM · Kimi K2.6 advisory
prompt gets coords + AQI + ranked only
schools/metro claims hardcoded → fabricates"]:::bad LLM --> EN[("END · save_house_rental() → ChatResponse
persists user_location + top_matches JSON")]:::ok classDef ok fill:#15102a,stroke:#f4a93b,stroke-width:1.5px,color:#f5ede0; classDef bad fill:#1c1635,stroke:#d96a4a,stroke-width:1.5px,color:#f5ede0; classDef dead fill:#15102a,stroke:#8a7f99,stroke-width:1px,color:#8a7f99,stroke-dasharray:4 3;
reaches the report (honest) fetched but discarded / fabricated dead / disabled

/ 03 — Schema

Data model

SQLAlchemy. backend/database.py. JSON columns imply Postgres. No migrations yet — create_all() runs at import. The relationships below, then v2's collections after the migration.

V1 — NOW · SQLAlchemy

erDiagram
  User ||--o{ HouseRental : "owns (nullable FK)"
  User {
    int id PK
    str username UK
    str email UK
    str password_hash "bcrypt"
  }
  HouseRental {
    int id PK
    int user_id FK "nullable"
    str user_location
    json top_matches
    json top_matches_coordinates
  }
  APICache {
    int id PK
    str api_name
    text cache_key UK
    json response_data
    datetime created_at
  }
        

V2 — POCKETBASE · per-user via user = @request.auth.id

erDiagram
  users ||--o{ searches : "owns"
  users ||--|| profile : "one per user"
  users {
    string id PK
    string email UK
    string password "built-in auth"
  }
  searches {
    string id PK
    string user FK
    string landmark
    float lat
    float lon
    json aqi
    json ranked
    json schools "REAL — honesty fix"
    json metro "REAL — honesty fix"
    text advisory
    date created
  }
  profile {
    string id PK
    string user FK
    json saved "localities"
    json notes
  }
  cache {
    string id PK
    string api_name
    string cache_key UK
    json data
    date created
  }
        

Detailed v1 field types (the tables being migrated away from):

User

1 ──< N HouseRental
idint · pk
usernamestr · unique
emailstr · unique
password_hashstr · bcrypt

HouseRental

belongs to User (nullable FK)
idint · pk
user_idfk · nullable
user_locationstr
top_matchesJSON
top_matches_coordinatesJSON

APICache

standalone · all external calls
idint · pk
api_namestr
cache_keytext · unique
response_dataJSON
created_atdatetime

/ 04 — Migration

Target architecture — v2

The flow is linear. It doesn't need LangGraph, FastAPI, SQLAlchemy, hand-rolled JWT, or external Postgres. Collapse to one PocketBase binary + one hook + a vanilla PWA — the stack already shipped at og.maahaa.dev. Full plan in docs/MIGRATION.md.

geocode
schools
metro
AQI
rank
LLM
Concern v1 — now v2 — PocketBase
Server FastAPI + uvicorn pocketbase serve — one binary
DB / ORM SQLAlchemy + external Postgres embedded SQLite + collections
Auth hand-rolled JWT + bcrypt PocketBase built-in bug 0.2 gone
Orchestration LangGraph graph sequential calls in one hook bug 0.1 gone
LLM OpenAI SDK (Python) $http.send in hook, server key only
Frontend none — CLI + JSON vanilla PWA, same origin, iOS-ready

v2 — one linear hook, fabrication impossible

flowchart TD
    A[landmark] --> B["POST /api/advisory
PocketBase auth"] B --> G[geocode
Nominatim / Ola] G --> O1[Overpass metro
cached] G --> O2[Overpass schools
cached] G --> AQ[Open-Meteo AQI
per locality] O1 --> ASM[assemble REAL data] O2 --> ASM AQ --> RK[rank ascending] --> ASM ASM --> LLM["LLM via $http.send
prompt = ONLY verified fields"] LLM --> S[(searches collection
ranked + schools + metro + advisory)] LLM --> R[JSON to PWA]

/ 05 — Read next