---
name: tableau-twbx
description: Использовать при разборе книги или экстракта Tableau — файлы .twbx / .twb / .hyper, «справка по продажам из Tableau», «что в этой книге», «посчитай метрики из дашборда», Goods Board Report, экстракты sqlproxy, Hyper API, а также когда передали файл Tableau и ждут цифры, сводку или отчёт. Работает без установленного Hyper API; ставит его только с явного согласия.
metadata:
  short-description: Читать книги и экстракты Tableau, обходить их капканы, отчитываться честно
---

# Разбор Tableau .twbx / .hyper

## Область применения

Пришёл файл Tableau, и от него хотят либо **структуру** («что это за книга»), либо
**цифры** («посчитай выручку за неделю»). Скилл покрывает оба случая и — что важнее —
не разваливается, когда Hyper API не установлен и установить его нельзя.

Работает с `.twbx` (упакованная книга), `.twb` (только XML) и голыми экстрактами `.hyper`.

## Этот проект

Факты конкретного проекта — книга, утверждённый периметр, реальные имена колонок,
подтверждённые капканы, недоступные метрики и готовые команды — лежат в `CLAUDE.md` в
корне проекта и грузятся каждую сессию. **Читать его до любой работы с данными и не
переписывать сюда.** Здесь только переносимый метод.

## Главное правило

**Имена полей в `.twb` — это НЕ имена колонок в `.hyper`.**

XML несёт подписи *модели* Tableau: вычисляемые поля, псевдонимы, переименованные
колонки. Экстракт несёт *физическую* схему исходного запроса. Реальный случай: книга,
чей XML рекламировал `GMV`, `Bill_Cnt`, `AUP_Sales_RUR`, `All_Inv_RUR_RRP`, стояла на
экстракте с колонками `sales_rur`, `sales_u`, `inv_rur_rrp` — **пересечение нулевое**.
Поля из XML могут вообще не существовать в данных.

Отсюда: никогда не писать запрос по именам из `.twb`. Сначала интроспекция схемы, всегда.
`.twb` говорит, что считает *отчёт*; что лежит в *данных*, говорит только экстракт.

## Уровни доступа — сначала разведка, потом план

```bash
bash scripts/probe.sh <путь-к-файлу>
```

Сообщает, какой уровень достижим, и ничего не устанавливает.

| Уровень | Требует | Даёт |
|---|---|---|
| **0 — Структура** | только stdlib `python3` | Дашборды, листы, источники, инвентарь полей, параметры, манифест экстрактов. Доступен всегда. |
| **1 — CSV** | выгрузка-кросстаб из Tableau | Полный расчёт метрик по тому, что выгрузили. Без установки. |
| **2 — Прямой запрос** | `tableauhyperapi` (установка по согласию) | SQL по экстракту. Всё. |

**Уровень 0 запускать безусловно** — он быстрый, бесплатный и объясняет, *для чего* книга,
а это определяет все дальнейшие вопросы. Подниматься выше только когда нужны цифры.

### Уровень 0 — структура

```bash
python3 scripts/twb_inspect.py "Goods Board Report.twbx"
```

Только stdlib. Достаёт `.twb` из zip **не распаковывая экстракты** — книга на 932 МБ
отдаёт структуру меньше чем за секунду. На выходе markdown: версия, сервер-источник,
источники данных с классами подключения, дашборды, листы, параметры, подписи полей и
манифест экстрактов с размерами.

### Уровень 1 — CSV, без установки

Когда нужны цифры, а уровень 2 недоступен или нежелателен, попросить выгрузку:

> В Tableau открыть нужный лист → **Download → Crosstab** (или **Data**) → CSV.
> Положить файл рядом с книгой.

Затем:

```bash
python3 scripts/csv_summary.py export.csv
```

Только stdlib `csv`, без pandas. Определяет кодировку и разделитель (Tableau часто отдаёт
UTF-16 с табами), разбирает форматирование чисел (`1 234,56`, `(123)`, проценты), делит
колонки на измерения и меры, по запросу строит группировку и двумерную сводку.
Считать можно только то, что выгрузили, — так и говорить, не намекая на полноту.

### Уровень 2 — прямой запрос (нужно согласие)

Установка — это загрузка пакета. **Спрашивать каждый раз.** Сказать что, откуда, сколько:

> `tableauhyperapi` с PyPI, ~250 МБ, внутри движок `hyperd` от Tableau — официальный SDK,
> единственный поддерживаемый способ читать `.hyper` вне Tableau. Плюс ~N МБ на распаковку
> экстрактов. Ставлю?

При согласии — venv в проекте, а не системный интерпретатор:

```bash
bash scripts/setup_hyper.sh /путь/к/проекту
```

Создаёт `.venv` рядом с книгой и ставит туда. Системный и homebrew-питон помечены как
externally-managed (PEP 668); venv снимает вопрос `--break-system-packages` и сносится
одним `rm -rf`. **Сразу сказать пользователю, как его удалить после работы.**

Дальше интроспекция — этот шаг не пропускать никогда:

```bash
.venv/bin/python scripts/hyper_introspect.py extracts/*.hyper
```

Выдаёт схемы, таблицы, число строк, колонки **и запускает автоматический свип капканов**
(описан ниже). Читать его предупреждения до первого агрегата.

Запросы:

```bash
.venv/bin/python scripts/hyper_sql.py extract.hyper "SELECT ... "
```

Флаг `--tsv` отдаёт сырые значения без разделителей и с пустотой вместо NULL — для
машинного чтения; без флага печатает выровненную таблицу для человека.

## Капканы данных — проверять все до отчёта

`hyper_introspect.py` печатает секцию `TRAPS` из шести автоматических проверок
(~1.5 с на экстракте в 10 млн строк). Полный каталог с SQL для ручной проверки —
`references/data-traps.md`.

| Проверка | Что ловит |
|---|---|
| Колонка сравнения периодов | `fy_period` ∈ {TY, LY, LLY, 3LY} — прошлые годы, переложенные на текущий календарь |
| Скелет будущих периодов | Заранее созданные строки незакрытых периодов; сообщает настоящий последний закрытый |
| Дублирование строк за флагом | Колонка типа `full_week`, делящая строки, но не меры |
| Sentinel-значения | Абсурдные целые (`12345678912345001`) вместо «неизвестно» |
| Предрасчитанные отношения | `*_wov`, `*_pc_*`, `*_index` — то, что нельзя суммировать |
| Разрежённые меры | План или бюджет на грубой грануляции, NULL почти везде |

**Порядок свипа несущий.** Колонка периода определяется первой, потому что скелет и
дублирование не видны вне среза текущего года: на всей таблице прошлогодние строки
маскируют скелет, а флаг дублирования на самой свежей дате показывает единственное
значение.

**Скелетные строки не обязательно пустые.** Снимки остатков доживают до незакрытых
периодов, поэтому проверка «все меры NULL» их пропускает. Надёжный сигнал — обвал числа
строк относительно медианы периода: 1 850 против 66 000.

**Если оба значения флага покрывают полный набор ключей**, два набора строк — копии друг
друга, и любой фильтр даёт верные итоги; свип рекомендует более полный.

Два капкана свип увидеть не может, им нужен контекст бизнеса: **партийные каналы** и
**сдвиг сезонных кодов**, оба ниже в «Гигиене сравнений».

## Гигиена сравнений

Помимо капканов схемы — четыре проверки, отделяющие находку от артефакта:

- **Выравнивание периодов.** В таблицах с `fy_period` ∈ {TY, LY, LLY, 3LY} прошлые годы
  лежат *переложенными на текущий календарь*. Сравнивать строки `LY` по **той же** дате;
  вычитать год из даты самостоятельно — ошибка.
- **Эффект базы.** До публикации любого YoY за один период построить тот же показатель за
  12–13 предыдущих. Неделя −9 % на фоне ряда около +1 % обычно означает, что аномалией был
  *прошлый* период, а не текущий. Прямо сказать, какой именно.
- **Партийные каналы.** Опт, B2B, франшиза, дистрибуция отгружаются партиями. Недельный YoY
  по такому каналу — шум: одна отгрузка составляет весь период. Смотреть собственную историю
  канала до того, как приписывать ему падение; отчитываться по нему месяцем или кварталом.
- **Сдвиг сезонных кодов.** Коды коллекций сдвигаются год к году (`AW27` сейчас против
  `AW26` тогда), поэтому разрез YoY по коду сезона сравнивает разное. Нужен относительный
  индекс сезона, а не сырой код.

## Отчётность

Структура и тон — `references/reporting.md`.

Без исключений: **сказать, что посчитать не удалось.** В экстрактах регулярно нет маржи
(нет себестоимости), количества чеков (штуки ≠ чеки) и плана (разрежён или NULL на нужном
периметре). Назвать три отсутствующие метрики полезнее, чем молча выдать пять имеющихся и
позволить читателю додумать остальное.

Тоже без исключений: **методику класть в сам документ.** Какие фильтры задали периметр,
какой капкан обезврежен и как, какое сравнение искажено базой. Читатель правления, который
не может восстановить периметр, не может действовать по цифре.

### Форма результата

| Форма | Когда |
|---|---|
| **Markdown рядом с книгой** | Базовый вариант, всегда |
| **Страница-дашборд, 5 стилей** | У отчёта есть аудитория: ссылка правлению, экранный показ |
| **Дашборд + PDF** | Нужна именно распечатка или файл в рассылку |

**Страница-дашборд** собирается из готовых стилей — `ledger`, `terminal`, `editorial`,
`panel`, `blueprint` — одним файлом данных на все пять:

```bash
python3 scripts/build_report.py --style ledger --data report-data.json --out dashboard.html
```

Контракт данных, характер каждого стиля и порядок — `references/report-styles.md`;
эталонный файл данных — `assets/report/example-data.json`. Стили экранные: собрать
несколько вариантов и дать выбрать глазами. В PDF их не гнать — печатного CSS там нет.

**PDF-ветка** остаётся на своём шаблоне (метод — `references/board-dashboard.md`):

```bash
cp assets/board-dashboard.html <проект>/dashboard.html   # заменить объект DATA
bash scripts/render_pdf.sh <проект>/dashboard.html "<проект>/Отчёт.pdf"
```

В обоих случаях **оформлять только после того, как капканы обезврежены** — иначе
получается красиво поданная ошибка.

**PDF после сборки прочитать глазами** — инструментом Read с параметром `pages`. Скрипт
проверяет число страниц и геометрию, но не видит раздутых графиков, схлопнувшихся колонок
и налезающих подписей. Главный капкан здесь: headless-браузер считает медиазапросы по окну,
а не по печатной странице, и без `--window-size` раскладка рассыпается только в PDF,
оставаясь верной на экране.

Ориентация задаётся третьим аргументом (`landscape` по умолчанию, `portrait`) и правилом
`@page` в самом HTML — менять оба, иначе окно и страница разойдутся.

## Гигиена рабочей папки

- Экстракты `.hyper` распаковывать во временную директорию, не рядом с книгой — они обычно
  в 3–10 раз больше `.twbx`.
- Для уровня 0 достаточно `.twb`; не распаковывать экстракты ради вопроса о структуре.
- Движок Hyper пишет `hyperd.log` в рабочую папку, и тот разрастается до мегабайтов —
  удалить после работы.
- По завершении предложить уборку: `rm -rf .venv` и временные экстракты.

## Файлы

| Путь | Уровень | Назначение |
|---|---|---|
| `scripts/probe.sh` | — | Определить доступный уровень, ничего не ставить |
| `scripts/twb_inspect.py` | 0 | Структура книги из XML, только stdlib |
| `scripts/csv_summary.py` | 1 | Разбор CSV-выгрузки Tableau, только stdlib |
| `scripts/setup_hyper.sh` | 2 | Создать venv и поставить Hyper API (нужно согласие) |
| `scripts/hyper_introspect.py` | 2 | Дамп схемы и автоматический свип капканов |
| `scripts/hyper_sql.py` | 2 | Выполнить SQL, напечатать результат |
| `scripts/build_report.py` | 3 | Собрать страницу-дашборд: тема + тело + данные, только stdlib |
| `scripts/render_pdf.sh` | 3 | HTML-дашборд в PDF через headless Chrome, с проверкой геометрии |
| `assets/report/` | 3 | Пять стилей страницы: `styles/*.css`, `bodies/*.html`, движок `core.js`, эталон `example-data.json` |
| `assets/board-dashboard.html` | 3 | Шаблон PDF-ветки: темы, печатная вёрстка, шесть графиков |
| `references/data-traps.md` | — | Полный каталог капканов с SQL для проверки |
| `references/reporting.md` | — | Структура отчёта, правила честности, блок методики |
| `references/report-styles.md` | 3 | Пять стилей: контракт данных, характер каждого, порядок сборки |
| `references/board-dashboard.md` | 3 | PDF-ветка: сборка, правила печати, капкан headless-рендера |
