# 5. Критичный путь ## Decide (`apps/decision/services.py`) 1. `flag` из cache/DB; при `FeatureFlag.default_value` update удаляется `flag:{key}`. 2. `active_experiment` из cache/DB. 3. Формируется cache key результата (`flag`, `subject`, digest атрибутов, ревизии `flag/experiment`). 4. При cache hit возвращается тот же outcome/reason/value с новым `decision_id`. 5. При cache miss выполняются проверки: running experiment, targeting, participation limits, domain conflicts, traffic allocation. 6. Если назначен вариант, выбирается детерминированно по hash + weights. 7. Результат может быть закэширован (по whitelist reason). 8. Запись `Decision` зависит от `DECISION_WRITE_MODE`: `sync|async|disabled`; для `experiment_assigned` запись всегда sync. Reason-коды: `flag_not_found`, `no_active_experiment`, `targeting_mismatch`, `participation_limit`, `domain_conflict`, `outside_traffic_allocation`, `no_variants`, `experiment_assigned`. ## Events (`apps/events/services.py`) 1. Валидация типа события и payload. 2. Дедуп по `event_id`. 3. Exposure: `Exposure` + `Event`, затем промоция `PendingEvent`. 4. Conversion: при `requires_exposure=True` без exposure -> `PendingEvent` (TTL 7 дней), иначе attributed `Event`. ## Reports (`apps/reports/services.py`) - По каждому варианту: `exposures`, `unique_subjects`, значения выбранных метрик. - Поддерживается период `start/end`. - `average` считает DB `Avg`, `percentile` - DB aggregate (`PERCENTILE_CONT` в PostgreSQL). - Связь событий с экспозициями строится через `Subquery`, без materialization списка `decision_ids` в Python. ## Guardrails (`apps/guardrails/services.py`) - Celery проверяет running эксперименты. - При breach: `GuardrailTrigger` + действие (`pause`/`rollback`) + `ExperimentLog` + enqueue уведомления.