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 отвечает на прикладные вопросы о личной музыкальной библиотеке: - -- какие артисты, треки и жанры реально доминируют; -- как меняется вкус по месяцам, эпохам релизов и жанровым периодам; -- какие любимые треки недопредставлены в плейлистах и стоят повторного открытия; -- какие плейлисты пересекаются, выделяются или требуют чистки; -- насколько полные, свежие и надежные локальные данные; -- какие данные нужны для будущих карт прослушивания без опасных догадок о геолокации. +![Обзор Streamify dashboard](docs/assets/dashboard-story.png) -Дашборд построен как продуктовый аналитический интерфейс, а не набор таблиц: `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-прогона и не содержат приватных данных аккаунта. +## Дашборд -![Обзор Streamify dashboard](docs/assets/dashboard-story.png) +Интерфейс не сводится к таблицам. Основные экраны: `Story`, `Taste Map`, `Atlas`, `Mix Shift`, `Rediscovery`, `Playlists`, `Explorer`, `Actions`, `Data Quality`. ![Atlas и визуальные инсайты](docs/assets/dashboard-atlas.png) ![Actions и очереди рекомендаций](docs/assets/dashboard-actions.png) -## Быстрый Локальный Запуск +## Быстрый запуск -Запуск на 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"])