Skip to content

Wave 7: алгоритмы мастера во все школы + default_school + ЮKassa + прод-фиксы - #1

Closed
AgentSmoki wants to merge 143 commits into
mainfrom
feat/wave7-master-algorithms-yookassa-default-school
Closed

Wave 7: алгоритмы мастера во все школы + default_school + ЮKassa + прод-фиксы#1
AgentSmoki wants to merge 143 commits into
mainfrom
feat/wave7-master-algorithms-yookassa-default-school

Conversation

@AgentSmoki

Copy link
Copy Markdown
Owner

Что внутри

Wave 7 Phase 9

  • Алгоритмы мастера → все школы. Ревью мастера применено к 5 алгоритмам; нейтральное ядро в ai/skills/* (видят все школы), голос ЭдоХи в base_edoha.md. Новый skill decision + router catalog.
  • Chart.default_school — выбор школы по умолчанию в меню карты; консультация и покупка прогноза пропускают селектор (1.18.14).
  • ЮKassa — нативные Telegram-платежи (провайдер ЮKassa), без webhook. Прогнозы 500/900 ₽ + вопросы 290/990/2490 ₽. Gate payments_live (токен + не bypass).
  • bge-m3 предложение; Apache AGE отменён (остаёмся на KuzuDB 0.10).

Прод-фиксы

  • A. Роутер больше не переспрашивает дату/такт (они в карте).
  • B. Дубли уведомлений о важных датах → per-date дедуп + commit per-chart.
  • B2. Приглашение рефлексии теперь В ДЕНЬ важной даты (отдельно от предупреждения за 1-2 дня).
  • C. Расшифровки 11 звёзд в уведомлениях.

Миграции (3)

b2c3d4e5f6a7 (default_school), c3d4e5f6a7b8 (payment_id), d4e5f6a7b8c9 (important_date_dedup).

Тесты

902 passed, покрытие 97.78%, ruff + mypy strict чисто.

Нужно для ЮKassa в проде

provider-токен из @Botfather (ЮKassa) в .env на VM + FORECAST_FREE_BYPASS=false. До этого оплата в free-bypass (как раньше).

🤖 Generated with Claude Code

AgentSmoki and others added 30 commits March 12, 2026 10:40
Добавлены: MASTER.md (мастер-документ), product_idea.md (бизнес-идея),
doc/vision.md (продуктовое видение), промпт AI-консультанта Анастасии.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add .cursor/rules/{vision,conventions,workflow}.mdc — архитектурные решения,
  соглашения по коду и рабочий цикл для агента-кодера
- Add CLAUDE.md — master prompt агента-кодера с примерами и ограничениями
- Add tasks.md — гранулярный бэклог по 4 этапам (50+ задач)
- Add .env.example — все env vars (OpenRouter, KuzuDB, Yandex Cloud, ЮKassa)
- Add docker-compose.yml — bot + web + worker + postgres + redis
- Add deploy.md — пошаговые инструкции для Yandex Cloud (yc CLI)
- Update MASTER.md v3.1 — новый стек: OpenRouter/Kimi K2, gpt-image-1, KuzuDB,
  Yandex Cloud, монетизация 3 тарифа + бесплатная базовая интерпретация
- Create project scaffold: bot/, calculator/, ai/, web/, db/, tasks/,
  monitoring/, migrations/, tests/, knowledge/, assets/hieroglyphs/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- assets/hieroglyphs/PROMPTS.md — 24 ready-to-use DALL-E prompts for all
  hieroglyph assets (10 stems + 12 branches + 2 Yin/Yang), with filenames,
  element colors, and naming table
- doc/gemini_research.md — 6 deep research prompts for Gemini:
  KuzuDB/Neo4j comparison, OpenRouter Kimi K2 evaluation, gpt-image-1
  card generation, Yandex Cloud cost analysis, aiogram FSM patterns,
  temporal BaZi context detection

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Switch AI primary model to Kimi 2.6 (moonshotai/kimi-2.6) across all docs
- Replace gpt-image-1 with Playwright HTML→PNG rendering in ADR-003, CLAUDE.md, tasks 1.7
- Update subscription tiers to 290/990/2490 RUB in vision.mdc, MASTER.md, tasks.md
- Mark P-1 (24 PNG hieroglyphs), P-4 (Gemini research), P-5 (scaffold) as done
- Add Yandex Cloud account creation guide (Step 0) to deploy.md
- Complete scaffold: tests/, knowledge/, monitoring/, web/, tasks/ directories
- Add 24 PNG hieroglyph assets and Playwright/Pillow fallback strategy

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- All project dependencies declared with pinned versions
- ruff: line-length=100, py311, E/W/F/I/B/C4/UP/N/S/ANN/RUF rules
- mypy: strict mode, py311, third-party overrides for kuzu/swisseph etc.
- pytest: asyncio_mode=auto, coverage for calculator/ai/bot/db
- .venv created with all deps installed (ruff+mypy+pytest all green)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- .pre-commit-config.yaml: ruff-check+fix, ruff-format, mypy, detect-secrets
- .secrets.baseline: 5 known false positives in docs/examples whitelisted
- pyproject.toml: fix versions — pydantic 2.10.6 (aiogram compat), langfuse 4.5.1, aioboto3 15.5.0
- All 4 hooks pass on full codebase

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Align CLAUDE.md and MASTER.md with the canonical tariff plan
(Месяц / 3 месяца / Год) referenced from tasks.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Stage 1 (builder): build-essential + gcc для pyswisseph,
  установка зависимостей в /opt/venv, Playwright Chromium.
- Stage 2 (runtime): только runtime-библиотеки (libpq5,
  fonts-noto-cjk для CJK иероглифов карт), копирование venv
  и Chromium из builder.
- Non-root user app (UID 1000), HEALTHCHECK по импорту bot.config.
- ENTRYPOINT не задан — команда задаётся в docker-compose
  для каждого сервиса (bot/web/worker).
- .dockerignore: исключает .git, tests, кэши, doc, graphify-out,
  .env, чтобы не раздувать build context.

Реализует tasks.md 1.1.3.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Drop obsolete `version: "3.9"` (Compose v2 ignores it).
- Worker entrypoint switched to `tasks.broker:broker` to match
  the planned TaskIQ layout (1.11).
- Redis healthcheck via `redis-cli ping`; bot/web/worker now wait
  on `service_healthy` for both db and redis.
- Removed empty `swisseph_data` volume — ephemerides will be baked
  into the image as part of 1.4.2.
- Added `kuzu_data` named volume mounted at `/app/knowledge/kuzu_db`
  for KuzuDB graph persistence across restarts (1.9).
- Web command no longer uses `--reload` (production-friendly default).
- Bump detect-secrets baseline to track the line shift in
  docker-compose.yml.

Реализует tasks.md 1.1.4.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Single `lint-and-test` job on ubuntu-latest, Python 3.11.
Runs on push to main and pull_request to main, with
concurrency cancelling stale runs.

Steps: ruff check → ruff format --check → mypy (strict) → pytest.
The pytest step temporarily tolerates exit code 5 (no tests
collected) until the calculator suite lands in 1.4.9 — the
wrapper will be removed there.

`permissions: contents: read` keeps the workflow least-privileged.

Реализует tasks.md 1.1.5.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
bot/config.py — flat Settings class covering all 33 variables
from .env.example, grouped by domain (Telegram, DB, AI, Swiss
Ephemeris, Geocoding, KuzuDB, Object Storage, Payments,
Monitoring, Web/Admin, Playwright, feature flags, app).

- SecretStr for every credential (BOT_TOKEN, OPENROUTER_API_KEY,
  YC_*, YUKASSA_*, LANGFUSE_*, ADMIN_BASIC_AUTH_PASSWORD) so they
  never leak through __repr__/logs.
- Path for filesystem locations, HttpUrl for endpoints,
  Literal for ENVIRONMENT / LOG_LEVEL / GEOCODING_PROVIDER.
- model_config: env_file=.env, case_sensitive=False,
  extra="forbid" — typos in .env fail fast.
- get_settings() with lru_cache(maxsize=1); Settings is never
  instantiated at module import time, so importing the module
  stays safe in CI / Docker healthcheck without an .env.

Реализует tasks.md 1.2.1.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
bot/logging.py — idempotent configure_logging() that wires
structlog with a shared processor pipeline:
- merge_contextvars (picks up trace_id / user_id from contextvars)
- add_log_level, ISO/UTC TimeStamper, exc_info/stack rendering
- ConsoleRenderer in development, JSONRenderer in staging/production

The stdlib root logger is bridged to the same stdout sink so
aiogram / sqlalchemy / httpx logs land in the same stream.
The log level comes from Settings.log_level.

Helpers bind_trace_id() and clear_context() expose the
contextvars surface that the tracing middleware (1.2.3) will
call on every Telegram update.

Реализует tasks.md 1.2.2.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
bot/middlewares/tracing.py — aiogram outer middleware that
generates a full UUID v4 for every incoming Telegram update,
binds it to structlog contextvars, and exposes it via
data["trace_id"] so handlers can propagate it into Consultation
rows, OpenRouter headers, and Langfuse traces.

- Logs telegram.update_received with event_type and user_id
  (no message content — keeps PII out of logs).
- On exception: logs telegram.update_failed with exc_info and
  re-raises, so aiogram's error handlers still fire.
- finally: clear_context() to avoid trace_id bleeding between
  updates handled on the same task.

Also adds aiogram to the mypy pre-commit hook's
additional_dependencies so the isolated hook env can resolve
BaseMiddleware (otherwise it sees aiogram as Any and fails the
strict subclass check).

Registration on dp.update.outer_middleware comes in 1.5.1 with
the bot entry point.

Реализует tasks.md 1.2.3.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
db/engine.py — infrastructure entry point for the database layer.

- get_engine(): lru_cache singleton backed by AsyncEngine.
  pool_size=10, max_overflow=5, pool_pre_ping=True (guards
  against stale connections on Yandex Managed PostgreSQL),
  echo enabled only in development.
- get_session_factory(): lru_cache async_sessionmaker with
  expire_on_commit=False, autoflush=False.
- session_scope(): asynccontextmanager for CLI scripts and
  TaskIQ workers; commits on success, rolls back on exception.
  Bot routers will receive sessions via db_session middleware
  (task 1.5.2) rather than calling this directly.

Реализует tasks.md 1.3.1.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tion, Event

db/base.py — shared DeclarativeBase.
db/models.py — all five domain models with:

- UUID v4 primary keys (postgresql.UUID, as_uuid=True).
- created_at / updated_at via server_default=func.now();
  updated_at uses ORM-side onupdate=func.now().
- chart_data as JSONB for flexible BaZi chart payload.
- lazy="raise" on all relationships — prevents silent N+1
  queries and DetachedInstanceError in async context;
  callers must use selectinload() / joinedload() explicitly.
- ondelete="CASCADE" on all FK so deleting a User removes
  charts, consultations, subscription, and events.
- SubscriptionPlan / SubscriptionStatus as str enums stored
  as VARCHAR (native_enum=False) — no PG enum migration needed.
- Subscription has unique constraint on user_id (1:1 with User).

Реализует tasks.md 1.3.2.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
alembic.ini:
- script_location → migrations/
- file_template: YYYYMMDD_<rev>_<slug> for readable filenames
- sqlalchemy.url removed — URL injected from Settings in env.py
- ruff post-write hook auto-formats new migration files

migrations/env.py:
- Async online mode: create_async_engine + conn.run_sync()
- Offline mode uses Settings.database_url directly
- Imports db.models to register all tables with Base.metadata
  before autogenerate diff; compare_type=True to detect col changes

Реализует tasks.md 1.3.3.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Written manually (no live DB available locally); matches db/models.py
exactly. Tables in dependency order: users → charts → subscriptions
→ consultations → events.

Each table: UUID PK, server_default timestamps, CASCADE FK.
Indexes: telegram_id, user_id/chart_id on join tables, trace_id.
JSONB for chart_data, Double for lat/lon/tz_offset.
downgrade() drops tables in reverse dependency order.

Run `alembic upgrade head` on the Yandex Cloud VPS as part of 1.16.

Реализует tasks.md 1.3.4.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…, SubscriptionRepository

Four repository classes under db/repositories/, one file per aggregate.
All methods are async and accept AsyncSession as first argument.
Callers never touch session.execute() directly — only repos do.

UserRepository:
- get_by_telegram_id, get_by_id, create, get_or_create (SELECT FOR
  UPDATE skip_locked to survive concurrent /start), mark_free_question_used.

ChartRepository:
- create (full kwargs), get_by_id, get_latest_by_user, list_by_user.

ConsultationRepository:
- create, list_by_user (default limit 50), count_today_by_user (UTC
  midnight boundary — used by Redis rate-limiter fallback in 1.12.1).

SubscriptionRepository:
- get_by_user_id, create_free, update_plan (handles monthly/quarterly/
  annual expiry fields).

Реализует tasks.md 1.3.5.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…odels

calculator/models.py — stateless Pydantic v2 I/O contracts:

- Stem / Branch — Literal types over the 10 heavenly stems and 12
  earthly branches; STEMS / BRANCHES tuples for runtime iteration.
- HiddenStemsSchool — Literal["traditional", "modern", "ken_lai"].
- ChartInput — validated entry point: latitude ∈ [-90, 90],
  longitude ∈ [-180, 180], field_validator raises ValueError on breach.
- Pillar — (stem, branch, name) value object.
- ChartOutput — full result container; populated progressively by
  modules 1.4.2–1.4.8.

tests/unit/test_calculator/test_models.py — 17 tests (TDD Red→Green):
- ChartInput happy-path, defaults, boundary values, invalid lat/lon/school.
- Pillar valid and invalid stem/branch.
- STEMS/BRANCHES counts and type annotations.
- ChartOutput construction and field access.
- 100% coverage on calculator/models.py.

Реализует tasks.md 1.4.1.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Julian day conversion + sun ecliptic longitude via pyswisseph.
All 8 unit tests pass. ruff + mypy strict clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LMT + Equation of Time via pyswisseph swe.time_equ().
10 unit tests cover EoT reference values and TST offsets.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Julian day search with <0.01 degree precision. Covers all 24 Jie Qi
including year-boundary terms (小寒..惊蛰 before vernal equinox).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Year/month/day/hour pillars with Early/Late Rat support.
Reference: 1999-09-12 Beijing noon → 己卯 癸酉 丁亥 丙午.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previous reference 1900-01-31 was 甲申 (index 40), not 甲子.
Verified against user birth date: 1999-09-12 Volzhsky -> 丁卯 辛亥.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1999-09-12 23:55 MSK Summer Time (UTC+4), Volzhsky lon=44.77.
Verified: Ji-Mao / Gui-You / Ding-Mao / Xin-Hai.
Fix TST-aware boundary tests for UTC+4 timezone.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements traditional, modern, and ken_lai schools.
Key differences: modern adds 壬 to 子; ken_lai removes 己 from 午.
32 tests, ruff+mypy strict clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Gemini research confirmed School B (Mingli-compatible) has
巳 = [丙, 戊, 庚] not [丙, 庚, 戊].
Order matters: 本气→中气→余气.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pure element-cycle logic: generating/controlling relationships
determine all 10 gods. chart_ten_gods() maps full chart including
hidden stems. Verified against user's 丁卯 day master chart.
32 tests, ruff+mypy strict clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AgentSmoki and others added 27 commits May 23, 2026 17:06
…ache)

Дополняет deterministic vocab+stem extractor (ai/rag/extract.py) LLM
проходом по тексту вопроса. Ловит концепты, которые vocab match
пропускает:
- Синонимы: «начальник» → 正官
- Подразумеваемые темы: «постоянно ругаюсь с женой» → 夫妻宫, 六冲
- Симптомы здоровья: «голова болит» → Огонь+Дерево+печень
- Временные референсы: «следующая неделя» → 流月

Архитектура (без async cascade в compose_messages):
- ai/rag/llm_extract.py — async extract_concepts_llm + ConceptCache
- consultation.py перед compose_messages вызывает llm_extract,
  объединяет результат с skill_router.concept_hints (router hints
  first, novelties LLM appended). compose_messages остаётся sync —
  не трогаем forecast/base_interpretation/2 production callers.
- bot/main shutdown closes the cache Redis pool.

Safety nets:
- Redis cache по sha256(lower(trim(question))) TTL 24h — repeated
  identical questions cost 0 LLM calls. Cache на same Redis что
  HistoryStore (pool reuse).
- LLM upstream error / parse failure → returns [] silently. Caller
  union'ит с vocab+stem hits, retrieval keeps working.
- Cap на 15 концептов чтобы Cypher UNWIND не уходил в quadratic.

Fast tier: yc_fast_model=qwen3.6-35b-a3b, max_tokens=4000 (тот же
что skill_router). Latency ~0.5-1.5s. Cost ~0.3₽/uncached, ~0.05₽
amortised на cache hit.

System prompt катало­гирует основные категории Ба Цзы (10 стволов,
12 ветвей, 10 божеств, звёзды, взаимодействия, структуры) + 4
few-shot примера. Output: JSON array of strings.

Tests:
- 15 unit tests в test_llm_extract.py — _parse_concepts edge cases,
  cache hit/miss, normalised key, upstream error, 15-cap, empty/
  whitespace short-circuit.
- test_consultation.py: stub_llm_extract autouse fixture
  предотвращает реальные Redis/Qwen-вызовы в существующих тестах.

Documentation:
- doc/school_layered_flow.md — полная end-to-end схема по слоям
  (где какие .md файлы подтягиваются, что меняется между школами,
  где находятся 6 драфтов Phase 4, roadmap для усиления различий
  между школами).

Tests: 858 passed (843 → +15 llm_extract), mypy strict ✓, ruff ✓,
coverage 97.85%. Не сломал ни одного существующего теста после
auto-stub в test_consultation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…гноза

Каждое утреннее доставленное daily-уведомление получает «открывающую»
landscape-фотографию под энергии столпа дня. Перед текстом прогноза
шлётся ``bot.send_photo`` с природным образом; ниже идёт сам текст
как раньше через ``bot.send_message``.

Pipeline (ai/day_image.py):
1. Извлекаем столп дня из chart на target_date (через calculate_chart
   при noon UTC — тот же приём что forecast.py использует для текста).
2. Cache lookup в Redis по ``stem+branch`` (всего 60 вариантов столпа
   → 60 кэш-ключей, один общий для всех юзеров с этим столпом).
3. Cache miss → LLM (Qwen3.6 fast tier) генерирует 2-4 английских слова
   природного образа под стихии столпа. Пример: 戊子 (Земля Ян + Вода) →
   "misty mountain dawn". System prompt + 5 few-shot, temperature=0.4.
4. Unsplash /search/photos?orientation=landscape&per_page=1 — берём
   первую landscape-фото, URL.regular (1080px).
5. Cache write (TTL 24h). Возвращаем URL.

Stack budget при больших нагрузках:
- 60 столбов × 1 LLM-вызов × 1 Unsplash-вызов = максимум 60 запросов
  к каждому upstream в сутки.
- Unsplash free tier: 50 req/h — с лихвой хватает.
- Cost: ~0.3₽/uncached LLM × 60 = 18₽/день при холодном старте,
  амортизация почти до 0₽ после первой генерации каждого столпа.

Graceful fallback (картинка — украшение, не контракт):
- ``settings.unsplash_access_key=None`` → short-circuit, return None.
- LLM upstream error / empty query → return None.
- Unsplash 4xx/5xx/empty results → return None.
- ``send_photo`` TelegramBadRequest → лог + продолжаем без картинки.

Integration (bot/scheduler/jobs.py):
- ``_send_or_record_error(hero_image_url=...)`` опц. kwarg.
- ``_send_daily_forecast_inner`` после ``generate_daily_forecast``
  считает день-пилар + вызывает ``fetch_day_energy_image``. Любая
  ошибка → ``hero_image_url = None`` (тогда поведение как было).
- Логи: ``forecast.delivered had_hero_image=true|false``.

Shutdown:
- bot/main._shutdown зовёт ``close_day_image_cache`` рядом с
  ``close_concept_cache`` (Phase 3.5).

Tests (13 unit в test_day_image.py):
- _sanitize_query: quotes, fences, cap-6-words, garbage, mixed scripts
- _pillar_to_prompt deterministic
- fetch happy path: cache write + cache hit reuses without LLM/HTTP
- fetch без access_key → None без вызовов
- fetch LLM error → None, cache not poisoned
- fetch Unsplash 4xx → None
- fetch Unsplash empty results → None
- fetch empty LLM query → None без вызова Unsplash
- ConceptCache key derivation, legacy dict format compat

Tests: 871 passed (858 → +13). mypy strict ✓, ruff ✓, coverage 97.85%.
Не сломал ни одного существующего теста.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ressed

Phase E live-verify 2026-05-24 показал что daily forecast пришёл без
картинки (had_hero_image=False). VM-probe в scheduler выявил два бага:

1. **VM .env не имел Unsplash ключей.** rsync исключает .env по
   ``--exclude='/.env'``, поэтому ключи добавленные локально (Phase E
   commit 9dab13a) на VM не доехали. Settings.unsplash_access_key=None
   → fetch short-circuits → None. Hotfix: ключи добавлены вручную в
   VM .env через scp фрагмента + ``docker compose up -d --force-recreate``
   (просто ``restart`` НЕ перечитывает env_file). Это известная
   проблема Docker — теперь зафиксирована в memory.

2. **YC требует полный gpt://<folder>/<short>/latest URI**, не короткое
   имя модели. Это та же проблема что в Wave 6 Phase 7 fix для
   ai/skill_router.py: после исправления access_key probe вернул
   ``yc 400: Failed to parse model URI``. Скопировал паттерн из
   ai/skill_router.py:142-146 в оба новых модуля:
   ```python
   model_uri = f"gpt://{settings.yc_ai_folder_id}/{settings.yc_fast_model}/latest"
   ```

Live probe после fix (scheduler container):
```
day_image.fetched pillar=丙午 query='sunlit calm lake'
URL: https://images.unsplash.com/photo-1761311263562-3ecb33e21773?...
```

Tests: 28 passed (day_image=13 + llm_extract=15). Полная suite не
прогонялась (только затронутые модули), но изменения чисто на
construct-call site — risk минимальный.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…chool

Архитектурная переработка по итогам Phase 2+5 live-verify (commit
dfb857e после hotfix): skill risk body жёстко содержал 3-vs-1 алгоритм
школы Мастера ЭдоХа, из-за чего даже под base_classic.md overlay LLM
применяла военные аналогии и финалила «это интерпретация ЭдоХа».

Теперь чистая separation of concerns:

| Файл | Роль |
|---|---|
| ai/skills/risk.md | ШКОЛО-НЕЙТРАЛЬНЫЙ дисциплинатор: ЧТО анализировать (карта рождения / приходящие столпы / смягчающие факторы) + КАК форматировать ответ + дисциплина запретов (не предсказывать смерть, признать школу в финале). Размер ~6 KB. БЕЗ конкретного алгоритма ранжирования. |
| base_edoha.md «# Алгоритм оценки рисков» | Полный chain-of-thought 3-vs-1 (5 шагов, военная аналогия, ранжировка 🥇🥈🥉, пример на карте Богдана). +~40 строк. |
| base_classic.md «# Алгоритм оценки рисков» | Классическая иерархия 8 факторов (三刑+СД самый сильный → одиночное 六冲+貴人 самый слабый). 5 шагов, ссылки на Цзы Пин Чжэнь Цюань. +~30 строк. |
| base_modern.md «# Алгоритм оценки рисков» | Переформулировка «опасность → зона роста» через архетипы + стихия-дефицит. Таблица перевода классических терминов в язык развития (Юнг/Чиксентмихайи). +~40 строк. |

Effect: запрос «опасный месяц 2026?» теперь даёт ТРИ принципиально разных
ответа в зависимости от школы:
- 🎓 Классическая: фокус на сентябре (двойная активация 卯酉冲) + май
  (六冲 со столпом часа), цитаты Цзы Пин Чжэнь Цюань
- 🌀 ЭдоХа: фокус на марте (3-vs-1 от Кролика) + сентябрь (2-vs-1) + май
  (классическое 六冲), военная аналогия «третья волна»
- 🧬 Современная: переформулировка как «обучающих периодов», практики
  заземления/телесной разрядки/дисциплины вместо предупреждений

Skill risk body теперь явно говорит «следуй методике школы из system
prompt» + «не путай методологии разных школ в одном ответе» — это
блокирует утечку 3-vs-1 в classic ответы.

Test updated: test_risk_skill_body_describes_3vs1_pattern → renamed to
test_risk_skill_body_is_school_neutral_disciplinator. Проверяет:
- school references (методика из base_<school>.md)
- universal analysis points (благородные, 六冲 как контекст)
- explicit ban on cross-school methodology mixing

Также включает doc/algorithm_review_prompts.md (создан в этой сессии)
— 6 шаблонов промптов для ревью алгоритмов Phase 4 сенсеем.

Tests: 871 passed (без новых, 1 переименован). mypy strict ✓, ruff ✓,
coverage 97.85%.

Backlog (когда сенсей провалидирует алгоритмы Phase 4):
- Аналогично выделить алгоритмы opportunity / relationships_match /
  career / decision / health под Опцию X для каждого skill
- Решить Опцию A/B/C из doc/school_layered_flow.md для финальной
  интеграции 6 драфтов

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e для всех

Refactor: bool free_question_used (1 вопрос) → int free_questions_used (счётчик).
Замена «1 free + admin skip» на «3 free + skip всем» с пользовательскими
плашками и неактивными тарифами (пока ЮКасса не подключена).

UX before/after:
- БЫЛО: 1 free → блок → admin-skip кнопка (только Богдану) → клик →
  НУЖНО ввести вопрос ЗАНОВО → ответ
- СТАЛО: 3 free + footer «осталось N/3» после каждого → 4-й блок → kb
  (неактивные тарифы + «🔓 Продолжить бесплатно» доступна всем) → клик →
  auto-resume pending → ответ без повторного ввода

Изменения:

1) Migration 4af483b51b7e: add free_questions_used INT NOT NULL DEFAULT 0,
   backfill True→3 / False→0, drop bool free_question_used.

2) UserRepository: increment_free_questions returns new count (int),
   reset_free_questions sets to 0.

3) bot/config: free_questions_limit = 3.

4) pricing_kb redesign — тарифы с «(скоро)» и callback pay:disabled:*
   (alert «оплата подключается»). allow_skip default True (для всех).

5) consultation.py — guards через remaining counter, _free_questions_used_msg
   динамический, _remaining_footer показывает «осталось N/3» как отдельное
   сообщение после ответа Анастасии. handle_pricing_skip без admin-gating.
   Новый handle_payment_disabled для pay:disabled:* callbacks.

6) Tests update (4 файла): fake_user.free_questions_used = 0,
   increment_free_questions mock с return_value=1, обновлены assertions
   для footer и pay:disabled:* callbacks.

Tests: 871 passed. mypy strict ✓, ruff ✓, coverage 97.85%.

Деплой требует alembic upgrade head на VM.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Закрывает 1.18.22 (Edoha KB expansion) из Wave 7 Phase 5 backlog.

До этой сессии при выборе школы 🌀 Мастер ЭдоХа RAG возвращал только
32 universal-нода. После импорта Digital Twin Мастера: classic 14 +
edoha 7742 + universal 32.

2-фазный pipeline через JSONL (EdoHa kuzu 0.11 single-file vs BaDzi
kuzu 0.10 directory storage — прямое чтение из BaDzi venv падает с IO
exception lock):

| Phase | Скрипт | Venv | Результат |
|---|---|---|---|
| 1 | scripts/edoha_export_json.py | EdoHa | /tmp/edoha_export/*.jsonl |
| 2 | scripts/import_edoha_kuzu.py | BaDzi | 7742 nodes + 7850 edges |
| 3 (опц) | scripts/export_edoha_to_md.py | любой | 524 .md в highlights/ |

Маппинг (id-префикс edoha:<type>:<original_pk>):

| Type | Count | L | Auth | Topic |
|---|---|---|---|---|
| Manifesto | 212 | 8 | 10 | edoha_manifesto |
| Quote | 112 | 8 | 10 | edoha_quote |
| Fact | 788 | 8 | 9 | edoha_fact |
| MentalModel | 808 | 7 | 9 | edoha_model |
| CausalBelief | 1595 | 7 | 8 | edoha_belief |
| Document | 537 | 7 | 9 | edoha_transcript |
| Relation | 539 | 6 | 8 | edoha_relation |
| StyleMarker | 3151 | 6 | 7 | edoha_style |

Все узлы: school='edoha', sha256(body), related_concepts через CJK
regex + BaZi-keywords. Edges: DERIVED_FROM (7850 — остальные REL
пустые в EdoHa) → REFERS_TO с kind='derived_from'.

Технические находки:
1. Schema несовместима напрямую (фрактальная Person/Digest + 7 L4
   entity tables vs плоская BaDzi.Node) — JSONL intermediate.
2. Kuzu 0.11/0.10 mismatch — 2-фазный pipeline без upgrade BaDzi
   (lock-in 0.10 из-за docker named volume, ADR-004).
3. Buffer pool OOM на 5000+ upserts (default 80% RAM). Фикс:
   buffer_pool_size=1GB + CHECKPOINT каждые 500 → 7742 за ~7 мин.
4. Kuzu 0.11 edges: RETURN a._label не работает. Фикс: RETURN a
   целиком → dict с _label ключом.

Deploy: tar (9.5 MB) + scp + docker compose cp + restart → prod
Cypher confirms classic=14, edoha=7742, universal=32 ✓.

Live-verify MCP «Прогноз 2026, школа edoha» — метафоры мастера
(«костёр в закрытой комнате», «куйте железо пока горячо, но не жгите
кочергой себя»), иероглифы расшифрованы (丁 Дин — Огонь Инь, 卯酉冲,
桃花, 文昌贵人), финал с признанием школы.

pyproject.toml: расширены per-file-ignores для scripts/ (S108/ANN401/
ANN201/ANN202/RUF002/RUF003 — one-shot tools с русскими комментариями).

Pytest 871/871, mypy strict ✓, ruff ✓. План: ~/.claude/plans/moonlit-doodling-zebra.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
base.md — новые правила понимания клиента (универсальные для всех 3 школ):
- M/W: каждый китайский термин расшифровывается inline
- Градиент: ≤1 нового концепта на абзац
- Mass/Significance: после абстракции — бытовой пример
- Цикл подтверждения: опциональная мини-проверка в финале
- Индикаторы «потерянного клиента»: откатиться и переформулировать
- Запрет на саентологический жаргон / латиницу

base_interpretation.py + тест — формат follow-up изменён с
«Дальше можно спросить:» на «Чтобы узнать больше, задайте вопрос
по этой карте:» (единый формат для всех 9 блоков + skill-ответов).

Pytest 871/871 ✓ ruff ✓ mypy ✓

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Современная школа усилена через педагогическую механику:

§1 Главный принцип — директива безопасности понимания (ясность важнее
объёма, термин не используется без расшифровки).

§2 Шаг 2 — добавлен принцип «архетип + распаковка термина», новый шаг
2.5 «Опциональная проверка понимания».

§3 Формат ответа — пункт 6 переделан в ARC-check мини-вопрос, добавлен
пункт 7 «Правило демо-набора» (визуальные мини-схемы через эмодзи).

§4 Метафоры — добавлен расширенный набор IT + работа с собой:
устаревший скрипт, сбой фокуса, осознанная отладка, заземление концепта,
поэтапная загрузка, таймлайн паттернов, автопилот стресса. Принцип
баланса «массы и значения».

§5 Алгоритм рисков — контроль крутизны градиента, 4-шаговая
стерилизация драматичных терминов.

§6 Переключение школ — проактивное предложение при разрыве контакта.

§7 Запреты — 6 новых red flags: anti-word-salad, no scientology-jargon,
no invalidation, no info-dumping, no overload, no risk без демо.

§8 Новый алгоритм расшифровки термина (4 шага) с mini-примером и
списком случаев когда термин обязателен vs не нужен.

Все принципы под капотом — Hubbard / Study Tech / Дианетика — снаружи
нейтральный словарь IT + поп-психологии. Никогда не упоминается ЛРХ.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…й корпус

Классическая школа усилена через современных носителей и трактаты:

§1 Главный принцип — иерархия времени (Такт=климат / Год=погода /
Месяц=событие) по Сюй Лэ-у.

§2 Алгоритм разбора (6 шагов) — полное расширение:
- Шаг 1: Укоренение (根) + Пустота (空亡) + Слияния стволов (干合)
- Шаг 2: 5 классических состояний Ци (旺/相/休/囚/死) + балльный
  калькулятор (месячная ЗВ 40-50%) + расширенная шкала категорий
  (專旺/身強/中和/身弱/從格) + принцип Цзинь Пэх «природа элемента
  первична»
- Шаг 3: Дерево решений по прорастанию (透出) + смешанные структуры
  (食神制殺格, 傷官佩印, 殺印相生格, 傷官生財格)
- Шаг 4: 4 классических метода Юн Шэнь в иерархии:
  調候 → 病藥 → 通關 → 扶抑 (вместо одного 扶抑 как было)
- Шаг 5: Матрица тактов + Фу Инь (伏吟) / Фань Инь (反吟)
- Шаг 6: 24 Цзе Ци для сезонной модуляции года/месяца

§3 Иерархия рисков — 9-ступенчатая (Поражение Юн Шэнь наверху,
Шэнь Ша внизу) вместо 8-ступенчатой.

§6 Корпус источников — добавлены 6 современных мастеров с цитатами-
маркерами: Raymond Lo, Joey Yap (ранние работы), Jin Peh, Janet Yung,
Сюй Лэ-у, Лян Сян-жунь. Плюс 10 готовых цитат из трактатов с
пиньинь + переводом для тем анализ/риски/карьера/здоровье.

§7 Запреты — 7 новых: не подменять структуры BaZi Profile, не путать
用神 и 喜神, не игнорировать 空亡, не давать микро-прогнозы, запрет
метода накопленных сил (это ЭдоХа), запрет Joey Yap для Шэнь Ша,
запрет ТКМ-диагнозов.

§8 Новый раздел — 4 готовых шаблона ответов (опасный месяц / карьера /
отношения / здоровье) с подстановкой данных карты и цитатой из трактата.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…Мастера

Глубокая переработка по 10 ответам Мастера ЭдоХи (86KB корпуса):

§1 Главные принципы — 5 сигнатур:
- «Вначале человек, потом теория»
- «Карта дисбаланса даёт больше успеха» (главная отстройка от классики)
- «Дубликат — это всегда усиление» / «Джокер на стол»
- «Слияние всегда сильнее»
- Триединство 33/33/33 (карта/окружение/воля)
Терминология поменяна с «3-vs-1» (выдумка автора) на «накопленные
ветви, замыкающие тройку» / «дубликат на дубликате».

§2 Алгоритмы — переработаны и углублены:
- Накопленные ветви (9 шагов): подсчёт скрытых стволов, ветвевая vs
  стволовая накопленность, сезонная сила, слои vs иерархия, флаг
  «Кабан 亥 в карте» как системный риск
- 7-уровневая шкала опасности (от зелёной до чёрной) вместо 4-уровней,
  смягчающие факторы без формулы
- Шахматный алгоритм углублён: контроль центра=столп года, развитие
  фигур=Полезные Божества до 30-35 лет, размен ферзей, гамбит, цейтнот
- Военный алгоритм: оборона/наступление, фронт/тыл/фланг, разведка
- Спортивный алгоритм: спарринг/турнир, марафон/спринт, выдох на ударе
- Скрытые ресурсы: 3 конкретных комбинации, 3 проверки от самообмана,
  календарный триггер 31 декабря, окно эзотерики 40-55, схема
  «Я → 食傷 → 財»
- Здоровье: «болезнь как защита», карта органов, Кабан как риск-флаг
  всю жизнь, замедление как медицинская стратегия

§3 Таблица «когда какой алгоритм» — расширена. Новый алгоритм
отношений «папа/мама в браке» (5 шагов) — сигнатура школы, который
шахматный не покрывает.

§4 Формат ответа — добавлены триединство 33/33/33, манифесты в финале,
минимальный набор обязательных элементов, максимальный лимит (5
терминов / 3 опасности / 3 варианта / 400 слов на блок).

§5 Метафоры — переписаны на 8 семей вместо 5:
1. Природа 2. 12 животных 3. Шахматы 4. Военные 5. Спорт
6. Аграрно-садоводческие 7. Карта местности 8. Быт и поговорки
Таблица выбора семьи по типу клиента. Сигнатурные образы: цветок
персика, нож в спину, золотая карета, беременность. Метафоры для
работы с собой: нейронная сеть, подготовка как тренировка,
погружение под воду.

§6 Запреты — расширены: запрет латиницы, запрет попсовой эзотерики,
запрет категоричных «расставайтесь», запрет лекционного монолога.

§7 Переключение школ — добавлены сценарии «когда сам отправил бы на
классику» и «когда отказался бы переключаться», синтез-режим.

§8 Новый — Возрастные фреймворки (5):
- 4 столпа = карта возрастных фаз (год 0-15, месяц 15-30, день 30-45,
  час 45+)
- Слои, а не иерархия
- Возрастной сдвиг 30-35 (от внешних целей к внутреннему поиску)
- Возраст Дракона 5-70 и пик архетипа 55-70
- Сброс кожи каждые 10 лет

§9 Новый — Стиль речи: диалоговый формат (2-3 предложения +
проверка), паузы, повторы и эхо, сигнатурные филлеры (значит,
короче, то есть), запрещённые фразы (ваш высший потенциал,
вселенная даёт знаки, я как нейросеть).

§10 Новый — Биография и сигнатуры: бизнес с 1990-х, апрель 2022
точка перелома, Волгоград как осознанный выбор, учитель Шибин с
1997, сигнатура «государство дважды хотело посадить».

§11 Новый — Критерии успешной консультации: 6 признаков «клиент
получил что нужно», 4 тревожных сигнала, особый критерий для
клиента в кризисе/болезни, главный самоконтроль Мастера.

§12 Новый — Дополнительные манифесты школы для финалов (8 фраз).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Промпты для Gemini Deep Research:
- gemini_prompt_modern_school.md (Hubbard Study Tech)
- gemini_prompt_classic_school.md (Raymond Lo + китайские мастера)
- gemini_prompt_edoha_master_questions.md (10 вопросов цифровой копии)

Ответы:
- gemini-modern-1779693741331.md (Hubbard отчёт, 28KB)
- gemini-code-1779693070880.md (Classic отчёт от Raymond Lo, 50KB)
- Ответы_Мастера_Эдохи_на_10_вопросов.md (86KB ответов цифровой копии)

Эти артефакты — источник правок для base_modern/classic/edoha.md
в трёх предыдущих коммитах.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
После live-теста и инцидента @S_Kate2011 закрыты 6 проблем:

1) Scheduler-loop fix (jobs.py). monthly forecast регенерировал LLM
   каждые 5 мин если delivery row застряла без sent_at. Теперь
   reusing existing.content. Аналогично для daily.

2) Forecast size + split. Daily 50-120 слов/блок (было 80-180),
   monthly 60-130 (было 100-220) + явный лимит «не больше 3800-7600
   chars». _send_or_record_error теперь использует split_for_telegram
   (вынесен в shared module bot/services/telegram_split.py) — длинный
   текст идёт несколькими сообщениями вместо «Bad Request: message
   is too long».

3) Pricing-skip Redis fallback. Вопрос сохраняется в FSM data И в
   отдельный Redis-ключ (TTL 1ч). handle_pricing_skip читает FSM,
   при пустом fallback'ит на Redis. Закрывает кейс когда FSM была
   затёрта между guard и нажатием «продолжить бесплатно».

4) Даты — [TODAY] секция + расширение keywords + spiritual event.
   Теперь в КАЖДОМ запросе к LLM есть [TODAY] с явной датой и правилом
   «не предлагать прошедшие даты». Calendar keywords +19 формулировок
   (когда мне можно, в ближайшее время, на этой неделе…). Новый
   EventType=spiritual с keywords (медитация, чайная церемония, ритуал)
   и rules (preferred water/wood, avoid fire).

5) Замена «натал» + образы животных. Все 9 мест с «натал/натале/
   натальный» заменены на «в карте Ба Цзы» / «декада карты рождения»
   / «врождённый». В base_edoha.md новый раздел §4.5 «Бытовые образы
   для столкновений животных»: шаблон сцены (Кролик/Мао = Кот),
   таблица 12 знаков, 6 сценариев 六冲 с конкретными образами и
   наводящими вопросами клиенту.

6) Подчёркивание follow-up. Формат финала теперь
   <b><u>Чтобы узнать больше, задайте вопрос по этой карте:</u></b>

Pytest 872/872 ✓ · ruff ✓ · mypy ✓

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ля work skill

1) АБСОЛЮТНЫЙ ЗАПРЕТ «натал/натале/натальный» (после live-теста LLM
   проскочила «в вашем натале» — корень в том что слово было в самих
   инструкциях и примерах, LLM повторяла за промптом):
   - ai/prompts/anastasia_system.md — заменено «натальной картой» →
     «картой рождения» (использовался активно через load_system_prompt
     в base_interpretation.py и legacy fallback в consultation.py)
   - ai/prompts/__init__.py — INSTRUCTION_PREFIX: «Резонансы натала» →
     «Резонансы карты рождения» + добавлен явный 🚫 АБСОЛЮТНЫЙ ЗАПРЕТ
     с примерами замен (инжектится в каждый запрос к LLM)
   - ai/prompts/base.md:56 — «Натальные звёзды» → «Врождённые звёзды»
     в примере [BAZI_DATA] (LLM повторяла этот пример в ответах)
   - ai/prompts/base.md:152 — мягкое правило усилено до критического
     с 8-строчной таблицей замен «было ❌ → стало ✅»
   - ai/prompts/algorithms/risk_assessment.md — «В натале 酉» →
     «В карте Ба Цзы 酉»
   - bot/routers/journal.py:623 — «Натальный Дневной Мастер» →
     «Дневной Мастер карты» (выдавалось напрямую клиенту в дневнике!)
   - База/teacher/L5_stars/baihu_white_tiger.md — 4 упоминания
     («Натальное расположение», «натального столпа», «Натальный 白虎»)
     заменены — попадало в RAG [KNOWLEDGE] блок
   - База/ba_zi_prompt_anastasia_v2.md — legacy, заменено

2) Бизнес-партнёрство в work skill (после запроса клиента):
   - ai/prompts/skill_router_system.md — правило needs_partner_chart
     расширено: теперь true и для work skill при упоминании
     соучредителя / сооснователя / компаньона / инвестора /
     совместного бизнеса. Добавлен few-shot пример 2.5.
   - ai/skills/work.md — frontmatter: required_inputs +
     partner_chart_optional, ui_actions + compare_partner_chart,
     trigger_keywords расширены. Новый § 7 «Бизнес-партнёрство»:
     сравнительная сетка через столпы месяца, Богатство, баланс
     官殺/比劫, Юн Шэнь обоих, тактовые столкновения. Метафоры:
     «винт и гайка», «два капитана», «огонь и кузнечный молот».

Pytest 872/872 ✓ · ruff ✓
Scheduler-loop monitor 15 мин — НИ ОДНОГО forecast.monthly.generated
(fix из предыдущего коммита подтверждён в проде).

⚠ После rsync на VM нужно re-ingest kuzu_db для baihu_white_tiger.md
(изменился RAG-контент учителя).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ested follow-up UI

1) Месячная weekly подписка — inline kick первой части в
   handle_buy_monthly_delivery через asyncio.create_task. Через 60 сек
   send_monthly_forecast_job стартует напрямую, не ждём scheduler.
   Раньше при scheduler restart > 1 час после подписки — week=1
   silently пропускался.

2) Текстовая анимация ожидания (_show_thinking_animation).
   Отдельное сообщение с тикающими 10 фразами + точки каждые 2 сек:
   «🔮 Провожу анализ», «🐱 Обсуждаю с котом», «✨ Считаю звёзды»,
   «🐇 Один мао, два мао», «🕯 Свечу разжигаю», «🐉 Слушаю дракона»,
   «📜 Заглядываю в столпы», «🌿 Перебираю стихии», «🐅 Тигр
   потягивается», «☯️ Сверяю иньское с янским». По завершении LLM
   статус-сообщение удаляется.

3) Suggested follow-up UI: tap-to-copy <code> + 2 кнопки.
   - Промпты теперь требуют <code>вопрос plain</code> вместо «...».
   - HistoryStore.stash/pop_suggested_followup (Redis, TTL 1ч).
   - _extract_followup_question с regex для <code> + fallback.
   - _after_answer_kb(has_suggestion) → 3 кнопки: «⬆️ Задать
     предложенный вопрос» / «🔄 Задать ещё вопрос» / «В меню».
   - Handler suggest:ask: pop из Redis → echo вопроса курсивом →
     _process_question_after_guards.

Pytest 872/872 ✓ · ruff ✓ · mypy ✓

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ой недели

1) Анимация ожидания — поправлен порядок слов и добавлена новая фраза
   (bot/routers/consultation.py::_THINKING_PHRASES):
   - «🕯 Свечу разжигаю» → «🕯 Зажигаю свечу»
   - Новая: «🌌 Смотрю на небо»
   Итого 11 фраз с случайной перестановкой на каждый запрос.

2) Unit-покрытие inline-kick первой части weekly-подписки
   (tests/unit/test_bot/test_forecast_handlers.py + 3 теста):

   - test_monthly_confirm_weekly_kicks_first_delivery_inline:
     проверяет что после fc:mc:weekly handler вызывает
     asyncio.create_task с inline-kick coroutine, и эта coroutine
     через asyncio.sleep(60) (замокан на 0) вызывает
     send_monthly_forecast_job(week=1, subscription_id=sub.id,
     period_start=sub.started_at.date()).

   - test_monthly_confirm_bulk_kicks_first_delivery_inline:
     то же для bulk-delivery — week=None (полный месячный одним
     сообщением).

   - test_monthly_confirm_inline_kick_failure_does_not_break_handler:
     защита от регрессии — если send_monthly_forecast_job падает
     с RuntimeError (LLM/KuzuDB down), handler уже вернул успешный
     ответ клиенту, исключение из background task залогировано но
     НЕ propagate'нуто наружу.

Регрессия которую тесты блокируют: scheduler-restart > 1 час после
создания weekly-подписки → _jobs_for_subscription пропускал week=1
через guard «fire_at < now - 1h» → клиент не получал первую часть.
Inline kick перекрывает гонку.

Pytest 875/875 ✓ · ruff ✓

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bot/routers/consultation.py::_after_answer_kb — текст кнопки переименован
по запросу пользователя. «Ещё вопрос» звучит как «снова тот же», «другой»
точнее передаёт смысл свободного ввода нового вопроса.

Pytest 9/9 test_consultation.py ✓

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…hips UX + history-leak fix

A. Forecast school selection (Wave 7 Phase 2 ext, по запросу клиента
   2026-05-26). Дневной и месячный прогнозы теперь идут в стиле
   выбранной школы (classic / edoha / modern).

   - migrations/20260526_a1b2c3d4e5f6 — ALTER TABLE ADD COLUMN
     chosen_school VARCHAR(16) NOT NULL DEFAULT 'classic'.
   - db/models.py + db/repositories/forecast_repo.py — поле +
     kwarg chosen_school в create().
   - bot/keyboards: school_selector_kb(callback_prefix) теперь
     параметризован — "school" / "fc:ms" / "fc:ds".
   - bot/routers/forecast.py — confirm раздвоены на 2 шага:
     fc:mc:<delivery> / fc:dc:<hour> сохраняют выбор в FSM и
     показывают school_selector. Новые handlers fc:ms:* / fc:ds:*
     создают подписку с chosen_school + inline kick для weekly.
   - ai/forecast.py — generate_*_forecast(school) пробрасывают в
     _build_system_prompt(school) → load_base_prompt(school).
   - bot/scheduler/jobs.py — _school_from_sub helper, чтение
     sub.chosen_school при send_*_forecast_job.

B. Relationships UX — ранний skip clarifying loop
   (bot/routers/consultation.py). Новый Branch 0 в
   _process_question_after_guards: если skill=relationships И
   needs_partner_chart И нет partner_chart_id — СРАЗУ показать
   partner_chart_kb, не задавать 2-3 clarifying-вопроса про дату
   партнёра текстом. Для work (бизнес-партнёрство) clarifying
   сохраняется.

C. History-leak fix (ai/context.py + consultation.py). Имя «Сергей»
   из теста бизнес-партнёрства утекло в новый диалог про парня
   клиента. Smart-reset: HistoryStore.get_last_skill /
   set_last_skill / clear_last_skill через Redis key. После
   select_skill сравниваем skill, если отличается — history_store
   .clear + log consultation.history_cleared_on_skill_change. После
   успешного ответа set_last_skill(skill_spec.name).

D. MASTER.md — секция «Известные методологические особенности» с
   разбором hour pillar TST vs mingli. Наш расчёт через
   true_solar_time.py + longitude correction + EoT классически
   корректен, mingli упрощает без TST. Разница методологий, не баг.

Tests: 876/876 ✓ (8 forecast тестов перепрошиты под 2-шаговый flow,
обновлены ассерты chosen_school + handlers fc:ms / fc:ds). Ruff ✓,
mypy ✓ (70 source files), alembic dry-run OK.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wave 7: применены правки мастера ЭдоХа (ревью 2026-05-31) к 5 алгоритмам
мышления (opportunity/relationships/career/decision/health; term_unpack
пропущен). Нейтральное аналитическое ядро вынесено в ai/skills/*.md —
видят все три школы через [SKILL]-инъекцию; голос/доктрина ЭдоХи — в
base_edoha.md.

- новый skill decision + SkillName + router catalog
- skills обогащены нейтральной методологией (time/relationships/work/health)
- base_edoha.md: дерево-с-корнями вместо шахмат, развилка по силе, Эльмира,
  столб брака, карьера-прежде-жены, дети-Огонь, земля=хранилище
- драфты algorithms/*.md обновлены как canonical reference
- тесты: 7 skills, decision школо-нейтрален

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per-chart дефолтная школа интерпретации (classic/edoha/modern). NULL =
спрашивать каждый раз (прежнее поведение). Закрывает backlog 1.18.14.

- db: Chart.default_school NULLABLE String(16) + migration b2c3d4e5f6a7
- ChartRepository.set_default_school (set/clear)
- keyboards: кнопка «Школа по умолчанию» в меню карты + default_school_kb
- start.py: handlers chart:defschool / defschool:set / defschool:clear
- consultation handle_ask_pressed: при заданном default_school пропускает
  селектор школы и идёт сразу к вопросу
- тесты: repo set/clear/target + skip-селектора в консультации

Forecast pre-fill дефолтной школой — в следующем коммите (ЮKassa).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Снимает mock free_dev_bypass: реальная оплата прогнозов (500/900 ₽) и
безлимита вопросов (290/990/2490 ₽) через ЮKassa как Telegram payment
provider. Без webhook/FastAPI — pre_checkout_query + successful_payment.

- config: telegram_payment_provider_token + .env.example
- db: payment_id в Subscription и ChartForecastSubscription + migration
  c3d4e5f6a7b8; repo.create/update_plan принимают payment_id
- bot/services/payments.py: payments_live gate, send_invoice, payload-билдеры
- bot/routers/payments.py: pre_checkout + successful_payment → активация
- forecast.py: finalize_monthly/daily helpers; _proceed_* ветвят invoice vs
  bypass; forecast default-school pre-fill
- consultation/start: pricing_kb(payments_active=) + handle_pay_buy invoice
- payments_router в dispatcher
- тесты: 11 payments + адаптированы 5 forecast

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… закрыт)

- doc/proposals/bge_m3_embeddings.md — готовое предложение семантического
  RAG через bge-m3, работает на KuzuDB 0.10 без Apache AGE
- tasks.md: Apache AGE migration ОТМЕНЕНА (решение Богдана — остаёмся на
  0.10); bge-m3 развязан от AGE; 1.18.14 default_school закрыт; добавлен
  Phase 9 (алгоритмы мастера / default_school / ЮKassa)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…жных дат, расшифровки звёзд

A. Роутер переспрашивал дату/год/такт хотя они в карте → запрет в
   skill_router_system.md §3 + плохой пример + few-shot.
B. Дубли уведомлений → per-date дедуп + commit после каждой карты.
B2. Рефлексию приглашаем В ДЕНЬ важной даты (отдельное сообщение,
    days_ahead==0); предупреждение за 1-2 дня — без кнопки рефлексии.
C. Расшифровки 11 звёзд (что значит + на что обратить внимание).

- db: ChartJournalSettings.last_important_warning_date /
  last_reflection_prompt_date + migration d4e5f6a7b8c9
- journal_repo: атомарные mark_warning_sent / mark_reflection_prompt_sent
- important_dates: _STAR_MEANING + format_important_date_reflection
- scan_important_dates_job переразбит на 2 блока
- тесты: scan-job (5) + reflection-format (2) + router-prompt (1); 902 passed

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t_skill

Симптом: после отправки вопроса по карте 8-15 сек тишины. Причина:
анимация «🔮 Провожу анализ…» стартовала только в
_continue_consultation_with_skill — ПОСЛЕ select_skill (Qwen3.6, ~5-8с)
и extract_concepts_llm. Весь этот участок шёл молча.

Фикс: _show_thinking_animation запускается в _process_question_after_guards
ДО select_skill и протягивается (thinking handle) в
_continue_consultation_with_skill (не перезапускается). В ветках
clarifying / partner анимация останавливается перед показом своего
сообщения. _continue также поднимает анимацию в начало (до route/
calendar/extract_concepts_llm), чтобы и остальные вызывающие показывали
индикатор сразу.

- тест порядка: show → select_skill → continue (thinking прокинут)
- autouse-stub анимации в test_consultation_skill_router (routing-тесты)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…форы по школам

Спланированы 2 доработки (ожидают реализации):
1. Рефлексия о важной дате в 18:00 местного времени карты (почасовой
   scan_reflection_prompts_job + reflection_hour_utc), предупреждение
   остаётся в скане 09:00 UTC.
2. Возрастные метафоры во всех 3 школах — у каждой школы свой стиль и
   под каждый возрастной бэнд свои; промпт подбора в base.md, базы
   примеров в base_<school>.md; контекст — любые пересечения звёзд.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…оры по школам

Задача 1 — рефлексия о важной дате в 18:00 местного времени карты:
- ChartJournalSettings.reflection_hour_utc (миграция e5f6a7b8c9d0 с
  backfill из charts.tz_offset)
- scan_reflection_prompts_job: почасовой глобальный скан, SQL-фильтр
  reflection_hour_utc == текущий UTC-час, локальная дата через tz_offset,
  per-day dedup
- scan_important_dates_job шлёт только warning (за 1-2 дня)
- toggle_important_dates(tz_offset=...) пересчитывает час рефлексии

Задача 2 — возрастные метафоры (фидбэк ученицы мастера Ирины):
- calculator/age_utils.py: client_age_years + 5 бэндов
- compose_messages(client_age=...) → [AUDIENCE] секция после [BAZI_DATA]
- base.md §6.4: правило 2-3 метафор-цитат при расшифровке звёзд и
  любых пересечений
- базы примеров бэнд×школа в base_classic/edoha/modern.md

Тесты: 924 passed, coverage 97.82%, mypy strict clean

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- MASTER.md: шапка актуализирована (Live, Wave 7 Phase 9, 924 тестов),
  секция сессии 2026-06-10
- tasks.md: 1.18.8 закрыт (ревью мастера применено f43d3dd, term_unpack
  без ревью — UX-правило уже live); L-1 отменён решением Богдана;
  W4e актуализирован (остался только W4e-4 auto-запись дневника);
  W5e — MVP live, full после bge-m3; 1.9.16 одобрен
- doc/proposals/bge_m3_embeddings_plan.md: план реализации с решением
  по хостингу (ONNX int8, замер RAM VM 2.3GB available)
- task.md: статус реализовано

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ки 1.15

- bge_m3_embeddings_plan.md: live-probe YC textEmbedding API —
  text-search-doc/query (256-dim) работают с ключом проекта; Вариант 1 —
  индексация и query через YC API (без макбука, без модели в Docker,
  ноль RAM на VM); Вариант 2 (bge-m3 ONNX int8) — fallback по
  quality-gate; движко-агностичный интерфейс embed_doc/embed_query
- admin_analytics_plan.md (new): /admin для Богдана — сводка,
  динамика день/неделя/месяц (PNG-спарклайн через CairoSVG), воронка
  /start→карта→вопрос→лимит→оплата, отток по последнему событию и
  skill последнего вопроса; миграция consultations.chosen_school;
  Phase 3 (опц.) bot_events
- task.md: очередь следующих задач A (embeddings) + B (админка),
  рекомендуемый порядок B-Phase1 → A → B-Phase2
- tasks.md: 1.9.16 решение по движку обновлено, 1.15 ссылка на план

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AgentSmoki AgentSmoki closed this Jul 7, 2026
@AgentSmoki
AgentSmoki deleted the feat/wave7-master-algorithms-yookassa-default-school branch July 7, 2026 19:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant