AAYMA
RU
Войти

Контур изменений

Агент сравнивает PDF/DOCX оргдокументов до и после реорганизации.

Доведённый продукт
Команда «Token burner» 4💬 0 комментариевс 23 сентября 2026 г.

О проекте

Команда: Token burner
Кейс HackAlem.ai: Спецтрек — Казахтелеком
Стадия: Доведённый продукт

Задача кейса: Агент, сравнивающий документы оргструктуры до и после реорганизации

Суть

Агент сравнивает PDF/DOCX оргдокументов до и после реорганизации. Он находит изменения подразделений, переносы и потери функций, дубли и кандидатов в конфликт интересов, и каждый вывод ссылается на проверенную цитату пункта.

Как устроено

Пайплайн: ingest (PyMuPDF/pdfplumber/python-docx, разбор на пункты со страницами) → extract (LLM через Responses API со structured output выделяет подразделения, роли и атомарные функции параллельно по чанкам) → prematch (детерминированно закрывает неизменённые соответствия) → match (LLM классифицирует остаток, конфликты оценивает код grade_conflicts по 3 условиям) → verify (без LLM: rapidfuzz partial_ratio ≥85 и не больше 2 «чужих» слов) → report. Интерфейс на Vue 3 показывает источник с подсветкой цитаты и выгружает PDF.

Ключевые решения

  • OpenAI Responses API structured outputs
  • детерминированный prematch
  • rapidfuzz partial_ratio
  • верификатор цитат
  • оценка уверенности конфликтов 3/3
  • фоновые задания
  • fake LLM в тестах

Особенности

LLM выдвигает гипотезы, код их проверяет: верификатор без LLM отклоняет находки с неточной цитатой и сохраняет причину отказа. «Потеря» функции отклоняется, если в новом комплекте найден аналог. Детерминированный prematch закрывает большинство пар без LLM и снижает стоимость. Есть валидационный набор эталонных случаев.

Стек

Python 3.12 · FastAPI · Pydantic v2 · PyMuPDF · pdfplumber · python-docx · rapidfuzz · Vue 3 · Docker Compose · pytest

Модели

OpenAI Responses API


Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-a1c36e68-token-burner.

README

README.md

Контур изменений

Команда Token burner · HackAlem AI

Краткое описание

«Контур изменений» сравнивает комплекты организационных документов до и после реорганизации и выявляет изменения подразделений, переносы функций, дублирование и кандидатов на проверку конфликта интересов. Каждый вывод связан с пунктами исходных документов, а цитаты проверяет программный верификатор. В демонстрационных сэмплах red8.pdf и red9.pdf разобраны соответственно 439 и 446 пунктов, по 25 страниц в каждом документе.

Решение предназначено для сотрудника, который проверяет реорганизацию: оно показывает, что изменилось в структуре и распределении функций, и даёт ссылку на пункт документа для каждого вывода. Выводы носят рекомендательный характер, окончательное решение принимает ответственный сотрудник.

Что реализовано

Кадры демонстрационного интерфейса на ширине 1280 px. Они сняты на раннем моке; состав находок в актуальном результате может отличаться.

Функции: фильтр «Переносы» и раскрытое сопоставление редакций со связанным выводом.

Функции: раскрытое сопоставление пунктов до и после реорганизации

Источник: документ, номер пункта, страница и подсвеченная цитата.

Открытая панель источника с подсвеченной цитатой

Структура: подразделения и должности сгруппированы по статусам, у каждого есть ссылки на пункты.

Структура: созданные, сохранённые, преобразованные и упразднённые подразделения

Питч — четыре слайда в HTML: открыть в браузере, листать стрелками; «Печать / PDF» печатает всю деку. На втором слайде оставлено место для живого демо.

Как работает решение

Демо-сценарий на data/samples

Запустите API в DEMO_MODE=1, выберите red8.pdf в «До реорганизации» и red9.pdf в «После», нажмите «Анализировать». Экран проходит ingest → extract → prematch → match → verify → report и открывает сохранённый результат. Для показа сразу с результатов используйте ?demo=1.

Начните со «Структуры», затем откройте «Функции» с фильтром «Только изменения». Ниже — ориентиры ручной сверки из CLAUDE.md и validation_set.py, а не фиксированное количество строк текущего отчёта.

Что показатьПункты и ожидаемая интерпретация
Два созданных департаментаДИТААД и ДОА: red9 § 3.4.а и § 3.4.б. ДНМ и ДККМ сохранены.
Преобразованная роль«Директор направления внутреннего аудита»: red8 § 3.5.а, § 5.3 → директора департаментов и направлений ДИТААД/ДОА, red9 § 5.3. Перенос функций не означает их потерю.
Три опорных случая потерьRed8 § 5.6.2 — право формировать группы контроля качества; § 5.6.3 — предложения по объёму внешней оценки БВА; § 5.7.2 — доведение результатов консультаций до руководства. Формулировка: «не найдена в загруженном комплекте после», с проверкой всего нового комплекта.
Два опорных случая дублейАнализ результатов непрерывного аудита: red9 § 5.3.8 и § 5.4.5. Контроль устранения недостатков: § 5.1.4, § 5.3.7 и § 5.5.5. Сверить действие, объект и область ответственности у разных исполнителей.
Конфликт ДККМ устранён реорганизациейВ red8 исполнители аудита из § 3.8 сочетаются с контролем качества в § 5.5.2 и § 10.7.а; в red9 § 3.9 состав изменён. Это ориентир проверки устранённого конфликта, а не утверждение о новом конфликте в редакции «после».
Находка вне исходной шпаргалкиRed8 § 5.3.4.б: контроль сроков выполнения графика проверки проектной командой. Дополнительный кандидат, который надо отдельно сопоставить с red9, в том числе § 5.3.4.в и полномочиями других исполнителей. Его итоговый статус проверяется по доказательствам.

Покажите перенос взаимодействия с субъектами СВК из red8 § 5.4.4 в red9 § 5.3.3: раскройте строку и сравните две колонки. Нажмите на чип источника, проверьте цитату, страницу и соседние пункты. Если есть отклонённые находки, откройте причину отказа. Завершите показ вкладкой «Заключение», оглавлением и кнопкой «Скачать» для сохранения PDF.

Состав находок зависит от версии сохранённого результата и нового анализа. В демо используются документы и результаты из репозитория; логика backend не содержит правил, привязанных к номерам пунктов этих сэмплов.

Как работает верификатор

verify.py не вызывает LLM и проверяет каждую цитату находки:

  1. Документ загружен, указанный пункт существует, есть хотя бы одно доказательство.
  2. Полное непустое совпадение цитаты с текстом пункта принимается сразу, в том числе для короткого пункта. Иначе в цитате должно быть минимум два слова после нормализации.
  3. Для нечёткого совпадения нормализуются регистр, ё/е, пробелы, пунктуация и переносы слов в PDF. Проверяется сам пункт, пункт с подпунктами либо вводная часть родительского пункта вместе с подпунктом.
  4. Требуются одновременно rapidfuzz.fuzz.partial_ratio ≥ 85 по умолчанию и не более двух «чужих» слов: слов цитаты, отсутствующих в проверяемом тексте.
  5. Перенос и изменение требуют доказательств из обеих редакций; дублирование — минимум из двух разных пунктов «после». Потеря должна ссылаться на редакцию «до»; для её цитат длиной от четырёх слов выполняется поиск по всему комплекту «после», и найденный аналог отклоняет потерю.
  6. Неудача сохраняется как verified=false с rejection_reason; у принятых доказательств страница берётся из разобранного документа. Если анализ неполный, пайплайн дополнительно отклоняет находки о потерях и выставляет analysis_complete=false с предупреждениями.

Подтверждённые выводы и вводный абзац Reporter строятся только по проверенным находкам. Отклонённые доступны отдельно в интерфейсе; текущий формат conclusion_md также сохраняет их в последнем справочном разделе «Отклонено верификатором (в выводах не учитывается)».

Уверенность конфликтов

Модель предлагает кандидатов, а grade_conflicts в match.py проверяет три условия:

  1. Сам субъект выполняет деятельность: у него есть собственная функция выполнения, отличная от функции контроля.
  2. Сам субъект контролирует ту же деятельность: совпадают значимые признаки объекта или области ответственности, общие слова исключаются.
  3. Состав или подчинение подкреплены проверенной цитатой из раздела структуры, указаны должности исполнителей или общие подчинённые, отличные от самого субъекта.
Подтверждено условийЧто попадает в результат
3 из 3type=conflict, confidence=high — высокая уверенность.
2 из 3type=conflict, confidence=medium — средняя уверенность.
1 из 3type=overlap, confidence=low — пересечение ответственности, а не подтверждённый кандидат в конфликт.
0 из 3Кандидат отбрасывается.

Если субъект не является подразделением или должностью оргструктуры, оценка ограничивается пересечением ответственности. Для одной редакции и субъекта остаётся наиболее обоснованный кандидат. confidence отражает полноту подтверждения, severity — важность; это разные поля. Даже при высокой уверенности формулировка остаётся «кандидат на проверку сотрудником»: совпадение цитат не доказывает правильность смысловой интерпретации.

Технологии

  • Python 3.12, FastAPI и uvicorn, pydantic v2.
  • OpenAI Python SDK: Responses API со структурированными ответами, разобранными в pydantic-модели.
  • PyMuPDF (резервно pdfplumber) для PDF, python-docx для DOCX, rapidfuzz для проверки цитат.
  • Фронтенд: один frontend/index.html с локальной копией Vue 3, без npm и сборки.
  • pytest для тестов; Docker и Docker Compose для запуска.

Архитектура

flowchart LR
    files["PDF / DOCX<br/>Комплекты до и после"] --> ingest["ingest<br/>Текст и пункты"]
    ingest --> extract["extract<br/>Структура и функции"]
    extract --> prematch["prematch<br/>Детерминированное сопоставление"]
    prematch --> match["match<br/>Смысловая классификация остатка"]
    match --> verify["verify<br/>Проверка ссылок и цитат"]
    verify --> report["report<br/>Заключение"]
    report --> result["AnalysisResult<br/>JSON"]
    result --> ui["Vue 3<br/>Инструмент аудитора"]

Matcher состоит из двух слоёв. Детерминированный prematch.py нормализует исполнителей, сопоставляет цитаты и действия с объектами и закрывает неизменённые соответствия — на сэмплах это большинство функций. LLM получает для классификации только остаток: функции без пары и кандидатов на перенос или изменение; уже закрытые соответствия повторно классифицировать не нужно. При этом match.py передаёт модели весь комплект «после» как контекст поиска аналогов и отдельно проверяет структуру, кандидатов в дубли и конфликты.

ШагРезультат
ingestТекст, пункты и подпункты с doc_id, clause_id и страницей.
extractПодразделения, роли и атомарные функции; параллельная обработка групп пунктов с контекстом исполнителя. Ошибочные ссылки исправляются поиском цитаты по документу, неподтверждённые функции отбрасываются.
prematchСохранённые функции и кандидаты для смыслового сопоставления без LLM.
matchИзменения структуры и функций, переносы, предполагаемые потери, дубли и оценённые кодом конфликты.
verifyПроверенные доказательства, причины отклонения, пересчитанные счётчики.
reportРазделы заключения по шаблону и вводный абзац модели; при ошибке модели абзац заменяется шаблоном.

Оркестрация — pipeline.py, модели данных — schemas.py. Задание выполняется в фоновом потоке; состояние хранится в памяти и JSON под data/jobs/. После перезапуска незавершённое задание становится ошибочным, автоматически продолжить его нельзя. trace хранит длительность шагов и сведения об использовании моделей, а stats — исправления ссылок, отбрасывание функций, время и расчётную стоимость анализа.

Фронтенд — один index.html с CSS внутри и локальной production-сборкой Vue 3: без npm, сборки и CDN. Есть загрузка двух комплектов, прогресс, вкладки результатов, фильтры и сопоставление редакций. Находки связаны с таблицей через function_ids; drawer загружает источник и соседние пункты по API, сохраняя цитату из результата как fallback при недоступности источника. Заключение печатается в PDF средствами браузера.

Устройство проекта

Карта backend/: входы и выходы модулей, включая служебные файлы и инструкции агентов.

ФайлЧто входитЧто выходит / назначение
main.pyФайлы before[] / after[], HTTP-запросы задания, источника и демо.FastAPI: job_id, состояние и результат задания, текст пункта с соседями; раздача фронтенда.
config.pyПеременные окружения и .env.Общие Settings: модели, адрес API, режим демо, пути и цены для расчёта стоимости.
cli.pyПути «до», --after, путь -o, флаг --no-cache.JSON результата на диске, прогресс и длительности в stderr; код 0 — полный анализ, 2 — частичный, 1 — ошибка входа или отсутствие ключа.
ingest.pyPDF с текстовым слоем, DOCX или тестовый TXT, сторона и идентификатор документа.Document с пунктами Clause, исходным текстом, страницами и предупреждениями разбора.
agent/schemas.pyДанные документов, извлечения, доказательств и результата.Pydantic-модели и проверка их структуры; при запуске модуля — экспорт contracts/result.schema.json.
agent/llm.pyИнструкции, контекст, схема ответа и настройки модели.Типизированный LLMCall с расходом токенов и временем либо LLMError; расчёт стоимости вызова.
agent/extract.pyРазобранные документы и контекст исполнителей.DocExtraction: подразделения, роли, атомарные функции, исправленные ссылки и отброшенные функции; записи trace.
agent/prematch.pyДокументы и извлечённые функции обеих редакций.Prematch: нормализованные исполнители, сохранённые пары, кандидаты на перенос, изменение и дубли, остаток без пары; без LLM.
agent/match.pyДокументы, извлечения и Prematch.MatchResult: изменения подразделений, функции, находки, уверенность конфликтов, предупреждения и полнота; запись trace.
agent/verify.pyРезультат и индекс исходных документов.Проверенные цитаты и страницы, verified, причины отклонения и пересчитанные счётчики; без LLM.
agent/report.pyРезультат после верификации.conclusion_md по шаблону с вводным абзацем и запись trace; отклонённые находки вынесены в справочный блок.
agent/pipeline.pyПути двух комплектов, обработчик прогресса, настройки клиента и кэша.AnalysisResult после шести шагов, события прогресса, trace, предупреждения и статистика; кэш извлечения в data/jobs/cache/.
prompts/extract.mdЗадача извлечения структуры и функций.Русские инструкции, которые extract.py передаёт модели вместе с текстом документа.
prompts/match.mdПравила сопоставления редакций и классификации находок.Русские инструкции для смыслового слоя match.py.
prompts/report.mdТребования к краткому резюме проверенных находок.Русские инструкции для вводного абзаца report.py.
init.py—Маркер Python-пакета backend; отдельной обработки данных нет.
agent/init.py—Маркер Python-пакета backend.agent; отдельной обработки данных нет.

API

Реализация — backend/main.py, схема результата — contracts/result.schema.json. Четыре основных эндпоинта:

Метод и путьЗапрос и ответ
POST /api/analyzeMultipart с повторяющимися полями before[] и after[]; минимум один файл с каждой стороны. Возвращает {"job_id":"…"}.
GET /api/jobs/{job_id}status: queued, running, done или error; step, progress от 0 до 1, message, trace, demo. При done содержит result, при error — понятный текст в error.
GET /api/clause/{doc_id}/{clause_id}?job_id=…doc_id, doc_name, clause_id, полный text, page и neighbors: до двух пунктов перед источником и до двух после. У DOCX page=null.
GET /api/demoСохранённый AnalysisResult из data/demo_result.json, без запуска анализа.

Примеры curl из корня репозитория:

# 1. Загрузить пару сэмплов; для нескольких файлов повторите нужное поле -F.
curl -fsS -X POST http://127.0.0.1:8000/api/analyze \
  -F 'before[]=@data/samples/red8.pdf' \
  -F 'after[]=@data/samples/red9.pdf'

# 2. Подставить job_id из ответа и опрашивать до done или error.
ANALYSIS_JOB_ID='job_id_из_ответа'
curl -fsS "http://127.0.0.1:8000/api/jobs/$ANALYSIS_JOB_ID"

# 3. После завершения получить пункт именно этого задания.
curl -fsS --get http://127.0.0.1:8000/api/clause/after-1/5.3.3 \
  --data-urlencode "job_id=$ANALYSIS_JOB_ID"

# 4. Получить сохранённый результат без загрузки файлов.
curl -fsS http://127.0.0.1:8000/api/demo

Фронтенд опрашивает задание каждые 1,5 с после ответа, без параллельных запросов. В trace приходят начало и завершение шагов. Даже при ошибке входных файлов POST может вернуть job_id: причину нужно читать в состоянии задания. Неизвестное задание, документ или пункт возвращают HTTP 404.

В запросе источника передавайте job_id, когда он известен: иначе сервер использует документы последнего показанного результата. Прямое демо сначала загружает /api/demo и открывает источники без job_id. Идентификаторы берутся из результата, например before-1 и after-1; это не имена файлов.

Установка и запуск

Запуск и результат без ключа: нужен запущенный Docker. Из корня репозитория выполните:

docker compose up --build

Откройте готовый демо-результат — localhost:8000/?demo=1: он загружается без API-ключа и вызовов модели. Чтобы также показать загрузку файлов и шесть шагов анализа в деморежиме, перед запуском задайте DEMO_MODE=1 в .env (создайте его из .env.example). Запуск без Docker описан ниже.

Быстрый старт

Все команды выполняются из корня репозитория. Создайте локальный .env из .env.example, если его ещё нет:

test -f .env || cp .env.example .env

Для демонстрации без вызовов модели установите в .env DEMO_MODE=1; ключ API не нужен. Для анализа собственных документов установите DEMO_MODE=0 и задайте доступ к модели. Не добавляйте .env в Git.

Docker

Нужны запущенный Docker и Docker Compose. Команда читает .env, собирает образ на Python 3.12 и запускает API вместе с интерфейсом:

docker compose up --build

Откройте http://localhost:8000. Прямой переход к сохранённому результату: http://localhost:8000/?demo=1. Каталог data/jobs/ подключён в контейнер: загруженные файлы, задания и кэш извлечения сохраняются между запусками. Для остановки используйте Ctrl+C; удалить контейнер и сеть проекта можно командой docker compose down.

Без Docker

Нужен Python 3.12. Создайте окружение и установите зависимости:

python3.12 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt

Запуск uvicorn напрямую, с тем же .env:

.venv/bin/uvicorn backend.main:app --port 8000

Для принудительного деморежима без редактирования существующего .env:

DEMO_MODE=1 .venv/bin/uvicorn backend.main:app --port 8000

Адрес интерфейса тот же. Автоперезагрузка не включена; остановка — Ctrl+C. Запускайте Docker и bare uvicorn по очереди, поскольку оба занимают порт 8000. Проверка доступности для любого способа:

curl -fsS http://127.0.0.1:8000/api/health

Ответ содержит status: "ok" и фактическое значение demo_mode.

Переменные окружения

Настройки читаются в backend/config.py. Переменные процесса имеют приоритет над .env при прямом запуске; Docker Compose передаёт значения из .env контейнеру.

Переменная из .env.exampleЗначение в примереНазначение
OPENAI_API_KEYпустоКлюч для реального анализа через API модели. В деморежиме не требуется.
OPENAI_BASE_URLпустоАльтернативный адрес сервера модели; пустое значение использует стандартный endpoint OpenAI.
MODEL_EXTRACTgpt-5-miniМодель извлечения подразделений, ролей и функций.
MODEL_MATCHgpt-5.5Модель смыслового сопоставления оставшихся функций.
MODEL_REPORTgpt-5-miniМодель вводного абзаца заключения.
REASONING_EXTRACTlowУровень рассуждения модели извлечения.
REASONING_MATCHmediumУровень рассуждения модели сопоставления.
DEMO_MODE01: API проверяет читаемость загрузок, воспроизводит шесть шагов и возвращает сохранённый data/demo_result.json, без LLM. 0: запускает анализ загруженных документов.

GET /api/demo всегда возвращает сохранённый результат независимо от DEMO_MODE; параметр страницы ?demo=1 вызывает именно этот эндпоинт. Деморежим предназначен для показа сэмплов и не делает новых выводов о произвольных загруженных документах. Время и стоимость в сохранённом результате относятся к его исходному анализу.

Для on-prem задайте OPENAI_BASE_URL своего сервера, например http://model-server:8000/v1, и укажите доступные на нём модели в MODEL_*. Сервер должен поддерживать используемые приложением Responses API и структурированные ответы; одной совместимости с Chat Completions недостаточно. Если локальный сервер требует ключ, укажите его в OPENAI_API_KEY; при пустом ключе и заданном адресе клиент использует EMPTY. Совместимость конкретного сервера и модели необходимо проверить на пилоте.

Дополнительно код поддерживает QUOTE_MIN_SCORE (порог сходства цитаты, по умолчанию 85) и MODEL_PRICES (JSON с ценами за миллион входных, кэшированных входных и выходных токенов). cost_usd — расчётная стоимость по этой таблице, а не сумма из биллинга; для неизвестной цены поле равно null.

Как проверить решение

Выполняйте команды из корня репозитория после установки зависимостей из раздела «Быстрый старт». Для нового анализа сэмплов настройте доступ к модели в .env и запустите:

source .venv/bin/activate
python -m backend.cli data/samples/red8.pdf --after data/samples/red9.pdf -o result.json
python -m tests.validation_set result.json

CLI запускает настоящий пайплайн и расходует токены; DEMO_MODE=1 не подменяет его результат. По умолчанию используется кэш извлечения, --no-cache отключает его чтение. Даже при частичном анализе CLI сохраняет JSON и возвращает код 2 — проверьте analysis_complete и warnings перед оценкой выводов.

Для просмотра готового результата без вызовов модели запустите веб-API в деморежиме:

source .venv/bin/activate
DEMO_MODE=1 python -m uvicorn backend.main:app --port 8000

Откройте демо в браузере. В другом терминале сохраните тот же результат и выполните сверку:

source .venv/bin/activate
curl -fsS http://127.0.0.1:8000/api/demo -o result.json
python -m tests.validation_set result.json

tests/validation_set.py печатает таблицу «найдено / пропущено / ложное / частично», дополнительные потери и конфликты. Проверяйте колонку «результат»: пропуски сами по себе не меняют код завершения этой команды. Это сверка с эталоном, а не новый анализ документов. JSON, созданный CLI, автоматически в интерфейс не загружается: для просмотра нового анализа загрузите сэмплы через веб-API с DEMO_MODE=0; ?demo=1 всегда показывает сохранённое демо.

Ниже — ожидаемые случаи из Validation set в CLAUDE.md, а не фиксированные счётчики текущего демо. Для подразделений указан статус Unit, для остальных строк — тип находки или статус функции. В колонках редакций приведены номера пунктов; «—» означает, что эталон не задаёт парный пункт.

Тип / ожидаемое поведениеПодразделение или рольДо — red8После — red9Где увидеть в интерфейсе
Создано (created)ДИТААД—3.4.а«Структура» → «Созданы».
Создано (created)ДОА—3.4.б«Структура» → «Созданы».
Сохранено (preserved)ДНМ3.4.а, 3.73.4.в, 3.8«Структура» → «Сохранены».
Сохранено (preserved)ДККМ3.4.б, 3.83.4.г, 3.9«Структура» → «Сохранены».
Роль преобразована (transformed), функции переданыДиректор направления ВА → директора департаментов и направлений ДИТААД/ДОА3.5.а, 5.35.3«Структура» → «Преобразованы»; «Функции» → «Переносы», раскрыть строку.
Изменён состав (structure): исключены исполнители аудитаДККМ3.83.9«Функции» → карточка «Структура», цитаты находки; «Заключение».
Потеря (loss, high): право формировать группы контроля качестваДиректор ДККМ5.6.2Не найдена в комплекте «после»«Функции» → «Потери», раскрыть строку и источник.
Потеря (loss, high): предложения по объёму внешней оценки БВАДиректор ДККМ5.6.3Не найдена в комплекте «после»«Функции» → «Потери»; «Заключение».
Потеря по эталону (loss, medium): доведение результатов консультацийДиректор ДНМ5.7.2По эталону не найдена; возможна классификация как изменение«Функции» → «Потери» или «Изменение», цитаты и сопоставление редакций.
Изменение / перенос, не потеря: предложения в план работДККМ → ДИТААД/ДОА, ДНМ; у ДККМ остаётся консолидация5.5.105.3.3, 5.4.2; консолидация — 5.5.7«Функции» → «Изменение» / «Переносы», раскрыть строку.
Изменение / перенос, не потеря: предложения по профессиональному уровнюДККМ → ДИТААД/ДОА, ДНМ; у ДККМ — организация обучения5.5.85.3.9, 5.4.6; обучение — 5.5.6«Функции» → «Изменение» / «Переносы», раскрыть строку.
Изменение (change): отчётность без указания периодичностиДККМ; периодичность остаётся у Главного аудитора5.5.55.5.3; периодичность — 5.1.6«Функции» → «Изменение», сравнить цитаты.
Перенос (transfer): карта гарантий / взаимодействие с субъектами СВКДНМ → ДИТААД/ДОА5.4.45.3.3«Функции» → «Переносы», колонки «Ред. до» / «Ред. после».
Перенос (transfer): делегирование надзора за проверкойДиректор направления ВА → Директор операционного аудита9.379.37«Функции» → «Переносы», сравнить получателя полномочий.
Дубль (duplication): анализ результатов непрерывного аудитаДИТААД/ДОА и ДНМ—5.3.8 и 5.4.5«Функции» → «Дубли», источники обеих функций в «после».
Дубль (duplication): контроль устранения недостатковГлавный аудитор, ДИТААД/ДОА, ДККМ—5.1.4, 5.3.7, 5.5.5«Функции» → «Дубли», проверить область ответственности по цитатам.
Конфликт (conflict) устранён реорганизацией: выполнение аудита и контроль его качестваДККМ3.8, 5.5.2, 10.7.а3.9 — изменён состав«Функции» → «Конфликты», текст находки и бейдж уверенности; «Заключение».
Кандидат в конфликт: двойное подчинениеДиректор проектов ДККМ и Менеджер по аудиту → Директор направления ВА и Директор ДККМ3.6, контекст 3.8—«Функции» → «Конфликты», цитаты оргструктуры; при пониженной оценке — «Пересечение ответственности».
Раскрытие конфликта интересов (conflict / change, дополнительный случай)Главный аудитор в ДЗО—4.4«Функции» → «Конфликты» / «Изменение»; «Заключение», если находка сформирована.
Дефект документа (structure, low, дополнительный случай): пустой пункт «;»ДККМ5.5.3—«Функции» → карточка «Структура», источник находки; «Заключение».

На вкладке «Функции» нажмите карточку типа находок и при необходимости выберите подразделение. Строка таблицы раскрывает сопоставление редакций, чип пункта открывает исходный текст и соседние пункты. По умолчанию видны только изменения; для связанных сохранённых функций включите «Показать все». Находка без function_ids остаётся в списке под карточкой, но не имеет связанной строки таблицы. Отклонённые находки проверяйте во вкладке «Отклонено верификатором» по rejection_reason.

Выводы рекомендательные; пункт 5.7.2 red8 модель может классифицировать как изменение (change), а не потерю (loss).

В текущем tests.validation_set для 5.7.2 ожидается loss, поэтому change будет отмечен как пропуск этого критерия; решение проверяется вручную по цитатам. Для двойного подчинения в 3.6 проверка ожидает conflict и отмечает понижение до overlap как расхождение с эталоном. Случаи 4.4 red9 и пустого пункта 5.5.3 red8 в проверке помечены как желательные.

Тесты

После установки зависимостей:

.venv/bin/pytest -q
.venv/bin/pytest tests/test_synthetic.py -q

Обычный запуск исключает тесты с меткой integration через pytest.ini и не требует обращений к модели. Синтетические сценарии 1–6 используют небольшие TXT/DOCX и подставной LLM-клиент:

№СценарийПроверяемый результат
1Один документ с обеих сторонПодразделения и функции сохранены, нет находок об изменениях.
2Тот же текст, другая нумерацияНет изменений и находок.
3Функция перешла другому исполнителюtransferred, без ложной потери.
4Пункт удалёнlost с доказательством из «до» и корректной формулировкой.
5Пункт скопирован другому подразделениюduplicated со ссылками на разные пункты «после».
6Одинаковые слова, разные области ответственностиКандидат рассмотрен и отвергнут как дубль.

Эти сценарии проверяют пайплайн от разбора документов до верификации и сборки результата, но не качество ответов реальной модели. Отдельные тесты покрывают парсинг PDF/DOCX, контракт, исправление ссылок, ложные цитаты, градацию конфликтов, Reporter и API.

Реальный интеграционный прогон на red8/red9 запускается отдельно, требует настроенного доступа к модели и расходует токены:

.venv/bin/pytest -m integration tests/test_validation_integration.py -q

Он проверяет эталонные случаи и сохраняет результат в data/jobs/integration_result.json. Этот тест напрямую вызывает пайплайн: DEMO_MODE=1 не превращает его в имитацию.

Данные и интеграции

  • Данные: тестовый комплект организатора — red8.pdf (редакция «до») и red9.pdf (редакция «после») в data/samples/; рядом их извлечённый текст (*.txt). Сохранённый результат анализа этой пары — data/demo_result.json.
  • OpenAI API: модели задаются в .env.example: MODEL_EXTRACT=gpt-5-mini, MODEL_MATCH=gpt-5.5, MODEL_REPORT=gpt-5-mini. Ключ OPENAI_API_KEY нужен только для нового анализа; демо работает без него.
  • Свой сервер модели: любой OpenAI-совместимый endpoint через OPENAI_BASE_URL (подробнее — в разделе «Переменные окружения»).
  • Внешних баз данных и сервисов нет: задания, загрузки и кэш извлечения хранятся в файлах под data/jobs/.

Ограничения

  • Поддерживаются PDF с текстовым слоем и DOCX; OCR для сканов отсутствует. У DOCX нет номера страницы.

  • Excel (.xls / .xlsx), перечисленный в кейсе, на входе не поддерживается.

  • Нечитаемый или повреждённый файл прерывает задание целиком, даже если остальные файлы читаются.

  • Один запуск — одно сравнение комплекта «до» с комплектом «после»; в каждом может быть несколько файлов. При частичной обработке показываются предупреждения, а потери не подтверждаются.

  • Выводы рекомендательные. Полнота извлечения и смысловая классификация зависят от модели и качества документов; окончательное решение принимает ответственный сотрудник.

  • Нет авторизации, базы данных, внешней очереди и автоматического возобновления прерванного задания. Это прототип для контролируемого запуска.

  • Сохранённое демо и локальная Vue работают без обращения к модели; реальный анализ требует доступного сервера модели. Деморежим не анализирует смысл произвольных новых загрузок.

  • Проверка законодательства и сравнение с другими организациями не входят в текущий анализ.

  • Разброс результатов между прогонами модели на red8 → red9 (какие находки стабильны, какие плавают) зафиксирован в docs/notes.md.

Roadmap

  • Соответствие организационных документов законодательству со ссылками на применимые нормы.
  • Бенчмаркинг структуры и распределения функций относительно сопоставимых организаций.
  • Анализ должностных инструкций и их согласованности с положениями о подразделениях.
  • Проверенный on-prem запуск через OPENAI_BASE_URL: совместимость моделей, качество на эталонной разметке и воспроизводимые измерения времени, полноты и ложных предупреждений.

Ссылка на deployed-версию

Не развёрнуто: запуск локально по разделу «Установка и запуск».

Звёзды
⭐ 0
Форки
0
Язык
Python
Последний push
в прошлом месяце
Коммиты
30
Авторы
1
Файлы
62
Ветки
1

Авторы коммитов

Rakhatbek 30

История коммитов

  • 2026-09-23 17:36README: organizer's 11-section order; data/integrations, technologies and deployment sections addedRakhatbek
  • 2026-09-23 17:21fix(frontend): show finding recommendations in result cardsRakhatbek
  • 2026-09-23 17:13docs: surface keyless demo and clarify input limitationsRakhatbek
  • 2026-09-23 17:10Reporter: default recommendation by finding type when the model gave none; demo filled with the same codeRakhatbek
  • 2026-09-23 16:59README: create the venv with python3.12 explicitlyRakhatbek
  • 2026-09-23 16:55Findings: cut the action tail repeated in the object; demo summaries regenerated with the same codeRakhatbek
  • 2026-09-23 16:54docs: add project map and validation walkthroughRakhatbek
  • 2026-09-23 16:51Pre-release checks: fresh API demo result, auditor-friendly conclusion, sibling gaps in parser, clear no-key error, stability notesRakhatbek
Все коммиты (30)
  • 2026-09-23 16:45docs: finalize README with verified startup and audit workflowRakhatbek
  • 2026-09-23 16:30feat(frontend): integrate clause API and expanded analysis resultsRakhatbek
  • 2026-09-23 16:26Conflict confidence (high/medium) and overlap type, verification stats, rejected block last, unreadable file namedRakhatbek
  • 2026-09-23 16:13docs: add result screenshots and four-slide HTML pitchRakhatbek
  • 2026-09-23 16:11Merge branch 'api'Rakhatbek
  • 2026-09-23 16:08docs: validation set updateRakhatbek
  • 2026-09-23 16:05Docker: python:3.12-slim image and compose service on port 8000 with .envRakhatbek
  • 2026-09-23 16:03API: analyze/jobs/clause/demo with background jobs, demo mode, input errors; API and synthetic pipeline testsRakhatbek
  • 2026-09-23 16:03Matcher (deterministic prematch + LLM), reporter, pipeline, CLI; real demo resultRakhatbek
  • 2026-09-23 15:53docs: draft README with pipeline, verifier and demo walkthroughRakhatbek
  • 2026-09-23 15:43feat(frontend): add job progress polling and API simulationRakhatbek
  • 2026-09-23 15:35feat(frontend): add document upload and analyze submissionRakhatbek
  • 2026-09-23 15:25feat(frontend): polish result filters, source drawer and conclusion navigationRakhatbek
  • 2026-09-23 14:43feat(frontend): show rejected findings and finish result validationRakhatbek
  • 2026-09-23 14:39Extractor: chunked parallel function extraction, fix empty OPENAI_BASE_URLRakhatbek
  • 2026-09-23 14:36feat(frontend): render safe Markdown conclusion and print viewRakhatbek
  • 2026-09-23 14:34feat(frontend): add function filters and evidence lineageRakhatbek
  • 2026-09-23 14:30feat(frontend): add result header and structure tab with local VueRakhatbek
  • 2026-09-23 14:21Extractor agent: Responses API structured output, retry, parallel docs, citation cleanupRakhatbek
  • 2026-09-23 14:20Verifier: clause existence + fuzzy quote match, tests for pre-delivery test 7Rakhatbek
  • 2026-09-23 14:15Result schemas, exported JSON contract, verifiable demo mock and contract testsRakhatbek
  • 2026-09-23 14:12Repo layout, config, PDF/DOCX ingest with clause parser, ingest testsRakhatbek
Структура репозитория (62 файлов)
.dockerignore
.env.example
.gitignore
AGENTS.md
CLAUDE.md
Dockerfile
README.md
backend/__init__.py
backend/agent/__init__.py
backend/agent/extract.py
backend/agent/llm.py
backend/agent/match.py
backend/agent/pipeline.py
backend/agent/prematch.py
backend/agent/report.py
backend/agent/schemas.py
backend/agent/verify.py
backend/cli.py
backend/config.py
backend/ingest.py
backend/main.py
backend/prompts/extract.md
backend/prompts/match.md
backend/prompts/report.md
contracts/result.schema.json
data/demo_result.json
data/samples/red8.pdf
data/samples/red8.txt
data/samples/red9.pdf
data/samples/red9.txt
docker-compose.yml
docs/case.pdf
docs/img/results-functions-lineage.jpg
docs/img/results-source-drawer.jpg
docs/img/results-structure.jpg
docs/notes.md
docs/pitch/index.html
frontend/demo_result.json
frontend/index.html
pytest.ini
requirements.txt
tests/__init__.py
tests/conftest.py
tests/fixtures/__init__.py
tests/fixtures/fake_llm.py
tests/fixtures/org/areas.txt
tests/fixtures/org/base.txt
tests/fixtures/org/deleted.txt
tests/fixtures/org/duplicated.txt
tests/fixtures/org/renumbered.txt
tests/fixtures/org/transferred.txt
tests/test_api.py
tests/test_contract.py
tests/test_extract.py
tests/test_ingest.py
tests/test_match.py
tests/test_pipeline.py
tests/test_report.py
tests/test_synthetic.py
tests/test_validation_integration.py
tests/test_verify.py
tests/validation_set.py

Обсуждение 0 комментариев

Войдите, чтобы комментировать

Пока нет комментариев — будьте первым.