Skip to main content

chancellery

Канцелярия — библиотека русского склонения и сборки служебных документов. Склоняет по падежам ФИО, должности и воинские звания (с автоопределением рода и согласованием слов во фразе) и собирает готовый docx по шаблону с русской разметкой, где падеж задаётся местом подстановки, а не данными.

Изначальное видение и границы — в CONCEPT.md.

Статус: 0.1.0. Движок переехал из «Дьяка» 0.3.3, где обкатан на настоящих кадровых документах; поведение сверено с эталоном донора поабзацно. План переноса — в specs/T001-engine-extraction/spec.md.

Потребители: «Дьяк» (кадровые документы пачкой из таблицы) и «Летопись» (приказы из кадровой базы).

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

uv add 'chancellery>=0.1.0,<0.2.0'

Зафиксируйте минорную версию. Пока библиотека в 0.x, обратная совместимость публичного API не обещается: ломающие правки контракта выходят в минорной версии (0.2.0), а не в мажорной. С открытой границей очередной выпуск подтянется молча и уронит потребителя — чаще всего не на машине разработчика, а при сборке у пользователя. Верхнюю границу поднимайте осознанно, прочитав CHANGELOG.md.

Сценарий целиком — таблица со строками данных плюс размеченный шаблон docx дают папку готовых документов, по одному на строку:

from chancellery import generate_documents

written = generate_documents(
    table='employees.xlsx',
    template='order_template.docx',
    out='out',
    filename='{{ Фамилия }}.docx',  # необязательно: по умолчанию — по ФИО
)

Если сценарий не подходит (источник данных не таблица, свой цикл, своя обработка ошибок) — те же слои доступны по отдельности: read_table, build_context, render_document, check_table. А если документы не нужны вовсе, а нужно только склонение:

from chancellery import Case, Person, PetrovichInflector, PhraseInflector, build_context

context = build_context(
    Person(
        cells={
            'Фамилия': 'Иванов',
            'Имя': 'Пётр',
            'Отчество': 'Семёнович',
            'Должность': 'старший механик-водитель',
        }
    ),
    roles={
        'surname': 'Фамилия',
        'name': 'Имя',
        'patronymic': 'Отчество',
        'position': 'Должность',
    },
    inflector=PetrovichInflector(),
    position_inflector=PhraseInflector(),
)
context['ФИО'].inflect(Case.DATV)  # Иванову Петру Семёновичу
context['Должность'].inflect(Case.DATV)  # старшему механику-водителю

Разметка шаблона

Падеж задаётся местом подстановки, а не данными: один и тот же человек в шапке стоит в именительном, а в теле приказа — в дательном, и шаблон говорит об этом фильтром.

Падежные фильтры:

{{ ФИО | ип }}   именительный    Иванов Пётр Семёнович
{{ ФИО | рд }}   родительный     Иванова Петра Семёновича
{{ ФИО | дт }}   дательный       Иванову Петру Семёновичу
{{ ФИО | вн }}   винительный     Иванова Петра Семёновича
{{ ФИО | тв }}   творительный    Ивановым Петром Семёновичем
{{ ФИО | пр }}   предложный      Иванове Петре Семёновиче

Тег без фильтра даёт именительный. Фильтр применим к любому склоняемому значению — ФИО, части имени, должности, званию, произвольной колонке.

Согласование по полу — фильтр согл, мужская форма первой; пол берётся из ФИО (определяется автоматически по имени и отчеству):

{{ ФИО | согл('ознакомлен', 'ознакомлена') }}

Теги ФИО. Колонка «ФИО» разбирается на части, а части собираются обратно, поэтому доступны сразу все формы:

{{ ФИО }}                 Иванов Пётр Семёнович
{{ Фамилия }}             Иванов          {{ Имя }}, {{ Отчество }} — так же
{{ Инициалы }}            Иванов П. С.
{{ Инициалы_впереди }}    П. С. Иванов
{{ Инициалы_слитно }}     Иванов П.С.
{{ Имя_инициал }}         П.              одна буква с точкой

Инициалы тоже склоняются: {{ Инициалы | рд }} → «Иванова П. С.». Пустое отчество не роняет рендер — форма просто становится короче.

Остальные колонки доступны по своему заголовку: {{ Должность | дт }}, {{ Номер приказа }}. Заголовок нормализуется (пробелы → подчёркивания, спецсимволы вычищаются), поэтому «л/н» в таблице пишется в шаблоне как {{ л_н }}.

Неизвестная переменная — ошибка, а не пустое место в готовом приказе: шаблон рендерится в строгом режиме. Пустое значение, наоборот, убирается вместе с осиротевшей пунктуацией.

Разработка

Менеджер зависимостей и окружения: uv.

uv sync                       # поставить зависимости
uv run pytest                 # прогнать тесты

Зависимости

uv add <pkg>                  # runtime
uv add --dev <pkg>            # dev

Ошибки

Все ожидаемые ошибки библиотеки наследуют ChancelleryError: ConfigError, TableError, TemplateError (и его частный случай UndefinedVariableError), ReverseError. Один обработчик ловит весь слой:

from chancellery import ChancelleryError

try:
    generate_documents(table, template, out)
except ChancelleryError as exc:
    print(f'Ошибка: {exc}')

Если у вашего приложения есть свой корень ошибок, его удобно подвесить под библиотечный — тогда один except ловит оба слоя:

class MyAppError(ChancelleryError):
    """Корень ошибок приложения."""

Ловушка, на которой спотыкаются при переходе на библиотеку. Наследование корня само по себе не чинит уже написанные обработчики. Код, который ловил except MyAppError, перестанет ловить ошибки движка: они наследуют ChancelleryError, а не ваш корень — наследование идёт в другую сторону. Обработчики верхнего уровня надо переписать на except ChancelleryError, иначе вместо понятного сообщения пользователь получит трейсбек. Ровно это всплыло при переводе «Дьяка»: четыре обработчика в его CLI перестали ловить TableError и TemplateError.

Выпуск версии

scripts/publish.sh --test    # репетиция на TestPyPI (нужен PYPI_TEST_TOKEN)
scripts/publish.sh           # боевой PyPI

Скрипт сам гоняет четыре гейта, собирает колесо и архив исходников, проверяет артефакты twine и выкладывает. Токен читается из .secrets (под git-ignore, образец — .secrets.example) и машину не покидает. Порядок: закрыть версию в CHANGELOG.md, поднять version в pyproject.toml, слить, поставить git-тег вида 0.1.0, выложить.

Проверки перед push

uv run ruff check .
uv run ruff format --check .
uv run mypy src
scripts/pytest-guard.sh --cov=src --cov-report=term-missing --cov-fail-under=80

Все четыре должны проходить с 0 ошибок. Обходные манёвры (# noqa, # type: ignore, расширение ignore-секции) — только по согласованию.

Структура проекта

  • src/ — корень исходников.
  • CONCEPT.md — изначальное видение проекта (immutable).
  • DECISIONS.md — архитектурные решения с обоснованиями (ADR-Lite).
  • BOARD.md — рабочая Kanban-доска (To Do / Doing / Done).
  • BACKLOG.md — парковка идей и побочных находок.
  • CHANGELOG.md — журнал заметных изменений.
  • specs/ — спецификации крупных фич.
  • CLAUDE.md — проектные правила для Claude (Claude Code).

Методика работы

Проект создан из шаблона vlakir/dreamteam. Подробное описание методики (scope discipline, ритуал spec/clarify/analyze для крупных фич, pre-push контроль) — см. репозиторий шаблона.

Release files for chancellery 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for chancellery 0.1.1
File Size Uploaded
chancellery-0.1.1.tar.gz 188.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for chancellery 0.1.1
File Interpreter ABI Platform
chancellery-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 262.8 kB

Release files / chancellery-0.1.1.tar.gz

Download URL chancellery-0.1.1.tar.gz
Size 188.7 kB
Tags Source
SHA-256 checksum
How to use checksums
2aea4267538ea2ff1abf80ac5e1d37b37f9bcf20fe1ae6aa0a2eb7003a542966
BLAKE2b-256 checksum
How to use checksums
c04b8fb6e588ee3ff06eedb70186d0632347c7e1917222d694f48b3297ecffe9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / chancellery-0.1.1-py3-none-any.whl

Download URL chancellery-0.1.1-py3-none-any.whl
Size 74.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
279d6176188343b743af7bd0b37d906342c74d6df3c21029a6c6ac28efc5674d
BLAKE2b-256 checksum
How to use checksums
58da470d52ee8804ab7f3924a9c5a1a248132d7c8c863afdb01eca505125f1dc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

0.3.0

2 release files

0.2.0

2 release files

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page