Pomodoro Adaptativo em Python Puro: O Timer Que Aprende Seu Ritmo e Ajusta Intervalos Sozinho
Você já tentou usar a técnica Pomodoro e sentiu que os 25 minutos eram arbitrários demais? Que às vezes você precisava de mais tempo para entrar no flow, e outras vezes estava exausto antes do timer tocar? Eu também. E foi exatamente essa frustração que me fez construir um Pomodoro que aprende seu ritmo em vez de impor um ritmo genérico.
O problema do Pomodoro tradicional não é a técnica em si — é a rigidez. 25 minutos de foco, 5 de pausa, repita. Funciona para algumas pessoas, mas ignora completamente que seu cérebro não é uma máquina com ciclos fixos. Alguns dias você precisa de 40 minutos para mergulhar em código complexo. Outros, 15 minutos já são suficientes antes de precisar respirar.
Hoje vamos construir um pomodoro adaptativo em Python puro — sem dependências externas, sem bibliotecas pesadas, só stdlib. Um timer que rastreia suas sessões reais, detecta padrões e ajusta automaticamente a duração ideal de trabalho e pausa para você, não para uma média estatística inventada nos anos 80.
O Problema Escondido nos Timers Rígidos
A técnica Pomodoro foi criada por Francesco Cirillo nos anos 80 usando um timer de cozinha em formato de tomate. A ideia era simples: dividir o trabalho em intervalos curtos para manter o foco e evitar burnout. Funcionou. Mas tinha um problema que Cirillo não poderia prever: contexto.
Em 1987, você trabalhava em uma coisa de cada vez. Em 2026, seu cérebro está sendo bombardeado por notificações, context switches e tarefas fragmentadas. Um dev escrevendo código complexo precisa de tempo diferente de alguém respondendo emails. Um designer criando um conceito precisa de tempo diferente de um analista revisando logs.
O Pomodoro tradicional trata todos igual. E é aí que ele falha.
A Filosofia do Pomodoro Adaptativo
Em vez de impor intervalos fixos, o Pomodoro adaptativo segue três princípios:
- Observar antes de prescrever: Coleta dados reais das suas sessões por alguns dias antes de sugerir ajustes.
- Respeitar seu ritmo circadiano: Detecta se você rende mais de manhã, à tarde ou à noite e ajusta os intervalos por período.
- Aprender com interrupções: Se você frequentemente interrompe sessões antes do fim, o timer encurta. Se sempre completa e ainda quer mais, ele alonga.
Isso não é só teoria. É estatística aplicada com Python puro. Vamos ver como.
Arquitetura do Sistema
O sistema tem quatro componentes principais:
- SessionTracker: Registra início, fim e duração de cada sessão de trabalho e pausa.
- PatternAnalyzer: Calcula médias móveis, detecta outliers e identifica padrões por horário.
- IntervalAdjuster: Usa os dados do analista para sugerir durações adaptativas.
- AdaptivePomodoro: Orquestra tudo e gerencia o ciclo de trabalho/pausa.
Tudo isso sem instalar nada. Só Python 3.8+ e a biblioteca padrão.
Coletando Dados de Sessões Reais
Antes de ajustar qualquer coisa, precisamos medir. O SessionTracker registra cada sessão em um arquivo JSON local, com timestamp, duração real e tipo (trabalho ou pausa):
import json
import time
from datetime import datetime
from pathlib import Path
from typing import Literal
class SessionTracker:
def __init__(self, storage_path: str = "pomodoro_sessions.json"):
self.storage = Path(storage_path)
self.current_start: float | None = None
self.sessions: list[dict] = self._load_sessions()
def _load_sessions(self) -> list[dict]:
if self.storage.exists():
return json.loads(self.storage.read_text())
return []
def _save_sessions(self) -> None:
self.storage.write_text(json.dumps(self.sessions, indent=2))
def start_session(self, session_type: Literal["work", "break"]) -> None:
self.current_start = time.time()
self.current_type = session_type
def end_session(self, completed: bool = True) -> dict:
if self.current_start is None:
raise ValueError("Nenhuma sessão ativa")
duration = time.time() - self.current_start
session = {
"start": datetime.fromtimestamp(self.current_start).isoformat(),
"end": datetime.now().isoformat(),
"duration_minutes": round(duration / 60, 2),
"type": self.current_type,
"completed": completed
}
self.sessions.append(session)
self._save_sessions()
self.current_start = None
return session
def get_sessions(self, last_n: int | None = None) -> list[dict]:
if last_n:
return self.sessions[-last_n:]
return self.sessions
Simples, direto, sem overengineering. Cada sessão é um dicionário com metadados essenciais. O arquivo JSON cresce linearmente, mas para uso pessoal isso não é problema — estamos falando de algumas sessões por dia.
O Algoritmo de Adaptação
Aqui está o coração do sistema. O PatternAnalyzer calcula médias móveis ponderadas e detecta padrões por período do dia:
from collections import defaultdict
from statistics import mean, stdev
class PatternAnalyzer:
def __init__(self, tracker: SessionTracker):
self.tracker = tracker
def get_work_sessions(self, last_n: int = 20) -> list[dict]:
return [
s for s in self.tracker.get_sessions(last_n)
if s["type"] == "work" and s["completed"]
]
def calculate_moving_average(self, window: int = 5) -> float:
"""Média móvel ponderada das últimas sessões completas."""
sessions = self.get_work_sessions(window)
if not sessions:
return 25.0 # padrão inicial
# Pesos exponenciais: sessões recentes valem mais
weights = [2 ** i for i in range(len(sessions))]
total_weight = sum(weights)
weighted_sum = sum(
s["duration_minutes"] * w
for s, w in zip(sessions, weights)
)
return weighted_sum / total_weight
def detect_time_patterns(self) -> dict[str, float]:
"""Detecta duração ideal por período do dia."""
sessions = self.get_work_sessions(50)
periods = {"morning": [], "afternoon": [], "evening": []}
for s in sessions:
hour = datetime.fromisoformat(s["start"]).hour
if 6 <= hour < 12:
periods["morning"].append(s["duration_minutes"])
elif 12 <= hour < 18:
periods["afternoon"].append(s["duration_minutes"])
else:
periods["evening"].append(s["duration_minutes"])
return {
period: mean(durations) if durations else 25.0
for period, durations in periods.items()
}
def detect_interruption_rate(self) -> float:
"""Taxa de sessões interrompidas vs completas."""
sessions = [s for s in self.tracker.get_sessions(20) if s["type"] == "work"]
if not sessions:
return 0.0
interrupted = sum(1 for s in sessions if not s["completed"])
return interrupted / len(sessions)
Note que usamos média móvel ponderada com pesos exponenciais. Por quê? Porque seu ritmo de hoje importa mais que seu ritmo de semana passada. Se você está mais cansado ou mais energizado, o sistema adapta rápido.
O método detect_time_patterns é especialmente útil. Se você percebe que rende mais de manhã (sessões mais longas e completas), o timer vai sugerir intervalos maiores nesse período. À tarde, quando a energia cai, ele encurta automaticamente.
O Ajuste Inteligente de Intervalos
Agora vem a parte divertida: usar esses dados para ajustar os intervalos em tempo real. O IntervalAdjuster aplica regras simples mas eficazes:
class IntervalAdjuster:
def __init__(self, analyzer: PatternAnalyzer):
self.analyzer = analyzer
self.min_work = 15.0 # mínimo: 15 minutos
self.max_work = 50.0 # máximo: 50 minutos
self.break_ratio = 0.2 # pausa = 20% do trabalho
def get_current_period(self) -> str:
hour = datetime.now().hour
if 6 <= hour < 12:
return "morning"
elif 12 <= hour < 18:
return "afternoon"
return "evening"
def suggest_work_duration(self) -> float:
"""Sugere duração ideal de trabalho baseada em padrões."""
# Base: média móvel das últimas sessões
base_duration = self.analyzer.calculate_moving_average(5)
# Ajuste por período do dia
time_patterns = self.analyzer.detect_time_patterns()
current_period = self.get_current_period()
period_adjustment = time_patterns[current_period] / 25.0 # normalizado
# Ajuste por taxa de interrupção
interruption_rate = self.analyzer.detect_interruption_rate()
interruption_factor = 1.0 - (interruption_rate * 0.3) # reduz se muito interrompido
# Cálculo final
suggested = base_duration * period_adjustment * interruption_factor
# Clamp entre min e max
return max(self.min_work, min(self.max_work, suggested))
def suggest_break_duration(self, work_duration: float) -> float:
"""Pausa proporcional ao trabalho."""
return max(3.0, work_duration * self.break_ratio)
As regras são:
- Mínimo de 15 minutos: Menos que isso e você não entra em flow.
- Máximo de 50 minutos: Mais que isso e a qualidade cognitiva cai drasticamente.
- Ajuste por período: Se você rende 30% mais de manhã, o timer sugere sessões 30% maiores.
- Ajuste por interrupções: Se 40% das suas sessões são interrompidas, o timer encurta para reduzir frustração.
- Pausa proporcional: 20% da duração do trabalho, com mínimo de 3 minutos.
Box Perrengue: Quando os Dados Mentem
Implementação Completa do Pomodoro Adaptativo
Agora juntamos tudo no orquestrador principal:
import threading
import sys
class AdaptivePomodoro:
def __init__(self):
self.tracker = SessionTracker()
self.analyzer = PatternAnalyzer(self.tracker)
self.adjuster = IntervalAdjuster(self.analyzer)
self.running = False
self.timer_thread: threading.Thread | None = None
def start_work_session(self) -> None:
duration = self.adjuster.suggest_work_duration()
print(f"\n🍅 Sessão de trabalho: {duration:.1f} minutos")
print("Pressione Ctrl+C para interromper\n")
self.tracker.start_session("work")
self._run_timer(duration, session_type="work")
def start_break_session(self) -> None:
# Busca última sessão de trabalho para calcular pausa proporcional
work_sessions = self.tracker.get_sessions(5)
last_work = next(
(s for s in reversed(work_sessions) if s["type"] == "work"),
None
)
work_duration = last_work["duration_minutes"] if last_work else 25.0
duration = self.adjuster.suggest_break_duration(work_duration)
print(f"\n☕ Pausa: {duration:.1f} minutos\n")
self.tracker.start_session("break")
self._run_timer(duration, session_type="break")
def _run_timer(self, duration_minutes: float, session_type: str) -> None:
self.running = True
total_seconds = int(duration_minutes * 60)
try:
for remaining in range(total_seconds, 0, -1):
if not self.running:
break
mins, secs = divmod(remaining, 60)
sys.stdout.write(f"\r⏱️ {mins:02d}:{secs:02d} restantes")
sys.stdout.flush()
time.sleep(1)
if self.running:
print("\n\n✅ Sessão completa!")
self.tracker.end_session(completed=True)
else:
print("\n\n⚠️ Sessão interrompida")
self.tracker.end_session(completed=False)
except KeyboardInterrupt:
self.running = False
print("\n\n⚠️ Sessão interrompida pelo usuário")
self.tracker.end_session(completed=False)
def show_stats(self) -> None:
sessions = self.tracker.get_sessions(20)
work_sessions = [s for s in sessions if s["type"] == "work"]
if not work_sessions:
print("Nenhuma sessão registrada ainda.")
return
avg_duration = mean(s["duration_minutes"] for s in work_sessions if s["completed"])
completion_rate = sum(1 for s in work_sessions if s["completed"]) / len(work_sessions)
patterns = self.analyzer.detect_time_patterns()
print("\n📊 Estatísticas (últimas 20 sessões):")
print(f" • Duração média: {avg_duration:.1f} min")
print(f" • Taxa de conclusão: {completion_rate * 100:.1f}%")
print(f" • Padrão manhã: {patterns['morning']:.1f} min")
print(f" • Padrão tarde: {patterns['afternoon']:.1f} min")
print(f" • Padrão noite: {patterns['evening']:.1f} min")
def run_interactive(self) -> None:
print("🍅 Pomodoro Adaptativo em Python Puro")
print("=" * 40)
while True:
print("\nOpções:")
print(" 1. Iniciar sessão de trabalho")
print(" 2. Iniciar pausa")
print(" 3. Ver estatísticas")
print(" 4. Sair")
choice = input("\nEscolha: ").strip()
if choice == "1":
self.start_work_session()
elif choice == "2":
self.start_break_session()
elif choice == "3":
self.show_stats()
elif choice == "4":
print("Até mais!")
break
else:
print("Opção inválida")
if __name__ == "__main__":
pomodoro = AdaptivePomodoro()
pomodoro.run_interactive()
Para rodar, basta salvar como pomodoro_adaptativo.py e executar:
python3 pomodoro_adaptativo.py
Na primeira vez, o sistema usa 25 minutos como padrão. Depois de 5-10 sessões, ele começa a sugerir durações personalizadas. Após uma semana, você vai notar que os intervalos fazem sentido para o seu ritmo específico.
Analisando Seus Padrões
Depois de alguns dias de uso, o arquivo pomodoro_sessions.json vira uma mina de ouro. Você pode analisá-lo com scripts simples:
import json
from collections import Counter
with open("pomodoro_sessions.json") as f:
sessions = json.load(f)
# Qual dia da semana você é mais produtivo?
from datetime import datetime
weekdays = Counter()
for s in sessions:
if s["type"] == "work" and s["completed"]:
dt = datetime.fromisoformat(s["start"])
weekdays[dt.strftime("%A")] += 1
print("Dias mais produtivos:")
for day, count in weekdays.most_common(3):
print(f" {day}: {count} sessões completas")
No meu caso, descobri que:
- Terças e quartas são meus dias mais focados (35% mais sessões completas).
- Sextas à tarde são inúteis para trabalho profundo (taxa de interrupção de 60%).
- Domingos de manhã são surpreendentemente produtivos quando eu não tenho reuniões na segunda.
Esse tipo de insight é impossível de ter com um timer genérico. Você precisa de dados.
Integração com Seu Workflow
O Pomodoro adaptativo é standalone, mas você pode integrá-lo com ferramentas que já usa:
Notificações no Desktop
Adicione notificações nativas com subprocess:
import subprocess
import platform
def notify(title: str, message: str) -> None:
system = platform.system()
if system == "Darwin": # macOS
subprocess.run([
"osascript", "-e",
f'display notification "{message}" with title "{title}"'
])
elif system == "Linux":
subprocess.run(["notify-send", title, message])
elif system == "Windows":
# Requer win10toast ou similar
pass
Exportação para CSV
Para análise em planilhas ou ferramentas de BI:
import csv
def export_to_csv(filename: str = "pomodoro_stats.csv") -> None:
with open(filename, "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=[
"start", "end", "duration_minutes", "type", "completed"
])
writer.writeheader()
writer.writerows(tracker.sessions)
Webhook para Automações
Dispare webhooks ao completar sessões (útil para integrar com outras automações de produtividade):
import urllib.request
import urllib.parse
def send_webhook(url: str, session: dict) -> None:
data = urllib.parse.urlencode(session).encode()
try:
urllib.request.urlopen(url, data, timeout=5)
except Exception as e:
print(f"Webhook falhou: {e}")
Limitações e Próximos Passos
Esse sistema é funcional, mas tem limitações honestas:
- Não detecta qualidade: Uma sessão de 40 minutos pode ser focada ou distraída. O timer não sabe. Você poderia adicionar auto-avaliação ao final de cada sessão.
- Não considera tipo de tarefa: Codar, escrever, revisar código — cada uma exige ritmo diferente. Uma evolução seria categorizar sessões.
- Aprendizado lento: Precisa de 5-10 sessões para começar a adaptar. Você poderia importar dados de outro timer para acelerar.
Para quem quer ir além, sugiro explorar:
- Deep Work Timer com detector de flow state — detecta quando você está em flow e evita interromper.
- Chunking cognitivo — agrupa tarefas por carga mental.
- Cognitive Load Balancer — distribui tarefas por energia disponível.
Conclusão: Seu Ritmo, Suas Regras
O Pomodoro tradicional é como um relógio de ponto: impõe horários sem entender contexto. O Pomodoro adaptativo é como um coach pessoal: observa, aprende e ajusta.
Com 150 linhas de Python puro, você tem um sistema que:
- Rastreia sessões reais em JSON local
- Detecta padrões por período do dia
- Ajusta intervalos baseado em dados, não em achismo
- Respeita seu ritmo circadiano
- Aprende com interrupções em vez de ignorá-las
A próxima vez que alguém te disser “use Pomodoro, é 25 minutos de foco”, você pode responder: “Não. É meu tempo de foco, calculado com base em meus dados.”
E se quiser evoluir isso, o código está todo aqui. Sem dependências, sem vendor lock-in, sem SaaS cobrando mensalidade. Só Python e seus dados.
Qual Automação Você Quer Ver?
Agora é sua vez. Qual aspecto de produtividade e automação te frustra mais?
- Um sistema que detecta quando você está procrastinando e sugere a próxima ação?
- Um agregador que junta métricas de GitHub, Todoist e calendário em um dashboard único?
- Um classificador automático de tarefas por urgência/importância usando NLP local?
Deixa nos comentários qual automação de produtividade você quer ver dissecada aqui no AutoMente. Se for tecnicamente viável e resolver um problema real, vira o próximo post.
E se você rodou o Pomodoro adaptativo, me conta: qual foi a maior surpresa nos seus dados? Descobrir que você rende mais à noite? Que suas sessões de sexta são inúteis? Esses insights valem mais que qualquer livro de produtividade genérico.
