wrongmove/tests/unit
Viktor Barzin 49e3514780 wrongmove: daily price-trend monitoring (per-listing badge + macro strip)
Two surfaces wired up so the user can "get a vibe of the market":

**Per-listing** — each PropertyCard now shows a small pill next to the
price when the listing's total_price moved >=1% over a 14-day lookback
(e.g. "↓ £200 (-4%) in 14d"). Drops render green, rises render red.
Computed from `price_history_json` by the daily aggregator and
denormalised onto the listing row so the streaming endpoint just
passes it through.

**Macro** — new always-visible inline strip above the chip strip
showing today's median total price, median £/m², and listing count
for the current filter's bedroom band, each with a 30-day % delta:
"Rent · 1-2 bed · 30d: Median £2,500 ↓ -4% · £/m² £50 ↓ -2% · Listings 4,200 ↑ +5%".

Both data sources are populated daily at 04:00 UTC by a new Celery
beat task that fires 1h after the 03:00 RENT scrape and feeds two
sinks: a per-listing update pass and an upsert to a new
`dailylistingaggregate` table keyed on
(snapshot_date, listing_type, min_bedrooms, max_bedrooms).

## Backend
- `models/listing.py`: Listing parent gains `price_14d_ago` + `price_
  change_pct_14d` nullable floats (inherited by RentListing/BuyListing).
  New `DailyListingAggregate` table model with unique constraint on
  (date, type, min_bed, max_bed).
- Alembic `a8b9c0d1e2f3`: adds the two columns to both listing tables
  and creates the aggregate table + date index.
- `services/market_aggregator.py` (new): `compute_trend_for_listing`,
  `update_per_listing_trend` (batched, idempotent), `_stats` (median
  + mean filtered to positive finite values), `compute_aggregate_
  snapshot` (dialect-aware MySQL / SQLite upsert), `fetch_trend_
  series` (range query for the API).
- `tasks/market_tasks.py` (new): `compute_daily_market_aggregates_task`
  Celery task wrapping both stages.
- `tasks/listing_tasks.py:setup_periodic_tasks`: registers the daily
  task at 04:00 UTC alongside the existing scrape schedules.
- `celery_app.py`: includes the new tasks module.
- `api/app.py`: new `GET /api/market_trend?listing_type=&min_bedrooms=&
  max_bedrooms=&days=` endpoint returning the daily series.
- `ui_exporter.py`: GeoJSON feature properties now carry
  `price_14d_ago` and `price_change_pct_14d` so the frontend can
  render the badge without an extra round-trip.

## Frontend
- `types/index.ts`: new `MarketTrendPoint`; `PropertyProperties` gains
  the two optional trend fields.
- `components/PropertyCard.tsx`: derived `trendBadge` (>=1% threshold,
  null-safe) rendered as a small pill on both card variants.
- `hooks/useMarketTrend.ts` (new): fetches the trend series, derives
  current-vs-oldest deltas per metric (% change rounded to 1dp).
- `components/MarketTrendStrip.tsx` (new): compact inline strip with
  three metric cells. Hidden when the aggregator hasn't produced any
  rows yet (graceful start during the first week post-launch).
- `App.tsx`: renders the strip above the chip strip whenever the
  active queryParameters are known.

## Tests
- pytest: 10 new (trend math edge cases including null history,
  malformed JSON, only-recent entries, drops, rises, zero current
  price; _stats empty / nonpositive filtering; upsert idempotency on
  an in-memory SQLite seed). 34 decision + aggregator tests pass.
- vitest: 8 new (useMarketTrend fetch URL, two-point delta,
  single-point null delta, empty series; PropertyCard trend badge
  arrow direction + sign for drops/rises, noise threshold, null
  guard). 229 tests pass total, tsc clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 12:02:25 +00:00
..
__init__.py Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
test_auth.py Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
test_cli.py Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
test_debug_cli.py Add debug CLI decisions, POIs, and tasks subcommands 2026-02-22 15:15:30 +00:00
test_decision_filtering.py fix: QA fixes for property decisions feature 2026-02-21 14:04:34 +00:00
test_decision_repository.py feat: add DecisionRepository with unit tests 2026-02-21 13:52:20 +00:00
test_decision_routes.py feat: add decision API routes with tests 2026-02-21 13:54:38 +00:00
test_decision_service.py wrongmove: add "seen" soft-hide decision with price-aware resurfacing 2026-05-16 11:07:44 +00:00
test_district_service.py Add comprehensive test suite: 219 new tests across backend and frontend 2026-02-10 21:59:45 +00:00
test_districts.py Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
test_dockerfile_uid.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
test_export_service.py Add comprehensive test suite: 219 new tests across backend and frontend 2026-02-10 21:59:45 +00:00
test_floorplan.py Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
test_floorplan_detector.py Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
test_frontend_build_config.py fix: skip frontend config tests when frontend/ dir absent (Docker build) 2026-02-21 15:52:43 +00:00
test_image_fetcher.py Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
test_listing_cache.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
test_listing_fetcher.py Refactor backend for cleaner error handling, DRY, and type safety 2026-02-10 22:19:24 +00:00
test_listing_processor.py Refactor backend for cleaner error handling, DRY, and type safety 2026-02-10 22:19:24 +00:00
test_listing_service.py Add comprehensive test suite: 219 new tests across backend and frontend 2026-02-10 21:59:45 +00:00
test_listing_tasks.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
test_market_aggregator.py wrongmove: daily price-trend monitoring (per-listing badge + macro strip) 2026-05-16 12:02:25 +00:00
test_metrics_guard.py Add API rate limiting, metrics guard, and audit middleware 2026-02-08 00:45:43 +00:00
test_models.py Add security regression tests for all hardening fixes 2026-02-08 19:42:53 +00:00
test_openapi_disabled.py Add API anti-abuse hardening: disable docs in prod, origin validator, exception handler 2026-02-08 20:06:46 +00:00
test_origin_validator.py Add API anti-abuse hardening: disable docs in prod, origin validator, exception handler 2026-02-08 20:06:46 +00:00
test_passkey_error_handling.py Add security regression tests for all hardening fixes 2026-02-08 19:42:53 +00:00
test_poi_validation.py Add security regression tests for all hardening fixes 2026-02-08 19:42:53 +00:00
test_query.py Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
test_query_splitter.py Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
test_rate_limiter.py Add security regression tests for all hardening fixes 2026-02-08 19:42:53 +00:00
test_redis_lock.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
test_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
test_repository.py Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
test_route_calculator.py Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
test_route_serializer.py Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
test_routing.py Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
test_schedule_config.py Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
test_security_headers.py Add API anti-abuse hardening: disable docs in prod, origin validator, exception handler 2026-02-08 20:06:46 +00:00
test_task_service.py Add security regression tests for all hardening fixes 2026-02-08 19:42:53 +00:00
test_throttle_detection.py Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/ 2026-02-07 23:01:20 +00:00
test_unhandled_exception.py Add API anti-abuse hardening: disable docs in prod, origin validator, exception handler 2026-02-08 20:06:46 +00:00