# PDF-расчёт гостю Telegram

`telegram_make_pdf` получает `{kind, quote, guestName}`. `kind` — group/biggroup/resident;
`quote` — непосредственно результат операции quote API Марка, без обёртки.
У group поле kind в ответе отсутствует, поэтому тип указан отдельно. Инструмент
не вызывает API и не пересчитывает цену: сумму, даты, состав, строки и условия
берёт из quote. В ответе `{path:"vydacha/<current-chat>/raschet-<n>.pdf"}`.

Используются установленные `/usr/bin/python3`, ReportLab и DejaVuSans.ttf;
данные идут через stdin, Python выдаёт PDF через stdout. В файле — текстовая шапка
«Остров», имя и серверный расчёт с предупреждениями. Локального логотипа в
прочитанных ресурсах приложения Марка не найдено; сетевые изображения не загружаются.

Путь и адресат определяются текущими Telegram-квитанциями. Аргументы path/chat_id
для генератора отсутствуют. Имена файлов создаются O_EXCL, прежний PDF не
перезаписывается. `telegram_send_file` принимает возвращённый путь; TG-гостю
разрешён только PDF в собственной папке выдачи. Рабочий режим сохраняет прежние
форматы. Сайтовый посетитель не получает эти инструменты, bash гостю не нужен.

Ограничения: вход ≤262144 UTF-8 байт, PDF ≤10 000 000 байт, генератор ≤30000ms,
создание имени ≤10 попыток при collision. Ошибка/закрытый ход/неоднозначная квитанция
дают отказ; автоматических повторов генератора или отправки нет. Документ отражает
переданный quote, не доказывает подлинность API-ответа, актуальность наличия мест
или согласие/бронь. Реальная отправка требует separate Telegram-квитанции.

Native и HTTP мост показывают TG-гостю file/PDF только при регистрации в её scope.
Расширение гостевых имён моста применяется только к telegram-gost-*; site остаётся
на прежнем наборе. Формат Ш4 и номер общего bridge выпуска согласуются с Гаммой.

## Фирменные бланки — B-N-1701, кандидат без нового выпуска

`telegram_make_pdf` принимает прежние `kind`, `quote`, `guestName` и необязательный
`letterhead`: `general_portrait` (№1, обычный вертикальный расчёт, по умолчанию),
`general_landscape` (№2, горизонтальный для широкого содержимого), `legal`
(№3, официальный бланк или явная просьба). №4/№5 и пути от модели не принимаются.
Суммы/условия не пересчитываются; общий layout содержит все поля серверного quote.

В конфигурации **канала** задать `pdfTemplatesDir` — абсолютный каталог шаблонов.
Предлагаемое место на sadik: `/etc/varvara/pdf-templates` (каталог root:varvara0750,
файлы root:varvara0640). Это предложение для монтажа Забавой, не уже созданный каталог.
Код не содержит литерала этого deployment-пути и не читает Drive. Будущий канонический
Drive-каталог даст оператору новые локальные копии; меняется настройка пути, не модельный аргумент.

Названия локальных копий, которые положить в выбранный каталог:

- `general_portrait.pdf` — принятый `01_Остров_общий_вертикальный.pdf` (SHA256 0a9bdc4560d0fa08…);
- `general_landscape.pdf` — принятый `02_Остров_общий_горизонтальный.pdf` (SHA256 4a88276234f0c128…);
- `legal.pdf` — принятый `03_Остров_юридический_бланк.pdf` (SHA256 f2ed392942f1e96c…).

Все три исходника одностраничные. Выбранный бланк повторяется на каждом листе;
текст ограничен свободной областью, номер листа стоит отдельно над подвалом.
Поля в points: вертикальный top136/bottom80, горизонтальный top110/bottom96,
юридический top152/bottom96; боковые44. Нельзя заменять эти утверждённые бланки
другим расположением шапки без проверки свободной области. Контакты/реквизиты исходников
не изменены (README источника отмечает их старый возраст); их актуализация — рука владельца/Марка.

ReportLab и pypdf доступны установленному `/usr/bin/python3` под varvara.
UnicodeFont/ToUnicode UTF-16BE и отказ неподдержанных glyph сохранены.
При неизвестном выборе, ненастроенном каталоге или недоступном/повреждённом бланке
инструмент возвращает причину; PDF/путь не создаётся. Старого fallback на небланковый PDF нет.
Текст вновь добавленного расчёта копируется с кириллицей/эмодзи; исходный юридический
PDF имеет legacy-кодировку некоторых текстов шапки, она не переписывается этим кандидатом.

В нормализованной native/MCP-схеме верхний `type:object`, без верхних oneOf/anyOf/allOf.
`stend-pdf-letterheads-engine.mjs` использует метод стенда Искры и настоящий SDK/движок
шлюза с временной HOME и подставной авторизацией: две простые схемы, различитель,
реальная schema `defineTool(pdfTool)`. Это проверка схемы PDF, не живое48/48 всех инструментов.
Копия bridge и ядро памяти нужны старому native-стенду только как соседние test-зависимости;
сам мост данным кандидатом не меняется. Вызов Telegram в native-стенде подставной.

## Сохранённый расчёт и гостевое предложение — B-N-1700

Новый вход: `{result: D, guestName, letterhead?}`, где D — именно result
plan.create/read/edit: D.kind, D.quote, D.url, D.updatedAt. Не внешний HTTP/SDK-конверт.
Kind берётся из D; при переданном отдельном kind он должен совпасть. Старый вход
`{kind, quote: Q, guestName, letterhead?}` работает без постоянной ссылки.
Передавать ровно одно из quote/result. guestName остаётся резервным именем;
когда есть имя/organizer в Q, показывается оно, согласно проекции Марка.

D.url печатается полностью видимым текстом и сохраняется как точный URI PDF-аннотации
(включая fragment/query). Ссылка не запрашивается по сети и не строится из догадок.
При saved result обязательна непустая абсолютная http(s) ссылка без control/whitespace
и user/password; неверная ссылка не превращается молча в PDF без ссылки.
D.kind и D.updatedAt обязательны для saved DTO: D.updatedAt показывается датой/временем UTC; дата из диалога не используется.

Проекция согласована Марком через подписанный ответ Забавы19:05:19Z:
PDF-QUOTE-PROJECTION.md e9249c90; окончательный API-CONTRACT.md cfaf1740.
Группы: label дословно, amount со знаком, порядок исходный; таблица «Состав расчёта / Сумма».
Количество в API не задано, его не вычислять из rate/label; отдельной пустой колонки нет.
Resident: housingTotal,foodTotal,две общие скидки с минусом,bathTotal при выбранной бане,total.
Детская скидка уже учтена: только примечание, без второго вычитания. Бесплатная квота
показывается как доступная, не как заказ; выбранная бесплатная баня остаётся «без доплаты».
Точный source total печатается даже при нуле, не вычисляется суммой таблицы.

Суммы — «1 250,50 ₽», пустые блоки не печатаются; JSON settings/settlement/allocation не дампятся.
Resident rooms/resourceName и Q.settings.includedActivities показаны адресно; у biggroup
конкретное расселение не придумывается, bathDays — дни по формату, не выбранный гостем заказ. Warnings сохранены, версия Q.version мелко в подвале.
Бланки/config.pdfTemplatesDir/ToUnicode и current-chat output из B-N-1701 сохранены.
Исторические контакты на исходных бланках не правились и не подтверждены как текущие.

PDF — снимок возвращённого расчёта; после plan.edit нужно сделать и отправить новый файл.
Сохранение не является заявкой/бронью. Генератор сам не вызывает сохранение и не отправляет файл.
Мост/API/гостевая инструкция монтируются назначенными руками отдельно от этой PDF-дельты.


## Нижняя граница замороженных бланков — исправление после проверки Марка

Для принятых контактных подвалов19844e85/a4359afd/fb75c960 нижние поля80/96/96points
держат основной текст И номера/версию выше footer link rectangles с запасом.
Границы от верха: portrait784.550764, landscape521.900937, legal768.500764.
Проверка берёт footerTop отдельно из аннотаций неизменённого исходника и вычитает8pt;
сопоставляет все DejaVuSans spans каждого листа и сравнивает pixels всей полосы подвала.
Допустимый single-line title128 через Store переносится естественно; приложенный красныйвход
после правки не перекрываетostrov.center. Цена/URI/строки не меняются, шаблоны не редактируются.
