База данных (schema v8)

Один SQLite-файл на все скиллы. 20+ таблиц, junction-механика для multi-project. Все скиллы пишут/читают сюда. Кто owner, кто reader, как scope'ить запросы — здесь.

Где лежит и как открыть

~/video-projects/director.db    # ← одна shared БД для ВСЕХ скиллов и ВСЕХ проектов
БД в корне video-projects/, НЕ внутри папки проекта. Все db.py скрипты резолвят её через os.path.dirname(project_dir) / "director.db". Если случайно есть ~/video-projects/<project>/director.db — это per-project (для отдельных stage6-импортов), и она НЕ читается основными скиллами.

Открыть руками:

sqlite3 ~/video-projects/director.db
sqlite> .tables                                  # список всех таблиц
sqlite> .schema clips                            # схема таблицы clips
sqlite> SELECT COUNT(*) FROM clips;              # количество клипов
sqlite> SELECT * FROM projects;                  # все проекты

Или GUI: DBeaver, TablePlus, DB Browser for SQLite.

Архитектура: ресурсы + junction-таблицы

Главная идея multi-project: один ресурс (например клип) может принадлежать нескольким проектам. Это реализовано через junction-таблицы (many-to-many between projects и ресурсами).

projects project_clips clips

Когда video-analyzer импортирует клип, он:
1. INSERT в clips (если ещё нет)
2. INSERT в project_clips (clip_id, project_id) — линкует к текущему проекту
3. Тот же клип можно использовать в другом проекте — просто ещё одна строка в project_clips

Таблицы (ownership)

ТаблицаOwner (пишет)ReadersЗачем
projectsvideo-project-setupВсеРеестр проектов (id, slug, name, niche, geo, created_at)
briefvideo-clipgencopywriter, orchestratorСтруктурированный бриф (target_audience, problem, solution, USP)
reference_imagesvideo-clipgenvideo-clipgenReference photos для Veo (path, description, analyzed_data)
clipsvideo-clipgen, video-analyzerdirector, orchestratorClip library: path, description, duration, scene_type, mood, reference_image_ids
generated_textsvideo-copywritervoiceover, director, orchestratorТексты озвучки/постов: type, text, target_audience, created_at
transcriptionsvideo-copywritervideo-copywriterBest-performing client creatives (whisper-расшифровки) для style-reference
competitor_refsdentistry-research stage6, copywritercopywriterКреативы конкурентов: text + transcript + ocr + scene + info_score для reference-адаптации
voiceover_historyvideo-voiceoverdirector, orchestratorСгенерированные аудиофайлы (path, duration, voice_id, generated_text_id)
voicesvideo-voiceovervoiceover, orchestratorVoice pool с last_used_at для rotation
voiceover_settingsvideo-voiceovervideo-voiceoverTTS-настройки (speed, model, stability per project)
videosvideo-directororchestrator, reviewerГотовые ролики (path, variant_name, voiceover_id, generated_text_id)
video_clipsvideo-directordirectorКакие клипы пошли в какой ролик (для rotation)
musicvideo-directordirector, orchestratorBackground music tracks (path, mood, duration)
models_3dvideo-remotiondirector, remotion3D-модели для motionfx (glb-файлы + meta)
creative_posterscreative-postercreative-orchestratorСгенерированные постеры: path, prompt, model, style, preset, cost, qa_score
creative_performancecreative-orchestratorcreative-orchestratorMeta-API метрики (ctr, cpl, conversions, spend) per poster_id
carousel_deckscreative-carouselcreative-carouselКарусели (project, topic, template, slide_count, plan_json)
carousel_slidescreative-carouselcreative-carouselPer-slide records линкованы к deck_id
explainer_videoscreative-explainer-videocreative-explainer-videoДлинные видео (project, topic, template, aspect, scene_count, duration, voice_id)
explainer_scenescreative-explainer-videocreative-explainer-videoPer-scene records линкованы к explainer_videos.id
performance(зарезервировано на будущее)ВсеМетрики per-ad для scoring (пока не используется)
schema_metaschema.pyВсеТекущая версия схемы (v8) + migration history
api_keysВсеВсеAPI ключи (GOOGLE_API_KEY, ELEVENLABS_API_KEY) per project

Junction-таблицы (multi-project linking)

Каждая junction = (project_id, resource_id, linked_at). Они дают many-to-many между проектами и ресурсами.

JunctionЛинкуетГде используется
project_clipsprojects ↔ clipsdirector, analyzer, clipgen фильтруют клипы по проекту
project_videosprojects ↔ videosorchestrator выдаёт «все ролики проекта X»
project_voicesprojects ↔ voicesvoiceover rotation: «следующий голос из пула проекта»
project_musicprojects ↔ musicdirector выбирает background-музыку с правом доступа
project_textsprojects ↔ generated_textscopywriter список «всех текстов проекта»
project_voiceoversprojects ↔ voiceover_history«какая озвучка была сгенерирована для проекта»
project_transcriptionsprojects ↔ transcriptionsreference-транскрипции для style copywriter'а
project_reference_imagesprojects ↔ reference_imagesclipgen берёт reference photos только своего проекта
project_modelsprojects ↔ models_3dmotionfx 3D-модели по проекту
project_creative_postersprojects ↔ creative_posterscreative-orchestrator winners report per project

Key relationships (schema v8)

Полная chain traceability: проект → текст → озвучка → видео → performance metrics.

projects.id ← project_clips.project_id → clips.id
projects.id ← project_videos.project_id → videos.id
projects.id ← project_voices.project_id → voices.id
projects.id ← project_music.project_id → music.id
projects.id ← project_texts.project_id → generated_texts.id
projects.id ← project_voiceovers.project_id → voiceover_history.id
projects.id ← project_transcriptions.project_id → transcriptions.id
projects.id ← project_reference_images.project_id → reference_images.id
projects.id ← project_models.project_id → models_3d.id
projects.id ← project_creative_posters.project_id → creative_posters.id

generated_texts.id ← voiceover_history.generated_text_id
voiceover_history.id ← videos.voiceover_id
generated_texts.id ← videos.generated_text_id
clips.id ← video_clips.clip_id
videos.id ← video_clips.video_id
reference_images.id → clips.reference_image_ids (JSON-массив)
competitor_refs.id ← generated_texts.competitor_ref_id
voices.id ← voiceover_history.voice_db_id

Project scoping в SQL-запросах

Все db.py скрипты берут slug проекта из имени папки (os.path.basename(project_dir)), резолвят project.id и фильтруют через junction:

Insert: ресурс + junction-линк

# Python (упрощённо)
clip_id = insert_clip(path, description, duration, ...)
_link_to_project(conn, "project_clips", "clip_id", clip_id, project_dir)

Query: фильтр по проекту

SELECT c.*
FROM clips c
JOIN project_clips pc ON c.id = pc.clip_id
WHERE pc.project_id = ?

Schema management

schema.py — single source of truth по схеме. Копия лежит в каждом скилле в scripts/schema.py (одинаковая). Версия меняется атомарно одной правкой во всех копиях.

python schema.py init <project-dir>      # создать с нуля все таблицы
python schema.py migrate <project-dir>   # обновить существующую (идемпотентно)
python schema.py info <project-dir>      # показать tables, columns, row counts
migrate идемпотентна — можно гонять много раз, ничего не сломает. Использует CREATE TABLE IF NOT EXISTS и ALTER TABLE ADD COLUMN внутри try/except.

Migration history

ВерсияЧто добавили
v6Junction-таблицы для multi-project (project_clips, project_voices и т.д.)
v7Расширение competitor_refs колонками из tools/dentistry-research/ (fb_ad_archive_id, clinic_name, transcript, ocr_text, scene_description)
v8Добавлены competitor_refs.info_score (0..100) и score_breakdown (JSON) для приоритизации референсов

v8 — текущая. schema.py migrate работает на любую v6+ базу.

Competitor research pipeline (separate tool)

Standalone TypeScript-инструмент в agents-monorepo/tools/dentistry-research/ заполняет competitor_refs реальными креативами конкурентов (с FB Ad Library).

Source data: data/research.sqlite (отдельная БД, НЕ director.db).

StageСкриптЧто делает
1stage1-parse-2gis.tsПарсит клиники из 2GIS → clinics table
2 / 2a / 2bstage2*.tsМатчинг клиник к FB pages (через websites, IG handles, keyword search)
3stage3-fetch-creatives.tsТянет рекламы из FB Ad Library → creatives table
4stage4-analyze.tsOCR + transcription через OpenAI → analysis table (transcript, ocr_text, scene_description)
4cstage4c-score.tsСчитает info_score для каждой analysis-строки (идемпотентный backfill)
5stage5-export.tsCSV/JSON export
6stage6-import-to-director.tsПушит выбранные креативы в director.db.competitor_refs

Stage 6 import command

cd agents-monorepo/tools/dentistry-research
npm run stage6 -- \
  --director-db ~/video-projects/director.db \
  [--min-creatives 5] \
  [--niche dentistry] \
  [--reclassify] \
  [--dry-run]
stage6 нужно запустить на КАЖДУЮ нужную БД:
  • ~/video-projects/director.db (shared, accessed через db.py get_db(..., shared=True))
  • ~/video-projects/<project>/director.db (per-project, default для db.py)
Скилл читает из <project-dir>/director.db по умолчанию — поэтому stage6 только на shared не хватит.

Info_score 0..100 — для приоритизации референсов

Считается в tools/dentistry-research/stage4c-score.ts и приходит в director.db через stage6. Видеокопирайтер использует для выбора лучших конкурентских креативов.

КомпонентВесИсточник
transcript length25analysis.transcript
ad active duration18creatives.total_active_time
clinic creative count15derived from creatives
body_text length12creatives.body_text
variations (A/B count)9creatives.ads_count
currently active9creatives.is_active
OCR text length8analysis.ocr_text
scene description length4analysis.scene_description

Default --min-score 50 отсекает короткие image-ads и неоттестированные one-offs. ≥70 — редко (~1% — топовые креативы крупных клиник).

Типичные запросы (для разработчика)

Все ресурсы проекта

-- сколько чего у проекта 'apparatus'
SELECT
  (SELECT COUNT(*) FROM project_clips pc JOIN projects p ON pc.project_id = p.id WHERE p.slug = 'apparatus') AS clips,
  (SELECT COUNT(*) FROM project_voices pv JOIN projects p ON pv.project_id = p.id WHERE p.slug = 'apparatus') AS voices,
  (SELECT COUNT(*) FROM project_texts pt JOIN projects p ON pt.project_id = p.id WHERE p.slug = 'apparatus') AS texts,
  (SELECT COUNT(*) FROM project_videos pv JOIN projects p ON pv.project_id = p.id WHERE p.slug = 'apparatus') AS videos;

Какие клипы давно не использовались (для rotation)

SELECT c.id, c.path, MAX(v.created_at) AS last_used_at
FROM clips c
JOIN project_clips pc ON c.id = pc.clip_id
JOIN projects p ON pc.project_id = p.id
LEFT JOIN video_clips vc ON c.id = vc.clip_id
LEFT JOIN videos v ON vc.video_id = v.id
WHERE p.slug = 'apparatus'
GROUP BY c.id
ORDER BY last_used_at ASC NULLS FIRST
LIMIT 10;

Winners report по статике (CPL)

SELECT cp.id, cp.path, cp.style, cp.preset, perf.cpl, perf.ctr, perf.conversions
FROM creative_posters cp
JOIN project_creative_posters pcp ON cp.id = pcp.poster_id
JOIN projects p ON pcp.project_id = p.id
JOIN creative_performance perf ON cp.id = perf.poster_id
WHERE p.slug = 'apparatus' AND perf.cpl IS NOT NULL
ORDER BY perf.cpl ASC
LIMIT 10;

Бэкап и восстановление

# бэкап
sqlite3 ~/video-projects/director.db ".backup '/tmp/director_backup_$(date +%Y%m%d).db'"

# восстановить из бэкапа
cp /tmp/director_backup_20260516.db ~/video-projects/director.db

# экспорт всей БД в SQL-дамп
sqlite3 ~/video-projects/director.db .dump > director_dump.sql
Бэкап автоматически не настроен. Стоит закинуть в cron / launchd ежедневный snapshot в Time Machine / iCloud Drive / git-репо (но НЕ публичный — содержит API-ключи).

Куда дальше