feat(site-finder): own-portfolio data source for §25.3 cannibalization (#1169 PR1) #1170

Merged
bot-backend merged 1 commit from feat/own-portfolio-cannibalization-952 into main 2026-06-08 11:29:47 +00:00
Collaborator

Summary

PR 1 (Foundation) of #1169 — the "own portfolio" data source that the §25.3 true cannibalization overlap engine (PR 2) will consume. (§25.3 today is only a proxy = same-class competitor density, #986.)

Two origins, normalized to a common OwnProject shape (the 4 §25.3 axes: class / release-month / price ₽/м² / unit-mix + geo):

  • currentdomrf_kn_objects filtered by settings.own_developer_ids (matched against the numeric prefix of the composite TEXT dev_id = companyGroup; empty → [] with no DB hit, no hardcoded id);
  • future ← new manual-entry entity own_planned_project (migration 148) for the private pipeline domrf can't see.

get_own_portfolio(db) returns current + future; per-source try/except so one origin failing can't break the other; missing fields → None (never fabricated 0) → PR 2 can degrade honestly (§26).

Pieces

  • Config OWN_DEVELOPER_IDS (comma-sep → list[int], default []).
  • Migration 148_own_planned_project.sql — range + unit_mix CHECKs (JSONB validated via IMMUTABLE helper, since PG bans subqueries in CHECK), generated geom. Validated against ephemeral PostGIS: idempotent, constraints reject bad input.
  • /api/v1/own-projects CRUD — mirror of the insight entity; created_by from X-Authenticated-User (unspoofable, not in Create/Update schemas).
  • services/site_finder/own_portfolio.pyOwnProject (frozen) + get_own_portfolio.

Test plan

  • uv run pytest -k "own_project or own_portfolio or own_developer"44 passed
  • ruff check + mypy (new files) → clean
  • Tests cover: created_by-from-header, 401-without-auth, CAST-not-::, empty-ids-skips-DB, per-source failure isolation, None-not-0, config-parse matrix
  • code-reviewer APPROVE (domrf query cross-verified against schema migrations 43/57/113 + scraper dev_id convention)
  • needs-live-verify (prod DB): once OWN_DEVELOPER_IDS is set, confirm price_per_m2_min/max fill-rate + that the configured ids are companyGroup-prefix ints (data/config check, not code).

Notes

  • PRINZIP developer_id not yet set — config defaults [] (dormant) until the real domrf companyGroup id is resolved from a prod query. No id hardcoded.
  • Does NOT touch special_indices.py (PR 2) or parcels.py.
  • PR 2 will normalize obj_class case/language on both sides (reuse competitors._normalize_class).

Refs #1169

## Summary PR 1 (Foundation) of #1169 — the "own portfolio" data source that the §25.3 **true** cannibalization overlap engine (PR 2) will consume. (§25.3 today is only a proxy = same-class competitor density, #986.) Two origins, normalized to a common `OwnProject` shape (the 4 §25.3 axes: class / release-month / price ₽/м² / unit-mix + geo): - **current** ← `domrf_kn_objects` filtered by `settings.own_developer_ids` (matched against the numeric prefix of the composite TEXT `dev_id` = `companyGroup`; empty → `[]` with **no DB hit**, no hardcoded id); - **future** ← new manual-entry entity `own_planned_project` (migration 148) for the private pipeline domrf can't see. `get_own_portfolio(db)` returns `current + future`; per-source `try/except` so one origin failing can't break the other; missing fields → `None` (never fabricated 0) → PR 2 can degrade honestly (§26). ### Pieces - Config `OWN_DEVELOPER_IDS` (comma-sep → `list[int]`, default `[]`). - Migration `148_own_planned_project.sql` — range + unit_mix CHECKs (JSONB validated via IMMUTABLE helper, since PG bans subqueries in CHECK), generated `geom`. Validated against ephemeral PostGIS: idempotent, constraints reject bad input. - `/api/v1/own-projects` CRUD — mirror of the insight entity; `created_by` from `X-Authenticated-User` (unspoofable, not in Create/Update schemas). - `services/site_finder/own_portfolio.py` — `OwnProject` (frozen) + `get_own_portfolio`. ## Test plan - [x] `uv run pytest -k "own_project or own_portfolio or own_developer"` → **44 passed** - [x] `ruff check` + `mypy` (new files) → clean - [x] Tests cover: created_by-from-header, 401-without-auth, CAST-not-`::`, empty-ids-skips-DB, per-source failure isolation, None-not-0, config-parse matrix - [x] code-reviewer ✅ APPROVE (domrf query cross-verified against schema migrations 43/57/113 + scraper dev_id convention) - [ ] needs-live-verify (prod DB): once `OWN_DEVELOPER_IDS` is set, confirm `price_per_m2_min/max` fill-rate + that the configured ids are companyGroup-prefix ints (data/config check, not code). ## Notes - **PRINZIP developer_id not yet set** — config defaults `[]` (dormant) until the real domrf `companyGroup` id is resolved from a prod query. No id hardcoded. - Does NOT touch `special_indices.py` (PR 2) or `parcels.py`. - PR 2 will normalize `obj_class` case/language on both sides (reuse `competitors._normalize_class`). Refs #1169
bot-backend added 1 commit 2026-06-08 11:22:27 +00:00
feat(site-finder): own-portfolio data source for §25.3 cannibalization (#1169 PR1)
All checks were successful
CI / changes (push) Successful in 6s
CI / frontend-tests (push) Has been skipped
CI / changes (pull_request) Successful in 6s
CI / frontend-tests (pull_request) Has been skipped
CI / backend-tests (push) Successful in 6m25s
CI / backend-tests (pull_request) Successful in 6m22s
Deploy / build-frontend (push) Has been skipped
Deploy / changes (push) Successful in 5s
Deploy / build-backend (push) Successful in 1m31s
Deploy / build-worker (push) Successful in 2m39s
Deploy / deploy (push) Successful in 1m12s
df34e55ab4
Foundation PR: unified "our projects" source the §25.3 overlap engine (PR2)
will consume. Two origins normalized to OwnProject (class/timing/price/unit-mix):
- current <- domrf_kn_objects filtered by settings.own_developer_ids (numeric
  prefix of composite dev_id; empty -> [] graceful, no DB hit, no hardcoded id)
- future  <- new manual-entry own_planned_project entity (migration 148)

Adds OWN_DEVELOPER_IDS config (comma-sep -> list[int], default []),
own_planned_project table (range/unit_mix CHECKs via IMMUTABLE helper, generated
geom), /api/v1/own-projects CRUD (created_by from X-Authenticated-User), and
get_own_portfolio(db). Per-source graceful degradation; psycopg-v3 CAST clean.

Does not touch special_indices.py or parcels.py (out of scope).

Refs #1169
bot-backend merged commit df34e55ab4 into main 2026-06-08 11:29:47 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#1170
No description provided.