references/moduly-volitelne.md
Uložte jako dance/references/moduly-volitelne.md. Soubor je rozdělený do dvou bloků – spojte je za sebe bez úprav.
# Volitelné moduly
**Žádný z těchto modulů nemá každý klient.** Moduly se mezi klientskými repy kopírují jako celek (žádný sdílený balíček). Než o modulu s uživatelem mluvíš nebo nad ním píšeš SQL, ověř, že v repu je (`ls definitions/`), že je zapnutý (`enabled` v config.js) a že tabulky v BigQuery existují. Když modul chybí, řekni to a nabídni, co jde udělat bez něj (nebo že modul lze nasadit).
## Obsah
1. User stitching (identity graph)
2. AB testování
3. Atribuce (L3_attribution)
4. Shoptet
5. Google Ads reportingová vrstva (L1_gads / L2_gads)
6. Cost monitoring
7. Anomálie (demo)
---
## 1. User stitching (identity graph)
**Poznávací znamení:** `definitions/core/L1_identity/` (`L1_identity_edges.sqlx`, `L1_identity_map.sqlx`, assertions), `config.identity.enabled = true`, tabulky `L1_dance.L1_identity_edges`, `L1_dance.L1_customer_map`, `L1_dance.L1_identity_stats`. Když je `enabled = false`, views vrací `master_customer_id = NULL`, `is_stitched = FALSE`.
**Co dělá:** deterministicky spojuje cookie (`stream_id|user_pseudo_id`) a user keys (`user_id`, user property `user_unique_id`, případně event param nebo identita z objednávek) do grafu; každá souvislá komponenta = jedno stabilní `master_customer_id` (`TO_HEX(SHA256(kanonický uzel))`). Žádná pravděpodobnost, žádný fingerprinting. Mapa se přepočítává celá každý den (`L1_identity_map` = operations, `CREATE OR REPLACE`), hrany inkrementálně.
**Kde to dostaneš:** `L1_model_base_vw` (join přes cookie + fallback přes první user key pro eventy bez cookie), `L2_sessions_vw`, `L2_events_vw` (jen přes cookie → Anonymous bez masteru). `master_source_type = 'user'` a `is_stitched = TRUE` = identita reálně slepená; `'cookie'` = samostatná cookie (master odvozený z uzlu, v mapě není). Vlastní join:
```sql
LEFT JOIN `<projekt>.L1_dance.L1_customer_map` m
ON m.identity_scope = a.dataset_id -- 'global' při identity.scope = "global"
AND m.identity_type = 'cookie'
AND m.identity_value = CONCAT(CAST(a.stream_id AS STRING), '|', a.user_pseudo_id)
```
**Konfigurace (`config.identity`):** `enabled`, `scope` (`"dataset"` / `"global"`), `user_key_sources[]` (`type: column|user_property|event_param`, `name`, `namespace` – zdroje typu property/param **musí být v pivotu `properties`/`params`**, jinak compile spadne), `normalize`, `value_blacklist`, `min_key_length`, `toxicity` (`max_cookies_per_user_key` 500, `max_user_keys_per_cookie` 50 – nad limitem se uzel nemerguje, `is_toxic = TRUE`), `hash_identity_values` (GDPR hash), `max_iterations`, `orders_source` (hrany z objednávkové tabulky přes `ecommerce_transaction_id`, default vypnuto). Nový zdroj klíče = jen config.
**Analytické pokyny:**
- Počet zákazníků = `COUNT(DISTINCT master_customer_id)` nad `L2_sessions_vw` (Cookies) – zahrnuje i nikdy nepřihlášené cookies (každá má vlastní master).
- Mapa je **current-state**: nová hrana může sloučit dvě komponenty a změnit `master_customer_id` i zpětně → čísla „zákazníků" za uzavřená období driftují. Vysvětluj to přes `L1_identity_stats` (denní snapshot: `n_components`, `stitch_rate`, `max_component_size`, `n_toxic_*`).
- Assertions `L1_customer_map_key_check`, `L1_identity_convergence`, `L1_identity_toxicity_sanity`.
- Ověřovací SQL pro scénáře (multi-device, bez consentu, toxicita, tranzitivita, orders): `definitions/core/docs/user_stitching_scenare.md`. Popis: `definitions/core/docs/popis_modelu.md`.
## 2. AB testování
**Poznávací znamení:** `definitions/ab_testing/`, `includes/ab_tests_config.js`, `includes/ab_stats.js`, `config.ab_testing.enabled = true`, dataset `ab_testing`. Template obsahuje příkladový test `kosik_20260510` a smoketesty nad `dataset_id = 'ab_smoketest'` – u klienta mají být smazané; pokud tam jsou, upozorni.
**Princip:** varianta = hodnota event parametru (`variant_source.param`, typicky `ab_test` – **musí být v pivotu `params`**), přiřazená cookie při první expozici. **Jednotka statistiky je uživatel (cookie), nikdy session** – session-level test nafukuje vzorek a vyrábí falešné vítěze. Metrika (`metrics[0]`, jen `type: "binary"`) může mít `scope: "session"` (konverze ve stejné session) nebo `"window"` (do `window_days` od expozice). Libovolný počet variant, právě jedna `is_control: true`; pairwise two-proportion z-test proti kontrole + Holm-Bonferroni korekce. SRM test proti `expected_share` nebo uniformnímu splitu. Kontaminace same_cookie / cross_device (druhé jen s identity modulem). Statistika je čistá aritmetika v SQL (`ab_stats.js`), validovaná proti scipy.
**Tabulky (`ab_testing.*`):**
| Tabulka | Grain | Použij pro |
|---|---|---|
| `L2_ab_exposures` | den × dataset × test × cookie × varianta | expozice, coverage |
| `L2_ab_user_results` | dataset × test × cookie | user-level výsledky (`variant, converted, conversions_count, revenue, is_contaminated, is_eligible, is_in_population, is_mature, is_new_user`) |
| `L3_ab_daily` | test × dataset × varianta × cohort_date × device | kohortní počty (`n_assigned → n_in_population → n_eligible → n_mature → n_evaluated`) |
| `L3_ab_evaluation` | test × dataset (+ `_ALL_`) | **integrita**: `srm_p_value`, `srm_violated`, `contamination_share`, `coverage_share`, `is_frozen`, `verdict` ∈ {ok, srm_violation} |
| `L3_ab_metric_evaluation` | test × dataset × metric_name × varianta | **verdikt vítěze**: `point_estimate`, `lift_relative`, `ci_low/high`, `p_value`, `p_value_holm`, `required_n_per_variant`, `has_sufficient_sample`, `verdict` ∈ {control, wins, loses, no_significant_difference, insufficient_data}, `verdict_note` (česky). Metriky: `primary`, `guardrail_engagement`, `guardrail_bounce`, `guardrail_top_device` |
| `L3_ab_timeseries` | test × varianta × ISO týden | kumulativní vývoj, jen informativně (peeking) |
| `L3_ab_independence` | dvojice testů × dataset | nezávislost souběžných testů (2×2 chi-square) |
**Jak číst výsledek testu:** 1) `L3_ab_evaluation.verdict = 'ok'` a `srm_violated = FALSE` – jinak test neinterpretuj; 2) `L3_ab_metric_evaluation WHERE metric_name = 'primary'` – `verdict` a `verdict_note` per varianta; 3) guardraily nesmí ukazovat `loses`; 4) `has_sufficient_sample` – bez něj je verdikt `insufficient_data`, i když p < alpha.
**Přidání testu = záznam v `includes/ab_tests_config.js`** (name `[A-Za-z0-9_-]`, `datasets` (`[]` = všechny), `start_date`, `end_date: null`, `variant_source {param, variants[{name, values|…, is_control, expected_share?}] | variant_regex}`, `metrics[{name:'primary', type:'binary', event_name, condition_sql, scope, window_days}]`, `eligibility`, `population: all|new|returning`, `mde_relative`). Pokud `param` není `ab_test`, přidej ho do `params.in_custom`, srovnej schéma `L1_model_base` přes `bq update` (nové pole ve STRUCTu `event_params`, viz `config.md` §10) a přebackfilluj `L1_model_base` za dny testu (bez toho `event_params.<param>` neexistuje). Compile – `ab_testing.validate()` hlídá vše ostatní. Podrobný návod a onboarding checklist: `definitions/ab_testing/docs/ab_testing_metodika.md`.
**Ukončení:** nastav `end_date`; po `window_days + 3` dnech se test **zmrazí** (negeneruje SQL, řádky v `L2_ab_user_results` se nemění, v L3 zůstává s `is_frozen = TRUE`). Vynucený přepočet: `dataform run --vars=ab_recompute=all` nebo výčet názvů. Backfill expozic: tag `ab_backfill`.
**Co modul neumí:** continuous/count metriky (AOV, revenue/user) jako primární, sekvenční/Bayes, vizualizace. Revenue je jen informativní (winsorizované).
## 3. Atribuce (L3_attribution)
**Poznávací znamení:** `definitions/L3_attribution/`, dataset `L3_attribution`. Tag `attribution`.
**Co dělá:** z `L1_model_base_vw` (Cookies) staví per `user_pseudo_id` cesty sessions mezi konverzemi (`L3_attribution_model_base_prep` → `L3_attribution_model_base`: `path_id`, `path_position`, `path_length`, `session_source_medium`, `channel` = `snippets.marketing.channel_grouping`, `transactions`, `revenue`, `margin`, `new/repeated_transactions`, `new/repeated_visits`, `user_pseudo_id_path_sources` jako `a >> b >> c`). `L3_attribution_model` rozděluje tržby/transakce podle modelů `first_click`, `last_click`, `linear`, `position_based` (40/20/40), `time_decay` (poločas 7 dní) a přidává Shapley (`L3_attribution_model_shapley`, deklarace `L3_attribution_daily_shapley(_mc)` – počítané mimo Dataform). Grain výstupu: `domain × event_date (datum konverze) × source × medium × source_medium × campaign × channel × model`, sloupce `cost, transactions, new_transactions, repeated_transactions, revenue, margin, sessions, new_visits, repeated_visits`.
**Pozor na template hodnoty:** `margin = revenue * 0.35` je **placeholder** (dokud klient nedodá marže), `L3_attribution_mkt_cost` je `disabled: true` s nulovými náklady (proto `cost = 0`, ROAS/POAS nejde), `L3_attribution_anonymous` (tržby anonymních objednávek per domain/den, mimo cesty) má natvrdo `dataset_id = 'analytics_xxx'` a kurz z jiného projektu – u klienta zkontroluj `SPECIFIKA`. Anonymous transakce v atribučních cestách nejsou.
**Analytické pokyny:** vždy filtruj `model`, jinak sčítáš tržby pětkrát. Součet `revenue` přes kanály v jednom modelu = tržby Cookies konverzí za období. Srovnání modelů = jak se posouvá kredit mezi kanály (first vs. last click).
## 4. Shoptet
**Poznávací znamení:** `definitions/shoptet/`, `includes/shoptet_config.js`, dataset `shoptet`. Zdroj = trvalé CSV exporty Shoptetu ingestované vlastní pipeline do `L1_shoptet_orders_<shop>` / `L1_shoptet_products_<shop>` (Shoptet nemá BQ transfer).
**Konfigurace:** `shoptetOrders.events` / `shoptetProducts.events` per e-shop (`shop_name`, tabulka, `local_currency`, `storno_patterns` – názvy stavů „storno/zrušen", klient si je může přejmenovat). `orderItemTypes.revenue = product, set, service` (bez shipping/billing/discount).
**Tabulky:** `L1_shoptet_orders` (UNION shopů + `is_storno`, `is_item_storno`), `L1_shoptet_products`, `L2_shoptet_orders` (1 řádek = objednávka, bez storen, tržby **bez DPH** i s DPH, `_reporting` i `_local` měna, marže, slevy, kupóny), `L2_shoptet_order_items`, `L2_shoptet_coupons`. Full load (stavy objednávek se mění).
**Analytické pokyny:** backendové tržby (Shoptet) vs. GA4 tržby (Dance) se liší (storna, DPH, doprava, consent) – při srovnání použij `revenue_0_wo_vat_reporting` vs. `reporting_transaction_revenue` a spáruj přes `order_code = transaction_id`. Marže existuje jen tady. Identity `orders_source` může z objednávek brát e-mail zákazníka, pokud ho export obsahuje.
## 5. Google Ads reportingová vrstva
**Poznávací znamení:** `definitions/core/L1_gads/`, `definitions/core/L2_gads/`, `includes/gads_config.js`. Nezaměňuj s core závislostí `L1_gads_gclid_clicks` (ta používá `config.tables.gads` a je nutná pro gclid → kampaň v L1).
**Co dává:** L1 kopie Google Ads transferu (customer, campaign, adgroup, ads, keywords, click/campaign/adgroup/keyword/account basic stats, conversion stats, search terms) s SCD historizací názvů (`*_name_changes`), L2 reportingové tabulky (`L2_campaigns_stats`, `L2_adgroup_stats`, `L2_ads_stats`, `L2_keywords_stats`, `L2_search_terms_stats`, `L2_click_stats`, `L2_account_*`, `L2_campaign_conversion_stats`) + assertion `L2_gads_data_sum_check`. `gads_transfer_refresh_window = 30` dní.
**Pozor:** template má placeholder účty (`gads_test1/2`, `XXXXXXXXXX`). Pokud klient nemá vyplněné reálné tabulky, modely nefungují – před analýzou ověř existenci `L2_gads.*` v BigQuery. Náklady jsou v měně účtu Ads (`customer_currency_code`).
## 6. Cost monitoring
`definitions/L1_monitoring/` → `L1_dance_monitoring.cost_monitoring_prep` (z `INFORMATION_SCHEMA.JOBS_BY_PROJECT` per `config.clients_id`) a `cost_monitoring.cost_monitoring` (14denní průměr, stddev, `max_daily_spend_limit` ze `snippets.js`). Tag `dance_monitoring`. Assertions `bq_cost_alerting`, `bq_cost_stddev_alerting`, `bq_project_alerting` v `dataform_assertions`. Použij pro „kolik stojí denní běh" a „která tabulka/dotaz je drahý":
```sql
SELECT date, destination_table, job_segment, ROUND(SUM(data_billed_GB), 2) AS gb, ROUND(SUM(cost_usd), 3) AS usd
FROM `<projekt>.cost_monitoring.cost_monitoring`
WHERE date >= DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY)
GROUP BY ALL ORDER BY usd DESC LIMIT 30
```
## 7. Anomálie (demo)
`definitions/core/L3_dance/L3_anomalies.sqlx` – týdenní detekce anomálií (odchylka od 4týdenního průměru, prahy per úroveň, top-N podle dopadu) nad **syntetickými daty**. Slouží jako demo analytického modulu „Anomálie"; produkční verze čte reálné objednávky mimo tohle repo. Pro reálnou detekci anomálií nad Dance daty postav dotaz nad `L3_acquisition_traffic` / `L3_monetization_transactions` (klouzavý průměr + stddev) a řekni, že demo tabulku nepoužíváš.
