feat(location): district Location entity + indices (#948 part B) #1165

Closed
bot-backend wants to merge 2 commits from feat/location-entity-948b into main
Collaborator

Что

#948 Part B — «Локака» как first-class entity (§8.2), АДДИТИВНО (без district→FK рефактора). Завершает #948 (Part A insight — #1164).

Новая таблица location (ключ district_name, joinable строкой) с 4 нормализованными [0,1] индексами (NULL при отсутствии данных, никогда фабричный 0), переиспользуя per-district forecast-функции:

  • infra: _district_poi_score / _city_avg_poi_score (per-district POI-агрегат)
  • competition: market_metrics.overstock_index (доступный/залежавшийся конкурирующий сток — ортогонально demand; НЕ sell-through, который market-heat коррелирует с demand) ← semantic-фикс по ревью
  • demand: market_metrics.unit_velocity (saturating /50)
  • future_supply: future_supply_pressure.index (passthrough)

Содержимое

  • data/sql/146_location.sql — idempotent таблица + UNIQUE(district_name) + range-CHECK [0,1] + centroid GIST.
  • services/site_finder/locations.pycompute_location_indices (per-signal try/except — один источник падает, нулится только его индекс) + refresh_locations (SAVEPOINT-per-row, CAST not ::, ON CONFLICT).
  • workers/tasks/location_refresh.py + beat (Пн 07:00 МСК, после supply-layers).
  • api/v1/locations.py read-only (GET list + GET by name, 404) — analyst+admin via rbac, pilot frontend-gated (ACL-docstring точный).

Качество

  • code-reviewer: gates (service SQL + миграция); semantic-фикс competition⊥demand внесён (overstock вместо sell-through — честная метка, ортогональность тестом доказана). 34 location + 156 regression тестов, ruff, без новых deps. Аддитивно (parcels.py не тронут, 3 registration-строки), disjoint с Part A.

Refs #948 (Part B — закрывает #948 вместе с Part A).

## Что #948 **Part B — «Локака» как first-class entity** (§8.2), АДДИТИВНО (без district→FK рефактора). Завершает #948 (Part A insight — #1164). Новая таблица `location` (ключ district_name, joinable строкой) с 4 нормализованными [0,1] индексами (NULL при отсутствии данных, никогда фабричный 0), переиспользуя per-district forecast-функции: - **infra:** `_district_poi_score / _city_avg_poi_score` (per-district POI-агрегат) - **competition:** `market_metrics.overstock_index` (доступный/залежавшийся конкурирующий сток — **ортогонально demand**; НЕ sell-through, который market-heat коррелирует с demand) ← *semantic-фикс по ревью* - **demand:** `market_metrics.unit_velocity` (saturating /50) - **future_supply:** `future_supply_pressure.index` (passthrough) ## Содержимое - `data/sql/146_location.sql` — idempotent таблица + UNIQUE(district_name) + range-CHECK [0,1] + centroid GIST. - `services/site_finder/locations.py` — `compute_location_indices` (per-signal try/except — один источник падает, нулится только его индекс) + `refresh_locations` (SAVEPOINT-per-row, CAST not ::, ON CONFLICT). - `workers/tasks/location_refresh.py` + beat (Пн 07:00 МСК, после supply-layers). - `api/v1/locations.py` read-only (GET list + GET by name, 404) — analyst+admin via rbac, pilot frontend-gated (ACL-docstring точный). ## Качество - `code-reviewer`: ✅ gates (service SQL + миграция); semantic-фикс competition⊥demand внесён (overstock вместо sell-through — честная метка, ортогональность тестом доказана). **34 location + 156 regression тестов**, ruff, без новых deps. Аддитивно (parcels.py не тронут, 3 registration-строки), disjoint с Part A. Refs #948 (Part B — закрывает #948 вместе с Part A).
bot-backend added the
scope/backend
GG-форсайт
site-finder
labels 2026-06-08 08:18:13 +00:00
bot-backend added 1 commit 2026-06-08 08:18:14 +00:00
feat(location): district-level Location entity + indices (#948 part B)
Some checks failed
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) Has been cancelled
CI / backend-tests (pull_request) Successful in 6m23s
84a4aa5782
Promote district to a first-class `location` entity (ТЗ §8.2), ADDITIVE — no
district->FK refactor. New `location` table keyed by district_name (joinable by
string), carrying 4 normalized [0,1] indices (NULL when no data, never 0):
- infra: _district_poi_score / _city_avg_poi_score (per-district POI aggregate)
- competition: market_metrics.overstock_index (available/stuck competing supply
  — orthogonal to demand; NOT sell-through, which is market-heat correlated w/ demand)
- demand: market_metrics.unit_velocity (saturating /50)
- future_supply: future_supply_pressure.index (passthrough, already 0..1)

- data/sql/146_location.sql: idempotent table + UNIQUE(district_name) + range
  CHECK + centroid GIST
- services/site_finder/locations.py: compute_location_indices (reuses forecast
  per-district fns) + refresh_locations (SAVEPOINT per-row, CAST, ON CONFLICT)
- workers/tasks/location_refresh.py + beat (Mon 07:00 MSK, after supply-layers)
- api/v1/locations.py: read-only GET list + GET by name (analyst+admin via rbac,
  frontend pilot-gated)
- tests: 34 (normalization 0..1/null, competition⊥demand orthogonality, idempotent
  upsert, read API list/by-name/404)

Part of #948 (Part A insight shipped #1164).
bot-backend added 1 commit 2026-06-08 08:23:55 +00:00
Merge remote-tracking branch 'origin/main' into feat/location-entity-948b
All checks were successful
CI / changes (push) Successful in 6s
CI / changes (pull_request) Successful in 6s
CI / frontend-tests (push) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / backend-tests (push) Successful in 6m27s
CI / backend-tests (pull_request) Successful in 6m28s
e5835b25b7
# Conflicts:
#	backend/app/main.py
Author
Collaborator

Superseded by a rebased branch (main.py conflict with #1164 Part A resolved cleanly). Closing in favour of the linear-history PR.

Superseded by a rebased branch (main.py conflict with #1164 Part A resolved cleanly). Closing in favour of the linear-history PR.
bot-backend closed this pull request 2026-06-08 08:28:57 +00:00
All checks were successful
CI / changes (push) Successful in 6s
CI / changes (pull_request) Successful in 6s
CI / frontend-tests (push) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / backend-tests (push) Successful in 6m27s
CI / backend-tests (pull_request) Successful in 6m28s

Pull request closed

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#1165
No description provided.