17 KiB
Spec: FightRate P0 — Perfil Avançado e Insights do Lutador
Objective
Criar a primeira versão do Perfil Avançado do Lutador para coletar dados pessoais, cardiovasculares, subjetivos e de recuperação suficientes para gerar insights úteis de treino sem deixar o app inchado.
O objetivo não é diagnóstico médico. O objetivo é apoio esportivo: entender carga, recuperação, hidratação, intensidade e evolução do atleta usando os batimentos reais do sensor BLE somados a dados simples informados pelo usuário.
Usuário principal: lutador ou praticante de treino de combate que usa o FightRate para acompanhar batimentos durante treinos, rounds e sessões.
Scope P0
Implementar somente o núcleo inicial:
- Perfil do Lutador.
- Check-in pré-treino.
- Registro pós-treino.
- Insights básicos do treino e recuperação.
Não implementar neste P0:
- exames médicos avançados;
- integração com wearable externo além do sensor BLE atual;
- Health Connect;
- prescrição médica;
- VO2max avançado;
- IA generativa para recomendação clínica;
- dashboard muito complexo.
Assumptions
- O app continua Android nativo Kotlin + Jetpack Compose.
- Textos visíveis continuam em PT-BR.
- Identificadores de código continuam em inglês.
- SQLite local-first continua como fonte de verdade.
- O sensor BLE continua sendo fonte dos batimentos em tempo real.
- Cálculos de saúde serão apresentados como estimativas esportivas, não diagnósticos.
- O P0 deve priorizar campos que geram cálculos imediatos.
Tech Stack
- Kotlin
- Jetpack Compose Material3
- SQLite via
SQLiteOpenHelperexistente emHeartRateSampleStore - Gradle Kotlin DSL
- JUnit 4 para testes unitários
- Vico para gráficos existentes
- Lucide para ícones existentes
Commands
Build completo:
./gradlew :app:testDebugUnitTest :app:assembleDebug
Teste unitário focado:
./gradlew :app:testDebugUnitTest --tests "*AthleteInsights*"
Compilação rápida:
./gradlew :app:compileDebugKotlin
Instalação debug:
./gradlew :app:installDebug
Existing Code to Reuse
Reaproveitar antes de criar novo:
AthleteProfile.ktHeartRateZones.ktWorkoutSession.ktHeartRateStats.ktHeartRateSampleStore.ktLiveHeartRateViewModel.ktLiveHeartRateUiState.ktHomeScreen.ktFightCardMetricTileLiveMetricPill- cards de Perfil existentes
- SQLite
fightrate.db
Data Model — P0 Fields
1. Perfil básico
Campos:
agebiologicalSexheightCmweightKgfightSportweightClassKgtrainingGoal
Uso:
- personalizar zonas;
- calcular IMC;
- acompanhar categoria de peso;
- contextualizar insights.
2. Perfil cardiovascular
Campos:
restingHeartRatemeasuredMaxHeartRate
Uso:
- limite cardíaco personalizado;
- zonas por percentual;
- reserva cardíaca;
- alertas de esforço.
3. Check-in pré-treino
Campos por sessão:
preWorkoutEnergyescala 1–5preWorkoutFatigueescala 1–5muscleSorenessescala 1–5sleepHourssleepQualityescala 1–5preWorkoutWeightKgwaterIntakeMlsymptomsNote
Uso:
- prontidão;
- risco de fadiga;
- contexto para batimentos altos;
- hidratação estimada.
4. Pós-treino
Campos por sessão:
postWorkoutRpeescala 1–10postWorkoutFatigueescala 1–5postWorkoutSorenessescala 1–5postWorkoutWeightKgpostWorkoutNote
Uso:
- carga subjetiva;
- perda hídrica;
- recuperação;
- comparação entre percepção e batimentos.
Calculations — P0
1. IMC
Entrada:
- peso
- altura
Resultado:
bmi- faixa textual simples
2. Reserva cardíaca
Entrada:
- limite cardíaco real ou estimado
- FC repouso
Resultado:
heartRateReserve
3. Zonas por Karvonen
Entrada:
- limite cardíaco
- FC repouso
Resultado:
- zonas mais personalizadas para o atleta.
4. Score de treino
Entrada:
- duração da sessão
- BPM médio
- BPM pico
- tempo em zonas
- RPE pós-treino
Resultado:
- score 0–100
- rótulo: leve, moderado, intenso, extremo
5. Carga subjetiva
Entrada:
- RPE pós-treino
- duração em minutos
Resultado:
sessionLoad = RPE × durationMinutes
6. Score de recuperação
Entrada:
- sono
- qualidade do sono
- energia antes
- fadiga antes
- dor muscular
- FC repouso opcional
Resultado:
- score 0–100
- rótulo: baixa, ok, boa, excelente
7. Hidratação estimada
Entrada:
- peso antes
- peso depois
- água ingerida
Resultado:
- perda de peso no treino
- perda hídrica estimada
- alerta se perda relativa for alta
8. Tempo em zona de luta
Entrada:
- samples de BPM da sessão
- zonas cardíacas
Resultado:
- tempo em zona alta/intensa
- percentual do treino em zona de luta
9. Alertas de segurança esportiva
Entrada:
- sintomas
- fadiga alta
- sono ruim
- dor alta
- esforço cardíaco alto
Resultado:
- mensagens de atenção no app.
Exemplos:
- “Sono baixo + fadiga alta: considere treino técnico ou leve.”
- “Perda de peso alta no treino: reponha líquidos.”
- “Esforço muito alto para uma recuperação baixa.”
UI/UX
Tela Perfil
Adicionar seções recolhíveis ou cards:
- Básico
- Cardiovascular
- Luta
Evitar formulário gigante em uma tela só.
Tela Check-in
Fluxo simples antes de iniciar sessão:
- energia
- fadiga
- dor
- sono
- peso
- água
- sintomas
Pode ser card ou bottom sheet antes de iniciar treino.
Tela Pós-treino
Após encerrar sessão:
- RPE
- fadiga
- dor
- peso pós
- observação
Tela Insights do Lutador
Primeira versão com cards:
- Score do treino
- Recuperação
- Hidratação
- Tempo em zona de luta
- Carga subjetiva
- Alertas
Project Structure
Novos arquivos prováveis:
app/src/main/java/com/healthtracking/fightrate/domain/AthleteInsights.kt
app/src/main/java/com/healthtracking/fightrate/domain/ReadinessScore.kt
app/src/main/java/com/healthtracking/fightrate/domain/HydrationEstimate.kt
app/src/test/java/com/healthtracking/fightrate/domain/AthleteInsightsTest.kt
Arquivos existentes a alterar:
AthleteProfile.kt
WorkoutSession.kt
HeartRateSampleStore.kt
LiveHeartRateUiState.kt
LiveHeartRateViewModel.kt
HomeScreen.kt
A criação de novos arquivos deve ser mínima e justificada por responsabilidade clara.
Code Style
Exemplo esperado:
data class ReadinessInput(
val sleepHours: Double?,
val sleepQuality: Int?,
val energy: Int?,
val fatigue: Int?,
val soreness: Int?,
)
object ReadinessScoreCalculator {
fun calculate(input: ReadinessInput): ReadinessScore {
val score = 100
.minus(input.fatigue.orDefaultPenalty(multiplier = 8))
.minus(input.soreness.orDefaultPenalty(multiplier = 6))
.plus(input.energy.orDefaultBonus(multiplier = 5))
.coerceIn(0, 100)
return ReadinessScore(value = score)
}
}
Convenções:
- código em inglês;
- UI em PT-BR;
- cálculo puro no domínio;
- Compose apenas renderiza estado;
- evitar mágica sem teste.
Testing Strategy
Testar com JUnit:
- IMC
- reserva cardíaca
- Karvonen
- score de recuperação
- carga subjetiva
- hidratação estimada
- tempo em zona de luta
- alertas esportivos
Comandos:
./gradlew :app:testDebugUnitTest --tests "*AthleteInsights*"
./gradlew :app:testDebugUnitTest :app:assembleDebug
Boundaries
Always
- Criar cálculo no domínio antes da UI.
- Usar testes para cálculo.
- Manter campos opcionais quando possível.
- Explicar que cálculos são estimativas esportivas.
- Reaproveitar
AthleteProfile,WorkoutSession,HeartRateSampleStore. - Salvar dados localmente.
Ask first
- Adicionar dependência nova.
- Adicionar Health Connect.
- Criar tela nova separada se puder ser card inicialmente.
- Fazer alteração destrutiva no banco.
- Criar cálculo médico sensível.
- Coletar dados muito pessoais sem justificativa.
Never
- Apresentar diagnóstico médico.
- Bloquear treino como decisão médica.
- Criar formulário gigante difícil de usar.
- Adicionar campos que não alimentam nenhum cálculo.
- Criar código morto para métricas futuras.
- Misturar todos os cálculos dentro da UI.
Success Criteria
- Usuário consegue preencher dados P0 sem fricção excessiva.
- App calcula pelo menos:
- IMC;
- reserva cardíaca;
- score de treino;
- carga subjetiva;
- score de recuperação;
- hidratação estimada;
- tempo em zona de luta.
- Dados ficam persistidos localmente.
- Insights aparecem em PT-BR.
- Cálculos puros têm testes.
- Build final passa.
- Nenhuma dependência nova é adicionada sem aprovação.
Decisions Approved
- Check-in pré-treino será obrigatório antes de iniciar uma sessão.
- Insights será uma nova aba no menu inferior.
- Sexo biológico será coletado como
MasculinoouFeminino. - Energia, fadiga, dor e sono usarão escala 0–10.
- Campos avançados devem poder ficar ocultos/expandidos para manter a interface limpa.
BLE Reliability and Battery Plan
Objective
A leitura do sensor deve continuar enquanto o app estiver em treino, inclusive com tela bloqueada, salvo quando:
- o usuário desconectar manualmente;
- o usuário encerrar o treino;
- o Android remover o processo por condição extrema fora do controle do app.
Quando houver queda por distância/sinal, o serviço deve tentar reconectar automaticamente quando o usuário se aproximar novamente. Se o usuário desconectar manualmente, o app não deve reconectar sozinho.
BLE Rules
- O
HeartRateForegroundServicecontinua sendo dono da conexão BLE. - O ViewModel não deve abrir conexão direta com o sensor.
- Toda queda inesperada deve ser registrada como estado de conexão e mensagem visível.
- Desconexão manual deve salvar uma flag
manualDisconnect = true. - Queda inesperada deve salvar
manualDisconnect = falsee iniciar retry/backoff. - O último sensor selecionado deve ser preservado para reconexão automática.
Reconnect Strategy
Estados sugeridos:
Idle
Connecting
Connected
LostSignal
RetryWaiting
ManualDisconnected
Failed
Backoff conservador:
1ª tentativa: 2s
2ª tentativa: 5s
3ª tentativa: 10s
4ª tentativa em diante: 30s
Regras:
- Não tentar reconectar se
manualDisconnect = true. - Não tentar reconectar se Bluetooth estiver desligado; aguardar próximo ciclo.
- Não tentar reconectar sem permissão Bluetooth.
- Não fazer scan agressivo contínuo.
- Para sensor já conhecido, tentar reconectar por
deviceIddireto antes de scan. - Scan só deve ser usado quando não houver deviceId válido.
Battery Strategy
Para menor consumo:
- Foreground service único durante sessão.
- Sem polling pesado no ViewModel a cada batimento.
- SQLite insert apenas por amostra recebida.
- Notificação com throttle.
- Reconexão com backoff, nunca loop apertado.
- Metadata do sensor lida na conexão e depois em intervalo longo, não a cada amostra.
- Gráficos atualizam pelo estado recebido, sem reconsultar o banco inteiro.
- Evitar wake locks permanentes. Usar foreground service e orientação de ignorar otimização de bateria.
Acceptance BLE
- Tela bloqueada não interrompe leitura durante treino.
- Distância/sinal perdido mostra status de sensor perdido.
- Ao aproximar novamente, o app tenta reconectar sozinho.
- Se usuário tocar em Desconectar, reconexão automática fica desativada.
- Se usuário escolher o sensor/iniciar novamente, reconexão automática volta a valer.
- Build passa.
Recommended First Implementation Plan
Após aprovação desta spec:
-
Criar domínio puro de insights e testes.
-
Expandir modelo de perfil e sessão com campos P0.
-
Adicionar migração SQLite aditiva.
-
Criar UI de Perfil Avançado com seções expansíveis.
-
Criar check-in pré-treino obrigatório antes de iniciar sessão.
-
Criar pós-treino ao encerrar sessão.
-
Criar nova aba Insights com cards básicos.
-
Melhorar BLE com desconexão manual vs queda inesperada, reconexão automática e backoff.
-
Rodar build/test completo.
Spec Expansion: Gráficos, Janela de Treino e Relatório Científico
Objective
Transformar a sessão do FightRate em uma janela de coleta confiável com relatório pós-treino detalhado, gráficos Vico mais legíveis/interativos e métricas esportivas fundamentadas em frequência cardíaca real do sensor BLE.
O objetivo é interpretação esportiva para lutas, não diagnóstico médico. Toda métrica estimada deve ser rotulada como estimativa.
Research Summary
Fontes consultadas antes da implementação:
- Cole et al., NEJM 1999, PubMed:
https://pubmed.ncbi.nlm.nih.gov/10536127/- HR Recovery é queda da FC do pico para 1 minuto pós-exercício.
- Queda <= 12 bpm em 1 min foi considerada anormal no estudo clínico.
- Brubaker & Kitzman, Circulation review, PMC:
https://pmc.ncbi.nlm.nih.gov/articles/PMC3065291/- Resposta cronotrópica é capacidade de aumentar FC com demanda.
- Critérios clínicos exigem teste progressivo e contexto; no app usar só interpretação esportiva.
- HRR 1 min < 12 bpm, 2 min < 42 bpm aparecem como thresholds de risco em contexto clínico, mas o app não diagnostica.
- Uth et al., Eur J Appl Physiol 2004, PubMed:
https://pubmed.ncbi.nlm.nih.gov/14624296/- VO2max estimado pode usar
15.3 * (HRmax / HRrest)em população treinada. - Aplicabilidade fora desse perfil deve ser tratada como aproximação.
- VO2max estimado pode usar
- Boxing HR response, PMC:
https://pmc.ncbi.nlm.nih.gov/articles/PMC3871409/- Boxe simulado passou grande parte acima de VT2; HR aumenta por round.
- Reforça foco em tempo em zonas altas, recuperação entre rounds e drift.
- Muay Thai physiological response, Ingenta:
https://www.ingentaconnect.com/content/uwic/ujpa/2012/00000012/00000003/art00004- Muay Thai competitivo teve FC média próxima do máximo e alto estresse cardiovascular.
- Edwards/TRIMP summaries:
https://www.trainingimpulse.com/edwards-trimpehttps://journals.lww.com/nsca-scj/fulltext/2018/04000/quantification_of_training_load_in_endurance.12.aspx- Edwards TRIMP é prático por zonas, mas coeficientes são arbitrários.
- Banister usa reserva cardíaca e ponderação exponencial; ambos devem ser apresentados como carga interna estimada.
- HRV em Muay Thai, PMC:
https://pmc.ncbi.nlm.nih.gov/articles/PMC12143277/- RMSSD/SDNN exigem RR/NN intervals confiáveis. O app hoje coleta BPM, portanto HRV fica fora do escopo até o parser/persistência salvarem RR intervals.
Implementable Metrics From Current Data
Viáveis agora:
- duração total da sessão;
- duração ativa aproximada e pausada;
- BPM inicial/final/mínimo/médio/máximo;
- desvio padrão;
- qualidade dos dados por densidade, gaps e duração;
- tempo e percentual por zona;
- tempo em zona de luta, RedLine e Limite;
- maior sequência contínua em zona intensa;
- picos e quedas relevantes;
- HR Recovery 1min/2min quando houver amostras pós-pico;
- drift primeira metade vs segunda metade;
- slope de BPM por minuto;
- resposta cronotrópica inicial aproximada;
- tempo até zona vigorosa/luta;
- Edwards TRIMP;
- Banister TRIMP quando houver FC repouso e limite cardíaco;
- carga subjetiva RPE x duração;
- VO2max estimado quando houver FC repouso e limite cardíaco;
- hidratação estimada com peso pré/pós e água;
- interpretação cruzada com energy/fatigue/soreness/sleep/symptoms/RPE.
Não viável agora sem novos dados:
- HRV RMSSD/SDNN/pNN50 real;
- diagnóstico de arritmia;
- limiar ventilatório real;
- VO2 real;
- economia cardíaca precisa entre sessões sem normalizar tipo/duração/intensidade.
Data Collection / Time Window
Reaproveitar workout_sessions:
startedAtMillisdefine início do range.endedAtMillisdefine fim do range.session_idemheart_rate_samplesassocia amostra à sessão ativa.- Ao finalizar, buscar amostras por
session_id; fallback por intervalo se necessário. - Foreground service continua dono do BLE.
- ViewModel não assume BLE.
- Migração SQLite só aditiva se relatório persistido exigir cache.
Domain Design
Criar ou expandir domínio puro para relatório:
TrainingReportMetricsSessionReportBuilderDataQualityAnalyzerZoneDistributionCalculatorHeartRateRecoveryCalculatorCardiacDriftCalculatorChronotropicResponseCalculatorTrainingLoadCalculatorHydrationTrainingInterpreter
Business rules fora do Compose.
UI / Charts
Reaproveitar HeartRateLineChart, FightCard, MetricTile.
Adicionar:
- chart summary pills de mínimo/médio/máximo;
- legenda de zonas;
- destaque textual para tempo em zona de luta/limite;
- estados profissionais para poucos dados;
- leitura de eixo X por tempo real;
- relatório em seções expansíveis:
- Resumo do treino;
- Gráfico principal;
- Zonas cardíacas;
- Recuperação;
- Carga de treino;
- Drift e fadiga;
- Hidratação;
- Comparação com histórico;
- Alertas e interpretação.
Evitar Canvas customizado e dependências novas.
Success Criteria
- Spec atualizada antes do código.
- Código existente auditado e reaproveitado.
- Testes JUnit cobrem todos os cálculos de domínio novos.
- Sessão finalizada gera relatório com amostras do range correto.
- Histórico permite abrir relatório de sessão.
- UI deixa claro estimativa esportiva vs dado medido.
- BLE continua no foreground service.
- Sem migração destrutiva.
./gradlew :app:testDebugUnitTest :app:assembleDebugtermina comBUILD SUCCESSFUL.- APK gerado e tentativa de
telegram_attach(localPath)realizada.