references/config.md
Uložte jako dance/references/config.md. Soubor je rozdělený do dvou bloků – spojte je za sebe bez úprav.
# includes/config.js – anatomie a návody
Config je jediné místo, kde se model přizpůsobuje klientovi. SQL se z něj generuje, `.sqlx` se kvůli běžným změnám nesahá. Před úpravou si soubor vždy přečti celý – klientská repa se liší (jiné property, zapnuté/vypnuté moduly, vlastní `custom_columns`).
## Obsah
1. Bloky config.js v pořadí, jak jsou v souboru
2. Návod: nová GA4 property
3. Návod: nový event parametr / user property
4. Návod: custom sloupce v L2
5. Návod: konverzní eventy
6. Návod: měny a reporting měna
7. Návod: timezone
8. Návod: CMP statistiky
9. Ostatní includes (snippets, gads_config, shoptet_config, ab_tests_config, dokumentace sloupců)
10. Po každé změně: compile, dopad na schéma, commit
---
## 1. Bloky config.js
| Blok | Co dělá | Kdy sahat |
|---|---|---|
| `SPECIFIKA PROJEKTU` (komentář nahoře) | klientské poznámky | při každé klientské odchylce |
| `version` | verze modelu (např. 1.1) | při upgrade template |
| `ga4.events[]` | seznam GA4 properties: `database`, `schema` (= dataset_id), `name` (`events_*`), `currency` | nová property, změna měny |
| `dataset_id_to_domain.sql_case(prefix)` | CASE `dataset_id → domain` (čitelný název webu) | nová property |
| `gAdsClick`, `gAdsCampaign`, `gAdsAdGroup`, `gAdsKeyword`, `*_stats`, `tables.gads.*` | tabulky Google Ads transferu pro core (gclid → kampaň, click/campaign costs) | klient má/nemá Google Ads transfer; jiný účet |
| **`no_edit_zone_start … end`** | backfill (`isBackfill`, `backfill_date_sql`, `backfill_delete_clause`, `user_sources_date_filter`, `ga4_checkpoint_end_declaration`), dedup shardů (`ga4_suffix_declarations`), UNION dotazy (`combinedQueryAll`, `combinedQuerySources`, `combinedgAds*`) | **neupravovat** bez výslovného zadání |
| `recalc.recalc` | FQN tabulky `L1_model_recalculation` | jiný projekt |
| `properties.in_all / in_custom` | pivot user properties do `user_properties.<key>` | nová user property |
| `params.in_all / in_custom` | pivot event params do `event_params.<key>` | nový event parametr |
| `conversion.all / by_dataset / sql()` | definice `is_conversion` | konverzní eventy |
| `identity {...}` | modul user stitching (volitelný) | viz `moduly-volitelne.md` |
| `ab_testing {...}` + `require("./ab_tests_config.js")` | modul AB testů (volitelný) | viz `moduly-volitelne.md` |
| `initdate` | od kdy se načítá historie při full refresh (`'"2021-05-12"'`) | první nasazení / omezení historie |
| `cmp_event_name`, `cmp_action_detail_param` | event a parametr CMP lišty pro `L3_cmp_stats` | klient má CMP tracking |
| `url_params.default / custom` | regex click ID parametrů odstraňovaných z `session_landing_page_reporting` | klient má vlastní click ID |
| `reporting_currency`, `dataset_currency_sql` | reporting měna a CASE `dataset_id → měna` (generuje se z `ga4.events[].currency`) | změna reporting měny |
| `lookback_window_last_click` (91) | **dnes nenapojená konstanta** – L1_model_base má 91 dní natvrdo | při změně uprav i SQL |
| `funnel_window_days` (5) | pro user-based funnel, který v core není | – |
| `url_decode` | FQN UDF `URLDECODE` (musí existovat v `L1_dance`) | jiný projekt |
| `custom_columns.*` | stringy vkládané na konec SELECTu L2_events/sessions/items a jejich `_vw` | klientské sloupce v L2 |
| `event_datetime_custom.timezone_mapping / default` | timezone per dataset pro `event_custom_datetime` | nová property v jiném pásmu |
| `domain.url_regex` | regex pro odstranění domény z landing page | – |
| `clients_id`, `combinedQueryMonitoring`, `combinedQueryAlertingCost24h` | cost monitoring (`INFORMATION_SCHEMA.JOBS_BY_PROJECT`) | jiný projekt / více projektů |
| `module.exports` | co je dostupné jako `config.<x>` v SQLX | nový export |
Použití v SQLX: `${config.reporting_currency}`, `${config.conversion.sql()}`, `${config.dataset_id_to_domain.sql_case('a.')}`, `${config.identity.master_columns_sql("a")}` atd. Soubory v `includes/` jsou dostupné pod názvem souboru (`config`, `snippets`, `gads_config`, …)
`${config.reporting_currency}`, `${config.conversion.sql()}`, `${config.dataset_id_to_domain.sql_case('a.')}`, `${config.identity.master_columns_sql("a")}` atd. Soubory v `includes/` jsou dostupné pod názvem souboru (`config`, `snippets`, `gads_config`, …).
`${config.reporting_currency}`, `${config.conversion.sql()}`, `${config.dataset_id_to_domain.sql_case('a.')}`, `${config.identity.master_columns_sql("a")}` atd. Soubory v `includes/` jsou dostupné pod názvem souboru (`config`, `snippets`, `gads_config`, …).
## 2. Návod: nová GA4 property
Přidání druhé (třetí…) property do jednoho Dance modelu:
1. `ga4.events` – přidej záznam:
```js
{ database: "signalscz-web", schema: "analytics_555555555", name: "events_*", currency: "EUR" }
```
`database` = projekt, kam GA4 exportuje (nemusí být projekt Dance). `currency` = měna, ve které property posílá `ecommerce.purchase_revenue`.
2. `dataset_id_to_domain.sql_case` – přidej `WHEN ...dataset_id = 'analytics_555555555' THEN 'shop.example.sk'`.
3. `event_datetime_custom.timezone_mapping` – přidej `"analytics_555555555": "Europe/Bratislava"`, pokud se liší od `default`.
4. `conversion.by_dataset` – jen pokud má property jinou sadu konverzí než `conversion.all`.
5. `cost monitoring` – nic, běží per projekt.
6. Zkontroluj, že `L1_exchange_rates` (`signals_exchange_rates.exchange_rates`) obsahuje pár `EUR → <reporting_currency>`, jinak budou `reporting_*` NULL.
7. Pokud klient používá AB testy s `datasets: [...]` filtrem nebo identity se `scope: "dataset"`, zvaž, zda se má nová property zahrnout.
8. Compile. Od dalšího běhu se nová property načítá **pro dny > checkpoint (poslední ~3 dny)**. Historie: backfill den po dni (`--vars=backfill_date=... --tags=L1_backfill`, přepočítává všechny properties pro daný den) nebo nastavit `start_dt` v recalc tabulce na první den historie a spustit daily (ten den pak přepočte všechno od `start_dt` – u velkých klientů drahé, řekni to uživateli).
`ga4_suffix_declarations` generuje `ga4_suffixes_0`, `ga4_suffixes_1`, … podle pořadí v `ga4.events` – nic dalšího není potřeba.
## 3. Návod: nový event parametr / user property
Kdy je to potřeba: parametr má být sloupec `event_params.<name>` v L1 (kvůli AB testu, identity klíči, custom logice nebo výkonu). Když stačí ad-hoc čtení v L2/L3, jde to i bez pivotu – viz níže.
1. `params.in_custom` – přidej **s úvodní čárkou** (řetězec se lepí za `in_all`, který čárkou nekončí):
```js
const params = {
in_all: "'ga_session_number', ... 'signals_dance_flag' ",
in_custom: ", 'variant_type', 'product_availability'"
};
```
Klíče lowercase (pivot dělá `LOWER(ep.key)`). Pro user properties totéž v `properties.in_custom`.
2. Compile – `identity.validate()` / `ab_testing.validate()` kontrolují, že zdroje, které potřebují, v pivotu jsou.
3. Nové pole se v `L1_model_base` objeví až v napočítaných partitionech → pro historii backfill / přepočet (viz architektura.md §8). Pole je STRING (castuj v L2/L3). **Pozor:** jde o změnu vnořeného STRUCTu inkrementální tabulky – před prvním během je potřeba srovnat schéma cílové tabulky (viz §10 bod 2), jinak `INSERT` spadne.
4. Chceš ho i v `L2_events`? L2 vybírá sloupce explicitně, nový pivot tam **automaticky není** – přidej ho přes `custom_columns.custom_l2_events` (viz §4), nebo uprav `L2_events.sqlx` + dokumentaci sloupců.
**Alternativa bez změny schématu:** všechny nepivotované parametry jsou v `event_params_other` (L1) – v L2_events je tak čtený `page_segment` a `cmp_action_detail`:
```sql
(SELECT ep.value FROM UNNEST(event_params_other) ep WHERE LOWER(ep.key) = 'product_availability') AS product_availability
```
Funguje i na historii, ale je to dražší (UNNEST na každém řádku) a v L2 je to jen pro sloupce, které L2_events výslovně vytahuje.
## 4. Návod: custom sloupce v L2
`config.custom_columns` obsahuje šest stringů; každý se vloží **na konec SELECT listu** daného modelu (za poslední sloupec s čárkou, takže string může být prázdný, nebo obsahovat jeden či víc výrazů oddělených čárkami, koncová čárka nevadí):
| Klíč | Kam | Charakter |
|---|---|---|
| `custom_l2_events` | `L2_events` | řádek = event, bez agregace: `event_params.variant_type AS variant_type` |
| `custom_l2_sessions` | `L2_sessions` (má `GROUP BY ALL`) | **musí být agregát**: `MAX(event_params.variant_type) AS variant_type` |
| `custom_l2_items` | `L2_items` | řádek = event × item |
| `custom_l2_events_vw` / `custom_l2_sessions_vw` / `custom_l2_items_vw` | příslušné view | pass-through fyzického sloupce: `variant_type` (view jinak sloupec ztratí!) |
Pravidlo: co přidáš do fyzické tabulky, přidej i do `_vw`, jinak to L3 neuvidí. Popis sloupce patří do `includes/custom_columns_documentation.js` (a případně do `columns:` v configu modelu)
`_vw`, jinak to L3 neuvidí. Popis sloupce patří do `includes/custom_columns_documentation.js` (a případně do `columns:` v configu modelu).
`_vw`, jinak to L3 neuvidí. Popis sloupce patří do `includes/custom_columns_documentation.js` (a případně do `columns:` v configu modelu).
## 5. Návod: konverzní eventy
```js
const conversion = {
all: "'purchase'", // default pro všechny datasety
by_dataset: {
"analytics_243381983": "'purchase', 'submit_form'", // vlastní sada per dataset
},
sql: function() { ... } // generuje boolean výraz pro is_conversion
};
```
`is_conversion` je v L1 (`config.conversion.sql()`), přenáší se do L2_events a `L2_sessions.totals.conversions`. Změna se projeví jen v přepočítaných partitionech. Konverze v `L3_session_funnels` a AB testech se na tomto flagu nezakládají (mají vlastní `event_name`).
## 6. Návod: měny a reporting měna
- Lokální měna dat = `ga4.events[].currency` (per dataset). `transaction_revenue`, `ecommerce_purchase_revenue`, `item_revenue` jsou v ní.
- `reporting_currency = "'CZK'"` (pozor, string včetně uvozovek – jde přímo do SQL literálu).
- `dataset_currency_sql` se generuje automaticky; `_vw` views joinují `L1_exchange_rates` na `(event_date, from_currency = měna datasetu, to_currency = reporting)`. Když se měny rovnají, kurz = 1. Když kurz chybí, `reporting_* = NULL` (ne 0!).
- `L1_exchange_rates` čte `` `<defaultDatabase>.signals_exchange_rates.exchange_rates` `` – centrální tabulka Signals, v klientském projektu musí existovat (nebo se `L1_exchange_rates.sqlx` přesměruje).
## 7. Návod: timezone
`event_datetime_custom.timezone_mapping` mapuje `dataset_id → IANA timezone`, `default` pro ostatní. Používá se v `L2_events_vw` / `L2_items_vw` jako `event_custom_timezone` / `event_custom_datetime`. `event_date` samotné je datum z GA4 exportu (v timezone property), `event_timestamp` je UTC.
## 8. Návod: CMP statistiky
`L3_cmp_stats` čte eventy `event_name = config.cmp_event_name` a akci z `event_params_other[config.cmp_action_detail_param]` (přes `L2_events.cmp_action_detail`). Nastav obě konstanty podle klientova trackingu. `L3_cmp_stats_pivot` má názvy akcí (`show`, `accept`, `deny`, `close_cross`, …) **natvrdo v SQL** – uprav podle klienta. Bez CMP trackingu obě tabulky vrátí 0 řádků, nic nespadne.
## 9. Ostatní includes
- **`snippets.js`** – `marketing.L1_channel_grouping(prefix)` (L1, s ošetřením Anonymous bez session_start) a `marketing.channel_grouping(prefix)` (L3 funnely, atribuce). Kanály: direct, shopping_paid, search_paid, social_paid, video_paid, display, other_paid, shopping_organic, social_organic, video_organic, search_organic, email, affiliate, referral, audio, sms, mobile_push, (other). Klientské kanály (např. sklik je už v search_paid) se přidávají sem. `max_daily_spend_limit.limit(prefix)` = denní limit nákladů BQ per projekt pro alerting.
- **`gads_config.js`** – Google Ads reportingová vrstva (`L1_gads/`, `L2_gads/`). Template obsahuje **placeholder účty `gads_test1/2` s ID `XXXXXXXXXX`** – u klienta bez Google Ads transferu modely selžou; buď vyplň reálné tabulky, nebo modely `disabled: true`. Core (`tables.gads` v config.js) je na tom nezávislé, ale také ukazuje na konkrétní transfer.
- **`shoptet_config.js`** – e-shopy Shoptetu (`events` per shop, `storno_patterns`, `local_currency`), viz moduly.
- **`ab_tests_config.js`** – seznam AB testů, viz moduly. Template obsahuje příkladový test `kosik_20260510` a smoketesty na dataset `ab_smoketest` – **před nasazením u klienta smazat**.
- **`*_columns_documentation.js`** – popisy sloupců (BigQuery description). Při přidání sloupce doplň.
## 10. Po každé změně
1. **Compile**: `npx @dataform/cli compile` (v repu je jen `@dataform/core`, CLI si npx stáhne) nebo `dataform compile`, nebo Dataform workspace v GCP. Compile spouští `identity.validate()` a `ab_testing.validate()` – chyby jsou české a konkrétní, řiď se jimi. **Rychlá kontrola bez CLI:** `node -e "require('./includes/config.js')"` v rootu repa spustí tu samou validaci (config je obyčejný CommonJS modul; `dataform` global není definovaný, takže běží ne-backfill větev) – když projde bez výjimky, projde i validace při compile. Hodí se i na inspekci generovaného SQL: `node -e "console.log(require('./includes/config.js').conversion.sql())"`.
2. **Dopad na schéma** – pivot/`custom_columns`/nový sloupec v L1/L2 = změna schématu inkrementální tabulky. Nový **top-level** sloupec (např. přes `custom_columns` v L2) Dataform doplní, historie má NULL. **Nové pole uvnitř STRUCTu `event_params` / `user_properties` (pivot v L1_model_base) je horší případ**: BigQuery neumí přidat vnořené pole přes `ALTER TABLE ADD COLUMN` a `INSERT` s jiným tvarem STRUCTu selže → inkrementální běh `L1_model_base` spadne, dokud se schéma cílové tabulky nesrovná. **Standardní postup Signals je `bq update` schématu**, ne full refresh:
```bash
bq show --schema --format=prettyjson <projekt>:L1_dance.L1_model_base > schema.json
# do pole "event_params" (RECORD) přidej {"name": "<param>", "type": "STRING", "mode": "NULLABLE"}
bq update <projekt>:L1_dance.L1_model_base schema.json
```
Totéž pro `L1_user_sources`, pokud se jí změna týká (dnes ne – nemá pivot). Historie má v novém poli NULL, dokud se nedotáhne backfillem den po dni. Full refresh `L1_model_base` (celý GA4 export od `initdate`) je záložní varianta pro malé klienty. Řekni uživateli dopředu, že bez srovnání schématu denní běh spadne, a do postupu ho zařaď **před** první běh po nasazení configu.
3. **Odstranění sloupce/pivotu** je horší než přidání – inkrementální tabulka sloupec neztratí, ale nová data ho mají NULL; L2/L3, které na něj odkazují, spadnou při compile. Grepni `event_params.<name>` přes `definitions/`, než něco odebereš.
4. **Commit message anglicky**, komentáře v kódu česky (drž styl repa). Zapiš změnu do `SPECIFIKA PROJEKTU`, pokud je klientská.
