docs(overview): витрина системы docs/overview/ — бизнес+тех, 3 аудитории, презентация (ORCH-011)

Единая точка входа в документацию платформы (ADR-001 D1–D9):
- docs/overview/ — 10 файлов: индекс (маршруты «Я заказчик / Я менеджер /
  Я разработчик» + норматив «изменил функциональность → обнови витрину в том же
  PR»), business.md (без жаргона, 6 сценариев), 7 тех-блоков (link-first),
  presentation.md (16 слайдов + процедура сборки «команда + Проверка:»).
- scripts/build_presentation.py — генератор .pptx в тёмном дизайне (python-pptx;
  чистый stdlib-парсер parse_slides + ленивый import pptx; бинарь не коммитится,
  build/ в .gitignore; зависимость НЕ в прод-образе — машинный гард TC-09).
- tests/test_system_docs.py — структурный анти-дрейф: derive-сверки стадий/
  гейтов/агентов импортом STAGE_TRANSITIONS/QG_CHECKS/glob промптов/config,
  валидность ссылок, FORBIDDEN-скан + секрет-эвристика, слайды каноническим
  парсером, NFR-2, указатели.
- reviewer.md — ось обзорных доков ORCH-079 расширена на витрину (D7; канон 52d
  байт-в-байт, только текст внутри секций) + анти-регресс ассерт в
  test_agent_prompts_canon.py.
- Указатели: README.md, CLAUDE.md (правила №2/№6, «Структура»),
  PRODUCT_VISION.md (врезка-ссылка), CHANGELOG.md.

Рантайм байт-в-байт: src/**, docker-compose.yml, Dockerfile, requirements* —
ноль изменений (docs+tests+dev-скрипт, паттерн ORCH-102/103). pytest: 1873 passed.

Refs: ORCH-011

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 09:24:01 +03:00
committed by orchestrator-deployer
parent c455931ae7
commit 6d798c01ef
20 changed files with 1528 additions and 6 deletions

View File

@@ -0,0 +1,192 @@
#!/usr/bin/env python3
"""ORCH-011 (ADR-001 D4): сборка `.pptx` из слайдо-источника витрины.
Источник истины — `docs/overview/presentation.md` (машинно-парсимая
слайдо-структура: `## Слайд N: Заголовок` + тезисы `- ...` + опциональная
строка `> Визуал: ...`). Скрипт собирает редактируемую PowerPoint-презентацию
в тёмном дизайне (D-1 Владельца): тёмный фон, светлый текст, один акцентный
цвет, системные шрифты с полной кириллицей.
Канон (D4/D5):
- запуск ТОЛЬКО вне рантайма конвейера (host/dev venv, явный запуск человеком —
паттерн ORCH-009); `python-pptx` НЕ входит в requirements*/Dockerfile (NFR-2);
- `parse_slides` — чистая stdlib-функция БЕЗ импорта pptx: её импортирует
`tests/test_system_docs.py` (один парсер = один источник истины о формате);
- рендерер импортирует pptx ЛЕНИВО внутри `build_pptx`;
- дефолтный выход — `build/orchestrator-overview.pptx` (в `.gitignore`;
собранный бинарь в git НЕ коммитится — D5).
Процедура запуска (канон «команда + Проверка:») — `docs/overview/presentation.md`,
раздел «Как собрать .pptx».
"""
from __future__ import annotations
import argparse
import re
from dataclasses import dataclass, field
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
DEFAULT_SOURCE = REPO_ROOT / "docs" / "overview" / "presentation.md"
DEFAULT_OUTPUT = REPO_ROOT / "build" / "orchestrator-overview.pptx"
# Тёмная тема (D4): фон ~#1F1F2E, светлый текст, один акцент, приглушённый серый.
DARK_BG = "1F1F2E"
TEXT_MAIN = "F2F2F7"
ACCENT = "8AB4F8"
TEXT_MUTED = "9A9AAD"
FONT_NAME = "Calibri" # системный шрифт с полной кириллицей (D4)
_SLIDE_RE = re.compile(r"^##\s+Слайд\s+(\d+)\s*:\s*(.+?)\s*$")
_BULLET_RE = re.compile(r"^-\s+(.+?)\s*$")
_VISUAL_RE = re.compile(r"^>\s*Визуал\s*:\s*(.+?)\s*$")
_ANY_HEADING_RE = re.compile(r"^#{1,6}\s+")
@dataclass
class Slide:
"""Один слайд источника: номер, заголовок, тезисы, подпись визуала."""
number: int
title: str
bullets: list[str] = field(default_factory=list)
visual: str | None = None
def parse_slides(text: str) -> list[Slide]:
"""Разобрать слайдо-источник в список :class:`Slide` (чистая, stdlib-only).
Формат (D4): слайд открывается строкой ``## Слайд N: Заголовок``; его тезисы —
строки ``- ...``; опциональная подпись визуала — ``> Визуал: ...``. Любой
другой markdown-заголовок (например, раздел «Как собрать .pptx») завершает
текущий слайд — служебные разделы источника в слайды не попадают.
"""
slides: list[Slide] = []
current: Slide | None = None
for line in text.splitlines():
m = _SLIDE_RE.match(line)
if m:
current = Slide(number=int(m.group(1)), title=m.group(2))
slides.append(current)
continue
if _ANY_HEADING_RE.match(line):
current = None # служебный раздел — не слайд
continue
if current is None:
continue
bullet = _BULLET_RE.match(line)
if bullet:
current.bullets.append(bullet.group(1))
continue
visual = _VISUAL_RE.match(line)
if visual:
current.visual = visual.group(1)
return slides
def build_pptx(slides: list[Slide], output: Path) -> None:
"""Собрать `.pptx` в тёмном дизайне из распарсенных слайдов.
Импорт `pptx` — ленивый (D4): без установленного `python-pptx` модуль
остаётся импортируемым (нужно тестам), а сборка честно подсказывает
`pip install python-pptx`. Текст пишется настоящими редактируемыми
run'ами — кириллица не растрируется, слайды правятся руками.
"""
from pptx import Presentation
from pptx.dml.color import RGBColor
from pptx.util import Inches, Pt
prs = Presentation()
prs.slide_width = Inches(13.333) # 16:9
prs.slide_height = Inches(7.5)
blank_layout = prs.slide_layouts[6]
for slide_def in slides:
slide = prs.slides.add_slide(blank_layout)
slide.background.fill.solid()
slide.background.fill.fore_color.rgb = RGBColor.from_string(DARK_BG)
title_box = slide.shapes.add_textbox(
Inches(0.6), Inches(0.45), prs.slide_width - Inches(1.2), Inches(1.2)
)
title_tf = title_box.text_frame
title_tf.word_wrap = True
run = title_tf.paragraphs[0].add_run()
run.text = slide_def.title
run.font.size = Pt(34)
run.font.bold = True
run.font.name = FONT_NAME
run.font.color.rgb = RGBColor.from_string(ACCENT)
body_box = slide.shapes.add_textbox(
Inches(0.8), Inches(1.85), prs.slide_width - Inches(1.6), Inches(4.4)
)
body_tf = body_box.text_frame
body_tf.word_wrap = True
for i, bullet in enumerate(slide_def.bullets):
para = body_tf.paragraphs[0] if i == 0 else body_tf.add_paragraph()
run = para.add_run()
run.text = f"{bullet}"
run.font.size = Pt(20)
run.font.name = FONT_NAME
run.font.color.rgb = RGBColor.from_string(TEXT_MAIN)
para.space_after = Pt(10)
if slide_def.visual:
cap_box = slide.shapes.add_textbox(
Inches(0.8), Inches(6.55), prs.slide_width - Inches(1.6), Inches(0.6)
)
cap_tf = cap_box.text_frame
cap_tf.word_wrap = True
run = cap_tf.paragraphs[0].add_run()
run.text = f"Визуал: {slide_def.visual}"
run.font.size = Pt(13)
run.font.italic = True
run.font.name = FONT_NAME
run.font.color.rgb = RGBColor.from_string(TEXT_MUTED)
output.parent.mkdir(parents=True, exist_ok=True)
prs.save(str(output))
def main(argv: list[str] | None = None) -> int:
"""CLI: распарсить источник, собрать `.pptx`, напечатать число слайдов."""
parser = argparse.ArgumentParser(
description="Сборка docs/overview/presentation.md -> .pptx (тёмный дизайн, ORCH-011 D4)."
)
parser.add_argument(
"--source",
type=Path,
default=DEFAULT_SOURCE,
help=f"слайдо-источник (default: {DEFAULT_SOURCE.relative_to(REPO_ROOT)})",
)
parser.add_argument(
"--out",
type=Path,
default=DEFAULT_OUTPUT,
help=f"выходной .pptx (default: {DEFAULT_OUTPUT.relative_to(REPO_ROOT)})",
)
args = parser.parse_args(argv)
if not args.source.is_file():
print(f"ОШИБКА: источник не найден: {args.source}")
return 1
slides = parse_slides(args.source.read_text(encoding="utf-8"))
if not slides:
print(f"ОШИБКА: в {args.source} не найдено ни одного слайда (формат: '## Слайд N: ...')")
return 1
try:
build_pptx(slides, args.out)
except ImportError:
print(
"ОШИБКА: python-pptx не установлен. Сборка выполняется в одноразовом "
"dev-venv ВНЕ прод-образа (NFR-2): pip install python-pptx"
)
return 1
print(f"Собрано слайдов: {len(slides)}{args.out}")
return 0
if __name__ == "__main__":
raise SystemExit(main())