diff --git a/.github/ISSUE_TEMPLATE/agent_task.yml b/.github/ISSUE_TEMPLATE/agent_task.yml
index df97652..fd6f96e 100644
--- a/.github/ISSUE_TEMPLATE/agent_task.yml
+++ b/.github/ISSUE_TEMPLATE/agent_task.yml
@@ -1,12 +1,12 @@
-name: Agent task
-description: Track a multi-agent implementation task for Streamify.
-title: "[Agent] "
+name: Агентская задача
+description: Задача для одного из рабочих направлений Streamify.
+title: "[Агент] "
labels: ["agent-task", "triage"]
body:
- type: dropdown
id: agent
attributes:
- label: Agent lane
+ label: Направление
options:
- Repo/Build
- Yandex Ingestion
@@ -18,16 +18,16 @@ body:
- type: textarea
id: objective
attributes:
- label: Objective
- description: State the product or engineering outcome, not only the code change.
- placeholder: "Example: Make real-account ingestion observable and idempotent for playlist metadata."
+ label: Цель
+ description: Опишите продуктовый или инженерный результат, а не только изменение кода.
+ placeholder: "Например: сделать ingestion плейлистов наблюдаемым и идемпотентным для реального аккаунта."
validations:
required: true
- type: textarea
id: acceptance
attributes:
- label: Acceptance checks
- description: Commands, artifacts, or runtime behavior that prove completion.
+ label: Проверка готовности
+ description: Команды, артефакты или runtime-поведение, которые доказывают результат.
placeholder: |
- make raw-contract
- make dbt-build
@@ -37,5 +37,5 @@ body:
- type: textarea
id: notes
attributes:
- label: Notes and risks
- description: API risk, privacy constraints, data quality assumptions, or dependencies.
+ label: Заметки и риски
+ description: API-риск, privacy-ограничения, предположения о качестве данных или зависимости.
diff --git a/.github/ISSUE_TEMPLATE/data_quality.yml b/.github/ISSUE_TEMPLATE/data_quality.yml
index 8e092fe..2a6f616 100644
--- a/.github/ISSUE_TEMPLATE/data_quality.yml
+++ b/.github/ISSUE_TEMPLATE/data_quality.yml
@@ -1,12 +1,12 @@
-name: Data quality issue
-description: Report a raw, dbt, dashboard, or readiness quality issue.
+name: Проблема качества данных
+description: Ошибка в raw, dbt, dashboard или readiness-проверках.
title: "[DQ] "
labels: ["data-quality", "triage"]
body:
- type: dropdown
id: layer
attributes:
- label: Affected layer
+ label: Слой данных
options:
- Raw/Bronze
- Silver/dbt staging
@@ -18,20 +18,20 @@ body:
- type: textarea
id: symptom
attributes:
- label: Symptom
- description: What failed or looked wrong?
+ label: Симптом
+ description: Что упало или выглядит неверно?
validations:
required: true
- type: textarea
id: evidence
attributes:
- label: Evidence
- description: Command output, table/model name, manifest counts, or screenshot notes. Do not paste tokens or raw private data.
+ label: Доказательства
+ description: Вывод команды, имя таблицы/модели, manifest counts или заметки по скриншоту. Не вставляйте токены и приватные raw-данные.
validations:
required: true
- type: textarea
id: expected
attributes:
- label: Expected behavior
+ label: Ожидаемое поведение
validations:
required: true
diff --git a/.github/ISSUE_TEMPLATE/product_request.yml b/.github/ISSUE_TEMPLATE/product_request.yml
index eb564e9..3cdba50 100644
--- a/.github/ISSUE_TEMPLATE/product_request.yml
+++ b/.github/ISSUE_TEMPLATE/product_request.yml
@@ -1,26 +1,26 @@
-name: Product request
-description: Propose a user-facing analytics or workflow improvement.
-title: "[Product] "
+name: Продуктовая идея
+description: Предложение для аналитики, интерфейса или пользовательского сценария.
+title: "[Продукт] "
labels: ["product", "triage"]
body:
- type: textarea
id: user_value
attributes:
- label: Product value
- description: What decision or action should this help a listener take?
+ label: Польза
+ description: Какое решение или действие это поможет принять слушателю?
validations:
required: true
- type: textarea
id: data
attributes:
- label: Data needed
- description: Which Yandex Music metadata, marts, or dashboard views are involved?
+ label: Нужные данные
+ description: Какие метаданные Яндекс Музыки, марты или экраны dashboard нужны?
validations:
required: true
- type: textarea
id: acceptance
attributes:
- label: Acceptance checks
+ label: Проверка готовности
placeholder: |
- make product-answers-smoke
- make dashboard-smoke
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 0c7d3d1..6c313b0 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,25 +1,25 @@
-## Summary
+## Что изменилось
-
-## Product Value
+## Продуктовая ценность
-
-## Data Engineering Impact
+## Влияние на данные
- Raw/Bronze:
- Silver/dbt:
- Gold/dashboard:
- Privacy/security:
-## Checks
+## Проверки
- [ ] `make test`
- [ ] `make acceptance-local`
-- [ ] `make acceptance-real` when changing real-account ingestion
-- [ ] No `.env`, raw Yandex Music data, DuckDB files, or audio artifacts are tracked
+- [ ] `make acceptance-real`, если менялся ingestion реального аккаунта
+- [ ] В git не попали `.env`, raw-данные Яндекс Музыки, DuckDB-файлы или audio artifacts
-## Notes
+## Заметки
-
diff --git a/README.md b/README.md
index edd6546..7b951a8 100644
--- a/README.md
+++ b/README.md
@@ -1,35 +1,32 @@
# Streamify
-Локальная self-analytics платформа для вашей Яндекс Музыки.
+**Личная аналитика Яндекс Музыки, которая живет на вашем ноутбуке.**
-Streamify превращает музыкальную библиотеку в воспроизводимый локальный lakehouse: сырые JSONL-метаданные, DuckDB/dbt-марты, Streamlit dashboard, Markdown-отчет, JSON snapshot, CSV-очереди действий и GitHub Pages документацию. Проект хранит только метаданные и производные аналитические признаки. Аудио не скачивается, не сохраняется и не воспроизводится.
+Streamify забирает только метаданные вашей музыкальной библиотеки, складывает их в локальный DuckDB lakehouse и превращает в понятный дашборд: любимые артисты, жанровые сдвиги, пересечения плейлистов, треки для rediscovery, качество данных и готовность к будущим картам.
-## Зачем Это Нужно
+Аудио не скачивается, не хранится и не воспроизводится. Токен остается в локальном `.env`, а приватные raw-данные, DuckDB-файлы, snapshots и рекомендации не попадают в git.
-Streamify отвечает на прикладные вопросы о личной музыкальной библиотеке:
-
-- какие артисты, треки и жанры реально доминируют;
-- как меняется вкус по месяцам, эпохам релизов и жанровым периодам;
-- какие любимые треки недопредставлены в плейлистах и стоят повторного открытия;
-- какие плейлисты пересекаются, выделяются или требуют чистки;
-- насколько полные, свежие и надежные локальные данные;
-- какие данные нужны для будущих карт прослушивания без опасных догадок о геолокации.
+
-Дашборд построен как продуктовый аналитический интерфейс, а не набор таблиц: `Story`, `Taste Map`, `Atlas`, `Mix Shift`, `Rediscovery`, `Playlists`, `Explorer`, `Actions`, `Data Quality`.
+## Что дает продукт
-## Демонстрация Дашборда
+- Видно, какие артисты, треки и жанры реально доминируют.
+- Можно понять, как меняется вкус по месяцам, эпохам релизов и жанровым периодам.
+- Есть очереди действий: что переслушать, какие плейлисты почистить, какие находки не потерять.
+- Есть инженерный контур: raw JSONL, DuckDB/dbt, lineage, quality gates, JSON snapshot и CSV exports.
+- Есть честный geo-контракт: Streamify не выдумывает местоположение, а готовит слой для явных enrichment-файлов.
-Скриншоты ниже собираются из локального sample-прогона и не содержат приватных данных аккаунта.
+## Дашборд
-
+Интерфейс не сводится к таблицам. Основные экраны: `Story`, `Taste Map`, `Atlas`, `Mix Shift`, `Rediscovery`, `Playlists`, `Explorer`, `Actions`, `Data Quality`.


-## Быстрый Локальный Запуск
+## Быстрый запуск
-Запуск на sample-данных без токена:
+Sample-режим без токена:
```bash
cp .env.example .env
@@ -38,11 +35,9 @@ make acceptance-local
make dashboard
```
-После этого откройте URL, который напечатает `make dashboard`.
+После запуска откройте URL, который напечатает `make dashboard`.
-Docker Compose использует профиль `local`, а `.env.example` задает `DBT_THREADS=1`, чтобы сборка была предсказуемой на ноутбуке. Make-команды загружают `.env` через `scripts/run_with_dotenv.py`, поэтому секреты передаются через environment, а не парсятся Makefile.
-
-Запуск на вашей Яндекс Музыке:
+Режим с вашей Яндекс Музыкой:
```bash
cp .env.example .env
@@ -52,53 +47,50 @@ make acceptance-real
make dashboard
```
-`make acceptance-real` падает, если последний manifest не доказывает `source=yandex_music`.
+`make acceptance-real` требует, чтобы последний manifest доказывал `source=yandex_music`. Docker Compose использует профиль `local`; `.env.example` задает `DBT_THREADS=1`, а команды запускаются через `scripts/run_with_dotenv.py`, чтобы Makefile не парсил секреты.
-## Основные Команды
+## Команды
```bash
make help # карта команд
-make status # безопасная диагностика локального состояния
-make token-help # подсказка по токену без печати секретов
-make ingest # ingestion метаданных реального аккаунта
+make status # безопасная диагностика без вывода токена
make ingest-sample # детерминированные sample-данные
+make ingest # ingestion метаданных реального аккаунта
make raw-contract # проверка raw JSONL и manifest
-make dbt-build # локальные DuckDB/dbt-марты
-make report # Markdown summary, JSON snapshot, CSV queues
-make snapshot # только JSON snapshot
-make recommendations # только CSV action queues
-make readiness-real # требовать source=yandex_music
-make dashboard-smoke # Streamlit content + HTTP smoke
-make pages-site # статический GitHub Pages сайт в public/
-make test # полный локальный quality gate
-make up-local # Docker Compose local product profile
-make compose-smoke-real # Docker Compose smoke с настроенным токеном
-make clean-local # удалить локально сгенерированные артефакты
+make dbt-build # DuckDB/dbt-марты
+make report # Markdown-отчет
+make snapshot # data/streamify_snapshot.json
+make recommendations # data/recommendations/*.csv
+make dashboard # Streamlit dashboard
+make pages-site # GitHub Pages сайт в public/
+make up-local # Docker Compose local profile
+make compose-smoke-real # smoke с реальным токеном
+make readiness-real # проверка source=yandex_music
+make clean-local # очистка локальных артефактов
+make test # полный локальный gate
```
-## Локальные Артефакты
-
-- Raw metadata: `data/raw/yamusic/*.jsonl`
-- DuckDB warehouse: `data/streamify.duckdb`
-- Markdown report: `data/streamify_summary.md`
-- JSON snapshot: `data/streamify_snapshot.json`
-- CSV action queues: `data/recommendations/*.csv`
-- Optional enrichment inputs: `data/enrichment/*.csv`
-
-Все локально сгенерированные артефакты игнорируются git. `.env` тоже игнорируется и не должен попадать в коммиты.
-
-`make clean-local` удаляет raw data, отчеты, DuckDB-файлы и dbt `target`/`logs`/`dbt_packages`, но не трогает `.env`.
-
-## Архитектура Данных
+## Архитектура
```text
Метаданные Яндекс Музыки
-> yamusic_ingest raw JSONL
-> dbt staging views
-> DuckDB marts
- -> Streamlit dashboard, reports, snapshots, recommendation queues
+ -> Streamlit dashboard, report, snapshot, recommendation queues
```
+Локальные артефакты:
+
+- `data/raw/yamusic/*.jsonl`
+- `data/streamify.duckdb`
+- `data/streamify_summary.md`
+- `data/streamify_snapshot.json`
+- `data/recommendations/*.csv`
+- `data/enrichment/*.csv`
+
+`make clean-local` удаляет raw data, отчеты, DuckDB-файлы и dbt `target`/`logs`/`dbt_packages`, но не трогает `.env`.
+
Ключевые марты:
- `yamusic_dim_tracks`, `yamusic_dim_artists`, `yamusic_dim_albums`, `yamusic_dim_playlists`
@@ -109,55 +101,34 @@ make clean-local # удалить локально сгенериров
Lineage описан в [docs/yamusic_lineage.md](docs/yamusic_lineage.md).
-## Что Показывает Дашборд
-
-- `Story`: профиль библиотеки, timeline активности и жанровый отпечаток.
-- `Taste Map`: гравитация артистов и разнообразие жанров.
-- `Atlas`: genre atlas, monthly rhythm, music time travel, playlist subway, playlist DNA и Geo Atlas readiness.
-- `Mix Shift`: жанровая heatmap, release-era mix и focus genre mix.
-- `Rediscovery`: любимые треки, которые мало представлены в плейлистах.
-- `Playlists`: здоровье плейлистов и overlap.
-- `Explorer`: фильтруемые карточки треков и точечный поиск.
-- `Actions`: следующие действия и downloadable queues.
-- `Data Quality`: source, raw counts, checksums и ingestion diagnostics.
+## География и карты
-## География И Карты
+Метаданные Яндекс Музыки не содержат надежную геолокацию прослушивания. Streamify не считает регион аккаунта, язык плейлиста, жанр или происхождение артиста вашим местоположением.
-Метаданные Яндекс Музыки не содержат надежную геолокацию прослушивания. Streamify не делает вид, что регион аккаунта, язык плейлиста, жанр или происхождение артиста равны вашему местоположению.
+Будущие карты требуют явных локальных enrichment-файлов:
-Будущие карты требуют явных локальных enrichment-файлов в `data/enrichment`:
+- `artist_locations.csv` — места, связанные с артистами;
+- `user_location_events.csv` — пользовательская timeline геолокации.
-- `artist_locations.csv`: места, связанные с артистами;
-- `user_location_events.csv`: пользовательская timeline геолокации.
-
-Схемы, источники и privacy-ограничения описаны в [docs/location_enrichment.md](docs/location_enrichment.md).
+Контракт описан в [docs/location_enrichment.md](docs/location_enrichment.md).
## GitHub Pages
-`make pages-site` собирает современный русскоязычный статический сайт в `public/`. Workflow Pages строит его на sample metadata с пустым `YANDEX_MUSIC_TOKEN`, поэтому публичная документация воспроизводима и не зависит от приватного аккаунта.
-
-Публичный сайт включает:
+`make pages-site` собирает русскоязычную продуктовую документацию в `public/`. Pages строятся на sample metadata с пустым `YANDEX_MUSIC_TOKEN`, поэтому публичный сайт воспроизводим и не зависит от приватного аккаунта.
-- продуктовый overview;
-- локальный runbook;
-- демонстрации dashboard;
-- Atlas и location enrichment;
-- lineage;
-- acceptance matrix;
-- release process;
-- generated sample summary, если он доступен.
+На сайте есть overview, runbook, демонстрации dashboard, Atlas + geo, lineage, acceptance matrix, release process и sample summary.
-## Quality Gates
+## Проверки качества
-`make test` запускает локальный product gate:
+`make test` проверяет:
-- repository contract validation;
-- secret/audio artifact guards;
-- empty/private account dbt smoke;
+- контракт репозитория;
+- защиту от секретов и аудио-артефактов;
+- dbt smoke для пустого/закрытого аккаунта;
- sample acceptance flow;
-- product-answer smoke;
-- real-account gate smoke;
-- Pages build;
+- smoke для продуктовых ответов;
+- smoke для real-account gate;
+- сборку Pages;
- Python compile checks;
- pytest;
- Docker Compose config и local profile smoke.
@@ -165,8 +136,8 @@ Lineage описан в [docs/yamusic_lineage.md](docs/yamusic_lineage.md).
## Документация
- [Локальный runbook](docs/yandex_music_local.md)
-- [Lineage](docs/yamusic_lineage.md)
-- [Product acceptance](docs/product_acceptance.md)
-- [Location enrichment contract](docs/location_enrichment.md)
-- [Project management](docs/project_management.md)
-- [Release process](docs/release_process.md)
+- [Lineage данных](docs/yamusic_lineage.md)
+- [Приемка продукта](docs/product_acceptance.md)
+- [Контракт гео-обогащения](docs/location_enrichment.md)
+- [Управление проектом](docs/project_management.md)
+- [Процесс релиза](docs/release_process.md)
diff --git a/dbt/README.md b/dbt/README.md
index e3a82a7..4dc2bbe 100644
--- a/dbt/README.md
+++ b/dbt/README.md
@@ -1,25 +1,25 @@
-# Streamify dbt Models
+# dbt-модели Streamify
-This dbt project builds local DuckDB marts for Yandex Music self-analytics.
+Этот dbt-проект собирает локальные DuckDB-марты для self-analytics Яндекс Музыки.
-## Run
+## Запуск
```bash
dbt deps
dbt build --profiles-dir . --target local --select yamusic
```
-For normal local work, run from the repository root:
+Для обычной локальной работы запускайте из корня репозитория:
```bash
make dbt-build
```
-## Model Shape
+## Состав моделей
- `staging/stg_yamusic_*`: typed views over raw JSONL metadata and the ingestion manifest.
- `marts/yamusic_dim_*`: track, artist, album and playlist dimensions.
- `marts/yamusic_fact_*`: library events and playlist-track membership.
- `marts/yamusic_*_signals`: practical self-analytics for affinity, repeats, genres, periods, playlists and library health.
-The local profile writes to `data/streamify.duckdb` by default and reads raw metadata from `data/raw/yamusic`.
+Локальный профиль по умолчанию пишет в `data/streamify.duckdb` и читает raw metadata из `data/raw/yamusic`.
diff --git a/docs/location_enrichment.md b/docs/location_enrichment.md
index e59f50b..078f826 100644
--- a/docs/location_enrichment.md
+++ b/docs/location_enrichment.md
@@ -1,37 +1,37 @@
-# Future Location Enrichment Contract
+# Контракт гео-обогащения
-This document sketches a future, optional geo/location data contract for Streamify. It is not part of the current Yandex Music local ingestion path and should not be treated as an implemented feature.
+Этот документ описывает будущий опциональный слой геоданных для Streamify. Он не входит в текущий ingestion Яндекс Музыки и не должен восприниматься как уже реализованная функция.
-## Why Yandex Music Metadata Is Not Enough
+## Почему метаданных Яндекс Музыки недостаточно
-The current Yandex Music adapter reads account-visible metadata through the `yandex-music` Python client: tracks, artists, albums, playlists, playlist membership, liked markers and derived library events. That metadata does not include a stable listening location field.
+Текущий адаптер Яндекс Музыки читает доступные аккаунту метаданные через Python-клиент `yandex-music`: треки, артистов, альбомы, плейлисты, состав плейлистов, лайки и производные события библиотеки. В этих данных нет надежного поля с местом прослушивания.
-Even when timestamps are present, they describe a music library action such as a like, playlist membership or account-visible history item. They do not prove where the user was when listening. Region, catalog availability, account locale, artist country or playlist language are not user location signals.
+Даже если timestamp присутствует, он описывает действие в библиотеке: лайк, добавление в плейлист или доступный аккаунту элемент истории. Он не доказывает, где пользователь находился во время прослушивания. Регион аккаунта, доступность каталога, язык плейлиста, страна артиста или жанр не являются сигналами местоположения пользователя.
-## Safe User Location Sources
+## Безопасные источники местоположения
-Location enrichment must be user-supplied, optional and separable from music ingestion. Possible sources over time:
+Гео-обогащение должно быть явным, опциональным и отделенным от музыкального ingestion. Возможные источники:
-- Google Timeline / Google Takeout Location History, if the user has it enabled and explicitly exports it.
-- iOS Significant Locations, noted as a possible on-device source but not practically exportable for reliable Streamify ingestion.
-- Photo EXIF GPS coordinates, only from user-selected photos and only with explicit consent.
-- Calendar or travel exports, such as flight, hotel, event or trip records that the user explicitly provides.
-- A manual city timeline maintained by the user, for example date ranges such as `2025-06-01` to `2025-06-14` in `Tbilisi, Georgia`.
-- Network or IP logs only if the user explicitly provides them and understands their limits. Streamify should not collect IP logs implicitly.
+- Google Timeline / Google Takeout Location History, если хронология включена и пользователь явно экспортирует данные.
+- GPS в EXIF фотографий, только из выбранных пользователем фото и только с явным согласием.
+- Экспорты календаря и поездок: перелеты, отели, мероприятия, билеты.
+- Ручная city timeline, например интервалы `2025-06-01` - `2025-06-14` в `Tbilisi, Georgia`.
+- Сетевые или IP-логи только если пользователь сам их предоставляет и понимает ограничения точности.
+- iOS Significant Locations можно считать потенциальным on-device источником, но он плохо подходит для надежного экспорта в Streamify.
-Sources that are not safe defaults:
+Что нельзя считать безопасным источником по умолчанию:
-- Inferring user location from artist origin, track language, genre, playlist name or Yandex account region.
-- Scraping device, browser or network location without a deliberate user import step.
-- Treating coarse country or IP geolocation as precise movement history.
+- выводить местоположение пользователя из страны артиста, языка трека, жанра, названия плейлиста или региона аккаунта;
+- собирать location из устройства, браузера или сети без отдельного шага импорта;
+- считать грубую IP-геолокацию точной историей перемещений.
## `user_location_events`
`user_location_events` should represent where the user may have been during a time interval, with confidence and provenance.
-Suggested fields:
+Рекомендуемые поля:
-| Field | Type | Notes |
+| Поле | Тип | Примечания |
| --- | --- | --- |
| `location_event_id` | string | Stable hash of source, source row id and normalized time interval. |
| `source` | string | `google_takeout`, `photo_exif`, `calendar_travel`, `manual_city_timeline`, `network_ip_log`, or another explicit import source. |
@@ -54,11 +54,11 @@ The table should allow overlapping rows because real-world sources conflict. Dow
## `artist_locations`
-`artist_locations` should describe artist-associated places, not user listening places. It can support questions such as geographic diversity of artists, but it must never be used as evidence of where the user listened.
+`artist_locations` описывает места, связанные с артистами, а не места прослушивания пользователя. Таблица может отвечать на вопросы о географическом разнообразии артистов, но не должна использоваться как доказательство того, где пользователь слушал музыку.
-Suggested fields:
+Рекомендуемые поля:
-| Field | Type | Notes |
+| Поле | Тип | Примечания |
| --- | --- | --- |
| `artist_location_id` | string | Stable hash of artist id, source and normalized location. |
| `artist_id` | string | Streamify/Yandex artist identifier when available. |
@@ -76,7 +76,7 @@ Suggested fields:
| `confidence` | double | `0.0` to `1.0`; biographies and crowd-sourced sources require care. |
| `notes` | string | Optional caveat for ambiguous or multi-location artists. |
-## Joining Location To Library Events
+## Join геоданных с музыкальными событиями
The future join should be timestamp-based and explicit about uncertainty:
diff --git a/docs/product_acceptance.md b/docs/product_acceptance.md
index a7041b5..c5e304e 100644
--- a/docs/product_acceptance.md
+++ b/docs/product_acceptance.md
@@ -1,10 +1,10 @@
-# Streamify Local Product Acceptance
+# Приемка локального продукта
-This document maps the MVP requirements to concrete repository artifacts and verification commands. The local product is considered ready for sample metadata after `make test` passes. It is considered ready for a real account only after `make acceptance-real` passes with a valid `YANDEX_MUSIC_TOKEN`.
+Этот документ связывает требования MVP с файлами репозитория и командами проверки. Локальный продукт считается готовым на sample-данных после успешного `make test`. Реальный аккаунт считается проверенным только после `make acceptance-real` с валидным `YANDEX_MUSIC_TOKEN`.
-## Requirement Matrix
+## Матрица требований
-| Requirement | Implementation Evidence | Verification |
+| Требование | Где реализовано | Проверка |
| --- | --- | --- |
| Fully local run without cloud services | `Makefile`, `docker-compose.local.yml`, `dbt/profiles.yml` local DuckDB target | `make acceptance-local`, `make compose-smoke-local` |
| Local operator entrypoint | `make help` lists sample, real-account, Docker, export, readiness and cleanup commands | `make help` |
@@ -21,14 +21,14 @@ This document maps the MVP requirements to concrete repository artifacts and ver
| Empty/private account handling | typed empty raw files and empty dbt smoke | `scripts/smoke_empty_yamusic_dbt.py`, `make test` |
| Token safety | `.env.example`, `.gitignore`, no token in manifest/report/status, preflight without raw writes | `make status`, `make preflight`, `scripts/check_no_local_sensitive_artifacts.py` |
-## Current Acceptance Status
+## Текущий статус приемки
-Sample metadata path:
+Путь на sample metadata:
- `make acceptance-local` proves local ingestion, raw contract, DuckDB/dbt marts, doctor, report, readiness and dashboard startup.
- `make test` proves static contracts, safety guards, empty-account handling, sample acceptance, Python unit tests, Compose config and Compose local smoke.
-Real account path:
+Путь на реальном аккаунте:
- A valid `YANDEX_MUSIC_TOKEN` in `.env` is still required to prove real-account ingestion.
- The final real-account command is:
@@ -40,9 +40,9 @@ make dashboard
The readiness audit must report `"real_account_verified": true` before the real-account MVP is considered proven. `make acceptance-real` enforces this through `make readiness-real`, which runs `scripts/audit_yamusic_readiness.py --require-real`.
-## Product Answers Covered
+## Продуктовые вопросы
-| User question | Primary artifact |
+| Вопрос пользователя | Основной артефакт |
| --- | --- |
| Who are my strongest artists? | `yamusic_artist_affinity`, dashboard Artists tab |
| Which tracks repeat across library contexts? | `yamusic_track_signals.repeat_signal`, dashboard Tracks tab |
@@ -56,7 +56,7 @@ The readiness audit must report `"real_account_verified": true` before the real-
| Is my local data trustworthy? | dashboard Data Quality tab, JSON snapshot quality block, `make doctor`, `make readiness` |
| Can Streamify analyze music by where I was? | Future-only contract in `docs/location_enrichment.md`; not part of current acceptance |
-## Known Product Limits
+## Известные ограничения
- Yandex Music availability depends on the unofficial `yandex-music` package and account-visible metadata.
- Listening timestamps/history are used only when exposed by the account/API response; otherwise the product falls back to liked-track and playlist-membership events.
diff --git a/docs/project_management.md b/docs/project_management.md
index feb2613..060f647 100644
--- a/docs/project_management.md
+++ b/docs/project_management.md
@@ -1,10 +1,10 @@
-# Streamify Project Management
+# Управление проектом Streamify
-Streamify work is tracked as agent-sized GitHub issues. Each issue should define the product outcome, the owning lane, and concrete acceptance evidence.
+Работа по Streamify ведется через задачи размером под одного агента. В каждой задаче должны быть продуктовый результат, ответственное направление и конкретные доказательства готовности.
-## Agent Lanes
+## Направления агентов
-| Lane | Scope | Default proof |
+| Направление | Зона ответственности | Проверка по умолчанию |
| --- | --- | --- |
| Repo/Build | Local setup, Docker Compose, Makefile, CI, release automation | `make test`, `make compose-smoke-local` |
| Yandex Ingestion | Token-safe metadata ingestion, raw contracts, API edge cases | `make preflight`, `make ingest`, `make raw-contract` |
@@ -12,7 +12,7 @@ Streamify work is tracked as agent-sized GitHub issues. Each issue should define
| Product/Dashboard | Streamlit UX, exports, product docs, answer quality | `make dashboard-smoke`, `make product-answers-smoke` |
| QA/Integration | End-to-end acceptance, privacy gates, release readiness | `make acceptance-local`, `make acceptance-real` |
-## Working Rules
+## Правила работы
- Do not paste tokens, raw account data, DuckDB files, or screenshots with private listening data into issues.
- Use sample metadata for CI, GitHub Pages, and public release artifacts.
@@ -20,7 +20,7 @@ Streamify work is tracked as agent-sized GitHub issues. Each issue should define
- Every PR should update tests or explain why the existing gates prove the change.
- Release candidates should link the GitHub issue set they close and include known API limitations.
-## Suggested Milestones
+## План релизов
1. `v0.1.0-local-mvp`: real-account metadata ingestion, DuckDB marts, dashboard, safety gates.
2. `v0.2.0-product-answers`: richer action queues, more dashboard filters, better empty/error states.
diff --git a/docs/release_process.md b/docs/release_process.md
index 9ae023d..787d496 100644
--- a/docs/release_process.md
+++ b/docs/release_process.md
@@ -1,8 +1,8 @@
-# Release Process
+# Процесс релиза
-Streamify releases are source-first and privacy-safe. Public artifacts must never include `.env`, real raw Yandex Music exports, DuckDB files, account snapshots, recommendation CSVs from a real account, or audio.
+Релизы Streamify публикуют исходный код и sample-артефакты, но не приватные данные. В публичные артефакты не должны попадать `.env`, real raw exports Яндекс Музыки, DuckDB-файлы, snapshots аккаунта, CSV-рекомендации реального аккаунта или audio.
-## Release Checklist
+## Чеклист релиза
1. Open or update GitHub issues for the agent lanes included in the release.
2. Run `make test` locally.
@@ -16,8 +16,8 @@ git tag vX.Y.Z
git push origin vX.Y.Z
```
-The `Release` workflow runs sample-data validation, builds public docs, packages tracked source via `git archive`, and creates a GitHub release from the tag notes.
+Workflow `Release` прогоняет проверку на sample-данных, собирает публичную документацию, упаковывает tracked source через `git archive` и создает GitHub release из release notes.
## GitHub Pages
-The `GitHub Pages` workflow builds a static product site from README/docs plus sample metadata report artifacts. It intentionally clears `YANDEX_MUSIC_TOKEN` so public pages are reproducible and do not depend on a private account.
+Workflow `GitHub Pages` собирает статический продуктовый сайт из README/docs и sample-отчета. Он намеренно очищает `YANDEX_MUSIC_TOKEN`, чтобы публичные страницы были воспроизводимыми и не зависели от приватного аккаунта.
diff --git a/docs/releases/v0.1.0.md b/docs/releases/v0.1.0.md
index edf3250..5703171 100644
--- a/docs/releases/v0.1.0.md
+++ b/docs/releases/v0.1.0.md
@@ -1,10 +1,10 @@
-# Streamify v0.1.0 Local MVP
+# Streamify v0.1.0: локальный MVP
-## Product Value
+## Продуктовая ценность
-Streamify turns a Yandex Music account into a local, reproducible self-analytics lakehouse. It answers practical questions about favorite artists and tracks, genre shifts, playlist overlap, repeated patterns, diversity, active periods, underrated tracks and playlists, data quality, and missing metadata.
+Streamify превращает аккаунт Яндекс Музыки в локальный воспроизводимый self-analytics lakehouse. Он отвечает на практичные вопросы о любимых артистах и треках, жанровых сдвигах, пересечениях плейлистов, повторах, разнообразии, активных периодах, недооцененных треках и качестве данных.
-## Highlights
+## Главное
- Metadata-only Yandex Music ingestion with token-safe `.env` handling.
- Local raw JSONL/optional Parquet outputs with manifest row counts and SHA256 checksums.
@@ -14,7 +14,7 @@ Streamify turns a Yandex Music account into a local, reproducible self-analytics
- Docker Compose local profile and sample-data CI path.
- Privacy gates preventing `.env`, raw account data, DuckDB files, recommendations, and audio from being tracked.
-## Known Limitations
+## Ограничения
- Live Yandex Music ingestion uses an unofficial client library and can break if Yandex changes private endpoints.
- Full listening history is only available where the client/API exposes usable timestamps; otherwise analytics are based on library, likes, playlists, and metadata.
diff --git a/docs/yamusic_lineage.md b/docs/yamusic_lineage.md
index 399df23..135617b 100644
--- a/docs/yamusic_lineage.md
+++ b/docs/yamusic_lineage.md
@@ -1,10 +1,10 @@
-# Yandex Music Local Lineage
+# Lineage локальной Яндекс Музыки
-This catalog documents the local metadata-only data path. Streamify does not download, store, transform, or play audio.
+Этот каталог описывает локальный путь данных только по метаданным. Streamify не скачивает, не хранит, не преобразует и не воспроизводит аудио.
-## Layer Map
+## Карта слоев
-| Layer | Artifact | Purpose |
+| Слой | Артефакт | Назначение |
| --- | --- | --- |
| Raw/Bronze | `data/raw/yamusic/tracks.jsonl` | Track metadata, album fields, artist arrays, liked flag, source and ingestion timestamp. |
| Raw/Bronze | `data/raw/yamusic/artists.jsonl` | Artist metadata discovered from tracks and account-visible liked artists. |
@@ -102,9 +102,9 @@ flowchart LR
track_signals --> recommendations
```
-## Product Questions
+## Продуктовые вопросы
-| Product question | Primary model | Supporting models |
+| Продуктовый вопрос | Основная модель | Вспомогательные модели |
| --- | --- | --- |
| Favorite artists | `yamusic_artist_affinity` | `yamusic_dim_tracks`, `yamusic_fact_playlist_tracks` |
| Favorite tracks | `yamusic_dim_tracks` | `yamusic_track_signals` |
@@ -116,7 +116,7 @@ flowchart LR
| Underrated playlists | `yamusic_playlist_signals.underrated_playlist_flag` | `yamusic_playlist_overlap`, `yamusic_dim_playlists` |
| Data freshness | `yamusic_library_profile.stale_ingestion_flag` | `yamusic_fact_library_events` |
-## Quality Gates
+## Quality gates
| Gate | Command | What it proves |
| --- | --- | --- |
diff --git a/docs/yandex_music_local.md b/docs/yandex_music_local.md
index 6fbe7f8..d14dcb1 100644
--- a/docs/yandex_music_local.md
+++ b/docs/yandex_music_local.md
@@ -1,10 +1,10 @@
-# Local Yandex Music Analytics
+# Локальная аналитика Яндекс Музыки
-Streamify builds a personal music self-analytics lakehouse from Yandex Music metadata. It is designed to run on a laptop without cloud credentials or cloud spend.
+Streamify собирает личный music self-analytics lakehouse из метаданных Яндекс Музыки. Он рассчитан на запуск на ноутбуке, без cloud credentials и облачных расходов.
-No audio is downloaded, stored, transformed, or played by this project. The ingestion adapter only reads metadata exposed to the account by the Yandex Music client library.
+Проект не скачивает, не хранит, не преобразует и не воспроизводит аудио. Ingestion-адаптер читает только метаданные, доступные аккаунту через клиентскую библиотеку Яндекс Музыки.
-## Quick Start
+## Быстрый старт
```bash
cp .env.example .env
@@ -28,7 +28,7 @@ make acceptance-real
make dashboard
```
-## Yandex Music Token
+## Токен Яндекс Музыки
Streamify does not ask for your Yandex password and does not fetch a token by itself. The installed `yandex-music` Python client accepts an existing OAuth token through `Client(token).init()`, but version 2.2.0 does not expose a `device_auth` helper.
@@ -40,7 +40,7 @@ Use an external Yandex Music OAuth token helper, then paste only the resulting t
YANDEX_MUSIC_TOKEN=your_oauth_token_here
```
-Known community helper:
+Известный community helper:
- `https://github.com/MarshalX/yandex-music-token`
@@ -97,7 +97,7 @@ For normal use, prefer `make dbt-build`: it runs `dbt deps` first, so a fresh ch
See [Yandex Music Local Lineage](yamusic_lineage.md) for the raw-to-dashboard model catalog and product-question mapping.
-## Real Account Acceptance Check
+## Проверка реального аккаунта
After setting `YANDEX_MUSIC_TOKEN`, a successful real-account run should satisfy this checklist:
@@ -122,7 +122,7 @@ Empty/private accounts are a supported state. The dbt smoke test builds against
- `_manifest.json`: source, generated timestamp, adapter/client metadata, row counts, JSONL checksums and output paths; it must not contain token material.
- `_manifest.json.diagnostics`: aggregate skip/fallback counters for liked-track hydration, liked album/artist/playlist metadata, playlist hydration, playlist-track hydration, missing IDs, duplicate liked shortcuts and duplicate playlist-track memberships; these are counts only and do not store skipped object identifiers. A `*_fetch_failed` counter means full metadata enrichment failed after retries, not necessarily that the library row was dropped.
-## Product Answers
+## Продуктовые ответы
The local marts are designed around practical self-analytics questions:
@@ -144,7 +144,7 @@ Location-aware analytics are intentionally out of scope for the current Yandex M
- `data/streamify_snapshot.json`: schema-versioned JSON with profile metrics, raw counts, ingestion diagnostics, favorite artists/tracks, genre shifts, active periods, repeat tracks, playlist overlap, underrated candidates and next actions.
- `data/recommendations/*.csv`: spreadsheet-friendly exports for top artists, rediscovery tracks, playlist cleanup, standout playlists and genre shifts.
-## Data Quality
+## Качество данных
The local dbt layer checks:
diff --git a/scripts/build_pages_site.py b/scripts/build_pages_site.py
index e3c7e70..cad6a08 100644
--- a/scripts/build_pages_site.py
+++ b/scripts/build_pages_site.py
@@ -44,12 +44,13 @@ class Page:
PAGES = [
Page("Главная", ROOT / "README.md", "index.html", "Что это за продукт, как запустить и какую пользу он дает.", "overview"),
- Page("Запуск", ROOT / "docs" / "yandex_music_local.md", "runbook.html", "Токен, локальный запуск, sample path и real-account acceptance.", "runbook"),
+ Page("Запуск", ROOT / "docs" / "yandex_music_local.md", "runbook.html", "Токен, локальный запуск, sample-режим и приемка реального аккаунта.", "runbook"),
Page("Дашборд", None, "dashboard.html", "Скриншоты и ключевые экраны Streamlit-интерфейса.", "dashboard"),
Page("Atlas + Гео", ROOT / "docs" / "location_enrichment.md", "location.html", "Как готовить будущие карты без опасных догадок о местоположении.", "atlas"),
- Page("Поток данных", ROOT / "docs" / "yamusic_lineage.md", "lineage.html", "Raw, staging, marts и dashboard data flow.", "lineage"),
+ Page("Поток данных", ROOT / "docs" / "yamusic_lineage.md", "lineage.html", "Raw, staging, marts и поток данных до дашборда.", "lineage"),
Page("Проверка", ROOT / "docs" / "product_acceptance.md", "acceptance.html", "Требования MVP и команды, которые их доказывают.", "quality"),
- Page("Релизы", ROOT / "docs" / "release_process.md", "release.html", "Privacy-safe release flow и GitHub Pages workflow.", "release"),
+ Page("Проект", ROOT / "docs" / "project_management.md", "project.html", "Направления агентов, правила работы и план релизов.", "project"),
+ Page("Релизы", ROOT / "docs" / "release_process.md", "release.html", "Privacy-safe релизы и GitHub Pages workflow.", "release"),
Page("Пример отчета", ROOT / "data" / "streamify_summary.md", "sample-summary.html", "Пример инсайтов, собранный на sample metadata.", "summary"),
]
@@ -62,11 +63,36 @@ def page_markdown(page: Page) -> str:
return f"# {page.title}\n\nЗапустите `make report`, чтобы собрать эту страницу на sample metadata."
+DOC_LINKS = {
+ "docs/yandex_music_local.md": "runbook.html",
+ "docs/yamusic_lineage.md": "lineage.html",
+ "docs/product_acceptance.md": "acceptance.html",
+ "docs/location_enrichment.md": "location.html",
+ "docs/release_process.md": "release.html",
+ "docs/project_management.md": "project.html",
+ "data/streamify_summary.md": "sample-summary.html",
+}
+
+
+def rewrite_href(href: str) -> str:
+ normalized = href.lstrip("./")
+ if normalized in DOC_LINKS:
+ return DOC_LINKS[normalized]
+ if normalized.startswith("docs/releases/") and normalized.endswith(".md"):
+ return "release.html"
+ return href
+
+
+def link_to_html(match: re.Match[str]) -> str:
+ label, href = match.groups()
+ return f'{label}'
+
+
def inline_markdown(text: str) -> str:
text = escape(text)
text = re.sub(r"`([^`]+)`", r"\1", text)
text = re.sub(r"\*\*([^*]+)\*\*", r"\1", text)
- text = re.sub(r"\[([^\]]+)\]\(([^)]+)\)", r'\1', text)
+ text = re.sub(r"\[([^\]]+)\]\(([^)]+)\)", link_to_html, text)
return text
diff --git a/scripts/validate_yamusic_local.py b/scripts/validate_yamusic_local.py
index f8b2b25..fb25746 100644
--- a/scripts/validate_yamusic_local.py
+++ b/scripts/validate_yamusic_local.py
@@ -130,22 +130,22 @@ def main() -> int:
require_markers(
"README.md",
- ["Яндекс Музыки", "DuckDB", "make help", "make status", "make ingest-sample", "make acceptance-real", "make dashboard", "как меняется вкус", "`local`", "DBT_THREADS=1", "scripts/run_with_dotenv.py", "make clean-local", "dbt `target`/`logs`/`dbt_packages`", "make readiness-real", "make up-local", "make compose-smoke-real", "make snapshot", "make recommendations", "make pages-site", "GitHub Pages", "streamify_snapshot.json", "data/recommendations", "География И Карты", "Atlas", "docs/assets/dashboard-story.png", "docs/assets/dashboard-atlas.png", "docs/assets/dashboard-actions.png"],
+ ["Яндекс Музыки", "DuckDB", "make help", "make status", "make ingest-sample", "make acceptance-real", "make dashboard", "как меняется вкус", "`local`", "DBT_THREADS=1", "scripts/run_with_dotenv.py", "make clean-local", "dbt `target`/`logs`/`dbt_packages`", "make readiness-real", "make up-local", "make compose-smoke-real", "make snapshot", "make recommendations", "make pages-site", "GitHub Pages", "streamify_snapshot.json", "data/recommendations", "География и карты", "Atlas", "docs/assets/dashboard-story.png", "docs/assets/dashboard-atlas.png", "docs/assets/dashboard-actions.png"],
)
require_markers(
"docs/yandex_music_local.md",
- ["YANDEX_MUSIC_TOKEN", "make acceptance-real", "make status", "make token-help", "make compose-smoke-real", "built-in device auth", "bounded retries", "dbt build --profiles-dir . --target local", "No audio", "underrated tracks", "Real Account Acceptance Check", "Empty/private accounts", "scripts/run_with_dotenv.py", "make dbt-build", "make up-local", "streamify_empty_smoke", "--require-real", "stale Parquet cleanup", "JSONL sha256 checksums", "ingestion diagnostics", "ingestion diagnostics consistency", "STREAMIFY_SNAPSHOT_PATH", "STREAMIFY_RECOMMENDATIONS_DIR", "streamify_snapshot.json", "data/recommendations", "latest manifest source", "Actions tab"],
+ ["YANDEX_MUSIC_TOKEN", "make acceptance-real", "make status", "make token-help", "make compose-smoke-real", "built-in device auth", "bounded retries", "dbt build --profiles-dir . --target local", "не скачивает", "underrated tracks", "Проверка реального аккаунта", "Empty/private accounts", "scripts/run_with_dotenv.py", "make dbt-build", "make up-local", "streamify_empty_smoke", "--require-real", "stale Parquet cleanup", "JSONL sha256 checksums", "ingestion diagnostics", "ingestion diagnostics consistency", "STREAMIFY_SNAPSHOT_PATH", "STREAMIFY_RECOMMENDATIONS_DIR", "streamify_snapshot.json", "data/recommendations", "latest manifest source", "Actions tab"],
)
require_markers(
"docs/yamusic_lineage.md",
- ["Raw/Bronze", "Silver", "Gold", "liked albums", "liked artists", "liked playlists", "stg_yamusic_manifest", "adapter/client metadata", "diagnostics counters", "JSONL sha256 checksums", "ingestion diagnostics consistency", "yamusic_genre_periods", "Product Questions", "Quality Gates", "make acceptance-real", "referential integrity", "Snapshot export", "JSON snapshot", "Recommendations export"],
+ ["Raw/Bronze", "Silver", "Gold", "liked albums", "liked artists", "liked playlists", "stg_yamusic_manifest", "adapter/client metadata", "diagnostics counters", "JSONL sha256 checksums", "ingestion diagnostics consistency", "yamusic_genre_periods", "Продуктовые вопросы", "Quality gates", "make acceptance-real", "referential integrity", "Snapshot export", "JSON snapshot", "Recommendations export"],
)
require_markers(
"docs/product_acceptance.md",
- ["Requirement Matrix", "make acceptance-local", "make test", "make acceptance-real", "real_account_verified", "No audio", "Yandex Music metadata ingestion", "make readiness-real", "make compose-smoke-real", "make product-answers-smoke", "stale Parquet cleanup", "Source provenance", "data/streamify_snapshot.json", "make snapshot", "data/recommendations/*.csv", "make recommendations", "dashboard Actions tab"],
+ ["Матрица требований", "make acceptance-local", "make test", "make acceptance-real", "real_account_verified", "No audio", "Yandex Music metadata ingestion", "make readiness-real", "make compose-smoke-real", "make product-answers-smoke", "stale Parquet cleanup", "Source provenance", "data/streamify_snapshot.json", "make snapshot", "data/recommendations/*.csv", "make recommendations", "dashboard Actions tab"],
)
require_markers("dbt/profiles.yml", ["type: duckdb", "target: local", "DBT_THREADS"])
- require_markers("dbt/README.md", ["Streamify dbt Models", "Yandex Music self-analytics", "staging/stg_yamusic_*", "marts/yamusic_dim_*", "data/streamify.duckdb"])
+ require_markers("dbt/README.md", ["dbt-модели Streamify", "self-analytics Яндекс Музыки", "staging/stg_yamusic_*", "marts/yamusic_dim_*", "data/streamify.duckdb"])
require_markers(".env.example", ["YANDEX_MUSIC_TOKEN=", "STREAMIFY_REPORT_PATH", "STREAMIFY_SNAPSHOT_PATH", "STREAMIFY_RECOMMENDATIONS_DIR", "STREAMIFY_ENRICHMENT_DIR", "DBT_THREADS=1"])
require_markers(
"dbt/models/yamusic/schema.yml",
@@ -160,10 +160,10 @@ def main() -> int:
require_markers(".github/workflows/data-quality.yml", ["make test", "YANDEX_MUSIC_TOKEN", "DBT_THREADS"])
require_markers(".github/workflows/pages.yml", ["GitHub Pages", "make acceptance-local", "build_pages_site.py", "YANDEX_MUSIC_TOKEN: \"\"", "actions/deploy-pages"])
require_markers(".github/workflows/release.yml", ["Release", "tags:", "make test", "git archive", "gh release create"])
- require_markers(".github/ISSUE_TEMPLATE/agent_task.yml", ["Agent lane", "Repo/Build", "Yandex Ingestion", "Analytics/dbt", "Product/Dashboard", "QA/Integration"])
- require_markers(".github/PULL_REQUEST_TEMPLATE.md", ["Product Value", "Data Engineering Impact", "make test", "make acceptance-real"])
- require_markers("docs/project_management.md", ["Agent Lanes", "Repo/Build", "Yandex Ingestion", "QA/Integration", "v0.1.0-local-mvp"])
- require_markers("docs/release_process.md", ["Release Checklist", "GitHub Pages", "sample metadata", "git tag vX.Y.Z"])
+ require_markers(".github/ISSUE_TEMPLATE/agent_task.yml", ["Направление", "Repo/Build", "Yandex Ingestion", "Analytics/dbt", "Product/Dashboard", "QA/Integration"])
+ require_markers(".github/PULL_REQUEST_TEMPLATE.md", ["Продуктовая ценность", "Влияние на данные", "make test", "make acceptance-real"])
+ require_markers("docs/project_management.md", ["Направления агентов", "Repo/Build", "Yandex Ingestion", "QA/Integration", "v0.1.0-local-mvp"])
+ require_markers("docs/release_process.md", ["Чеклист релиза", "GitHub Pages", "sample-данных", "git tag vX.Y.Z"])
require_markers("scripts/build_pages_site.py", ["PUBLIC_DIR", "Главная", "Дашборд", "Atlas + Гео", "streamify_summary.md", "dashboard.html", "location.html", "hero-visual", "side-link", "media-frame", "inline_markdown", "docs/assets/", "assets/", "index.html"])
require_markers("scripts/yamusic_token_help.py", ["TOKEN_HELPER_URL", "supports_device_auth", "token_configured", "make preflight", "make acceptance-real"])
require_markers("scripts/check_no_local_sensitive_artifacts.py", ["FORBIDDEN_TRACKED_PATHS", "data/raw/yamusic", "DuckDB files", "audio artifacts are tracked"])