Metadata-Version: 2.4
Name: post-analysis-toolkit
Version: 1.0.4
Summary: Reusable helpers for post-analysis, quasi-causal designs, and diagnostics.
Author: OpenAI Codex
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: numpy>=2.4
Requires-Dist: pandas<3,>=2.3
Requires-Dist: scipy>=1.17
Requires-Dist: statsmodels>=0.14
Requires-Dist: scikit-learn>=1.9
Requires-Dist: matplotlib>=3.11
Requires-Dist: seaborn>=0.13
Requires-Dist: causalimpact>=0.2.6
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: pytest>=9.1; extra == "dev"
Requires-Dist: twine>=6.1; extra == "dev"

# Post Analysis Toolkit

`post_analysis_toolkit` - локальная библиотека для пост-анализа промо, rollout-ов и других квази-экспериментальных задач, где нужен воспроизводимый causal / quasi-causal пайплайн.

Пакет закрывает типовой цикл:

1. подготовили панель или временной ряд;
2. проверили предпосылки метода;
3. посчитали эффект;
4. проверили устойчивость результата;
5. получили табличные результаты и готовые графики.

Документация в этом файле ориентирована на пользователя пакета.

## Что умеет пакет

- классический `Difference-in-Differences`
- регрессионный `DiD`
- `DiD` с `HC1` robust SE или cluster-robust SE
- `DiD` с `TWFE`
- `DiD` с лог-трансформацией outcome
- `DiD` с относительной индексацией outcome
- `Synthetic Control`
- `Causal Impact`
- `Interrupted Time Series`
- `Triple Difference`
- `Regression Discontinuity Design`
- `Propensity Score Matching`
- `Doubly Robust / AIPW`
- `Event Study` с эффектами по временным бинам
- проверки предпосылок по каждому методу
- базовые проверки устойчивости
- встроенные `matplotlib`-графики

## MDE и чувствительность

Для всех MDE по умолчанию используются `alpha=0.05`, `power=0.80` и
двусторонний тест. Если метрика и дизайн допускают аналитическую оценку,
базовый ориентир — t-test/формула через корректную стандартную ошибку.

MDE относится к конкретному estimand и спецификации. В рабочем результате
нужно отдельно показывать абсолютный MDE в единицах метрики, относительный MDE
относительно явно указанного baseline и cumulative MDE за весь post-period.

Режим `mde_mode="auto"` используется по умолчанию: если у метода есть корректная
стандартная ошибка, выбирается аналитический расчет, иначе — параметрическая
симуляция. `mde_mode="parametric"` принудительно включает быструю параметрическую
симуляцию.
`mde_mode="resampling"` включает полноценные симуляции с генерацией данных и
повторным запуском оценивателя. Быстрый proxy доступен только явно как
`mde_mode="proxy"`. Для симуляций число повторов будет задаваться параметром
`n_simulations`: `500` для отладки, `2000` для рабочего
расчета и `5000+` для финального отчета. Вместе с ним нужно сохранять
`random_state` и power curve.

Параметрическая симуляция: для каждого кандидатного эффекта пакет
генерирует оценки по схеме `delta + Normal(0, uncertainty_scale)`. Для
регрессионных и observational-методов `uncertainty_scale` — SE. Для Event Study
это SE конкретного коэффициента конкретного временного бина, поэтому MDE
считается отдельно для каждого бина. Для
Synthetic Control/Causal Impact/ITS — pre-fit или validation RMSE,
нормированный на размер post-period.

В режиме `resampling` пакет создает псевдоданные: берет fitted/counterfactual
значения без эффекта, добавляет ресэмплированные центрированные остатки и
кандидатный эффект, а затем повторно запускает весь поддержанный оцениватель.
Для Synthetic Control заново подбираются веса, для PSM заново строится matching,
для Causal Impact заново обучается state-space модель, а для ITS заново
оценивается регрессия. Такой режим заметно медленнее и требует явно задать
разумное `n_simulations` и, при необходимости, `mde_effect_grid`.

Power curve можно посмотреть в
`result.details["mde_power_curve"]`.

После вызова любой `fit_*` MDE читается так:

```python
result.summary[["mde_absolute", "mde_relative", "mde_cumulative", "mde_type", "mde_simulation_kind"]]
result.details["mde_power_curve"]
```

Пример полноценного resampling-MDE для поддержанного метода:

```python
result = fit_synthetic_control(
    panel_df,
    outcome_col="outcome",
    unit_col="unit",
    time_col="sale_date",
    treated_unit="treated_city",
    intervention_time="2026-02-01",
    mde_mode="resampling",
    n_simulations=200,
    mde_effect_grid=[0, 2, 4, 6],
    random_state=42,
)
result.summary[["mde_mode_resolved", "mde_simulation_kind", "mde_absolute"]]
```

В этой версии для каждого кандидата эффекта создаются псевдоданные и заново
вызывается Synthetic Control. Поэтому `resampling` значительно медленнее
параметрического режима. Для нестандартного оценивателя можно передать
`mde_simulator`, который возвращает массив повторных оценок эффекта.

Параметры `mde_mode`, `alpha`, `power`, `alternative`, `n_simulations`,
`random_state`, `mde_effect_grid` и `mde_simulator` добавлены в функции оценки эффекта. Вызов с
`mde_mode="proxy"` дополнительно заполняет `proxy_mde_*`; при analytic-,
parametric- или resampling-режиме эти legacy-поля остаются пустыми.

### 8. PSM

Используй, когда:

- treatment бинарный
- есть наблюдаемые ковариаты, через которые можно объяснять селекцию в treatment
- хочется построить matched control на основе propensity score

В текущей версии пакет реализует `ATT` через nearest-neighbor matching по propensity score.

### 9. Doubly Robust

Используй, когда:

- treatment бинарный
- есть набор ковариат
- хочется оценку, устойчивую к ошибке в одной из двух моделей:
  propensity model или outcome model

В текущей версии это `AIPW / doubly robust ATE`.

## Что пакет не делает

- не строит данные из сырых таблиц
- не проверяет бизнес-смысл метрик за тебя
- не знает, был ли contamination доноров
- не умеет автоматически находить “правильный” метод
- не снимает необходимость ручной интерпретации

То есть пакет отвечает за оценивание и diagnostics, а не за бизнес-контекст и data engineering.

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

### Установка

В ноутбуке или Python-скрипте:

```python
%pip install post-analysis-toolkit
```

Чтобы библиотека была доступна во всех ноутбуках персонального Databricks-кластера,
один раз добавьте PyPI-библиотеку
`post-analysis-toolkit` в разделе **Libraries** его настроек.

### Базовый импорт

```python
from post_analysis_toolkit import (
    check_did_assumptions,
    check_doubly_robust_assumptions,
    fit_did_regression,
    fit_event_study,
    fit_doubly_robust,
    fit_synthetic_control,
    fit_causal_impact,
    fit_its,
    fit_psm,
    fit_rdd,
    fit_triple_difference,
)
```

## Как начать, если видишь пакет впервые

Практически всегда работа с пакетом выглядит так:

1. Подготовь данные в одном из поддерживаемых форматов:
   панель `unit x time`, один временной ряд или observational dataset.
2. Выбери метод под свой дизайн.
3. Сначала запусти `check_*_assumptions(...)`.
4. Потом запусти `fit_* (...)`.
5. Если метод выглядит применимым, запусти `run_*_robustness(...)`.
6. Посмотри `summary`, `details` и `figures`.

Минимальный шаблон:

```python
diag = check_did_assumptions(...)
display(diag.checks)

result = fit_did_regression(...)
display(result.summary)

robust = run_did_robustness(...)
display(robust.summary)
```

Особенно важно:

- подробное описание параметров теперь находится прямо внутри описания каждой функции, а не в отдельном общем словаре;
- если видишь аргумент вида `*_options`, это почти всегда значит “перебор нескольких спецификаций”, а не “одна настройка модели”.

## Как читать result-объекты

Почти все функции возвращают один из двух dataclass-объектов:

- `DiagnosticResult`:
  результат проверки предпосылок
- `MethodResult`:
  результат расчета эффекта или robustness-check

Обычно внутри есть:

- `summary`:
  компактная итоговая таблица
- `details`:
  более подробные таблицы, например коэффициенты, веса, placebo-результаты
- `figures`:
  готовые `matplotlib`-графики
- `metadata`:
  технические объекты, конфиги, fit-результаты библиотек

## Базовые структуры данных

### Panel data

Для `DiD`, `Synthetic Control`, `Causal Impact` ожидается длинная панель, обычно такого вида:

```python
import pandas as pd

panel_df = pd.DataFrame(
    {
        "sale_date": [...],
        "unit": [...],          # город / пиццерия / страна / магазин
        "metric": [...],        # outcome
        "treated_flag": [...],  # 1 для treated, 0 для control
        "post_flag": [...],     # 1 после старта промо, 0 до
    }
)
```

Минимальные требования:

- `time_col` должен парситься в дату
- `outcome_col` должен быть числовым
- `treated_col` и `post_col` должны кодироваться как `0/1`
- для `Synthetic Control` и `Causal Impact` должен существовать `unit_col`

### Time series data

Для `ITS` нужен один временной ряд:

```python
ts_df = pd.DataFrame(
    {
        "sale_date": [...],
        "metric": [...],
    }
)
```

Дополнительно можно передавать внешние фичи:

```python
ts_df["weather_temp"] = ...
ts_df["holiday_flag"] = ...
```

### Observational data

Для `PSM` и `Doubly Robust` нужен наблюдательный датасет с бинарным treatment:

```python
obs_df = pd.DataFrame(
    {
        "user_id": [...],
        "treatment_flag": [...],   # 0/1
        "outcome": [...],
        "x1": [...],
        "x2": [...],
        "x3": [...],
    }
)
```

Минимальные требования:

- `treatment_col` бинарный `0/1`
- `outcome_col` числовой
- covariates должны быть числовыми или заранее закодированными

### RDD data

Для `RDD` нужен срез наблюдений с running variable:

```python
rdd_df = pd.DataFrame(
    {
        "running": [...],   # расстояние до порога или исходная шкала
        "outcome": [...],
        "x1": [...],        # optional covariates
    }
)
```

Минимальные требования:

- `running_col` числовой
- известен `cutoff`
- по обе стороны cutoff есть наблюдения

### Triple Difference data

Для `Triple Difference` нужна панель с третьим бинарным измерением:

```python
ddd_df = pd.DataFrame(
    {
        "sale_date": [...],
        "unit": [...],
        "treated_flag": [...],
        "post_flag": [...],
        "subgroup_flag": [...],  # 1 = affected subgroup, 0 = internal control subgroup
        "outcome": [...],
    }
)
```

## Полностью готовые минимальные примеры

Ниже четыре примера, которые можно буквально скопировать в ноутбук и запустить как первый smoke-test пакета.

### 1. End-to-end пример: DiD

```python
import pandas as pd

from post_analysis_toolkit import (
    check_did_assumptions,
    fit_did_regression,
    run_did_robustness,
)

did_df = pd.DataFrame(
    {
        "sale_date": pd.date_range("2026-02-01", periods=12, freq="D").tolist() * 2,
        "unit": ["Batumi"] * 12 + ["Tbilisi"] * 12,
        "metric": [
            10, 11, 10, 12, 11, 10, 15, 16, 15, 17, 16, 15,
            9, 10, 9, 11, 10, 9, 10, 11, 10, 12, 11, 10,
        ],
        "treated_flag": [1] * 12 + [0] * 12,
    }
)
did_df["post_flag"] = (did_df["sale_date"] >= pd.Timestamp("2026-02-07")).astype(int)

did_diag = check_did_assumptions(
    did_df,
    outcome_col="metric",
    time_col="sale_date",
    treated_col="treated_flag",
    post_col="post_flag",
    intervention_time="2026-02-07",
    unit_col="unit",
    cluster_col="unit",
)
print(did_diag.checks)

did_result = fit_did_regression(
    did_df,
    outcome_col="metric",
    time_col="sale_date",
    treated_col="treated_flag",
    post_col="post_flag",
    unit_col="unit",
    cluster_col="unit",
    twfe=True,
)
print(did_result.summary)

did_robust = run_did_robustness(
    did_df,
    outcome_col="metric",
    time_col="sale_date",
    treated_col="treated_flag",
    post_col="post_flag",
    intervention_time="2026-02-07",
    unit_col="unit",
    cluster_col="unit",
)
print(did_robust.summary)
```

### 2. End-to-end пример: Synthetic Control

```python
import pandas as pd

from post_analysis_toolkit import (
    check_synthetic_control_assumptions,
    fit_synthetic_control,
    run_synthetic_control_robustness,
)

dates = pd.date_range("2026-02-01", periods=14, freq="D")

sc_df = pd.DataFrame(
    {
        "sale_date": list(dates) * 3,
        "unit": ["Batumi"] * 14 + ["Tbilisi"] * 14 + ["Kutaisi"] * 14,
        "metric": (
            [100, 101, 99, 100, 102, 101, 100, 110, 111, 109, 112, 111, 110, 109]
            + [98, 99, 97, 98, 100, 99, 98, 99, 100, 98, 101, 100, 99, 98]
            + [102, 103, 101, 102, 104, 103, 102, 103, 104, 102, 105, 104, 103, 102]
        ),
    }
)

sc_diag = check_synthetic_control_assumptions(
    sc_df,
    outcome_col="metric",
    unit_col="unit",
    time_col="sale_date",
    treated_unit="Batumi",
    intervention_time="2026-02-08",
)
print(sc_diag.checks)

sc_result = fit_synthetic_control(
    sc_df,
    outcome_col="metric",
    unit_col="unit",
    time_col="sale_date",
    treated_unit="Batumi",
    intervention_time="2026-02-08",
)
print(sc_result.summary)

sc_robust = run_synthetic_control_robustness(
    sc_df,
    outcome_col="metric",
    unit_col="unit",
    time_col="sale_date",
    treated_unit="Batumi",
    intervention_time="2026-02-08",
)
print(sc_robust.summary)
```

### 3. End-to-end пример: ITS

```python
import pandas as pd

from post_analysis_toolkit import (
    check_its_assumptions,
    fit_its,
    run_its_robustness,
)

its_df = pd.DataFrame(
    {
        "sale_date": pd.date_range("2026-01-01", periods=20, freq="D"),
        "metric": [
            100, 102, 101, 103, 102, 101, 100, 102, 101, 103,
            108, 110, 109, 111, 110, 109, 108, 110, 109, 111,
        ],
    }
)

its_diag = check_its_assumptions(
    its_df,
    outcome_col="metric",
    time_col="sale_date",
    intervention_time="2026-01-11",
)
print(its_diag.checks)

its_result = fit_its(
    its_df,
    outcome_col="metric",
    time_col="sale_date",
    intervention_time="2026-01-11",
)
print(its_result.summary)

its_robust = run_its_robustness(
    its_df,
    outcome_col="metric",
    time_col="sale_date",
    intervention_time="2026-01-11",
)
print(its_robust.summary)
```

### 4. End-to-end пример: PSM

```python
import pandas as pd

from post_analysis_toolkit import (
    check_psm_assumptions,
    fit_psm,
    run_psm_robustness,
)

psm_df = pd.DataFrame(
    {
        "user_id": list(range(1, 13)),
        "treatment_flag": [1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
        "outcome": [12, 11, 13, 12, 14, 13, 9, 8, 10, 9, 11, 10],
        "x1": [30, 32, 31, 29, 33, 34, 30, 31, 29, 28, 32, 33],
        "x2": [5, 6, 5, 4, 6, 7, 5, 5, 4, 4, 6, 6],
        "x3": [100, 110, 105, 98, 115, 120, 99, 108, 101, 97, 112, 118],
    }
)

psm_diag = check_psm_assumptions(
    psm_df,
    outcome_col="outcome",
    treatment_col="treatment_flag",
    covariate_cols=["x1", "x2", "x3"],
    caliper=0.2,
)
print(psm_diag.checks)

psm_result = fit_psm(
    psm_df,
    outcome_col="outcome",
    treatment_col="treatment_flag",
    covariate_cols=["x1", "x2", "x3"],
    caliper=0.2,
)
print(psm_result.summary)

psm_robust = run_psm_robustness(
    psm_df,
    outcome_col="outcome",
    treatment_col="treatment_flag",
    covariate_cols=["x1", "x2", "x3"],
    caliper_options=(None, 0.1, 0.2),
    neighbor_options=(1, 2),
)
print(psm_robust.summary)
```

## Что возвращают функции

У пакета два основных result-объекта:

- `MethodResult`
- `DiagnosticResult`

Оба описаны в [contracts.py](contracts.py)

### `MethodResult`

Поля:

- `method` - имя метода
- `summary` - главный итоговый `DataFrame`
- `details` - словарь с дополнительными таблицами
- `figures` - словарь `matplotlib`-фигур
- `metadata` - служебные объекты, fit-модели, выбранные доноры и т.д.

### `DiagnosticResult`

Поля:

- `method` - имя диагностики
- `checks` - таблица проверок предпосылок
- `details` - вспомогательные таблицы
- `figures` - диагностические графики
- `metadata` - служебная информация

### Как обычно с этим работать

```python
diag = check_did_assumptions(...)
display(diag.checks)

result = fit_did_regression(...)
display(result.summary)
display(result.details["coefficients"])

fig = result.figures["actual_vs_counterfactual"]
display(fig)
```

## Рекомендуемый workflow

Для любого метода лучше идти одинаково:

1. Проверить, что метрика и grain корректны.
2. Проверить предпосылки через `check_*_assumptions`.
3. Посчитать основной эффект через `fit_*`.
4. Прогнать `run_*_robustness`.
5. Уже после этого писать вывод.

Минимальный шаблон:

```python
diag = check_did_assumptions(...)
display(diag.checks)

result = fit_did_regression(...)
display(result.summary)

robust = run_did_robustness(...)
display(robust.summary)
```

## DiD: пример

### Формат данных для функции

Пример для `unit x day`:

| sale_date | unit | metric | treated_flag | post_flag |
|---|---|---:|---:|---:|
| 2026-02-10 | Batumi | 0.084 | 1 | 0 |
| 2026-02-10 | Tbilisi | 0.121 | 0 | 0 |
| 2026-02-11 | Batumi | 0.079 | 1 | 0 |
| 2026-02-25 | Batumi | 0.116 | 1 | 1 |
| 2026-02-25 | Tbilisi | 0.118 | 0 | 1 |

Обязательные колонки:

- `sale_date`
- `unit`
- `metric`
- `treated_flag`
- `post_flag`

Опционально:

- ковариаты
- веса
- колонка для кластеризации ошибок

### 1. Проверка предпосылок

```python
from post_analysis_toolkit import check_did_assumptions

did_diag = check_did_assumptions(
    df=panel_df,
    outcome_col="metric",
    time_col="sale_date",
    treated_col="treated_flag",
    post_col="post_flag",
    intervention_time="2026-02-25",
    unit_col="unit",
    cluster_col="unit",
    event_bin_size=7,
)
```

Что смотреть:

- строку `Корреляция treated/control на предпериоде`
- строку `Разница наклонов тренда на предпериоде`
- строку `p-value разницы наклонов на предпериоде`
- строку `p-value joint-теста лидов в event study`
- графики `pretrend_triptych` и `event_study`

### 2. Оценка эффекта

```python
from post_analysis_toolkit import fit_did_regression

did_result = fit_did_regression(
    df=panel_df,
    outcome_col="metric",
    time_col="sale_date",
    treated_col="treated_flag",
    post_col="post_flag",
    unit_col="unit",
    cluster_col="unit",
    twfe=True,
    log_transform=False,
    relative=False,
)
```

Главные поля в `summary`:

- `estimate`
- `p_value`
- `ci_low`, `ci_high`
- `relative_effect_vs_treated_pre`
- `mde_absolute`, `mde_relative`, `mde_cumulative`, `mde_type`
- `details["mde_power_curve"]`

Для DiD режим `auto` по умолчанию считает MDE аналитически через SE interaction-
оценки. Параметрический режим можно включить через `mde_mode="parametric"`, а
полноценный повторный запуск регрессии — через `mde_mode="resampling"`. При
кластеризации ориентируйся также на `stderr_type`.

### 3. Robustness

```python
from post_analysis_toolkit import run_did_robustness

did_robust = run_did_robustness(
    df=panel_df,
    outcome_col="metric",
    time_col="sale_date",
    treated_col="treated_flag",
    post_col="post_flag",
    unit_col="unit",
    cluster_col="unit",
)
```

Это прогоняет набор спецификаций:

- с `TWFE` и без
- с логами и без
- с относительной индексацией и без

## Synthetic Control: пример

### Формат данных для функции

Пример для `unit x day` с одним treated unit и несколькими донорами:

| sale_date | unit | metric |
|---|---|---:|
| 2026-02-10 | Batumi | 0.084 |
| 2026-02-10 | Tbilisi | 0.121 |
| 2026-02-10 | Kutaisi | 0.097 |
| 2026-02-11 | Batumi | 0.079 |
| 2026-02-11 | Tbilisi | 0.120 |

Обязательные колонки:

- `sale_date`
- `unit`
- `metric`

Что важно:

- одна строка на один `unit` в одну дату
- donor units не должны быть затронуты treatment
- metric должна быть сопоставима между unit-ами

```python
from post_analysis_toolkit import (
    check_synthetic_control_assumptions,
    fit_synthetic_control,
    run_synthetic_control_robustness,
)

synth_diag = check_synthetic_control_assumptions(
    df=panel_df,
    outcome_col="metric",
    unit_col="unit",
    time_col="sale_date",
    treated_unit="Batumi",
    intervention_time="2026-02-25",
    donor_units=["Tbilisi", "Kutaisi"],
)

synth_result = fit_synthetic_control(
    df=panel_df,
    outcome_col="metric",
    unit_col="unit",
    time_col="sale_date",
    treated_unit="Batumi",
    intervention_time="2026-02-25",
    donor_units=["Tbilisi", "Kutaisi"],
)

synth_robust = run_synthetic_control_robustness(
    df=panel_df,
    outcome_col="metric",
    unit_col="unit",
    time_col="sale_date",
    treated_unit="Batumi",
    intervention_time="2026-02-25",
    donor_units=["Tbilisi", "Kutaisi"],
)
```

Что смотреть:

- `donor_count`
- `pre_rmse`
- `pre_r2`
- `pre_corr`
- `avg_post_effect`
- `avg_post_relative_effect`
- таблицу весов `details["weights"]`

Проверки устойчивости:

- placebo по каждому донору
- leave-one-out по донорам

## Causal Impact: пример

### Формат данных для функции

Формат на вход такой же, как у `Synthetic Control`.

Пример:

| sale_date | unit | metric |
|---|---|---:|
| 2026-02-10 | Batumi | 0.084 |
| 2026-02-10 | Tbilisi | 0.121 |
| 2026-02-10 | Kutaisi | 0.097 |
| 2026-02-11 | Batumi | 0.079 |
| 2026-02-11 | Tbilisi | 0.120 |

Обязательные колонки:

- `sale_date`
- `unit`
- `metric`

Что важно:

- treated unit один
- pre-period должен быть достаточно длинным
- после выравнивания treated и donor рядов не должно быть пропусков

```python
from post_analysis_toolkit import (
    check_causal_impact_assumptions,
    fit_causal_impact,
    run_causal_impact_robustness,
)

ci_diag = check_causal_impact_assumptions(
    df=panel_df,
    outcome_col="metric",
    unit_col="unit",
    time_col="sale_date",
    treated_unit="Batumi",
    intervention_time="2026-02-25",
    donor_units=["Tbilisi", "Kutaisi"],
)

ci_result = fit_causal_impact(
    df=panel_df,
    outcome_col="metric",
    unit_col="unit",
    time_col="sale_date",
    treated_unit="Batumi",
    intervention_time="2026-02-25",
    donor_units=["Tbilisi", "Kutaisi"],
)

ci_robust = run_causal_impact_robustness(
    df=panel_df,
    outcome_col="metric",
    unit_col="unit",
    time_col="sale_date",
    treated_unit="Batumi",
    intervention_time="2026-02-25",
    donor_units=["Tbilisi", "Kutaisi"],
)
```

Что смотреть:

- строку `R2 на валидации`
- строку `RMSE на валидации`
- `average_abs_effect`
- `average_rel_effect`
- `average_abs_effect_lower`, `average_abs_effect_upper`
- `posterior_tail_area`
- `mde_absolute`, `mde_relative`, `mde_cumulative`, `mde_type`
- `details["mde_power_curve"]`

Для Causal Impact simulation-режим много раз получает случайную оценку с масштабом,
равным RMSE на валидации. Это еще не повторный запуск state-space pipeline для каждого
pseudo-dataset; такой callback остается отдельным расширением.

Проверки устойчивости:

- placebo по более ранним датам
- leave-one-out по донорам

## ITS: пример

### Формат данных для функции

Базовый пример для одного временного ряда:

| sale_date | metric |
|---|---:|
| 2026-01-01 | 125.0 |
| 2026-01-02 | 118.0 |
| 2026-01-03 | 133.0 |
| 2026-02-25 | 149.0 |
| 2026-02-26 | 152.0 |

Если есть дополнительные регрессоры:

| sale_date | metric | weather_temp | holiday_flag |
|---|---:|---:|---:|
| 2026-01-01 | 125.0 | 8.3 | 1 |
| 2026-01-02 | 118.0 | 7.9 | 0 |
| 2026-02-25 | 149.0 | 10.4 | 0 |

Обязательные колонки:

- `sale_date`
- `metric`

Что важно:

- одна строка на одну дату
- доп. фичи должны быть на том же дневном grain
- ряд должен быть числовым и упорядочиваемым по времени

```python
from post_analysis_toolkit import (
    build_standard_its_features,
    check_its_assumptions,
    fit_its,
    run_its_robustness,
)

its_diag = check_its_assumptions(
    df=ts_df,
    outcome_col="metric",
    time_col="sale_date",
    intervention_time="2026-02-25",
)

its_result = fit_its(
    df=ts_df,
    outcome_col="metric",
    time_col="sale_date",
    intervention_time="2026-02-25",
)

its_robust = run_its_robustness(
    df=ts_df,
    outcome_col="metric",
    time_col="sale_date",
    intervention_time="2026-02-25",
)
```

По умолчанию `ITS` использует:

- `time_index`
- `post_flag`
- `time_after_intervention`
- day-of-week dummies
- `month_start_flag`
- `month_end_flag`

Можно добавить дополнительные фичи:

```python
its_result = fit_its(
    df=ts_df,
    outcome_col="metric",
    time_col="sale_date",
    intervention_time="2026-02-25",
    extra_feature_cols=["weather_temp", "holiday_flag"],
)
```

Что смотреть:

- `avg_abs_effect`
- `avg_rel_effect`
- `level_change_coef`
- `slope_change_coef_per_day`
- строку `R2 на валидации`
- строку `RMSE на валидации`
- строку `Средний rolling R2 на валидации`
- `mde_absolute`, `mde_relative`, `mde_cumulative`, `mde_type`
- `details["mde_power_curve"]`

Для ITS simulation-режим много раз получает случайную оценку с масштабом,
равным RMSE на валидации. Он относится к выбранному агрегированному постпериодному estimand и не
является отдельным MDE для level и slope.

Проверки устойчивости:

- разные длины pre-window
- placebo intervention dates
- разные feature sets

## Стандартные проверки предпосылок

Во всех `checks`-таблицах пакет теперь возвращает русские колонки:

- `проверка`
- `значение`
- `ориентир`

### Для DiD

- визуальная параллельность трендов
- pre-period correlation
- slope gap на pre-period
- joint pre-trend test через event study
- отсутствие параллельных изменений

### Для Synthetic Control

- достаточно доноров
- хороший pre-fit
- отсутствие contamination доноров
- reasonable donor weights

### Для Causal Impact

- достаточно длинный pre-period
- нормальное predictive quality на pre-period
- отсутствие пропусков после выравнивания
- отсутствие contamination контрольных рядов

### Для ITS

- достаточно длинный pre-period
- нет сильных дырок в датах
- модель более-менее объясняет pre-period
- residual diagnostics не выглядят катастрофически
- нет явных concurrent changes

## Интерпретация результатов

Пакет возвращает числа, но не ставит автоматический ярлык “causal effect proven”.

Практически удобно мыслить так:

- если assumptions слабые, а эффект красивый - не переоценивай вывод
- если assumptions хорошие, но `MDE` огромный - отсутствие значимости мало что значит
- если robustness разваливается - основной результат надо считать хрупким
- если control-конструкция сомнительна - лучше честный descriptive / benchmark framing

## Типичные ошибки

### 1. Неправильный grain

Например:

- treated на уровне города
- а outcome собран на уровне товара x день без аккуратной агрегации

Сначала выровняй unit анализа.

### 2. Плохой pre-period

Если pre-period слишком короткий, то:

- `ITS` будет слабым
- `Causal Impact` будет плохо предсказывать
- `Synthetic Control` будет переобучаться

### 3. Перепутан `post_flag`

Проверь, что:

- до даты intervention `post_flag = 0`
- начиная с даты intervention `post_flag = 1`

### 4. Causal-язык без проверок

Нельзя говорить “акция вызвала рост”, если:

- assumptions не прошли
- donor pool слабый
- robustness разваливается

## Рекомендации по использованию в пост-анализах

- Для продаж с нормальным donor pool начинай с `Synthetic Control` или `Causal Impact`
- Для benchmark-сравнения treated vs control держи `DiD`
- Если контроль надежно собрать не удалось, переходи к `ITS`
- Если даже `ITS` слабый, оставляй descriptive analysis

## Где смотреть готовые примеры

- демо-скрипт: [run_post_analysis_toolkit_demo.py](/Users/aleksandr/ai-workspace/projects/post_analysis/coins_for_products/examples/run_post_analysis_toolkit_demo.py)
- демо-скрипт: [demo.py](demo.py)
- smoke-тесты: [test_smoke.py](/Users/aleksandr/ai-workspace/projects/post_analysis/coins_for_products/tests/post_analysis_toolkit/test_smoke.py)
- Batumi notebook v2: [batumi_sales_effect_validation_notebook_v2.ipynb](/Users/aleksandr/ai-workspace/projects/post_analysis/coins_for_products/analysis/batumi_sales_effect_validation_notebook_v2.ipynb)

## Публичный API

Пакет экспортирует:

- `compute_classic_did`
- `fit_did_regression`
- `fit_event_study`
- `check_did_assumptions`
- `run_did_robustness`
- `fit_triple_difference`
- `check_triple_difference_assumptions`
- `run_triple_difference_robustness`
- `fit_rdd`
- `check_rdd_assumptions`
- `run_rdd_robustness`
- `fit_psm`
- `check_psm_assumptions`
- `run_psm_robustness`
- `fit_doubly_robust`
- `check_doubly_robust_assumptions`
- `run_doubly_robust_robustness`
- `fit_synthetic_control`
- `check_synthetic_control_assumptions`
- `run_synthetic_control_robustness`
- `fit_causal_impact`
- `check_causal_impact_assumptions`
- `run_causal_impact_robustness`
- `build_standard_its_features`
- `fit_its`
- `check_its_assumptions`
- `run_its_robustness`

## Ограничения текущей версии

- `Synthetic Control` здесь реализован в облегченной форме, без сложного набора pre-treatment predictors
- `Causal Impact` зависит от сторонней библиотеки и требует аккуратного окружения
- `MDE` по умолчанию выбирает аналитический расчет при наличии SE, а для
  методов без аналитической формы использует параметрическую симуляцию; режим
  `resampling` переобучает поддержанный расчет на каждом повторе
- contamination и concurrent changes проверяются вручную, а не автоматически

## Версия

Текущая версия пакета: `1.0.4`
