No description
Find a file
Viktor Barzin c2e08fe46e wrongmove: add "seen" soft-hide decision with price-aware resurfacing
Opening a listing's detail sheet for >3s now passively marks it `seen`
and snapshots its current `total_price`. Seen listings are hidden from
the main list by default but automatically resurface when the price
changes (any direction). Distinct from `disliked` — explicit
like/dislike always overrides the passive seen state.

New "Hidden (N)" toggle on the FilterBar appears whenever at least one
listing is currently being hidden by the seen filter. Toggling it
reveals those rows in-place (without unmarking them) so the user can
review or explicitly clear via the existing Like/Dislike/Clear flow.

## Backend
- Alembic f7a8b9c0d1e2: `ALTER TABLE listingdecision ADD COLUMN
  price_at_decision FLOAT NULL`.
- `models/decision.py`: ListingDecision gains nullable
  `price_at_decision: float | None`.
- `services/decision_service.py`: adds `seen` to VALID_DECISIONS;
  set_decision accepts an optional `price_at_decision`; it's only
  forwarded to the repo for decision='seen' (other types null-out the
  column to keep semantics clean).
- `repositories/decision_repository.py`: upsert_decision now carries
  price_at_decision through the MySQL + SQLite upsert paths.
- `api/decision_routes.py`: SetDecisionRequest + DecisionResponse
  expose the new field.

## Frontend
- `types/index.ts`: DecisionType = 'liked' | 'disliked' | 'seen';
  ListingDecision gains `price_at_decision?: number | null`.
- `services/decisionService.ts`: setDecision sends the price only for
  decision='seen' (and only when it's a finite number).
- `hooks/useDecisions.ts`: rewritten to store `Map<key, DecisionEntry>`
  (decision + price snapshot). New `markSeen(id, price, type)` short-
  circuits on existing liked/disliked. New `getDecisionEntry`,
  `seenCount`.
- `App.tsx`: 3s `setTimeout` dwell timer fires markSeen when the
  detail sheet stays open. Filter logic in `processedListingData`
  hides `seen` rows whose `total_price === price_at_decision`, with
  `showHidden` bypass. Computes `hiddenCount` to drive the toggle.
- `components/FilterBar.tsx`: new conditional "Hidden (N)" / "Showing
  hidden (N)" toggle button (Eye / EyeOff lucide icons), surfaces only
  when hiddenCount > 0.

## Tests
- pytest: 2 new (test_set_seen_carries_price, test_liked_drops_price_
  even_if_supplied) + 1 updated to assert the new 5-arg repo
  signature. 24 passed.
- vitest: 6 new for useDecisions (markSeen liked/disliked skip, price
  snapshot, re-mark, null price, seenCount) + 5 new for decisionService
  payload shape. 221 total passed, tsc clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 11:07:44 +00:00
.claude remove local agents, replaced by global dev team [ci skip] 2026-03-23 00:17:54 +02:00
.github/workflows adding ruff auto check for pull requests as well as fixing all ruff errors (#1) 2025-09-14 19:40:18 +01:00
.woodpecker wrongmove: write VITE_MAPBOX_TOKEN to .env.production in CI (replaces broken build_args) 2026-05-15 22:10:25 +00:00
alembic wrongmove: add "seen" soft-hide decision with price-aware resurfacing 2026-05-16 11:07:44 +00:00
api wrongmove: add "seen" soft-hide decision with price-aware resurfacing 2026-05-16 11:07:44 +00:00
cli Add debug CLI listings subcommand (list, detail, stream, refresh) 2026-02-22 15:17:56 +00:00
config Add configurable request timeout and retry on TimeoutError 2026-02-21 17:50:36 +00:00
docs docs: add UI/UX redesign implementation plan 2026-02-28 15:46:30 +00:00
frontend wrongmove: add "seen" soft-hide decision with price-aware resurfacing 2026-05-16 11:07:44 +00:00
grafana Add Server-Timing headers to all API endpoints for per-request latency breakdown 2026-02-23 21:30:51 +00:00
k8s wrongmove: round-3 fix sweep — scrape pipeline, BUY tab, filter URL state, render hygiene, map polish 2026-05-10 22:27:29 +00:00
models wrongmove: add "seen" soft-hide decision with price-aware resurfacing 2026-05-16 11:07:44 +00:00
rec Add configurable request timeout and retry on TimeoutError 2026-02-21 17:50:36 +00:00
repositories wrongmove: add "seen" soft-hide decision with price-aware resurfacing 2026-05-16 11:07:44 +00:00
scripts Add setup-test-venv.sh for running tests without Docker [ci skip] 2026-02-14 11:44:49 +00:00
services wrongmove: add "seen" soft-hide decision with price-aware resurfacing 2026-05-16 11:07:44 +00:00
tasks wrongmove: round-3 fix sweep — scrape pipeline, BUY tab, filter URL state, render hygiene, map polish 2026-05-10 22:27:29 +00:00
tests wrongmove: add "seen" soft-hide decision with price-aware resurfacing 2026-05-16 11:07:44 +00:00
utils wrongmove: round-3 fix sweep — scrape pipeline, BUY tab, filter URL state, render hygiene, map polish 2026-05-10 22:27:29 +00:00
.dockerignore Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
.env.sample Add OSRM and OTP Docker services with setup scripts 2026-02-08 13:16:10 +00:00
.gitignore Add .worktrees/ to .gitignore for git worktree isolation 2026-02-21 15:48:34 +00:00
.style.yapf Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
alembic.ini Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
celery_app.py wrongmove: round-3 fix sweep — scrape pipeline, BUY tab, filter URL state, render hygiene, map polish 2026-05-10 22:27:29 +00:00
CLAUDE.md Add setup-test-venv.sh for running tests without Docker [ci skip] 2026-02-14 11:44:49 +00:00
csv_exporter.py Replace pandas with stdlib csv, apprise with direct Slack webhook, switch to opencv-headless 2026-02-21 19:47:10 +00:00
data_access.py Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
database.py Fall back to SQLite default when DB_CONNECTION_STRING is unset 2026-02-10 22:38:52 +00:00
docker-compose.yml Add structured JSON logging, OTel business metrics, and Grafana dashboard 2026-02-14 10:59:12 +00:00
Dockerfile wrongmove: round-3 fix sweep — scrape pipeline, BUY tab, filter URL state, render hygiene, map polish 2026-05-10 22:27:29 +00:00
GUIDE Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
listing_processor.py Fix metric imports: use module-level access instead of name imports 2026-02-14 11:21:49 +00:00
logging_config.py Add structured JSON logging, OTel business metrics, and Grafana dashboard 2026-02-14 10:59:12 +00:00
main.py Add debug CLI listings subcommand (list, detail, stream, refresh) 2026-02-22 15:17:56 +00:00
notifications.py Replace pandas with stdlib csv, apprise with direct Slack webhook, switch to opencv-headless 2026-02-21 19:47:10 +00:00
podman-compose.yml Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
poetry.lock Regenerate requirements.txt after dependency cleanup 2026-02-21 19:47:15 +00:00
pyproject.toml Replace pandas with stdlib csv, apprise with direct Slack webhook, switch to opencv-headless 2026-02-21 19:47:10 +00:00
README.md Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
redis_repository.py wrongmove: round-3 fix sweep — scrape pipeline, BUY tab, filter URL state, render hygiene, map polish 2026-05-10 22:27:29 +00:00
requirements.txt Regenerate requirements.txt after dependency cleanup 2026-02-21 19:47:15 +00:00
runall.sh Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
start.sh Add shared pre-push hook to run tests before pushing 2026-02-14 11:36:26 +00:00
TASKS.md Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
ui_exporter.py Fix photo extraction: look for both 'photos' and 'images' keys 2026-02-22 01:21:50 +00:00

Setup

  1. Instal deps:
poetry install && cp .env.sample .env
  1. Check .env if you want to customize settings for broker and db
  2. run ./start.sh

This starts the backend

To start the fronend:

cd frontend && cp .env.sample .env

Change the DEV_HOST to any name you want to use to access the web interface.

Next, setup the DNS record (e.g in your /etc/hosts) file. This is important as auth is done via external [authentik] service that needs to redirect to a name.

Run ./start.sh

This starts a Caddy proxy with correct certificates, and npm dev server. All requests going to the frontend are forwarded to the npm server and the ones for the backed (that go to /api/*) are forwarded to the backend service.

Lastly, reachout to Viktor to allowlist your DEV_HOST so that authentik can authorize callbacks to your host.

Formatting

yapf --style .style.yapf --recursive .

For VSCode - install yapf extension. Enable formatting using yap and the style file in this repo (there may be an easier way; I put this in my user settings json):

{
    "[python]": {
        "editor.formatOnSaveMode": "file",
        "editor.formatOnSave": true,
        "editor.defaultFormatter": "eeyore.yapf",
        "editor.formatOnType": false
      },
      "yapf.args": ["--style", "/home/wizard/code/realestate-crawler/crawler/.style.yapf"]
}

ADB commands (from /Applications/BlueStacks.app/Contents/MacOS):

Set proxy

./hd-adb shell settings put global http_proxy 192.168.9.110:8080

Disable proxy:

/hd-adb shell settings put global http_proxy :0

Connect adb

./hd-adb connect 127.0.0.1:5555

Disconnect adb

/hd-adb disconnect