# Известные ограничения

INF-15 «Полотно» (`grid`), пакет v1.0.0, 2026-07-26. Часть А дословно
основана на разделе 8 ТЗ («Что мы честно не сможем сказать») —
`handoff/09_next_research/BY_Maps_INF-15_grid_research_spec.md`, 5 пунктов.
Часть Б добавляет находки, специфичные для этого пакета: провал сырой
сверки G-1, провал перекрёстной проверки G-3, нерешённый подрайонный
confound, ограничение снимка дорог и операциональный список «полесских»
районов. Пути — относительно корня пакета `artifacts/grid/`.

Главный вывод, к которому относятся эти ограничения: см. `README.md` и
`EXECUTIVE_BRIEF.md` — C-1/C-2 публикуются со статусом «открытый вопрос»
(не «подтверждено»), C-3 подтверждена.

---

## Часть А. Пять пунктов раздела 8 ТЗ (дословная основа)

### 1. Наша карта — не независимый источник численности населения

**Суть (раздел 8 ТЗ, п.1).** Карта сделана из официальной статистики и
показывает рисунок расселения, а не альтернативный подсчёт людей.

**Как это реализовано и проверяется здесь.** Сумма клеток каждого района на
каждую эпоху принудительно приравнена к интерполированному официальному
ряду (`constrained dasymetric redistribution`, `docs/decisions/INF-15.md`
D-005) — потому что сырой продукт систематически расходится с официальными
числами (см. Часть Б, пункт 6). Это значит, что после калибровки G-1
проходит «по построению», а не как эмпирический тест —
`sources/raw/metrics_observed.json → reconciliation_summary.calibration_applied`
явно помечает это текстом. GHS-POP в этом пакете выступает **только**
источником внутрирайонного пространственного веса — где именно внутри
района расположены люди, — а не источником итоговой цифры.

**Что осталось открытым.** Внутрирайонный рисунок (где именно в районе
гуще/реже) остаётся допущением самого GHS-POP; калибровка сумм по району
его не проверяет и не устраняет (см. Часть Б, пункт 8 — нерешённый
подрайонный confound).

**Как проверить самому.**
```bash
python3 -c "
import json
d = json.load(open('sources/raw/metrics_observed.json'))
print(d['reconciliation_summary']['calibration_applied'])"
```

### 2. В малозаселённых местах у данных есть искажения

**Суть (раздел 8 ТЗ, п.2).** Это известная особенность продукта, а не наша
ошибка; в таких зонах отдельные клетки читать не следует.

**Как это видно в данных.** Самый яркий пример — Брагинский район
(частично в чернобыльской зоне отчуждения): сырое (до калибровки)
отклонение суммы клеток от официального ряда достигало **+209,78 %** на
отдельной эпохе (2015) — контрольное число `g1_raw_max_abs_pct_diff` в
`checks/expected_results.json`. Малые чернобыльские районы — «отдельная,
ожидаемая история» от общего географического паттерна переоценки
городов-хозяев (`docs/decisions/INF-15.md` D-005): экстремальный, но
предсказанный разделом 8 ТЗ (этим же пунктом) артефакт дазиметрии в
эвакуированных/малозаселённых зонах.

**Как проверить самому.**
```bash
python3 -c "
import csv
rows = list(csv.DictReader(open('sources/raw/reconciliation.csv')))
worst = max(rows, key=lambda r: abs(float(r['pct_diff'])) if r['pct_diff'] else 0)
print(worst)"
```

### 3. Прогноз по клеткам опирается на сильное предположение

**Суть (раздел 8 ТЗ, п.3).** Внутренний рисунок расселения района
продолжит меняться примерно так же, как менялся раньше. Поэтому сделаны
два варианта, но правда может оказаться вне обоих.

**Как это реализовано.** Вариант A — лог-линейная экстраполяция доли клетки
в населении района по всем 10 наблюдаемым эпохам; вариант Б — та же
экстраполяция, домноженная на явно модельный пригородно-периферийный
фактор (буст до +35 % у пригородов 7 автоматически найденных городских
ядер, штраф до −25 % на периферии дальше 60 км, параметры
`docs/decisions/INF-15.md` D-006). Клетки, ни разу не заселённые за
1975–2020, остаются нулевыми в проекции — «нет роста из ничего».

**Что осталось открытым.** На национальном уровне варианты A и Б почти не
различимы (`area_share_below(5)` на 2050 год: A=0,8492, Б=0,8478 в базовом
сценарии — разница <0,15 п.п.), потому что G-2 фиксирует РАЙОННЫЕ итоги
идентичными для обоих вариантов по построению; различие — исключительно во
внутрирайонном распределении. Значит на уровне отдельной клетки/малого
региона расхождение A/Б значимо больше, чем видно в национальной панели —
не проверяйте гипотезу «варианты почти совпадают» на клеточном уровне по
национальным цифрам.

**Как проверить самому.** `sources/raw/metrics_forecast.json →
national.area_share_below."5"`, ключи `2050:base:A` и `2050:base:B`.

### 4. По дорогам у нас снимок, а не история

**Суть (раздел 8 ТЗ, п.4).** Мы знаем сеть на 2026 год. Корректно говорить
«столько сети на жителя сейчас», некорректно — «сеть выросла на столько».

**Как это соблюдается.** `network_per_capita` везде строится на последней
фактической точке ряда `data.json` (обычно 2026 год, у отдельных районов —
ближайший предыдущий доступный год, помечено `is_fallback: true`), не на
прогнозе. История правок OSM нигде не используется как дата постройки
дороги. Корреляция C-3 (Spearman ρ = −0,7508) — описательная, не причинная:
`sources/raw/network_metrics.json → correlation_c3` не содержит причинных
формулировок, а `docs/notes/grid_network_findings.md` (раздел 3) отдельно
предупреждает, что часть корреляции почти механическая (длина сети
фиксирована, при падении знаменателя `network_per_capita` растёт почти
арифметически).

**Как проверить самому.**
```bash
grep -riE "сеть (выросл|сократил)|дорог. построен" README.md METHODS.md EXECUTIVE_BRIEF.md
```
Ожидаемо: совпадений нет.

### 5. Между пятилетними точками мы интерполируем

**Суть (раздел 8 ТЗ, п.5).** Промежуточные годы на карте — сглаженная
картинка, они помечаются отдельным типом достоверности.

**Как это реализовано.** Эпохи GHSL — узлы 1975, 1980, …, 2020 (шаг 5 лет);
официальный ряд района к этим узлам приводится линейной интерполяцией
между ближайшими точками `data.json` — только 2005 и 2015 совпадают
напрямую с годом ряда, остальные 8 эпох интерполированы
(`etl/grid.py::interp_official`, экстраполяция за пределы диапазона не
выполняется). Год 2050 прогноза — тоже не нативный узел `forecast.json`
(шаг 5 лет от 2026 не попадает в 2050) — линейно интерполируется между 2046
и 2051 и помечается `dtype: "f"`, а не «c»/«e».

**Как проверить самому.** `params/assumptions.yaml → forecast.year_2050`;
`sources/raw/metrics_observed.json → national.centroid_track` — поле
`dtype` у каждой точки (`c` = перепись, `e` = оценка, `f` = интерполяция
прогноза).

---

## Часть Б. Находки, специфичные для этого пакета

### 6. Сырая сверка G-1 провалена почти полностью (D-005)

Сырой (неоткалиброванный) GHS-POP не сходится с официальным рядом района в
допуске ±3 % практически ни для одного из 119 территорий (118 районов +
Минск): все **119 из 119** хоть раз за 10 эпох вышли за допуск
(`g1_n_failed_raions_ever`); по отдельным эпохам проваливается от **40 до
111** из 118–119 (`sources/raw/metrics_observed.json →
reconciliation_summary.per_epoch_fail`). Национальная сумма тоже вне допуска
±1 % на всех 10 эпохах — реальное отклонение от **+1,69 %** (2005) до
**+3,35 %** (1995). Это далеко за порогом «более 15 районов» раздела 13 ТЗ.

Расследование (`docs/decisions/INF-15.md` D-005, `docs/notes/
grid_validation.md`) исключило баг конвейера (площадь маски страны
207 312–209 307 км² соответствует официальным ~207 600 км² с точностью
<1 %) и нашло системный географический паттерн: районы с отдельно
учитываемым городом областного подчинения систематически ПЕРЕоценены
(+12 %…+46 %: Брест, Витебск, Могилёв, Гродно, Гомель, Пинск, Минский
район, Полоцкий, Борисовский), пригородные районы Минской агломерации —
систематически НЕДОоценены (−12 %…−29 %: Смолевичский, Островецкий,
Дзержинский, Логойский, Червенский, Пуховичский, Лидский, Воложинский,
Узденский, Березинский). Правдоподобный дополнительный корень —
несовпадение референсной численности переписи 2009 года в самих
метаданных JRC (10 018 692 против настоящих 9 503 807, +5,4 %,
`sources/raw/ghsl_pop_input_metadata_belarus_extract.txt`). Устойчивость
знака отклонения во времени слабая (корреляция %-отклонения района между
1975 и 2020 = 0,20) — это не постоянный мультипликатор на район.

**Влияние на выводы.** Публикуемая сетка использует GHS-POP только как
источник внутрирайонного распределения; итог района/страны — тождественно
официальный ряд (Часть А, пункт 1). Список районов, не прошедших сырую
сверку, публикуется целиком, а не прячется — `sources/raw/reconciliation.csv`
(1190 строк = 119 территорий × 10 эпох).

### 7. Перекрёстная проверка G-3 провалена (D-007)

Согласие знака изменения 2012→2020 (официальная численность района vs
сумма светимости VIIRS) = **32,8 %** (39 из 119 районов) при пороге 70 %.
Разбивка по направлениям: «население упало, свет вырос» — **76 районов
(63,9 %, доминирующий паттерн)**; «оба упали» — 30 (25,2 %); «оба выросли»
— 9 (7,6 %); «население выросло, свет упал» — 4 (3,4 %). У
«районов-хозяев городов» согласие 36,4 % (4/11), у остальных 32,4 %
(35/108) — расхождение не сосредоточено в группе, вызывавшей опасения при
ревью D-005, это общестрановой эффект.

Вероятное (не доказанное причинно) объяснение — эффект модернизации
уличного освещения (переход на LED, которое ярче видно спектральной
чувствительности VIIRS, чем старые натриевые лампы) или рост
инфраструктурной/промышленной активности, отвязанной от численности
населения; причина не устанавливается окончательно (вне объёма
исследования).

**Влияние на выводы.** Раздел 7 ТЗ (проверка 3) предписывает буквально: при
совпадении ниже 70 % расхождение двух сенсоров становится главным
дополнительным сюжетом, а утверждения о концентрации (C-1) и опустении
(C-2) подаются как открытый вопрос — не как подтверждение. Это применено
без исключений во всех текстах пакета (`README.md`, `EXECUTIVE_BRIEF.md`,
`claims.yaml`) и защищено регрессионным тестом
(`checks/tests/test_invariants.py`: тест **упадёт**, если `g3.pass`
когда-нибудь станет `True` без обновления `docs/decisions/INF-15.md`).
C-3 (дорожная сеть) этим провалом не затронута — использует независимый
источник (OSM + официальный ряд района), не сетку населения/огней.

**Как проверить самому.** `sources/raw/g3_nightlights_crosscheck.json`
(разбивка по группам) и `sources/raw/g3_nightlights_rows.csv`
(район-level, `d_pop`, `d_light`, `agree`) — полный, неотредактированный
результат, включая список несогласных районов.

### 8. Нерешённый подрайонный confound (справочно)

При голосовании троих по D-005 (`docs/notes/grid_validation.md`, раздел
«Голосование») третий проверяющий поставил условие: прежде чем считать C-1
подтверждённым, нужна целевая проверка «города-хозяева vs пригороды» на
подрайонном уровне через огни — потому что тот же механизм дазиметрического
распределения, из-за которого города-хозяева оказались переоценены МЕЖДУ
районами (устранено калибровкой сумм по району), теоретически мог бы
действовать и ВНУТРИ района (между городским
ядром района и его периферией), а калибровка сумм по району это не
проверяет и не устраняет. В протоколе зафиксировано: это условие
**«частично выполнено сейчас, довыполняется на этапе 4»** — сравнением
`national` (constrained) и `national_raw` (сырой), показавшим совпадение
направления национального тренда на обоих наборах. Явная сверка
«города-хозяева vs пригороды по огням ВНУТРИ района» (а не только
национальная агрегированная проверка G-3) как отдельный, более точный тест
именно этого риска — **не выполнена в рамках MVP** и остаётся открытым
пунктом, дополнительным к общему провалу G-3 (пункт 7 выше).

**Влияние на выводы.** Это ещё одна причина, по которой C-1 не может
подаваться как подтверждённый факт даже без учёта провала G-3 — целевая
проверка конкретно этого риска не завершена.

### 9. Снимок дорог покрывает только 5 из 8 классов OSM

Снимок дорожного графа INF-04 (`data/raw/osm/graph_edges.csv.gz`,
833 438 рёбер) извлекался только под нужды межрайонного travel-time и
содержит **только** motorway/trunk/primary/secondary/tertiary (+`_link`) —
классы residential/unclassified/track в нём отсутствуют по построению
(`docs/notes/grid_network_findings.md`, раздел 1). Официальная статистика
Белдорцентра включает ВСЕ дороги общего пользования, включая местные
проездные. Административная («республиканские»/«местные») и функциональная
(классы OSM) шкалы не сопоставимы один в один, поэтому сравнение G-6
сделано только по общему итогу, не по классам.

**Влияние на выводы.** `network_per_capita` систематически занижен
относительно «всех дорог общего пользования» (OSM-итог 76 958,8 км против
официальных 86 446 км, на −10,97 % ниже — G-6 всё же пройдена, допуск
±15 %). Риск односторонний: доснятие residential/unclassified/track могло
бы только приблизить сумму к официальной, не увести её дальше за допуск —
поэтому пройденный G-6 здесь «робастный», не «на грани». Разбивка OSM-итога
по классам (`sources/raw/network_metrics.json → g6.osm_total_km_by_class`):
tertiary даёт **≈64 %** всей длины снимка.

### 10. Полесский operational-proxy список — административный, не физико-географический (D-004)

У Белорусского Полесья нет единого общепринятого перечня современных
районов (физико-географическое районирование делит его на 5 областей без
официальной привязки к границам районов). Для проверки G-4 зафиксирован
операциональный список из **27** районов (16 Брестской области + 11 южных
районов Гомельской, прилегающих к бассейну Припяти) — заморожен в
пререгистрации ДО расчёта, не подобран под результат. Список пересекается,
но не совпадает со списком чернобыльских районов (12, `class ∈ {1,2}`);
объединение обоих множеств для G-4 даёт **36** районов (не 39 — 3 района
входят в оба списка).

**Влияние на выводы.** G-4 (устойчивость C-2 к исключению Полесья и
Чернобыля) технически корректна на этом списке, но список — прокси, не
точная физико-географическая граница; более узкая или более широкая версия
Полесья могла бы дать несколько иной, хотя, вероятно, качественно тот же
результат (рост `area_share_below(5)` сохраняется: 0,6828 → 0,7120,
**+0,0292**, монотонно). Уточнение границы до уровня отдельных сельсоветов
потребовало бы отдельного геоданных-источника, которого у проекта нет.

**Как проверить самому.** `params/assumptions.yaml → polesye_operational_list`
(список районов и определение); `sources/raw/metrics_observed.json →
validation.g4_polesye_chernobyl` (n_excluded=36, growth_1975_2020, полный
ряд `area_share_below_5_excluding`).
