Задача
Владелец ПВЗ Ozon выбирает помещение под новую точку так: смотрит карту открытия в кабинете партнёра, объезжает район, звонит по объявлениям, прикидывает поток на глаз. Решение на 300–400 тысяч рублей вложений принимается по ощущению «тут вроде людно». Данных о том, сколько людей реально дойдёт до этой двери и когда вложения вернутся, у него нет.
Система отвечает на четыре вопроса:
- Где в городе вообще есть незакрытый спрос?
- Насколько хорошо конкретное место — и почему именно так?
- Пройдёт ли помещение формальные требования Ozon?
- Когда оно окупится при этой площади и этой аренде?

Что показали данные
Три вещи изменили модель после того, как выгрузку посчитали целиком.
Правила «не ближе 700 метров» не существует
Считается, что между двумя пунктами должно быть не меньше 700 метров. Выгрузка всех действующих ПВЗ города показала: медиана фактического расстояния между ними — 185 метров, 87 % стоят ближе 300. Больше того, места, которые рекомендует открывать сам Ozon, лежат в среднем в 170 метрах от уже работающего пункта.
Жёсткая отсечка по расстоянию убрана. Вместо неё — гравитационная модель Хаффа: доля спроса, которая достаётся точке, падает как квадрат расстояния и делится с соседями. Близкий конкурент не запрещает открытие, а уменьшает охват ровно настолько, насколько уменьшает.
Круг радиусом 500 метров завышает охват
Круг не знает про реку, железную дорогу, промзону и заборы. На улице 8 Марта, 188 он насчитывает 22 266 жителей, а зона, реально проходимая пешком за 7 минут, — 12 427. Завышение на 79 %.
Система строит изохроны по графу улиц и считает охват по ним, а в карточке прямо пишет, на сколько процентов круг соврал бы в этой точке.

У помещения есть физический потолок
Пропускная способность ограничена 95 товарами на м² в месяц. Без этого ограничения модель давала 362 тыс ₽/мес на лучших локациях при рыночном потолке около 200. Побочный эффект оказался важнее самого ограничения: площадь стала влиять на выручку, а не только на аренду. В одной и той же точке 25 м² окупаются за 16 месяцев, 35 м² — за 6, а 50 м² — за 4.
Чем система отличается от карты с точками
Балл объясним до последнего слагаемого
В карточке видно: группа → вес → вклад в балл, внутри группы каждая метрика со своим значением, ниже — исходные числа по точке и поимённый список объектов притяжения с расстояниями. Веса можно поменять прямо в интерфейсе, всё пересчитается.
Решение на сотни тысяч рублей нельзя принимать по числу, происхождение которого нельзя проверить.


Население считается снизу вверх
Жители не размазаны по районам, а собраны по зданиям: пятно застройки × этажность ÷ норму площади на человека, для 2 820 домов — точное число квартир из реестра. Сумма по городу сведена с официальной численностью 1 548 187 человек.
Поэтому соседние дома в одном квартале получают разный охват, а высотка отличается от пятиэтажки.
Улица перед входом считается по дорожному графу
Улицы разобраны на точки через каждые 30 метров, у каждой класс и вес: пешеходная 3,5, магистральная 3,0, дворовый проезд 0,8. Расстояние меряется от края здания, а не от его центра, — иначе большой торговый центр всегда оказывался бы «далеко от улицы».
Это ровно тот фактор, из-за которого место во дворе не должно обгонять место на проходной улице.
Достаток района тянет балл в две стороны
Дороже жильё — выше средний чек, но ниже доля самовывоза: обеспеченные чаще заказывают курьером. Эффекты разведены по разным эластичностям. Ценовой пояс берётся по микрорайону, а не по расстоянию до центра: Академический дороже Химмаша, хотя дальше от центра.
Модель сошлась с внешним бенчмарком
При принятой интенсивности спроса средний доход партнёра выходит около 141 тыс ₽/мес — при заявленных самим Ozon 125–150 тысяч. Величина не подгонялась под результат, она получилась.
Считается каждое здание, а не выборка
Первая версия отбирала кандидатов по одному числу — населению в радиусе 500 метров — и считала балл только верхушке. Порог отсечения доходил до 20 тысяч человек, и хорошие места в стороне от жилых массивов не попадали в рейтинг вообще. Теперь балл считается всем 28 тысячам зданий фоновой задачей с прогрессом.


Обновление данных не может испортить базу
Автообновление работает без человека, поэтому защита выстроена слоями: резервная копия снимается перед началом; выгрузка идёт отдельным процессом, чтобы всплеск памяти не задел веб-сервис; неполный ответ Overpass не сохраняется и роняет шаг с ошибкой; сборка не обнуляет слой, для которого нет свежей выгрузки — иначе первое же обновление стёрло бы 1 124 пункта, которые собираются только вручную.

Тяжёлый стек не понадобился
Пространственные запросы работают на собственном индексе — хэше по ячейкам 250 метров поверх SQLite. Ни PostGIS, ни сервера базы, ни платных подписок. Сервис укладывается в 512 МБ памяти и стоит меньше трёхсот рублей в месяц.
Как устроено внутри
ИСТОЧНИКИ СБОРКА СЕРВИС карта партнёра Ozon ──┐ OpenStreetMap ──┤ скрипты выгрузки FastAPI + SQLite реестр домов ──┼──▶ и сборки базы ──▶ расчёт баллов 2ГИС Isochrone API ──┤ отдельными процессами фоновые задачи УПН · Росстат ──┘ карта на Leaflet
Решения, которых потребовала предметная область:
- гравитационная модель Хаффа для дележа спроса между близкими точками;
- сшивание границ районов из отрезков по совпадающим концам — OpenStreetMap отдаёт их в произвольном порядке, и склейка подряд даёт самопересекающийся многоугольник;
- квадрантная выгрузка тяжёлых слоёв с ротацией трёх зеркал Overpass и проверкой полноты ответа;
- перенос справочных слоёв из образа на постоянный диск при старте с защитой от отката на более старые данные;
- восстановление ссылок на здания по координатам после каждой пересборки: идентификаторы выдаются заново, а заведённые владельцем помещения не должны потерять привязку.
Чем сделано
| Слой | Технологии |
|---|---|
| Бэкенд | Python 3.12, FastAPI, Uvicorn |
| База | SQLite без сервера; собственный пространственный индекс — хэш по ячейкам 250 м |
| Фронтенд | Vanilla JS без фреймворка, Leaflet + MarkerCluster, CSS |
| Данные | Overpass API, собственный парсер реестра домов, скрытое API карты партнёра Ozon, 2ГИС Isochrone API |
| Выгрузки | openpyxl (Excel), CSV |
| Развёртывание | Docker, VPS, Caddy с TLS, деплой через git push в bare-репозиторий |
Объём: около 5 900 строк Python и 3 900 строк фронтенда, 13 слоёв данных, 9 скриптов сбора и сборки.

Что система не делает
В геоаналитике честно названные ограничения важнее заявленной точности.
Слой ПВЗ Ozon обновляется вручную. Их карта отдаёт 403 на любой запрос без браузерной сессии. Автоматизировать это можно только headless-браузером, который не помещается в выбранный тариф.
Изохроны построены не для всех мест. Ключ 2ГИС демонстрационный, поэтому зоны пешей доступности считаются только для лучших мест; уже построенные хранятся в базе.
Аренда и площадь вводятся руками. Открытых источников с достоверными ставками по конкретным помещениям нет, а брать их из объявлений — значит закладывать в расчёт цену запроса, а не сделки.