Срез 11.10.2026. Репо pal-norms, префикс pn, порт 8701, на Pages /pal-norms/. Название для людей «Нормы и сроки», иконка scale
Основа: ../PLAN.md (D1-D10), ../PLATFORM.md (стандарт), ../UX_MAP.md (адреса, тексты, переходы), итоговый план проекта (идеи 5 и 7). Здесь только решения поверх них. Где этот файл молчит, действует PLATFORM.md
Уже лежит в репо (пишет ARCH, дальше владеет DATA):
data/schema/*.schema.json: 8 схем публикуемых датасетов. Это контракт между DATA и SITEtests/fixtures/*.json: синтетические конверты по этим схемам, все id с префиксомdemo-. На них SITE строит сайт, пока нет настоящих данных
- Сайт открывается от задачи, а не от номера акта: «Посчитать срок», «Если не ответили», «Показания», «Все документы». Номер акта везде мелкой строкой под человеческим названием
- Факты (реквизиты, eoNumber, статус по тексту отмены, дословные цитаты) живут без юриста. Толкования (с какого события считать, кто обязан, куда жаловаться) публикуются только с пометкой «Толкование не проверено юристом в РФ»
- Два уровня проверки вместо одного:
verifiedзначит «текст сверен со сканом официальной публикации»,legal_reviewзначит «толкование проверил юрист». Иначе fail closed парализует потребителей: юриста в РФ пока нет - Наблюдатель сам коммитит только служебный снимок
data/snapshots/. Всё, что видят семьи, приходит через PR или issue - Календарь рабочих дней ведём сами из постановлений о переносе выходных с eoNumber (PLATFORM, добавка 2).
prodcalendarтолько сверяет 2025-2026 в тестах - Своего OCR-корпуса не строим (решение итогового плана): полные тексты OCR лежат только в
raw/cache/(в .gitignore), в репо попадают цитаты, вырезки скана под ними и sha256 PDF
| Что | Как на самом деле | Решение |
|---|---|---|
| «Приложение 2 к 208н/243н = показания детям» | В OCR скана 0001202505290031 приложение 2 описывает кабинет паллиативной помощи. Показания детям даны в п. 5 Положения (стр. 4 скана). У взрослых: п. 4 Положения (стр. 3-4) и приложение 1 (стр. 10-13) |
indications: взрослые = п. 4 + прил. 1, дети = п. 5. Кто выдаёт заключение: п. 12, 13, 15 (стр. 6) |
| Фильтр «2 и более общих» как отбор | ПП 1684 и идея 7 итогового плана: без чекбоксов, баллов и вывода «положено» | Правило п. 2 крупно сверху, текст групп в <details>. Выбор группы только прячет остальные группы, ничего не считает и не попадает в URL |
| Карточка «Кому положена паллиативная помощь» | Слово «положено» запрещено (идея 7 итогового плана) | «Показания к паллиативной помощи» и подпись «Дословный текст приказа» |
| «До 2011 года актов на портале нет» | 502н (2012) нет, 187н (2015) есть | Источник текста записываем по факту поиска в API, а не по году |
changes.yaml и OCR коммитятся сами |
PLATFORM 6.3 п. 4: что видят семьи, само не коммитится | Сам коммитится только data/snapshots/watch-state.json. Записи ленты приходят PR |
Опрос регионов (Block=subjects) |
Против D8 и рамок наблюдателя | Только чтение блоков разрешённых регионов: названия и сканы официальных публикаций о нерабочих праздничных днях (pipeline/regional.py, data/calendar/regional.yaml). Блоки region80, region81, region84, region85, region91, region92 и их публикации не запрашиваются никогда, клиент отказывает на уровне кода (pipeline/pravo.py, тест с поддельной сетью). Исключённых территорий в данных pal-norms нет и быть не может |
| OCR первых страниц каждого нового акта Правительства | Это сотни сканов в месяц и тот же корпус | Только заголовки и поиск от ДД.ММ.ГГГГ № N по актам из acts.yaml |
/check/ как главный вход для семей |
Это расширение C03, у ЧекДок есть платный аналог | Вторичный вход «Для НКО и координаторов». Находка формулируется как факт с основанием, а не как «замените на» |
/indications/ публично до ревью |
Идея 7 итогового плана: до ревью медика и юриста публикуется только датасет | Локально страница полная. На Pages страница-заглушка со ссылкой на датасет, пока content/gates.yaml не разрешит (раздел 2.4). Строка в «Решить Кириллу» |
- Акты. Не меньше 15 записей с реквизитами из API и
verified:true, всего до 30. Список кандидатов в разделе 3.3 - Цитаты. Только пункты, на которые опираются статусы, сроки, показания, ссылки на отменённые акты и правила проверки текста. Примерно 40-70 цитат, каждая с вырезкой скана
- Сроки. 8-12 правил. В данные идёт только правило с дословной цитатой, где стоит число и единица. Правило без найденной цитаты остаётся в
data/candidates.yamlи не публикуется - Эскалация. 3 ступени: «срок прошёл, ответа нет», «жалобу переслали тем, на кого жаловались», «есть угроза жизни: подать параллельно». Все с
legal_review: none - Показания. Взрослые (п. 4, прил. 1 целиком) и дети (п. 5), плюс п. 2, «перечень не является исчерпывающим», кто выдаёт заключение (п. 12, 13, 15)
- Календарь. Базовые праздники по ст. 112 ТК и переносы на 2025, 2026, 2027 годы из ПП 1335, 1466, 1187
- Проверка текста. 6 правил: 345н/372н, 187н и 193н → 208н/243н; 348н → 398н; 502н → 180н (если в 180н найдена отмена); ПФР и ФСС → СФР; «30 рабочих» рядом с «18 лет» → п. 23. Один эталонный корпус для
lib/textcheck.jsиtools/pack_lint.py - Лента изменений. Только реальные события с eoNumber, все в состоянии
accepted: ПП 1187 о переносах на 2027 год, вступление 208н/243н, 398н, 180н, публикация 692н и другие найденные по API - Наблюдатель. Код, тесты на записанных ответах API,
watch.yml. В CI в этом цикле не запускается (ничего не пушим) - Сайт. 13 видов страниц из раздела 2, калькулятор срока, проверка текста, печать A4, работа без JS, светлая и тёмная темы
- Наблюдатель regulation.gov.ru (проекты) и apicr (клинические рекомендации старше 3 лет)
review-sheet(печатный лист для юриста: цитата, вырезка скана, вопросы «верно / неверно / не знаю») иreview-ingest- Atom-лента
/changes/changes.xml - Релизы SQLite и Zenodo с DOI
- Таблица бланков рецептов и сроков (C31), реестр законов о возмещении (C14), домены
bereavement,social,links.yaml - Правила проверки текста, которые сами собираются из
aliasesиsuperseded_by(в MVP правила явные, а тест следит, чтобы у каждого отменённого акта сaliasesбыло правило) - Региональный слой: только PR от физлиц со ссылкой на официальную публикацию, после решения мейнтейнера в РФ
Пути от /pal-norms/. Шапка, подвал, op-now-ссылка, «Вид» и крошки из платформы
| Адрес | H1 | Источник данных | Черновик | Печать | Без JS |
|---|---|---|---|---|---|
| `` | Нормы и сроки | entrypoints, счётчики из всех датасетов |
нет | нет | всё |
deadlines/ |
Посчитать срок | deadline-rules, calendar, quotes |
legal | результат на 1 лист | таблица правил |
deadlines/<rule_id>/ |
situation правила |
то же | legal | 1 лист | всё, кроме расчёта |
escalation/ |
Если не ответили или ответили не по существу | escalation, quotes, acts |
legal | да | всё |
indications/ |
Показания к паллиативной помощи | indications |
both | да | все группы раскрыты |
acts/ |
Документы | acts |
нет | нет | полный список |
acts/<norm_id>/ |
short_title акта |
acts, quotes, deadline-rules |
нет, у толкований статус | да | всё |
calendar/ |
Праздники и выходные | calendar |
нет | нет | список на год сборки |
calendar/<год>/ |
Нерабочие дни в 2027 году | calendar, quotes |
нет | 1 лист | всё |
changes/ |
Что изменилось | changes, watch-state.json |
нет | нет | всё |
check/ |
Проверить памятку или шаблон | lint-rules, acts, quotes |
legal | нет | текст «нужен JavaScript» и список правил |
data/ |
Данные для разработчиков | index.json |
нет | нет | всё |
404.html |
из платформы |
site.nav (UX_MAP 5.2): Сроки (deadlines/), Если не ответили (escalation/), Показания (indications/), Документы (acts/)
Корень /pal-norms/
- H1 «Нормы и сроки», строка из UX_MAP 7: «Дословные цитаты из официальных публикаций, сроки ответа и куда обращаться дальше. У каждой цитаты ссылка на публикацию и дата сверки»
- Четыре крупные карточки
op-cardс иконкой: «Посчитать срок ответа» (calendar-clock), «Если не ответили» (file-text), «Показания к паллиативной помощи» (book-open), «Все документы» (scale). Подпись у каждой одной строкой - Ниже H2 «Для НКО и координаторов»: «Проверить памятку или шаблон» (
file-check), «Что изменилось» (history), «Праздники и выходные» (calendar-days), «Данные для разработчиков» (code-xml) op-data-dateи строка «Последняя проверка новых публикаций: 12.10.2026» ниже сгиба- Первый экран 360×640: шапка в две строки, H1, одна фраза, карточки 1-3 целиком, четвёртая начинается
deadlines/: калькулятор <pn-deadline>
- Шаг 1 «Что произошло?»:
fieldsetс крупными радиокнопками от 56px, сгруппированными подзаголовками: «Обращения и жалобы», «Медицинские документы», «Медицинские изделия», «Ребёнку скоро 18». Текст радио =situationправила. Неselect - Шаг 2: подпись =
from_event_labelправила («Когда обращение получили», «Дата, когда исполнится 18»),<input type="date">. Если браузер не знаетtype=date, три поля «день, месяц, год». Ошибка под полем с иконкой и словом «Ошибка» - Кнопка «Посчитать» (
op-btn--primary) - Результат в
<section aria-live="polite">:result_labelи дата крупно, Onest 700, 28px: «Ответ должен прийти не позже» / «14 ноября 2026, суббота»- если
ambiguous: статусinfo«Важно. Ориентировочно» иambiguity_note. Еслиdue_altесть, вторая дата: «Если срок переносится с выходного: 16 ноября 2026, понедельник» - если
calendar_verified:false: статусunverifiedс текстом UX_MAP «Календарь на [год] ещё не утверждён. Дата может сдвинуться на несколько дней». Для обратного счёта ещё «Мы взяли запас 5 рабочих дней» - если правило
verified:false: статусunverified«Текст нормы не сверен с официальной публикацией» и откуда он взят - всегда строка «Толкование не проверено юристом в РФ», пока
legal_review.statusнеok - «Как считали»: шаги
stepsс датами списком <details>«Что сказано в норме»:op-quoteс цитатами правила- кнопки «Добавить в календарь» (
.icsчерезui/js/ics.js, событие в день срока, напоминание за 1 день, в тексте только «Срок: ...» без имён), «Отправить» (op-share, только текст: срок, дата, как посчитано; без адреса с датой), «Распечатать или сохранить PDF» - если срок уже прошёл: статус
deadline«Срок. Ответ не пришёл в срок. Это повод для следующего шага» и ссылки «Пожаловаться на нарушение срока» → ok/case/#new=1&s=no-answer&rule=<id>&from=<дата>(UX_MAP 4.3) и «Куда обращаться дальше» →escalation/ - ссылка «Откуда этот срок» →
deadlines/<rule_id>/
- Ниже калькулятора H2 «Все сроки»: таблица
op-table(ситуация, срок, с какого события, норма черезnorm()). Без JS видна только она, форма скрыта до готовности компонента - Принимает фрагмент
#rule=<id>&from=<дата>&back=<ситуация>черезreadParams, сверяет сdeadline-rules, предзаполняет и стирает фрагмент. Для правила перехода в 18 летfromиз адреса не принимается: дата 18-летия почти равна дате рождения - Ничего не хранит: ни дату, ни выбор, ни в URL, ни в localStorage
- Первый экран: H1, плашка черновика компактно, «Шаг 1. Что произошло?», первые 3-4 варианта
- Печать: только результат, формула, цитата, плашка черновика,
.op-sheet-footer. Ровно 1 лист A4
deadlines/<rule_id>/
- H1 =
situation. Блок «Срок» крупно: «30 дней со дня регистрации, 3 дня на регистрацию», кто обязан (who), с какого события - Цитаты правила,
ambiguity_note, статус проверки - Тот же
<pn-deadline>с одним правилом (без шага 1) - «Если срок нарушен»: ступени из
escalation_idsссылками наescalation/#<id> - Якорь страницы принимает
#from=из ok (UX_MAP 4.3), кроме правила перехода в 18
escalation/
- H1 «Если не ответили или ответили не по существу», строка «Что можно сделать дальше и на какую норму опереться»
- Ступени списком
<ol>, каждаяop-cardсidдля якоря: заголовокtitle(«Прошёл срок, а ответа нет»), что сделать (what), куда (addressee_kindсловами: «Прокуратура района или города», «Территориальный орган Росздравнадзора», «Центральный аппарат Росздравнадзора», «Орган управления здравоохранением региона», «Руководитель организации»), пометка «Можно одновременно с другими шагами» приparallel - В
<details>«Основание»: цитаты,norm() - Кнопки «Собрать документ» → ok (
ok_doc, если есть, иначе страница сценария изcontent/escalation-links.yaml) и «Посчитать срок» →deadlines/<rule_id>/ - Статус
unverified«Толкование не проверено юристом в РФ» у каждой ступени - Конкретных адресов и телефонов нет. Номера региона даёт
pal-directory, ссылка «Номера и адреса вашего региона» → pd/ - Первый экран: H1, строка, плашка черновика, заголовок первой ступени
indications/
draft: both,noindex: true- H1 «Показания к паллиативной помощи», строка «Дословный текст приказа. Решение принимает врачебная комиссия. Этот текст помогает говорить с врачом»
- Блок «Главное правило» крупно,
op-quote: п. 2 прил. 1 (при 2 и более общих показаниях оцениваются показания по группам) и строка «перечень ... не является исчерпывающим» - H2 «Взрослым»: п. 4 Положения (кому оказывается), «Общие показания» списком, «Показания по группам заболеваний» каждой группой в
<details open>сgroup_titleв<summary> <pn-indications>: над группами полеselect«Показать одну группу» с вариантом «Все группы». Выбор только скрывает остальные<details>, адрес и история не меняются, в хранилище ничего не пишется. Без JS все группы раскрыты- H2 «Детям»: п. 5 Положения дословно
- H2 «Кто решает»: п. 12, 13, 15 дословно
- «Что дальше»: «Врачебную комиссию не созывают» → ok
/scenarios/vk-refused/, «Распечатать лист для разговора с врачом» → hc/conclusion/(UX_MAP 4.3) - Каждая строка:
<blockquote data-verbatim>, под группойop-citeс пунктом, страницей скана, eoNumber. Строки сverified:falseсо статусом «Не проверено» - Печать: выбранная группа или все. Плашка черновика на листе
- Первый экран: H1, плашка черновика, строка, начало блока п. 2
acts/
- H1 «Документы», строка «Приказы, законы и постановления, на которые опираются сроки и показания. У каждого статус и ссылка на официальную публикацию»
- Поле поиска
op-searchбезactionи без?q=: фильтрует список на месте поshort_title,title,number,aliases - Фильтр радиокнопками: «Все», «Действуют», «Утратили силу», «Не проверено»
- Группы по
topics: «Паллиативная помощь», «Обращения и жалобы», «Медицинские изделия», «Врачебная комиссия и документы», «Обезболивание», «МСЭ», «Календарь», «Ведомства» - Строка акта:
short_titleссылкой, ниже мелко вид, орган, дата и номер из данных; бейдж статусаpn-act-status(иконка и слово:circle-check«Действует»,circle-alert«Утратил силу»,clock«Ещё не вступил»,circle-help«Статус не проверен»); «Заменён: [ссылка]»; бейдж «Не проверено», еслиverified:false - Первый экран: H1, строка, поле поиска, фильтр, первые 2 акта
acts/<norm_id>/
- Крошки «openpalliative › Нормы и сроки › Документы › [short_title]»
- H1
short_title, ниже официальное названиеtitle - Блок статуса: слово и иконка, даты (
in_force_from,valid_untilкак «Действует до 01.09.2031»,repealed_at), основание статуса цитатой (status_basis) - «Реквизиты» списком
<dl>: вид, орган, дата, номер, номер публикации (eoNumber, моноширинно, кнопкаop-copy), дата публикации, страниц. Ссылки «Официальная публикация» и «Скан PDF» с иконкойexternal-linkи текстом «откроется сайт publication.pravo.gov.ru» - Для
text_source: ips_archive: статусunverified«Текста этого акта нет на портале официальной публикации. Цитата взята из ИПС «Законодательство России» по снимку web.archive.org от [дата] и с официальным текстом не сверена» - «Ключевые пункты»: цитаты с
id="p-23"(полеanchor), чтобы работали ссылки#p-23из ok и dv - «Связи»: «Заменяет», «Заменён», «Изменения» (
amended_byс датой и ссылкой на публикацию), «Ссылается на документ, который утратил силу» (cites_repealed, как факт текста, с цитатой; пример: п. 25 Положения 208н/243н указывает на 348н) - «Используется в сроках»:
rule_idsссылками - «Проверка юристом: не было» или дата и роль из
review - Первый экран: крошки, H1, официальное название в 2-3 строки, блок статуса
calendar/ и calendar/<год>/
calendar/: H1 «Праздники и выходные», строка «Нерабочие дни по Трудовому кодексу и постановлениям Правительства о переносе выходных». Блок «Ближайшие нерабочие дни» (<pn-holidays>считает от сегодняшней даты; без JS список на год сборки). Ссылки на годы со статусом годаcalendar/<год>/: H1 «Нерабочие дни в 2027 году», по месяцам: даты словами («1-8 января», «23 февраля»), переносы («суббота 4 января перенесена на пятницу 2 мая»), рабочие субботы. Источник: постановление черезnorm()и цитата. Печать на 1 лист- Год без постановления: статус
unverified«Календарь на [год] ещё не утверждён»
changes/
- H1 «Что изменилось», строка «Новые и изменённые документы на портале официальной публикации, которые касаются паллиативной помощи. Запись появляется после проверки»
- Список по
published_atубыванием: дата, вид словами («Новый документ», «Изменения», «Утратил силу», «Календарь на следующий год», «Скоро истекает срок действия», «Вступил в силу»),title,summary, ссылка на публикацию, связанные акты - Строка «Последняя проверка новых публикаций: [checked_at]» из
data/snapshots/watch-state.json - Короткий абзац, как работает проверка: раз в неделю, что ищем, что не трогаем
check/: <pn-check>
draft: legal. H1 «Проверить памятку или шаблон», строка «Вставьте текст. Проверка идёт на этом устройстве, текст никуда не отправляется и не сохраняется»textarea10 строк,autocomplete="off", кнопки «Проверить» и «Очистить» (вторичная)- Итог: «Найдено: 3» или статус
done«Готово. Ссылок на документы, которые утратили силу, мы не нашли. Мы проверяем только [N] правил, список ниже» - Находка: номер строки, фрагмент с
<mark>, сообщение-факт («Упомянут приказ, который утратил силу 01.09.2025»), статусerrorдляseverity: error,infoдляwarning, «Основание» цитатой изquote_ids, «Действующий документ: [ссылка]» приsuggest_act_id - Внизу «Словарь от [data_date], правил: 6» и список правил в
<details> - Текст только в DOM. Ни localStorage, ни IndexedDB, ни URL
data/
- H1 «Данные для разработчиков». Таблица датасетов из
index.json: название, что внутри, записей, непроверенных, версия, ссылки на JSON и схему - Как подключить:
lib/deadlines.js,lib/textcheck.js,tools/pack_lint.pyс короткими примерами - Лицензия: данные CC BY 4.0 с атрибуцией «publication.pravo.gov.ru», код MIT. Как предложить правку (PR в YAML, что попадает на ревью)
- Принимаем из белого списка UX_MAP 4.1:
rule,from,backнаdeadlines/иdeadlines/<id>/;backна всех страницах. Обычные якоря#p-23наacts/<id>/ - Отдаём: в ok (
/case/#new=1&s=no-answer&rule=..&from=..,/scenarios/vk-refused/, документы поok_doc), в hc (/conclusion/), в pd (/и/federal/). Только черезxurl() - Внизу страниц «Что ещё может пригодиться» по
back=или поsituationsстраницы (UX_MAP 5.3)
content/gates.yaml (SITE):
indications_public: falsesite_hooks.py читает переменную окружения OP_TARGET. Если она pages и ворота закрыты, indications/ собирается заглушкой: H1 тот же, текст «Страница ждёт проверки медиком и юристом в РФ. Дословный текст есть в датасете и в официальной публикации», ссылки на data/v1/indications.json и публикацию. Локально (OP_TARGET не задан) страница полная. pages.yml выставляет OP_TARGET=pages
Тексты из UX_MAP 6. Свои для pn:
| Состояние | Где | Что видно |
|---|---|---|
| Правило без проверенной цитаты | калькулятор, страница правила | статус «Не проверено», дата считается, но подписана «ориентировочно» |
| Толкование без ревью | сроки, эскалация | строка «Толкование не проверено юристом в РФ» |
| Акт без текста на портале | акт, цитаты | статус «Не проверено» с источником ИПС и датой снимка |
| Срок действия акта истекает меньше чем через 120 дней | акт | статус info «Важно. Срок действия этого приказа истекает [дата]» |
| Давно не проверялось | review.status: expired |
статус stale «Проверка юристом устарела» |
YAML читается BaseLoader, пустое значение = null, типы приводит схема (PLATFORM 4.5). Даты YYYY-MM-DD
| Файл | Что | Публикуется как |
|---|---|---|
meta.yaml |
version, data_date, примечания |
поля конвертов |
acts.yaml |
граф актов | acts |
quotes/<act_id>.yaml |
дословные цитаты одного акта | quotes |
deadline-rules.yaml |
правила сроков | deadline-rules |
candidates.yaml |
правила и акты без найденной цитаты или основания: что ищем и где | не публикуется |
calendar/holidays.yaml |
базовые праздники ст. 112 ТК, правило переноса ч. 2 ст. 112 | calendar (item baseline) |
calendar/transfers.yaml |
по году: постановление, переносы {from, to} |
calendar (items по годам, off и work вычисляет export) |
escalation.yaml |
ступени | escalation |
indications.yaml |
строки показаний | indications |
lint-rules.yaml |
правила проверки текста | lint-rules |
changes.yaml |
принятые события ленты | changes |
entrypoints.yaml |
входы для хаба | entrypoints |
review/*.yaml |
ревью толкований: target, role, reviewed_at, expires_after, verdict |
сливается в review / legal_review |
snapshots/watch-state.json |
служебное состояние наблюдателя | не публикуется, читает site_hooks |
Точные схемы лежат в data/schema/<name>.schema.json (JSON Schema 2020-12, без $ref, чтобы работало opsite.data.coerce). Ниже смысл полей
acts
id: стабильный слаг (208n-243n,59-fz,pp-1187). Год в конце (505n-2019) только если номер повторяется внутриacts.yamlkind:federal_law | code | government_decree | government_order | ministry_order | joint_orderauthority,number,doc_date,title(полеnameиз API),short_title(человеческое название, его выводитnorm()),topics[]eoNumber(16 цифр или пусто),publish_date,pages,url(http://publication.pravo.gov.ru/document/<eo>),pdf_url(http://publication.pravo.gov.ru/file/pdf?eoNumber=<eo>),pdf_sha256text_source:official_pdf | ips_archive | none;alt_sourceдля ИПС:nd,snapshot_url,sha256,retrieved_atstatus:in_force | not_yet_in_force | repealed | draft | unknown;status_basis {act_id, point, quote_id}обязателен уrepealedin_force_from,valid_until(срок действия по «гильотине», только с цитатой),repealed_atsupersedes[],superseded_by[](симметричны),amended_by[{eoNumber, doc_date, title}]cites[{act_id, point, quote_id}]: ссылки, видимые в тексте.cites_repealedиrule_idsвычисляет exportaliases[]: написания для поиска и проверки текста («345н/372н», «345н»)quote_ids[],review {status, role, reviewed_at, expires_at, ref},notes- общие:
verified,verified_at,source_ref
quotes: id (208n-243n-p23), act_id, point («п. 23», «прил. 1, п. 2», «ч. 1 ст. 12»), anchor (p-23, p-a1-2, p-12-1), page (страница PDF), text дословно, text_source, ocr {passes, diff_lines, dpi[]}, scan_checked {at, method, crop}
deadline-rules: situation, result_label, from_event (код) и from_event_label, who, value, unit (calendar_days | working_days | within_24h), direction (forward | backward), pre {value, unit, label} (3 дня на регистрацию), extension (продление по ч. 2 ст. 12 59-ФЗ), shift_if_nonworking (yes | no | unknown), margin_working_days (запас для обратного счёта, 5 у п. 23), norm_ids[], quote_ids[] (минимум одна), ambiguous, ambiguity_note, escalation_ids[], audience[], legal_review
calendar: item baseline (holidays_mmdd[], carry_over, carry_exclude_mmdd[] для январских, quote_ids) и item на год (year, decree_act_id, decree_eoNumber, transfers[], off[] все нерабочие дни кроме обычных суббот и воскресений, work[] рабочие субботы и воскресенья, quote_ids). Год verified только если проверены постановление, его цитата и базовые праздники
escalation: order, trigger (no_answer_after_due | redirected_to_accused | answer_not_on_merits | threat_to_life), title, what, addressee_kind (только тип органа), parallel, norm_ids[], quote_ids[], rule_ids[], ok_doc, legal_review
indications: audience (adults | children), section (scope п. 4 и п. 5 Положения, general, rule п. 2, group, decision п. 12, 13, 15), group_id, group_title (дословный заголовок пункта группы), order, act_id, point, anchor, page, text, text_source, ocr, scan_checked
lint-rules: kind (repealed_act | agency | deadline_mismatch), patterns[], flags (i или пусто), context {pattern, window} (находка засчитывается, только если в окне window символов вокруг есть context.pattern), act_id, suggest_act_id, message (факт, без «замените»), severity (error | warning), quote_ids, since
Диалект регулярных выражений общий для JS (флаг u) и Python re: классы символов, альтернативы, квантификаторы, просмотр назад только фиксированной длины. Без \b (в JS он не видит кириллицу) и без \p{..}. Границы слова пишем так: (?<![0-9A-Za-zА-Яа-яЁё]) и (?![0-9A-Za-zА-Яа-яЁё]). Перед проверкой текст нормализуется одинаково: NFC, неразрывный пробел и табуляция в пробел, № с пробелом и без
changes: published_at, detected_at, eoNumber, title (из API), kind (new_act | amendment | repeal | transfer_decree | sunset_soon | in_force), related_act_ids[], summary (факт одной фразой), state: accepted, review_ref (адрес PR или issue)
entrypoints: схема платформы opsite/schema/entrypoints.schema.json. Входы:
| id | path | situations | audience | printable | maturity |
|---|---|---|---|---|---|
deadlines |
deadlines/ |
refused, turning-18 | family, org | true | beta |
escalation |
escalation/ |
refused, conclusion, discharge | family, org | true | beta |
indications |
indications/ |
conclusion | family, medic | true | demo |
calendar |
calendar/ |
night-pain | family | true | beta |
acts |
acts/ |
org, lawyer | false | beta | |
check |
check/ |
org | false | demo | |
changes |
changes/ |
org, lawyer | false | beta | |
data |
data/ |
developer | false | beta |
offline: false у всех. Заголовки и подписи берутся из UX_MAP (например «Посчитать срок ответа»)
Все запросы к официальному порталу публикации. Ни сайтов НКО, ни сайтов учреждений. Региональные блоки портала только на чтение и только разрешённые (см. §0), для справки о региональных нерабочих днях
| Что | Адрес |
|---|---|
| метаданные акта | http://publication.pravo.gov.ru/api/Document?eoNumber=<eo> |
| поиск | http://publication.pravo.gov.ru/api/Documents?PageSize=30&Index=1&Name=<слова>&Block=<блок>, ещё Number=, SignatoryAuthorityId=, PublishDateFrom=, PublishDateTo= |
| блоки | president (в том числе федеральные законы), government, federal_authorities. subjects не используем |
| скан | http://publication.pravo.gov.ru/file/pdf?eoNumber=<eo> (без текстового слоя, /api/DocumentText отдаёт false) |
| страница публикации | http://publication.pravo.gov.ru/document/<eo> |
| запасной канал | http://publication.pravo.gov.ru/api/Rss?Block=federal_authorities |
| описание API | http://publication.pravo.gov.ru/swagger/v1/swagger.json |
| ИПС через архив | https://web.archive.org/cdx/search/cdx?url=pravo.gov.ru/proxy/ips/*&filter=urlkey:.*nd=<nd>.*&output=json, затем снимок https://web.archive.org/web/<ts>id_/http://pravo.gov.ru/proxy/ips/?doc_itself=&backlink=1&nd=<nd>&page=1&rdk=<rdk>, кодировка cp1251. Для 59-ФЗ: nd=102106413, снимок 20260626091943 (отвечает 200, 67 КБ, проверено 11.10.2026) |
| перекрёстная проверка календаря | npm prodcalendar@0.1.2, только в тестах |
Подводные камни API (проверено из AT 11.10.2026): PageSize только 10, 30, 100, 200, иначе пустой ответ без ошибки. Name ищет по complexName целыми словами: «паллиатив» даёт 0, «паллиативной» 303 (федеральных 14). Номера повторяются: «100н» 32 совпадения, «345н» 6 разных актов, поэтому акт находим по тройке «номер, дата, орган», а храним по eoNumber. Номер, дату и орган берём из метаданных API, не из OCR (шапка 208н распознаётся мусором). При параллельных запросах сервер отдаёт пустые тела и таймауты: строго последовательно
Кандидаты в acts.yaml. eoNumber с пометкой «API» сверены запросом /api/Document 11.10.2026 в этой архитектуре, остальные сверили архитекторы в тот же день. DATA перепроверяет каждый
| id | Акт | eoNumber | Источник текста |
|---|---|---|---|
208n-243n |
Положение о паллиативной помощи (Минздрав и Минтруд) | 0001202505290031 (196 стр.) |
official_pdf |
345n-372n |
прежнее Положение от 31.05.2019, отменено 208н/243н | 0001201906270031 API (108 стр.) |
official_pdf |
187n, 193n |
паллиативная помощь взрослым и детям, 2015 | 0001201505120026, 0001201505150003 |
official_pdf |
398n |
перечень медизделий для дома от 09.07.2025 | 0001202508130021 API |
official_pdf |
348n |
прежний перечень от 31.05.2019 | 0001201907010015 API |
official_pdf |
505n |
передача медизделий пациенту от 10.07.2019 | 0001201909250013 API |
official_pdf |
180n |
врачебная комиссия от 10.04.2025 | 0001202505160009 API |
official_pdf |
502n |
прежний порядок ВК, 2012 | нет на портале | none, статус по цитате 180н |
789n |
копии медицинских документов от 31.07.2020 | 0001202009240027 API |
official_pdf |
100n |
правила отпуска лекарств от 07.03.2025 | 0001202504100006 API |
official_pdf, только процедурные пункты |
1093n |
прежние правила отпуска, 2021 | искать | статус только по цитате 100н |
1094n |
назначение лекарств и бланки от 24.11.2021 | 0001202111300115 API |
official_pdf, только процедурные пункты |
692n |
МСЭ от 09.12.2025 | 0001202604140027 API |
official_pdf |
pp-588 |
признание лица инвалидом | 0001202204080035 |
official_pdf |
pp-2188 |
программа госгарантий | 0001202512300036 |
official_pdf |
1583-r |
распоряжение от 23.06.2026, тема по метаданным | 0001202606260118 |
official_pdf |
236-fz |
О Фонде пенсионного и социального страхования | 0001202207140012 API |
official_pdf, основание правила ПФР/ФСС → СФР |
59-fz |
о порядке рассмотрения обращений граждан, 2006 | нет на портале | ips_archive, verified:false |
547-fz, 480-fz, 355-fz |
поправки к 59-ФЗ | 0001202412280052, 0001202308040085, 0001201711270077 |
official_pdf, только amended_by у 59-fz |
koap |
КоАП, ст. 5.59 | нет на портале | ips_archive, verified:false |
35-fz |
поправки в ТК 2012 (ст. 112) | 0001201204240009 API (4 стр.) |
official_pdf, основание базовых праздников |
pp-240 |
ТСР, 2008 | нет на портале | ips_archive или в candidates.yaml |
pp-1335, pp-1466, pp-1187 |
переносы выходных на 2025, 2026, 2027 | 0001202410040023, 0001202509240023, 0001202609180037 |
official_pdf |
Не вносим без eoNumber и цитаты-основания: 356н, «срок действия до 01.09.2031» у 180н (пока не найден пункт), 23н, ПП 2351
Кандидаты правил сроков (id, что ищем в тексте). Значения в данных только из найденной цитаты
| id | Что | Где искать |
|---|---|---|
59fz-answer |
ответ на письменное обращение, регистрация + 30 дней, продление | ч. 2 ст. 8, ч. 1-2 ст. 12 59-ФЗ (ИПС) |
59fz-redirect |
перенаправление по компетенции, 7 дней | ч. 3 ст. 8 59-ФЗ |
208n-p23-transition |
передача сведений о ребёнке во взрослую сеть, не менее чем за 90 рабочих дней до 18 лет, обратный счёт | п. 23, стр. 8 скана |
505n-vk-decision, 505n-transfer |
решение ВК о передаче изделия, передача по договору | 505н |
180n-protocol-extract |
выписка из протокола ВК по запросу | 180н п. 15-16 |
789n-copies, 789n-inpatient |
копии и выписки, в стационаре в суточный срок | 789н |
100n-deferred |
отсроченное обслуживание рецепта | 100н, только сроки |
692n-mse, pp-588-mse |
сроки МСЭ | 692н, ПП 588 |
pp-240-tsr |
сроки по ТСР | ПП 240 (ИПС) или candidates.yaml |
pravo.py: клиент API.urllib.request,User-Agent: openpalliative-watch (+https://github.com/openpalliative), один запрос за раз, пауза 2 с между запросами, таймаут 30 с, 4 попытки с паузами 2, 4, 8, 16 с.PageSizeпроверяется по списку 10, 30, 100, 200. Разбор ответа в dataclass, неизвестная форма ответа = исключениеSourceFormatErrorfetch_pravo.py:uv run python -m pipeline.fetch_pravo <act_id>|--all. Метаданные вraw/meta/<eo>.json(коммитятся, около 2 КБ), PDF вraw/cache/<eo>.pdf(не коммитится), строка вraw/SOURCES.tsv:eoNumber, url, sha256, bytes, retrieved_atocr.py: только нужные страницы. Проход A:pdftoppm -r 300 -grayиtesseract -l rus --psm 4. Проход B:-r 400и--psm 6. Нормализация для сравнения (NFC, пробелы, перенос слова через дефис в конце строки). Разница строк вraw/ocr-diff/<quote_id>.diff. Вырезка скана под цитатой по координатам изtesseract ... tsvвraw/crops/<quote_id>.png(оттенки серого, до 150 КБ). Полный OCR страниц только вraw/cache/ocr/quotes.py:uv run python -m pipeline.quotes add <act_id> <page> "<начало>" "<конец>"вырезает фрагмент между маркерами из прохода A, пишет черновик цитаты вdata/quotes/<act_id>.yamlсverified:false, делает вырезку и difffetch_ips.py: CDX web.archive поnd, последний снимок сid_, cp1251, вырезка статьи по заголовку, sha256 снимка вalt_source. Всегдаverified:falsecalendar.py: изholidays.yamlиtransfers.yamlстроитoffиworkпо годам. Правило ч. 2 ст. 112: праздник в субботу или воскресенье переносится на следующий рабочий день, кроме январских, которые переносит постановлениеexport.py:datasets(root) -> list[dict]. Собирает конверты черезopsite.data.make_envelope, считаетcites_repealed,rule_ids,off/work, сливаетreview/. СPN_FIXTURES=1возвращаетtests/fixtures/*.jsonкак есть (для SITE до данных и для e2e на синтетике)watch.py: раздел 5
Что уже есть локально во временной папке сборки $OP_SCRATCH (путь даёт ведущий, в репо его не пишем; это не репо, нужное переносить в raw/ и pipeline/tests/fixtures/)
pn/208n.pdf(8,8 МБ, 196 стр.) иpn/p208/(PNG 171 страницы),o208/p-001.txt ... p-171.txt(один проход tesseract). Ориентиры: п. 4 стр. 3-4, п. 5 стр. 4, п. 12-15 стр. 6, п. 23 и п. 25 (ссылка на 348н) стр. 8, прил. 1 стр. 10-13, прил. 2 (кабинет) с стр. 14. Страницы 172-196 не отрендереныdev/208ocr.txt(стр. 1-44),pn/n398.pdfиpn/n398.txtpnarch/: ответы API для фикстур наблюдателя:pal.json(Name=паллиативной, 303),tr.json(14 постановлений о переносах, 2014-2027),q.json,m.json,f.json(пустой ответ),rss.xml,swagger.json,od30.csvpn/swagger.json
| Уровень | Что значит | Кто ставит | Условие |
|---|---|---|---|
цитата verified |
текст совпадает со сканом официальной публикации | DATA при подготовке, позже мейнтейнер | text_source: official_pdf, два прохода OCR, расхождения разобраны по вырезке, scan_checked записан, вырезка лежит в raw/crops/ |
акт verified |
реквизиты совпадают с API, статус подтверждён | DATA | метаданные из API, поиск от ДД.ММ.ГГГГ № N выполнен в дату verified_at, у repealed проверенная цитата-основание. Акты ips_archive и none (кроме отменённых по цитате другого акта) всегда false |
правило срока verified |
число и единица стоят в проверенной цитате | DATA | все quote_ids проверены, value встречается в тексте цитаты (pytest), нормы in_force |
год календаря verified |
постановление и праздники сверены | DATA | цитаты ПП о переносах и 35-ФЗ проверены, совпадение с prodcalendar за 2025-2026 |
строка показаний verified |
как цитата | DATA | как цитата |
legal_review.status: ok |
толкование проверил юрист в РФ | только мейнтейнер через review/*.yaml и CODEOWNERS |
в MVP везде none |
Чего нельзя: verified:true у цитаты из ИПС, у акта без eoNumber (кроме отменённого по проверенной цитате), у правила без цитаты. Если расхождение OCR не удаётся разобрать по вырезке, цитата остаётся verified:false. Медицинские пороги из приказа (например, «потеря массы тела более чем на 10%») показываются только дословно в <blockquote data-verbatim>, без пересказа. Из 100н и 1094н не берём перечни препаратов и бланков, только сроки и порядок
Конверт PLATFORM 4.1, лицензия CC-BY-4.0, source с eoNumber. stale_after_days: 90
| Датасет | Кто берёт | Зачем |
|---|---|---|
acts |
все четыре инструмента | norm(), ссылки acts/<id>/#p-N, fail closed |
quotes |
ok, hc | op-quote в документах и на листах |
deadline-rules |
ok, dv | сроки дела, ссылка «Сколько ждать» |
calendar |
ok, hc | рабочие дни, праздники на карте на ночь |
escalation |
ok | следующий шаг после срока |
indications |
hc | лист /conclusion/ |
lint-rules |
ok | pack_lint.py при сборке пакета |
changes |
хаб (позже) | |
entrypoints |
хаб | ситуации и /tools/ |
Код: lib/deadlines.js, lib/textcheck.js (в _site/lib/), tools/pack_lint.py (в _site/tools/, только стандартная библиотека Python)
Строки для deps.yaml потребителей (их правят сами потребители):
- ok:
datasets: [acts, quotes, deadline-rules, calendar, escalation, lint-rules],lib: [deadlines.js, textcheck.js],tools: [pack_lint.py] - hc:
datasets: [acts, quotes, calendar, indications, deadline-rules],lib: [deadlines.js] - dv:
datasets: [acts, deadline-rules]
Версии: правка текста цитаты или записи = новый version (CalVer). Новое необязательное поле = минор schema_version. Переименование или удаление поля = v2/, v1/ живёт ещё 12 месяцев
Данных соседей не берём, pal-norms первый в графе. Из хаба через scripts/sync-ui: opsite/ (сборщик, UI, situations.json для «Вернуться»)
// lib/deadlines.js, без DOM и сети
computeDeadline({ rule, start, calendar })
// rule: элемент deadline-rules; start: "YYYY-MM-DD"; calendar: конверт calendar
// -> { rule_id, due, due_alt, latest, formula, steps: [{ label, date }],
// norm_ids, ambiguous, verified, calendar_verified, margin_applied }
isWorkingDay(iso, calendar) // -> { working, verified }
addWorkingDays(iso, n, calendar) // n может быть отрицательным
nonWorkingDays(fromIso, toIso, calendar) // -> [{ date, kind: "weekend" | "holiday" | "transfer" }]
calendarYears(calendar) // -> { 2027: { verified } }- Календарные дни считаются со следующего дня после события.
preприбавляется первым.within_24hдаёт следующий календарный день shift_if_nonworking: yesпереносит срок на ближайший рабочий день,unknownоставляетdueбез переноса и кладёт перенесённую дату вdue_alt,latest= более поздняя из двух- Обратный счёт:
due= рабочий день, до которого от него до события (не включая день события) ровноvalueрабочих дней. Если хоть один год в диапазоне неverified, вычитается ещёmargin_working_days,margin_applied> 0 - Вне известных лет: выходные и праздники
baselineс переносом по ч. 2 ст. 112,calendar_verified:false formula: строка по-русски («30 дней со дня регистрации, 3 дня на регистрацию»), без номеров актов
// lib/textcheck.js
normalize(text) // NFC, пробелы, «№»
checkText(text, rules, { acts })
// -> [{ rule_id, kind, severity, line, col, start, end, fragment, context, message,
// act_id, suggest_act_id, quote_ids }]# tools/pack_lint.py
python3 tools/pack_lint.py <файлы или папки> --rules lint-rules.json --acts acts.json [--json]
# выход 1, если есть находка severity: error или norm('id') на неизвестный, не in_force или не verified актcheck_text в Python повторяет checkText построчно. Совпадение двух реализаций держит общий эталон tests/golden/lint/ (раздел 7)
| Файл | Когда | Права | Что |
|---|---|---|---|
pages.yml |
push в main, вручную, запуск из watch.yml |
build contents: read, deploy pages: write, id-token: write |
шаблон PLATFORM 6.1, плюс env: OP_TARGET: pages |
checks.yml |
PR, push в main, вручную (так его запускает watch.yml на ветке черновика) |
contents: read |
шаблон PLATFORM 6.2, плюс actionlint и uv run python -m pipeline.watch --fixtures pipeline/tests/fixtures/pravo |
watch.yml |
cron: "17 4 * * 1" и cron: "17 4 * 9 *" (в сентябре ежедневно, окно постановления о переносах), вручную |
contents: write, pull-requests: write, issues: write, actions: write |
раздел 5.2 |
release.yml |
тег v* |
contents: write |
шаблон PLATFORM 6.5 с OP_TARGET: pages: zip _site, zip data/v1/*.json, те же наборы в SQLite (.github/scripts/datasets_sqlite.py), SHA256SUMS |
watch.yml запускается на ${{ fromJSON(vars.WATCH_RUNNER || '"ubuntu-24.04"') }} и на Linux ставит poppler-utils tesseract-ocr tesseract-ocr-rus, если их нет, для шага OCR постановления о переносах
uv run python -m pipeline.watch [--fixtures DIR] [--report _reports/watch.json] [--apply]
- Канарейка.
GET /api/Document?eoNumber=0001202505290031: есть ключиeoNumber, number, documentDate, pagesCount, name,pagesCount == 196.Documents?PageSize=30&Name=паллиативнойдаётitemsTotalCount > 0. Не прошла: ничего не меняем, в отчётsource_format. Сеть не ответила:consecutive_failures += 1 - Отслеживаемые акты. Для каждого акта с eoNumber:
Name=от ДД.ММ.ГГГГ № Nпо блоку органа. Найденный eoNumber, которого нет ни вacts.yaml, ни вamended_by, ни вseen, даётreviewвидаamendment_or_repeal - Тематический поиск.
Nameпо словоформам «паллиативной», «паллиативная», «паллиативную», «паллиативных» в блокахpresident,government,federal_authorities,PublishDateFrom = last_ok_at - 14 дней. Новое даётnew_act - Переносы.
Name=О переносе выходных дней,Block=government. Новый eoNumber даётtransfer_decree: скачать PDF, OCR, черновик года вdata/calendar/transfers.yamlсverified:falseи запись вdata/changes.yaml - Срок действия.
valid_untilближе 120 дней даётsunset_soon - Снимок.
data/snapshots/watch-state.json:checked_at,last_ok_at,consecutive_failures,seen {eo: {pagesCount, pdfFileLength, publishDateShort}},queries {имя: {total, last_eo}}. ИзменениеpagesCountилиpdfFileLengthу известного акта даётreviewвидаrepublished
С --apply (только в Actions или у мейнтейнера):
- Коммитится само: только
data/snapshots/watch-state.json, сообщениеdata: снимок pravo.gov.ru <дата>. После коммитаgh workflow run pages.yml: пуш токеном Actions сам Pages не запускает. Расписание коммит не держит, поэтому последний шаг сif: always()делаетPUT .../actions/workflows/watch.yml/enable - PR (
peter-evans/create-pull-request, веткаwatch/<дата>): черновик года календаря и запись ленты (меткаreview:data), затемgh workflow run checks.yml --ref watch/<дата>. Мерж PR = событие принято - Issue (
gh issue create, заголовок[watch:<sha8>] ..., где sha8 отkind + eoNumber; дубли ищутся по заголовку среди открытых и закрытых):new_act,amendment_or_repeal,republished,sunset_soon,source_format, и «Наблюдатель не достучался до pravo.gov.ru» приconsecutive_failures >= 3 - Никогда само:
status,superseded_by, цитаты, правила, показания, эскалация, правила проверки текста. Только PR с меткойreview:legalи ревью человеком
Запасной путь, если pravo.gov.ru не пускает раннеры GitHub: scripts/watch-local раз в месяц у мейнтейнера (с его компьютера API отвечает), тот же отчёт и те же PR через gh. В README: «6 месяцев без ревьюера - архив», релиз zip для зеркала
data_date в meta.yaml правит человек после сверки записей. Дата последнего прохода наблюдателя показывается отдельно и data_date не обновляет
pal-norms/
├── ARCHITECTURE.md ARCH
├── README.md LICENSE LICENSE-DATA DATA (позже INT)
├── site.yaml SITE
├── pyproject.toml uv.lock .python-version DATA
├── package.json pnpm-lock.yaml .node-version DATA (prodcalendar) + SITE (просит в STATUS.md)
├── playwright.config.js SITE
├── .gitignore DATA: _site/ _reports/ .venv/ node_modules/ raw/cache/
├── data/ DATA
│ ├── meta.yaml acts.yaml deadline-rules.yaml candidates.yaml escalation.yaml
│ ├── indications.yaml lint-rules.yaml changes.yaml entrypoints.yaml
│ ├── quotes/<act_id>.yaml
│ ├── calendar/holidays.yaml calendar/transfers.yaml
│ ├── review/ snapshots/watch-state.json
│ └── schema/*.schema.json (написал ARCH)
├── pipeline/ DATA
│ ├── __init__.py pravo.py fetch_pravo.py fetch_ips.py ocr.py quotes.py calendar.py export.py watch.py
│ └── tests/ test_*.py, fixtures/pravo/*.json (ответы API из pnarch), fixtures/ips/*.html
├── raw/ DATA: SOURCES.tsv, meta/<eo>.json, crops/*.png, ocr-diff/*.diff; cache/ в .gitignore
├── lib/deadlines.js lib/textcheck.js DATA
├── tools/pack_lint.py DATA
├── content/ SITE: pages/*.md, home.yaml, gates.yaml, escalation-links.yaml, labels.yaml
├── templates/pages/ SITE: index, deadlines, deadline-rule, escalation, indications, acts, act, calendar, calendar-year, changes, check, data (.html.j2)
├── templates/partials/ SITE: pn-act-row, pn-act-status, pn-rule-table, pn-quote-list, pn-step
├── assets/css/pal-norms.css SITE, классы pn-*
├── assets/js/ SITE: pn-deadline.js pn-check.js pn-acts.js pn-indications.js pn-holidays.js
├── site_hooks.py SITE: страницы актов, правил, лет; карты id → запись; чтение watch-state.json
├── opsite/ SYNC (scripts/sync-ui)
├── scripts/ DATA: sync-ui, sync-data, serve-one, gate.sh, watch-local
├── tests/unit/ DATA: deadlines.test.js, textcheck.test.js, calendar.test.js
├── tests/golden/ DATA: deadlines.yaml, lint/*.txt + *.expected.json
├── tests/fixtures/*.json DATA (синтетика, написал ARCH)
├── tests/e2e/ SITE: pages.json, *.spec.js
└── .github/workflows/ DATA: pages.yml checks.yml watch.yml release.yml
- DATA не трогает
content/,templates/,assets/,site_hooks.py,site.yaml,tests/e2e/. SITE не трогает остальное - Связь только через: схемы
data/schema/, конверты (pipeline.exportили фикстуры), APIlib/*.jsиз 4.3, поляwatch-state.json(checked_at,last_ok_at), файлdata/meta.yaml - SITE начинает сразу:
PN_FIXTURES=1 uv run python -m opsite build. Пока DATA не написалexport.py, SITE может временно положить свойpipeline/export.pyтолько с веткой фикстур и сказать об этом в STATUS.md. DATA заменяет его первым делом - Изменить схему может только DATA. Если SITE нужно поле, строка в
STATUS.mdраздел pal-norms, DATA добавляет поле и фикстуру - Порядок DATA: 1)
sync-ui,pyproject,export.pyс фикстурами,lib/deadlines.jsна синтетике; 2) акты и метаданные; 3) цитаты 208н/243н (п. 2 прил. 1, п. 4, 5, 12, 13, 15, 23, 25, прил. 1 целиком); 4) календарь и golden-тесты; 5) остальные цитаты и правила; 6)lint-rules,textcheck.js,pack_lint.py, эталонный корпус; 7) наблюдатель и workflows - Порядок SITE: 1)
site.yaml, корень,acts/,acts/<id>/; 2)deadlines/и<pn-deadline>; 3)indications/,escalation/; 4)calendar/,changes/,check/,data/; 5) e2e, печать, скриншоты
Полные конверты в tests/fixtures/: 3 акта (действующий, отменённый, закон из ИПС), 4 цитаты, 3 правила (вперёд календарные с неоднозначностью, назад рабочие с запасом, в течение суток), календарь (baseline и 2026 без постановления), 3 ступени, 9 строк показаний, 3 правила проверки, 2 события. Все тексты начинаются со слова «Пример», id с demo-, ссылки пустые или на example.invalid. Фрагмент:
{ "id": "demo-turning-18", "verified": true, "verified_at": "2026-10-11", "source_ref": "demo-source",
"situation": "Пример: ребёнку скоро 18", "result_label": "Сведения должны передать не позже",
"from_event": "turns-18", "from_event_label": "Дата, когда исполнится 18",
"who": "детская медицинская организация", "value": 90, "unit": "working_days", "direction": "backward",
"pre": null, "extension": null, "shift_if_nonworking": "no", "margin_working_days": 5,
"norm_ids": ["demo-polozhenie"], "quote_ids": ["demo-polozhenie-p23"], "ambiguous": false,
"ambiguity_note": null, "escalation_ids": [], "audience": ["family", "org"],
"legal_review": { "status": "none", "role": null, "reviewed_at": null, "expires_at": null, "ref": null } }Фикстуры проходят opsite/schema/envelope.schema.json и свои схемы (проверено при записи). Тест сборки: без PN_FIXTURES в _site/data/v1/ нет ни одного id demo-
sh scripts/sync-ui
uv sync --frozen
uv run pytest -q
PN_FIXTURES=1 uv run python -m opsite build && uv run python -m opsite check
uv run python -m opsite build && uv run python -m opsite check
pnpm install --frozen-lockfile
pnpm test:unit
pnpm test:e2e
pnpm shots
sh scripts/gate.sh
uv run python -m pipeline.watch --fixtures pipeline/tests/fixtures/pravo --report _reports/watch.jsonpytest (DATA)
- каждый файл
data/проходит свою схему послеcoerce, все id уникальны, ссылки на акты, цитаты, правила и ступени существуют supersedesиsuperseded_byсимметричны, циклов замен нет- у
repealedестьstatus_basisс проверенной цитатой, уin_forceпустоrepealed_at - eoNumber 16 цифр, у
official_pdfон есть,urlиpdf_urlсобраны из него verified:trueу цитаты только сofficial_pdf,scan_checkedи файломraw/crops/<id>.png; приdiff_lines > 0естьraw/ocr-diff/<id>.diff- у правила срока
valueвстречается в тексте одной из его цитат;ambiguousсambiguity_note - у каждого отменённого акта с
aliasesесть правилоrepealed_act - нет строк про исключённые территории и названий из
opsite/lint/drugs.txtвdata/ watchна записанных ответах: канарейка, пагинация, пустой ответ при неверномPageSize, новый акт, поправка, постановление о переносах, дубль issue не создаётся, при сбое сети ничего не меняетсяfetch_ipsна сохранённом снимке cp1251tools/pack_lint.pyнаtests/golden/lint/даёт ровно*.expected.json
node --test (DATA)
tests/golden/deadlines.yaml: не меньше 30 строк «правило, дата события → due, due_alt, formula» на 2025-2027: 31 декабря, январские, майские, 12 июня, 4 ноября, рабочие субботы, обратный счёт п. 23, 59-ФЗ с переносом и без, суточный срок, год вне календаря с запасомcalendar.jsonсовпадает сprodcalendar@0.1.2по каждому дню 2025 и 2026 годовtextcheck.jsнаtests/golden/lint/: синтетическая памятка ловит все 6 правил и не трогает «208н/243н», «398н», «180н», «СФР»; «30 рабочих» без «18 лет» рядом не срабатывает
Playwright (SITE), tests/e2e/pages.json: home, deadlines, deadline-208n-p23-transition, escalation, indications (nojs), acts (nojs), act-208n-243n, act-345n-372n, calendar, calendar-2027 (print_pages: 1), changes, check, data. Плюс сценарии:
deadlines.spec.js: выбрать правило, ввести дату, увидеть дату и формулу; неоднозначное правило показывает две даты; прошедший срок даёт ссылку в ok сruleиfrom; фрагмент#rule=..&from=..предзаполняет форму и исчезает из адреса;.icsскачивается; печать результата 1 лист A4; на 390px нет горизонтальной прокруткиcheck.spec.js: вставить синтетическую памятку, 6 находок; сторож сети пуст;localStorageи IndexedDB после проверки пустыindications.spec.js: без JS видны все группы; выбор группы не меняетlocation.hrefиhistory.length; плашка «Черновик» видна; сOP_TARGET=pagesи закрытыми воротами страница-заглушкаacts.spec.js: фильтр «Утратили силу» оставляет отменённые;acts/208n-243n/#p-23прокручивает к пункту; ссылки на публикацию ведут наpublication.pravo.gov.ru- axe без serious и critical, один
h1,lang="ru", ноль внешних запросов, ни одной ошибки в консоли
- Все команды 7.1 дают 0, сборка до 15 секунд
- В
actsне меньше 15 записейverified:trueс eoNumber, сверенным через API; вdeadline-rulesне меньше 6 правилverified:true; 59-ФЗ виден со статусом «Не проверено» и источником ИПС indications: прил. 1 целиком, п. 4, п. 5, п. 2, п. 12, 13, 15; доляverified:trueне меньше 90%, остальные видны со статусом- Календарь 2025-2027
verified:true, совпадает сprodcalendarза 2025-2026 - 6 правил проверки текста, JS и Python дают одинаковый результат на эталоне
- Наблюдатель проходит тесты на фикстурах,
_reports/watch.jsonпо схеме PLATFORM 6.3 - Скриншоты desktop и mobile, светлая и тёмная тема, печать просмотрены глазами, найденное исправлено
- На
http://localhost:8080/pal-norms/работают ссылки из хаба (/s/refused/,/s/conclusion/,/s/turning-18/,/s/night-pain/) и обратные ссылки в ok, hc, pd - Ни длинного, ни среднего тире вне дословных цитат, ни эмодзи, ни номеров актов литералом в
content/иtemplates/
| Риск | Мера |
|---|---|
| Нет официального текста 59-ФЗ, КоАП, ПП 240, а 59-ФЗ нужен ok каждый день | ips_archive с verified:false и честной подписью. Сверка по живой ИПС мейнтейнером в РФ (scan_checked.method: ips-live). Не утверждаем, что 547-ФЗ и 480-ФЗ не меняли ст. 8 и 12, пока не прочитан их текст |
| OCR ошибается одинаково в двух проходах (н/и, цифры, даты) | Вырезка скана под каждой цитатой и сверка глазами, без неё verified:false |
| Семья прочтёт показания как «вам положено» | П. 2 сверху, «решает врачебная комиссия», без выводов, черновик для медика и юриста, ворота на Pages |
| Дата срока читается как гарантия | «Ориентировочно» и две даты у неоднозначных, строка про юриста, обратный счёт с запасом |
| Ложные находки «30 рабочих» | Только при «18 лет» рядом, уровень «Важно», в находке цитата п. 23 |
| API меняет формат или режет раннеры GitHub | Канарейка, «при сомнении ничего не менять», RSS как второй канал, WATCH_RUNNER, scripts/watch-local |
| Нет ревьюера | Факты живут сами, толкования помечены, expires_after гасит старые ревью, «6 месяцев без ревьюера - архив» |
В платформу
- Два уровня проверки:
verified(текст сверен со сканом) иlegal_review(толкование).norm()уже требуетverifiedиin_force. ok и hc нужно отдельно проверятьdeadline-rules.verifiedи показывать «Толкование не проверено юристом в РФ», покаlegal_review.status != ok - В
deps.yamlok и hc добавить датасетquotes, в ok ещёlint-rulesиlib/textcheck.js(раздел 4.1) - Бейдж статуса акта (
pn-act-status: «Действует», «Утратил силу», «Ещё не вступил», «Статус не проверен») пока локальный в pn. Если понадобится ok и dv, поднять вop-status - Иконки
circle-xв стартовом наборе нет, pn обходитсяcircle-alert
Решить Кириллу
- Публиковать ли
indications/на Pages до ревью медика и юриста в РФ (итоговый план по идее 7 говорит «только датасет»). По умолчанию ворота закрыты, датасет публикуется - 59-ФЗ, КоАП и ПП 240 есть только в ИПС, а из AT она открывается лишь через снимки web.archive.org. До мейнтейнера в РФ правило ответа на обращение остаётся «Не проверено», и ok в документы его не возьмёт