In English. Public site and REST API showing how Knesset factions and individual members voted, by name. Each MK is shown with the faction they belonged to on the vote date, and every number links to its official source. Site: https://knessetvotes.org (Hebrew, English, Russian, Arabic beta). API docs: https://knessetvotes.org/docs. Stack: Python (FastAPI, psycopg), Postgres 17, Next.js. To run it locally, see "Run locally" in CLAUDE.md. Notes in English: docs/roadmap.md, docs/deploy.md. License: code MIT (LICENSE), data CC BY 4.0 (LICENSE-DATA.md). Security reports: SECURITY.md. The rest of this README is in Russian.
Публичный ресурс и REST API: как фракции и отдельные депутаты Кнессета голосовали по законопроектам. Поимённо, с историей фракций на дату голосования и ссылкой на официальный источник для каждой цифры.
Сайт: https://knessetvotes.org · API: https://knessetvotes.org/docs
Статус (2026-10-04): работает в продакшене, на русском, английском и иврите; арабская версия — бета (перевод ещё не проверен носителем языка). Данные: 37 045 пленарных голосований (2003-10-20 … 2026-07-28), 2,02 млн поимённых голосов; до июля 2021 года поимённые списки точно совпадают с официальными итогами в 96,5% голосований (с точностью до двух голосов — в 99,9%). 37 правительств с коалицией и оппозицией по каждой фракции, 31 партия за все созывы. Обновляются автоматически на сервере. Дальнейшие планы — docs/roadmap.md.
- docs/architecture.md — проектная спецификация v0.1 (целевая система)
- docs/adr/0001-mvp-scope.md — что из спецификации входит в MVP
- docs/audit/source-audit.md — какие официальные данные есть и насколько они полны
- docs/roadmap.md — план развития: удобство, языки (he/en/ru/ar), история данных, эксплуатация
- docs/deploy.md — продакшен: сервер, деплой, расписание обновлений, бэкапы, восстановление
Скрипты используют только стандартную библиотеку Python 3.12.
python3 audit/fetch_official.py # официальные таблицы OData → audit/raw/official/
# дампы Open Knesset: https://production.oknesset.org/pipelines/data/knesset/<table>/<table>.csv → audit/raw/oknesset/
python3 audit/compare.py > docs/audit/compare-output.mdaudit/raw/ не коммитится.
PostgreSQL 17 в Docker, схема — db/migrations (0001 ядро, 0002 особенности источника, 0003 старый Votes.svc, 0004 варианты написания имён, 0005 темы и поиск, 0006 имена на трёх языках, 0007 официальная классификация законов, 0008 фото депутатов, 0009 короткие названия фракций на иврите, 0010 правительства, коалиции и партии, 0011 коалиция и оппозиция по голосованиям, 0012 арабский: имена, партии, нормализация для поиска). Миграции применяются по порядку; изменённая уже применённая миграция — ошибка.
docker compose -f infra/compose.yaml up -d --wait # localhost:5433
uv run db/migrate.py # применить миграции
uv run pytest # тесты инвариантов схемыuv run hkv ingest --from 2025-01-01 --to 2025-06-30 # справочники + голосования периода из OData v4
uv run hkv ingest --from ... --to ... --skip-reference # только голосования
uv run hkv verify --from 2025-01-01 --to 2025-06-30 --sample 30 # покрытие + выборочная сверка с источником
# история: периоды по 3 месяца раздаются N процессам; можно прервать и перезапустить — продолжит с места остановки
uv run hkv backfill --from 2016-09-27 --to 2026-09-27 --workers 5 --skip 2025-01-01..2025-06-30
tail -f logs/backfill.log # строка на каждые ~10 голосований
uv run hkv status # покрытие по годам + когда каждый процесс последний раз писал в лог
# старый Votes.svc (до 2021-07): официальные итоги, голосования, которых нет в v4, пометки «не вошло в итог»
uv run hkv legacy --from 2016-09-27 --to 2021-07-31 # лог: logs/legacy.log; запускать после v4 за тот же период, повтор безопасенСервер Кнессета отдаёт ~1 страницу (100 записей) за 2–2,5 с на процесс; 5 процессов работают без замедления и без ошибок WAF.
Сырые страницы источника сохраняются в data/raw/ (не коммитится); каждая строка БД ссылается на свой source_snapshot. Проблемы данных не отбрасываются, а записываются в data_issue.
hkv update — справочники (депутаты, фракции, должности, посты в правительстве) и перечитывание голосований за последние 30 дней (--days): новые голосования и исправления источника (прежние версии сохраняются в row_revision), затем темы, имена новых депутатов, партии и коалиции. В конце пишет data_release — его показывает GET /api/v1/status и подвал сайта («данные на …»). Около 2–3 минут.
На сервере запускается таймером systemd: ежедневно в 05:30 и каждые 2 часа в дни заседаний (пн–ср, до 02:15 следующего дня). Ночной дамп БД — в 04:30. Подробности, команды проверки и восстановления — docs/deploy.md.
Локально: uv run hkv update (лог logs/update.log, или --log - в stderr).
uv run uvicorn hkv.api.app:app --reload # http://127.0.0.1:8000/docs — OpenAPI| Endpoint | Что возвращает |
|---|---|
GET /api/v1/votes |
Голосования, новые сверху. Фильтры: date_from, date_to (дата голосования, Asia/Jerusalem, границы включены), stage, motion_type, bill, faction, person, q; повтор параметра = OR; limit ≤ 100, cursor |
GET /api/v1/votes/{id} |
Вопрос, поимённые счётчики, официальные итоги (до 2021-07), раскладка по фракциям на дату голосования |
GET /api/v1/votes/{id}/ballots |
Поимённый список: депутат, фракция тогда, выбор, статус участия |
GET /api/v1/members, /members/{id} |
Депутаты: мандаты, история фракций, участие в голосованиях и расхождения с большинством фракции (числитель, знаменатель) |
GET /api/v1/members/{id}/votes |
Голоса депутата; deviated=true — только против большинства своей фракции |
GET /api/v1/factions, /factions/{id}, /factions/{id}/votes |
Фракции созыва, состав во времени, единство; split_only=true — голосования с разделением, contested, min_cast — как в /votes |
GET /api/v1/parties, /parties/{slug} |
Партии за все созывы: фракции по созывам, коалиция/оппозиция на конец каждой |
GET /api/v1/governments |
Правительства (номер, созыв, даты, глава правительства) |
GET /api/v1/bills, /bills/{id} |
Законопроекты, по которым голосовали: поиск, статус, инициаторы, все голосования |
GET /api/v1/terms, /status |
Созывы; дата и покрытие последнего обновления |
GET /api/v1/topics, /topics/{slug} |
Темы и последние законопроекты темы; голосования темы — /api/v1/votes?topic= |
GET /api/v1/search?q= |
Поиск: по-русски — темы и фракции; на иврите — законопроекты, депутаты, фракции (нормализация, опечатки); число — законопроект |
Фильтр topic есть у /votes и /bills.
Язык: ?lang=he|en|ru|ar (по умолчанию he) заполняет поля для показа — name (депутат), name/short (фракция), faction_name (фракция в поимённом списке), label (тема); при отсутствии перевода — иврит. Поля на всех трёх языках (name_ru, short_en, short_he, label_en…) есть в ответах всегда.
uv run hkv coalition загружает посты в правительстве (KNS_PersonToPosition с GovernmentNum) и выводит из них правительства и роль каждой фракции (src/hkv/coalition): фракция в коалиции, пока кто-то из её членов — министр или замминистра в правительстве этого дня; министр, сложивший мандат по «норвежскому закону» (с 2015), считается за свою фракцию; дни без постов (после выборов, до присяги) — unknown. Известные исключения — overrides.toml (origin='curated', с обоснованием). Затем пересчитывается vote_bloc: голоса коалиции и оппозиции по каждому голосованию и признак «спорное» (большинства разошлись). hkv update делает всё это при каждом запуске.
Партии за все созывы — parties.toml: какие фракции каждого Кнессета относятся к партии; совместный список относится ко всем входившим партиям. Загружается hkv names и hkv update.
У /votes фильтры contested=true (большинства коалиции и оппозиции разошлись) и min_cast (не меньше N голосов за/против/воздержался); в ответах — blocs (голоса коалиции и оппозиции), у фракций в голосовании — alignment.
uv run hkv names — официальные имена депутатов на английском, русском и арабском с сайта Кнессета (ID сайта через KNS_MkSiteCode, списки текущего созыва, Викиданные P9770, ручной список src/hkv/names/curated/mk_names.toml), варианты для поиска из Викиданных и названия фракций (factions.toml, для текущего созыва — официальные с сайта). Там же ссылки на официальные фотографии депутатов (person_photo) и короткие названия фракций на иврите (he_short в factions.toml: «ש״ס» вместо полного названия списка). Около 10 минут при первом запуске; hkv update дополняет имена и фото только новых депутатов; чтобы добавить имена на новом языке уже известным депутатам — hkv names --refresh (~30 минут). Не найденные — data_issue типа person_name_missing.
uv run hkv translate --lang en,ru — автоматический перевод названий законопроектов и голосований (Claude API, ключ ANTHROPIC_API_KEY; модель HKV_TRANSLATE_MODEL, по умолчанию claude-haiku-4-5-20251001). Переводится каждая уникальная строка на иврите один раз; ключ — SHA-256 исходного текста (text_translation), так что исправленный источник автоматически остаётся без перевода до следующего запуска. Фиксированный глоссарий («Основной закон», «поправка №», только григорианский год) и проверки: числа сохранены, иврита не осталось, длина разумная — иначе перевод не сохраняется, а попадает в data_issue (translation_check_failed). --stub — прогон без ключа (пометка вместо перевода). hkv update переводит новые названия сам, если ключ задан: в API уходят только названия, появившиеся с прошлого запуска, а если нового нет — ни одного запроса. Названия, не прошедшие проверки, автоматически не повторяются (каждый раз было бы то же самое) — hkv translate --retry-failed; проблема закрывается, как только у названия появляется перевод. Неудачный пакет (модель вернула не столько переводов, сколько названий) переводится по одному названию. Вся история (~21 700 названий на язык) переведена 2026-10-05 моделью Haiku 4.5. На сайте перевод показан под оригиналом с пометкой «автоматический перевод» и ссылкой «предложить исправление»; предложения (POST /api/v1/suggestions, translation_suggestion) никогда не применяются автоматически — их принимает редактор.
Тем же путём переводятся официальные резюме законов (SummaryLaw → bill.summary_he, 1 144 законопроекта с голосованиями, с 20-го созыва): отдельный промпт для связного текста, пакеты по 5, модель Sonnet 5 для всех языков (Haiku в выборке 2026-10-07 искажал смысл; HKV_TRANSLATE_MODEL_SUMMARY переопределяет). Рассуждения (thinking) в запросах перевода выключены — переводу они не нужны, а у Sonnet 5 оплачиваются как вывод. hkv translate --batch отправляет весь накопившийся объём одним Message Batch (вдвое дешевле, ответ обычно в пределах часа, максимум 24 ч); запросы, не прошедшие в пакете, повторяются напрямую. Для резюме проверки мягче: даты вида «31.03» пишутся словами, разделители тысяч и ведущие нули могут меняться, номера статей сохраняют буквы иврита («סעיף 14טו»). hkv translate --recheck — без обращений к API: прогоняет текущие проверки по ранее отклонённым переводам (они хранятся в data_issue) и сохраняет прошедшие. hkv update переводит напрямую — новых строк за раз немного. --kind title,summary,notes (по умолчанию все три); hkv update переводит и новые резюме и описания. В API — summary/summary_origin/summary_{en,ru,ar} у /bills/{id}; на странице законопроекта перевод показан первым, оригинал на иврите — под катом.
uv run hkv notes --documents --batch — описания законопроектов, у которых нет официального резюме (~2 600 из 3 781 с голосованиями: все законы 16–17-го созывов и многие после), по пояснительной записке («דברי הסבר»), которую написали инициаторы. --documents сначала загружает ссылки на файлы законопроектов (KNS_DocumentBill → bill_document, ~5 мин). Берётся собственный текст инициаторов: версия к предварительному чтению (группа 1), иначе к первому (у правительственных законопроектов только она); Word читается локально, PDF (правительственные брошюры в две колонки, извлечение текста их портит) уходит в модель как документ. Файлы кэшируются в data/raw/knesset_files/. Sonnet 5 пишет 2–4 предложения на иврите с атрибуцией («לדברי המציעים») → bill_explanation; переводы — hkv translate (вид notes). Проверки: текст на иврите, разумной длины, и каждое число в нём есть в записке. Не нашлась записка или не прошли проверки — data_issue explanation_failed с причиной, повтор только с --retry-failed. --stub — без ключа, --limit N — только N последних. hkv update загружает файлы законопроектов за окно и описывает новые, если задан ключ. В API — explanation_he, explanation/explanation_origin/explanation_{en,ru,ar}, explanation_source_url у /bills/{id}, только если нет официального резюме.
uv run hkv debate --batch — что говорили в пленуме о законопроектах, дошедших до окончательного голосования (L7, шаг 2). Кнессет публикует одну стенограмму на заседание (KNS_DocumentPlenumSession, группа 28 «דברי הכנסת» → plenum_document, ссылки грузятся для нужных заседаний), а не на законопроект; из стенограмм всех заседаний, где по законопроекту голосовали (предварительное, первое, второе-третье чтение), вырезаются пункты повестки с его названием. Три поколения формата: с 2018 — .docx с метками << הצח >>/<< דובר >>/<< יור >>/<< קריאה >>; примерно 2009–2017 — Word 97 со скрытыми строками <название> и <имя (фракция):>; раньше — без меток (пункты повестки из оглавления, выступающие — отдельные строки с двоеточием). Список выступавших (bill_debate_speaker: как напечатано, чтения, число выступлений и знаков, депутат и фракция на день первого выступления — hkv.people) строится без модели. Председатель, реплики с мест и сотрудники Кнессета не считаются выступлениями. Sonnet 5 пишет на иврите 2–3 нейтральных предложения о сути обсуждения и до пяти доводов за и против, каждый со ссылками на номера выступлений → bill_debate. Проверки: ссылки на существующие выступления, иврит, разумная длина, каждое число есть в выступлениях. Очень длинные обсуждения (больше 150 000 знаков) укорачиваются равномерно: каждое выступление обрезается до одной и той же длины, это помечено. Стенограммы последнего заседания ещё нет — законопроект ждёт (после 120 дней берутся те, что есть); не нашёлся пункт повестки или не прошли проверки — data_issue debate_failed. По умолчанию только спорные окончательные голосования (большинство коалиции и оппозиции разошлось), --all — все.
uv run hkv reservations --batch — оговорки ко второму чтению (L7, шаг 3) из версии комиссии ко второму-третьему чтению (bill_document, группы 4, 101 и переизданные 46/49/103/104; берётся последняя). Раздел «הסתייגויות ובקשות רשות דיבור» вырезается из текста (PDF — pypdf); модель говорит, кто какие номера оговорок предложил (группы и отдельные депутаты, совместные — у всех авторов), по каким статьям, кто просил слова, и даёт по фразе на иврите о сути → bill_reservations, bill_reservation_group, bill_reservation_person. Проверка: присвоенные номера — ровно напечатанные в документе (без пропусков, повторов и лишних), каждое имя есть в тексте. В некоторых PDF номеров в тексте нет (автонумерация списка или строки, извлечённые в обратном порядке слов) — тогда модели отправляются сами страницы раздела, номера проверяются только на непрерывность 1..N, и строка помечена numbers_checked = false. Разделы длиннее 300 000 знаков (законы об упорядочении экономики с тысячами оговорок) — data_issue reservations_failed too_long. Авторы сопоставляются с депутатами и их фракциями на дату окончательного голосования.
Тексты обоих шагов переводятся hkv translate (вид positions). hkv update делает до HKV_UPDATE_POSITIONS_LIMIT (5) законопроектов каждого шага за запуск. В API у /bills/{id} — debate (резюме, доводы со ссылками на выступавших, выступавшие с фракцией, блоком и голосом в окончательном голосовании, ссылки на стенограммы) и reservations (всего, по фракциям — совместная оговорка засчитывается каждой фракции, по группам, просившие слова, ссылка на документ комиссии).
Два источника: официальная классификация законов Кнессета (src/hkv/topics/official.py: законопроект получает категории закона, названного в его заголовке; uv run hkv topics --official обновляет таблицы, ~3 мин) и ключевые слова. Таксономия и правила — src/hkv/topics: ~20 тем с русскими, английскими и арабскими названиями и синонимами (поиск по-арабски не различает формы хамзы, та марбута и огласовки — функция ar_norm), ключевые слова на иврите (целые слова с приставками ו/ש + ה/ב/ל/מ/כ). uv run hkv topics пересчитывает назначения (также в hkv update). Каждое назначение хранит сработавшее слово и статус unreviewed; ручные решения редактора (accepted/rejected, origin='human') пересчёт не трогает.
ID — официальные ID Кнессета; source_url ведёт на карточку голосования на сайте Кнессета.
apps/web — Next.js (App Router), серверный рендеринг поверх API; ответы API кешируются на 10 минут.
Четыре языка: /ru, /en, /he, /ar (иврит и арабский — справа налево; арабская версия помечена как бета на каждой странице, translation.beta в словаре). Строки интерфейса — src/i18n/dict (ru — образец, en, he и ar того же типа), тексты словаря и методологии — src/i18n/content. Адрес без языка (старые ссылки, корень сайта) перенаправляется src/proxy.ts на язык, выбранный ранее (cookie), иначе по языку браузера, иначе на русский. Ссылки внутри сайта — через components/Link.tsx, который добавляет язык.
Страница (под /[lang]) |
Что показывает |
|---|---|
/ |
Поиск на любом языке; спорные голосования (коалиция и оппозиция голосовали по-разному) карточками с итогом словами и шкалой на 120 мест; темы; разделы; заметка о каникулах |
/votes |
Лента голосований; по умолчанию главные; «Спорные» — окончательные, где коалиция и оппозиция голосовали по-разному; фильтр стадии |
/votes/[id] |
Вопрос, итог словами, раскладка по фракциям на дату голосования, поимённый список с фильтрами (имя, фракция, «против большинства своей фракции»), ссылка на источник |
/members, /members/[id] |
Депутаты созыва сеткой с фото (поиск по имени на любом языке); фото, фракция, итог одной фразой, окончательные голосования, статистика, история фракций |
/parties, /parties/[slug] |
Партии; страница партии: текущий список и коалиция/оппозиция, как большинство голосовало в третьем чтении, состав с фото; история по созывам свёрнута |
/factions, /factions/[id] |
Фракции созыва; партия, история «коалиция/оппозиция», состав во времени, единство, голосования с разделением |
/bills, /bills/[id] |
Законопроекты; статус, инициаторы, темы, все голосования по порядку |
/topics, /topics/[slug] |
Темы и законопроекты темы |
/search |
Депутаты, партии (фракции сгруппированы по партии) и темы на четырёх языках; законопроекты на иврите |
/about/glossary, /about/methodology |
Словарь (в т.ч. кворум) и методология |
cd apps/web && npm install && npm run dev # http://localhost:3000, API ожидается на :8000 (HKV_API_URL)scripts/deploy.sh # закоммиченные файлы → сервер, пересборка и перезапускСтек — infra/compose.prod.yaml: Postgres, API, веб, Caddy (HTTPS). Всё о сервере — docs/deploy.md.