Meetings (Aulas + Gravações)
Status: 🟢 Estável (Daily.co + Bunny CDN integrados) Code: backend/app/modules/meetings UI: frontend/src/app/(admin)/[slug]/admin/meeting Última revisão deste doc: 2026-05-13 por Felipe + Claude Dependências fortes: members (lista de alunos pra reminders), whatsapp (envio de lembretes), intelligence (summary), diary (auto-prompt pós-aula)
1. Identidade
O que faz (uma frase)
Agendamento e operação de aulas ao vivo via Daily.co (room provisioning, tokens, transcrição), gravação automática armazenada no Bunny CDN (URL signed), attendance tracking (joined/left), summary IA pós-aula e lembretes WhatsApp automáticos (1h e 15min antes).
Por que existe (negócio)
Mais Consciente faz aulas semanais ao vivo (yoga, meditação, mentorias). Sem este módulo:
- Felipe usaria Zoom (ou Google Meet) externo, sem integração com membros + acesso restrito.
- Gravações ficariam no Drive sem player nativo no app.
- Lembretes manuais pra cada aluno (work humano).
Com Meetings:
- Sala Daily.co provisionada lazy (só quando admin abre — evita salas vazias).
- Recording auto pipeline: Daily webhook → download → upload Bunny → ready.
- Lembretes WA automáticos pros membros do programa.
- Summary IA + diary prompt pós-aula (integração rica).
Status atual
Em prod, integração Daily + Bunny estável. Reminders ativos.
2. Cases de uso reais
Case 1: Aula semanal de yoga
- Admin agenda aula em
/admin/meeting/schedule→ criameetings_class(scheduled_at, duration=70min, program_id=yoga). - Beat
meetings.send_reminders(5min) → 1h antes envia WA pra todos members do programa. - Admin abre sala (POST
/classes/{slug}/open-room) → DailyAdapter cria room idempotent. - Aula acontece. Webhook Daily registra
participant_joined/leftemmeetings_attendance. - Aula termina (status=ended). Webhook
recording_ready→tasks.upload_recordingbaixa do Daily + uploadeia pro Bunny. - Webhook Bunny
video_status→ polling →meetings_recording.status=ready. tasks.generate_summary→ intelligence summariza transcript →summary_jsonpopulado.tasks.diary_prompt→ diary cria auto-prompt pós-aula.
Case 2: Aluno acessa replay
UI /clube/aulas/passadas → GET /classes/{slug}/recording/url → backend gera URL signed Bunny (HMAC-SHA256, expires 24h) → embed iframe.
3. Oportunidades de negócio
- Recording analytics: quem assistiu replay, quanto tempo, em qual ponto pausou — produto premium.
- Live engagement scoring: participants que fazem perguntas no chat ganham + pontos CRM.
- Multi-tenant Daily: cada cliente Mandir tem subdomínio Daily próprio (whitelabel).
- Summary IA cross-aula: "o que foi recorrente nas últimas 4 aulas?" — content insight.
Riscos: dependência Daily (preço por minute) + Bunny (storage); recording chain pode falhar (URL Daily válida só ~1h após webhook).
4. Arquitetura interna
Arquivos
| Arquivo | Propósito |
|---|---|
models.py | 5 tabelas |
routes.py | 12 endpoints + webhooks |
webhooks.py | Daily + Bunny receivers |
service.py | Adapter factory + CRUD classes |
adapters/daily.py | DailyAdapter (rooms, tokens, webhooks) |
adapters/bunny.py | BunnyStreamAdapter (upload, collections, signed URLs) |
tasks/send_reminders.py | Beat 5min |
tasks/upload_recording.py | Daily download → Bunny upload |
tasks/poll_bunny_status.py | Polling status |
tasks/generate_summary.py | IA resume transcript |
tasks/diary_prompt.py | Integração diary pós-aula |
Tasks Celery
| Task | Schedule | O que faz |
|---|---|---|
meetings.send_reminders | Beat 5min | Envia WA 1h e 15min antes (marca reminder_*_sent_at) |
meetings.upload_recording | On-demand (webhook trigger) | Daily → Bunny |
meetings.poll_bunny_status | Polling | Verifica bunny_video_guid state |
meetings.generate_summary | Post-ready | Resume transcript via intelligence |
meetings.diary_prompt | Post-summary | Cria diary auto-prompt |
Adapters externos
| Provider | Adapter | Endpoints |
|---|---|---|
| Daily.co | DailyAdapter | create_room, get_room, delete_room, generate_token |
| Bunny Stream | BunnyStreamAdapter | list_collections, create_collection, upload_from_url (Bunny fetch direto da URL — sem passar bytes), get_video, generate_signed_url (HMAC-SHA256) |
5. Tabelas (5)
meetings_class
| Coluna chave | Notas |
|---|---|
tenant_id | |
title / slug / description | UNIQUE (tenant_id, slug) |
instructor_id | UUID staff (sem FK) |
program_id | Backref pra reminders |
scheduled_at / duration_minutes (default 70) | |
status | scheduled / live / ended / canceled |
daily_room_name / daily_room_url | Lazy — criados on-demand |
room_config (JSONB) | max_participants, enable_recording, etc. |
transcript_raw (JSONB) | Deepgram |
summary_json (JSONB) | título + tópicos + Q&A gerados por IA |
reminder_1h_sent_at / reminder_15m_sent_at | Anti-duplicate |
meetings_class_facilitator
m:n class↔staff. UNIQUE (class_id, staff_id).
meetings_class_guest
Convidado externo (name, title, bio, photo_url).
meetings_recording
| Coluna chave | Notas |
|---|---|
class_id (FK CASCADE) | |
daily_recording_id | UNIQUE |
bunny_video_guid / bunny_collection_guid | |
status | pending / downloading / uploading / ready / failed |
duration_seconds / started_at / ended_at |
Pipeline: webhook Daily ready → pending → daily.recording.download → downloading → bunny.upload_from_url → uploading → poll → ready.
meetings_attendance
| Coluna chave | Notas |
|---|---|
class_id (FK CASCADE) | |
daily_session_id | Idx — upsert key |
daily_user_id / display_name | |
joined_at / left_at / duration_seconds | Calculada on left |
Relacionamentos cross-módulo
| Direção | Outro módulo | Como |
|---|---|---|
| ↗ Lê | members | list_contact_accounts pra reminders |
| ↘ Escreve | whatsapp | send_message em reminders |
| ↗ Lê | intelligence | run_conversation_summary (transcript → summary_json) |
| ↘ Escreve | diary | Auto-gera prompt reflexivo pós-aula |
6. API / Endpoints (12)
| Método | Rota | O que faz |
|---|---|---|
| GET | / | Health |
| POST | /classes | Criar |
| GET | /classes | Lista |
| GET | /classes/{slug} | Detalhe |
| POST | /classes/{slug}/open-room | Idempotent — provision Daily room |
| PUT | /classes/{slug}/facilitators | Batch replace |
| POST / GET | /classes/{slug}/guests | CRUD guests |
| GET | /classes/{slug}/recording | Metadados |
| GET | /classes/{slug}/recording/url | URL signed Bunny (se ready) |
Webhooks (sem auth)
POST /webhooks/daily/recording-readyPOST /webhooks/daily/participant-{joined,left}POST /webhooks/bunny/video-status
7. Configuração
Env vars
| Var | Propósito |
|---|---|
MANDIR_DAILY_API_KEY | Daily auth (env global fallback) |
MANDIR_DAILY_DOMAIN | ex: "maisconsciente" → maisconsciente.daily.co |
MANDIR_BUNNY_STREAM_API_KEY | |
MANDIR_BUNNY_STREAM_LIBRARY_ID (int) | |
MANDIR_BUNNY_STREAM_COLLECTION_DEFAULT | Collection GUID default |
MANDIR_BUNNY_STREAM_TOKEN_AUTH_KEY | Pra signed URLs |
Tenant overrides via tenant_provider_credentials (provider=daily / bunny_stream).
8. Operações + Troubleshooting
Sintoma: Recording fica em "uploading" eternamente
Causa: URL Daily expirou (~1h após webhook). Fix: Admin retrigger manual do upload (sem auto-retry).
Sintoma: Reminders não enviam
Causa: program_id NULL na class → skip.
Fix: Vincular class a program.
9. Limitações e débitos técnicos
| # | Item |
|---|---|
| 1 | Recording chain serial — falha em qualquer stage = failed |
| 2 | Sem auto-retry no upload |
| 3 | URL Daily válida ~1h |
| 4 | Reminders dependem de program_id |
| 5 | Webhooks sem auth |
10. Histórico
Em prod desde Suite v2. Daily + Bunny adapters estáveis. Pipeline de gravação validado em produção.