No description
Find a file
OpenCode Agent b411fe0a38 agent(coder_local): fix evaluateCriteria platform filter, add pipeline event logging, fix tests
- CRITICAL: evaluateCriteria platform filter used AND logic (both checks fire
  independently) causing 100% flat rejection. Fixed to OR-logic allowedSources
  matching (matching list-flats.ts pattern). 4 new test cases.
- Pipeline event logging: migration 00033 (pipeline_events table + indexes +
  cleanup), TypeScript types, pipelineRepo (logEvent, logEventWithTiming, getEvents),
  POST /api/webhooks/pipeline-log endpoint with SCRAPER_WEBHOOK_SECRET auth
- scripts/backfill-evaluations.sh for re-evaluating all flats after platform fix
- Fixed pre-existing test failures: added getServiceClient mock to stats test,
  unstable_cache mock to POI test
- SessionInfoBar: Search Now always visible
- Session token validation: migration 00032
- Agent configs, husky pre-commit hook, RLS migration
2026-07-03 00:57:20 +02:00
.agent_states agent(coder_local): fix evaluateCriteria platform filter, add pipeline event logging, fix tests 2026-07-03 00:57:20 +02:00
.husky agent(coder_local): fix evaluateCriteria platform filter, add pipeline event logging, fix tests 2026-07-03 00:57:20 +02:00
.opencode/agents agent(coder_local): fix evaluateCriteria platform filter, add pipeline event logging, fix tests 2026-07-03 00:57:20 +02:00
agents agent(pm_local): criteria-change re-evaluation + periodic link verification (with tests) 2026-07-01 00:22:44 +02:00
docker agent(pm_local): fix 0-flats criteria filter, notes IDOR (C2/C7), webhook de-fork (C3), admin session removal, Caddyfile fh domain 2026-06-30 11:27:22 +02:00
docs agent(pm_local): security hardening, criteria fix, password login, theme toggle, deploy 2026-06-29 19:50:37 +02:00
e2e agent(coder_local): fix e2e tests with admin auth, update SESSION_STATE, extend pm_local permissions 2026-06-28 21:47:56 +02:00
public agent(coder_heavy): scaffold Flat-Hunter project with Next.js 15, Supabase, PostGIS, LangGraph 2026-06-23 19:42:08 +02:00
scripts agent(coder_local): fix evaluateCriteria platform filter, add pipeline event logging, fix tests 2026-07-03 00:57:20 +02:00
src agent(coder_local): fix evaluateCriteria platform filter, add pipeline event logging, fix tests 2026-07-03 00:57:20 +02:00
supabase/migrations agent(coder_local): fix evaluateCriteria platform filter, add pipeline event logging, fix tests 2026-07-03 00:57:20 +02:00
templates agent(pm_local): rebuild all agent definitions with proper frontmatter, flatten tiers to _local naming 2026-06-28 12:34:51 +02:00
.agents.local.md agent(pm_local): rebuild all agent definitions with proper frontmatter, flatten tiers to _local naming 2026-06-28 12:34:51 +02:00
.artifacts.json agent(coder_local): add Prague district tests for evaluateCriteria + update SESSION_STATE.md 2026-07-01 23:43:39 +02:00
.dockerignore deploy: local PostgREST migration, docker compose, fixed auth + config files for Docker builds 2026-06-24 23:33:27 +02:00
.eslintrc.json agent(coder_heavy): scaffold Flat-Hunter project with Next.js 15, Supabase, PostGIS, LangGraph 2026-06-23 19:42:08 +02:00
.gitignore agent(pm_local): webhook secret alignment, PostGIS distance RPC (C5), slim middleware (C6), deploy to RPi 2026-06-29 23:54:20 +02:00
.project_rules.md agent(pm_local): rebuild all agent definitions with proper frontmatter, flatten tiers to _local naming 2026-06-28 12:34:51 +02:00
dev-proxy.mjs fix: local dev proxy for /rest/v1/ prefix stripping, valid JWT, db+rest containers, staleness fixes 2026-06-25 13:57:31 +02:00
eslint.config.mjs agent(coder_heavy): scaffold Flat-Hunter project with Next.js 15, Supabase, PostGIS, LangGraph 2026-06-23 19:42:08 +02:00
gotchas.md agent(pm_local): update SESSION_STATE.md + gotchas.md for session close 2026-07-01 13:28:33 +02:00
next-env.d.ts agent(coder_heavy): scaffold Flat-Hunter project with Next.js 15, Supabase, PostGIS, LangGraph 2026-06-23 19:42:08 +02:00
next.config.mjs deploy: local PostgREST migration, docker compose, fixed auth + config files for Docker builds 2026-06-24 23:33:27 +02:00
package-lock.json feat: notifications, collaboration, per-user scrapers, city/country search 2026-06-28 00:47:23 +02:00
package.json feat: notifications, collaboration, per-user scrapers, city/country search 2026-06-28 00:47:23 +02:00
playwright.config.ts agent(coder): add per-user platform selection, check scheduler platform flags, redesign LLM provider config, fix dashboard flash, add e2e tests 2026-06-27 13:09:10 +02:00
pm_local_backup.md agent(coder_local): fix e2e tests with admin auth, update SESSION_STATE, extend pm_local permissions 2026-06-28 21:47:56 +02:00
postcss.config.mjs agent(coder): rewrite scrapers (Sreality HTML parsing, Bezrealitky Apollo cache), add Docker infra, migrate to ESM 2026-06-24 21:39:10 +02:00
README.md agent(pm_local): fix RLS global flats policy + universal password gate + C8 caching + C9 cleanup, deploy to RPi 2026-06-30 09:52:18 +02:00
requirements.txt feat: stats dashboard, search criteria filtering, POI seeding 2026-06-26 00:33:40 +02:00
SESSION_STATE.md agent(coder_local): add Prague district tests for evaluateCriteria + update SESSION_STATE.md 2026-07-01 23:43:39 +02:00
TOOLS.md agent(coder_heavy): scaffold Flat-Hunter project with Next.js 15, Supabase, PostGIS, LangGraph 2026-06-23 19:42:08 +02:00
tsconfig.json deploy: local PostgREST migration, docker compose, fixed auth + config files for Docker builds 2026-06-24 23:33:27 +02:00
vitest.config.ts agent(coder): scaffold test infra — vitest + testing-library + Playwright e2e, 22 unit + 3 e2e tests passing 2026-06-24 22:09:19 +02:00

Flat-Hunter — Prague Property Command Center

Stack: Next.js 15.3 / React 19 / Tailwind 4 / Supabase (Postgres+PostGIS) / Mapbox GL JS / LangGraph
Runtime: Node 24, Python 3.12 (LangGraph agent on RPi 5)
Deploy target: Raspberry Pi 5 (ARM64, headless), containerized via Docker Compose
Repo: Private


What It Does

  • Scrapes Czech rental flat listings from Sreality.cz and Bezrealitky.cz via Python agents
  • Stores them in PostgreSQL with PostGIS geospatial support
  • Presents listings in a Next.js web dashboard with dark-themed UI, map view, charts
  • Evaluates flats against per-user search criteria (price range, layout, status)
  • Machine translation of Czech descriptions to English via LangGraph + LLM
  • Link verification, contact extraction (phone/email/agency) via LangGraph agents
  • Points of Interest: proximity search for metro, tram, train stations, shopping malls (via PostGIS ST_Distance)
  • Notifications via email (SMTP) and WhatsApp (Twilio)
  • Flat sharing between sessions, flat notes, custom fees, CSV export
  • Admin panel for scraper config, LLM provider management, monitoring
  • Financial summaries (total cost, sunk costs, monthly run-rate)
  • Session-based auth (no credentials needed — SHA256 hash = identity)
  • Deployed to Raspberry Pi 5 with Caddy reverse proxy

Architecture Diagram

┌─────────────────────────────────────────────────────┐
│                   Caddy (reverse proxy)              │
│   :443 → :9000 (Next.js)   :9001 → :3001 (PostgREST)│
└──────┬──────────────────────────┬───────────────────┘
       │                          │
       ▼                          ▼
┌──────────────┐          ┌──────────────┐
│  Next.js App │          │  PostgREST   │
│   (Node 24)  │          │  (port 3001) │
│   port 9000  │          └──────┬───────┘
└──────┬───────┘                 │
       │                         │
       │  ┌──────────────────────┤
       │  │                      │
       ▼  ▼                      ▼
┌─────────────────────────────────────┐
│         PostgreSQL 16 + PostGIS     │
│         (Docker, port 5434)         │
│                                     │
│  Tables: flats, users_sessions,     │
│  scrape_queue, pois, notifications, │
│  flat_notes, scraper_config, ...    │
└─────────────┬───────────────────────┘
              │
              ▼
┌─────────────────────────────────────┐
│     Python Scheduler (Docker)       │
│     Polls scrape_queue every 60s    │
│     │                               │
│     ├── scrape_sreality.py          │
│     └── scrape_bezrealitky.py       │
│              │                      │
│              ▼ POST /api/webhooks   │
│     ┌────────────────────┐          │
│     │  LangGraph Agents   │          │
│     │  - link_verifier    │          │
│     │  - contact_extractor│          │
│     │  - translator       │          │
│     └────────────────────┘          │
└─────────────────────────────────────┘

Data Flow

Request → middleware (session gate)
        → route handler
        → withSession() (auth guard, session resolution, IP tracking)
        → application use-case
        → repository (Supabase client)
        → PostgreSQL / PostGIS

TypeScript Project Structure

src/
├── app/api/              # HTTP route handlers (~30 endpoints)
│   ├── admin/            # Admin panel API (separate auth)
│   ├── auth/             # Session login, password, token refresh
│   ├── flats/            # Flat CRUD, notes, fees, batch, export, stats
│   ├── notifications/    # User notification read/seen
│   ├── pois/             # Nearby Points of Interest
│   ├── scraper/          # Scraper trigger, status
│   ├── scraper-sessions/ # Admin scraper session listing
│   ├── shares/           # Flat sharing (invite, accept, reject)
│   └── webhooks/         # Inbound from Python agents (scraper, metrics)
├── application/          # Use-case orchestration layer
│   ├── auth/             # login, verify-session
│   ├── fees/             # add, delete, list fees
│   ├── flats/            # list, create, update, get, batch, export, stats, evaluate
│   └── scraper/          # get/update config, trigger scrape, status
├── domains/              # Pure types + business logic
│   ├── auth/types.ts     # SessionHash, FlatStatus, SearchCriteria
│   ├── flat/types.ts     # Flat entity (all fields)
│   ├── flat/financials.ts
│   ├── poi/types.ts
│   └── notification/types.ts
├── infrastructure/       # External service adapters
│   ├── supabase/repositories/  # session, flat, fee, contact, notification,
│   │                            # scraper-config, scraper-session, admin, share
│   └── notifications/          # email-service (nodemailer), whatsapp-service (Twilio),
│                                # dispatcher (routes to both)
├── lib/                  # Cross-cutting utilities
│   ├── auth/             # session, session-tokens, session-password,
│   │                      # admin-auth, timing-safe
│   └── supabase/client.ts
└── shared/               # Shared infrastructure
    ├── errors.ts         # AppError, UnauthorizedError, NotFoundError, etc.
    └── api/              # withSession() wrapper, getSessionHash()

Python Agents (agents/)

File Description
scheduler.py Job scheduler — polls scrape_queue, spawns scrapers per-user with concurrency limit
scrape_sreality.py Sreality.cz scraper — parses rental listings, posts to webhook
scrape_bezrealitky.py Bezrealitky.cz scraper — fetches via JSON API
flat_hunter_agent.py Main LangGraph runtime — reads LLM config from DB, runs inference
link_verifier_agent.py Link verification — checks if listing URLs are still active
contact_extractor_agent.py Contact extraction — regex + LLM phone/email/agency extraction
translator_agent.py Czech→English translation via LLM
seed_pois.py POI seeder — queries Overpass API for Prague transit stations

Database Migrations (supabase/migrations/)

27 idempotent SQL migrations (0000100027):

  • Core schema: flats, users_sessions, pois (with PostGIS geometry)
  • RPC functions: get_flat_stats, nearby_pois, list_flats_by_distance, ingest_flat
  • Scraper: scraper_config, scrape_queue (per-user with cron/manual trigger types)
  • Notifications: channels (email/WhatsApp), rules (critical/general)
  • Multi-tenancy: session-aware filters, per-user dedup
  • Admin: admin_users, admin_sessions
  • Caching: stats and POI endpoints use unstable_cache with tag invalidation

Setup

Prerequisites

  • Node.js 24+
  • Python 3.12+
  • Docker + Docker Compose

Local Development

  1. Clone and install dependencies:

    npm install
    pip install -r requirements.txt
    
  2. Configure environment:

    cp .env.local.example .env.local  # if example exists, otherwise create .env.local
    

    Required: NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY Optional: SMTP/Twilio credentials for notifications, ADMIN_BACKDOOR_CODE for e2e tests

  3. Start the database stack:

    cd docker
    docker compose up -d db migrate rest
    
  4. Seed Points of Interest (one-time):

    python3 agents/seed_pois.py
    
  5. Start the dev server:

    npm run dev
    

    Opens on http://localhost:3000 with the dev proxy forwarding Supabase requests to PostgREST on :3001.

  6. Start the scheduler (optional, for scraping):

    cd docker
    docker compose up -d scheduler
    

Docker Production Deploy (Raspberry Pi 5)

# On the RPi, clone the repo and:
cd docker
ADMIN_BACKDOOR_CODE=your-secret docker compose up -d

Services: db (PostgreSQL), migrate (auto-runs on start), rest (PostgREST), web (Next.js, port 9000), scheduler (Python scraper loop).

Place a Caddy reverse proxy in front (see docker/Caddyfile):

  • flat.example.comlocalhost:9000 (Next.js)
  • Handles /rest/v1/ prefix stripping for PostgREST on localhost:3001

Environment Variables

Variable Required Description
NEXT_PUBLIC_SUPABASE_URL Yes Supabase/PostgREST endpoint
NEXT_PUBLIC_SUPABASE_ANON_KEY Yes Anon key for PostgREST
SUPABASE_SERVICE_ROLE_KEY Admin Service role key for privileged operations
ADMIN_BACKDOOR_CODE E2E tests Session hash that bypasses all auth checks
DATABASE_URL Scheduler PostgreSQL connection string for scheduler.py
SMTP_HOST/PORT/USER/PASS/FROM Notifications Email notification config
TWILIO_ACCOUNT_SID/AUTH_TOKEN/WHATSAPP_FROM Notifications WhatsApp notification config
POSTGRES_PASSWORD Docker DB password (default: flat_hunter_local_dev)
SCHEDULER_POLL_SECONDS Scheduler Poll interval (default: 60)
MAX_CONCURRENT_SCRAPERS Scheduler Max parallel scrapers (default: 2)
SCRAPER_WEBHOOK_SECRET Scheduler+Web Shared secret for scraper→webhook auth

Available Scripts

Script Description
npm run dev Start dev server (Next.js + proxy)
npm run build Production build (includes lint + typecheck)
npm start Start production server
npm run lint ESLint
npm run typecheck tsc --noEmit
npm test Vitest unit/integration tests
npm run test:e2e Playwright end-to-end tests
npm run migrate Apply Supabase migrations to local DB

Key Design Decisions

Session Auth (zero-credential): Users are identified by SHA256(random-seed) = session_hash. No passwords, no OAuth. Sessions expire after 1 month unless marked is_paid. Admin access uses a separate cookie-based auth system.

Admin Backdoor: The ADMIN_BACKDOOR_CODE env var allows a known session hash to bypass all auth checks — used exclusively for e2e tests. All comparisons use constant-time timingSafeEqual().

Data Deduplication: Every incoming listing is deduplicated by source_url (URL-normalized) and composite_hash (MD5 of address + layout + price). Handled in the ingest_flat() RPC.

Caching: Stats (/api/flats/stats) and POI (/api/pois/nearby) endpoints use Next.js unstable_cache with tag-based invalidation. Flats mutation paths call revalidateTag('flat-stats') automatically.

Middleware (slim): The Edge middleware only checks session presence (redirects missing sessions). Heavy session resolution, validation, expiry checks, and IP tracking live in withSession(), executed in the Node.js runtime where Supabase access is available.

Repository Pattern: All database access goes through repository objects in src/infrastructure/supabase/repositories/. Route handlers never call Supabase directly (with controlled exceptions for admin routes).

Quality Gates: tsc --noEmit (0 errors required), ESLint (0 errors), Vitest, Playwright e2e. All gates must pass before commit.

Commit Convention: agent({agent_id}): {imperative verb} {summary} Example: agent(coder_local): implement PostGIS nearest-POI RPC


Codebase Health

See docs/CODEBASE_AUDIT.md for the most recent full audit (C1C9, priority-ordered).
See gotchas.md for accumulated failure log and prevention rules.
See TOOLS.md for custom tool configurations.