feat(vida): horário de dormir acompanha a rotina da casa e lição honesta
- A lição da rotina separa o que cai na madrugada: "das 10h às 23h vou
estar acordado; das 23h à 1h é a minha hora de dormir, fico quietinho
a não ser que alguém me chame" (antes prometia ficar acordado de
madrugada, o que nunca acontecia).
- Com 5+ dias de rotina, na virada do dia ele mexe sozinho no horário
da madrugada: atrasa o início se a primeira hora costuma ter gente,
adianta o fim se a casa acorda cedo; 1 h por noite, no máximo 3 h
depois / 2 h antes do que a pessoa escolheu, nunca menos de 5 h de
madrugada, e volta quando as horas esvaziam. Mudança à mão vira a
nova base. Fica no diário e no "Dia a dia"; liga/desliga no cartão.
- Faixas que passam da meia-noite viram uma só ("das 23h à 1h").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+94
-6
@@ -24,7 +24,7 @@ from pathlib import Path
|
||||
|
||||
from comum import gravar_atomico, ler_json
|
||||
|
||||
PADRAO = dict(ligado=True, analise_ia=True, ia='gemini')
|
||||
PADRAO = dict(ligado=True, analise_ia=True, ia='gemini', ajustar_sono=True)
|
||||
IAS = ('gemini', 'local', 's10')
|
||||
JANELA = 60 # s depois de um ato em que a resposta de alguém conta para ele
|
||||
PESO_RESPOSTA = {'carinho': 1.0, 'brincar': 1.0, 'conversa': 1.0, 'alimentar': 0.6, 'cutucar': 0.5}
|
||||
@@ -38,6 +38,9 @@ MIN_MINUTOS_HORA = 10 # minutos observados numa hora para ela entrar na rotin
|
||||
ROTINA_CHEIA, ROTINA_VAZIA = 0.5, 0.3
|
||||
AVISO_CHEGADA = 15 * 60 # s antes da hora movimentada em que ele acorda do cochilo
|
||||
MAX_LICOES = 30
|
||||
# Horário de dormir pela rotina: anda 1 h por noite, no máximo 3 h depois / 2 h antes do que a pessoa
|
||||
# escolheu, com pelo menos 5 dias observados, e a madrugada nunca fica menor que 5 h.
|
||||
SONO_MIN_DIAS, SONO_ATRASO_MAX, SONO_ADIANTO_MAX, SONO_MIN_HORAS = 5, 3, 2, 5
|
||||
MAX_DECISOES = 800 # decisões guardadas (com o motivo), umas duas semanas
|
||||
AVISO_SONO = 10 * 60 # no máximo uma nota de "não acordou com o sensor" a cada 10 min
|
||||
DIAS_SEMANA = ['segunda', 'terça', 'quarta', 'quinta', 'sexta', 'sábado', 'domingo']
|
||||
@@ -87,11 +90,17 @@ def _faixas(horas):
|
||||
faixas[-1] = (faixas[-1][0], h + 1)
|
||||
else:
|
||||
faixas.append((h, h + 1))
|
||||
if len(faixas) > 1 and faixas[0][0] == 0 and faixas[-1][1] == 24: # passa da meia-noite: uma faixa só
|
||||
faixas = [(faixas[-1][0], faixas[0][1] + 24)] + faixas[1:-1]
|
||||
return faixas
|
||||
|
||||
|
||||
def _na_noite(h, ini, fim):
|
||||
return ini <= h < fim if ini < fim else (h >= ini or h < fim)
|
||||
|
||||
|
||||
def _texto_faixas(faixas):
|
||||
partes = ['das %dh às %dh' % (a, b % 24) for a, b in faixas]
|
||||
partes = ['%s %dh %s %dh' % ('da' if a == 1 else 'das', a, 'à' if b % 24 == 1 else 'às', b % 24) for a, b in faixas]
|
||||
return partes[0] if len(partes) == 1 else ', '.join(partes[:-1]) + ' e ' + partes[-1]
|
||||
|
||||
|
||||
@@ -126,6 +135,11 @@ class Aprendizado:
|
||||
self.pode_ia = lambda provedor: False
|
||||
self.decisoes = [] # [{horario, tipo, texto, motivo}] mais nova primeiro
|
||||
self.sono_avisado = 0.0
|
||||
# horário da madrugada (Ajustes do celular): o main liga; None = madrugada silenciosa desligada
|
||||
self.noite = lambda: None
|
||||
self.mudar_noite = lambda ini, fim: None
|
||||
self.sono_base = None # (início, fim) que a pessoa escolheu
|
||||
self.sono_auto = None # (início, fim) que ele mesmo pôs por último
|
||||
try:
|
||||
salvo = json.loads(self.caminho.read_text(encoding='utf-8'))
|
||||
self.config.update(self.validar(salvo.get('config', {})))
|
||||
@@ -151,6 +165,9 @@ class Aprendizado:
|
||||
self.analise['em'] = salvo['analise_em']
|
||||
if isinstance(salvo.get('hoje'), dict) and salvo['hoje'].get('dia'):
|
||||
self.hoje.update(salvo['hoje'])
|
||||
for chave in ('sono_base', 'sono_auto'):
|
||||
if isinstance(salvo.get(chave), list) and len(salvo[chave]) == 2:
|
||||
setattr(self, chave, tuple(int(v) % 24 for v in salvo[chave]))
|
||||
except (OSError, ValueError, TypeError, KeyError, AttributeError):
|
||||
pass
|
||||
|
||||
@@ -200,7 +217,8 @@ class Aprendizado:
|
||||
licoes=self.licoes, rotina_avisada=self.rotina_avisada, rotina_avisada_dia=self.rotina_avisada_dia,
|
||||
hoje=self.hoje, semana=self.semana,
|
||||
semana_n=self.semana_n, historico=self.historico, plano=self.plano, sugestao=self.sugestao,
|
||||
analise_em=self.analise['em'], decisoes=self.decisoes)
|
||||
analise_em=self.analise['em'], decisoes=self.decisoes, sono_base=self.sono_base,
|
||||
sono_auto=self.sono_auto)
|
||||
self.salvo_em = time.time()
|
||||
try:
|
||||
gravar_atomico(self.caminho, dados)
|
||||
@@ -436,14 +454,27 @@ class Aprendizado:
|
||||
licoes.append('Tentei %s %d vezes e ninguém ligou. Vou fazer menos.' % (self.nome(ignorado[0][1]), ignorado[0][0]))
|
||||
if observou and hoje['atos'] >= 5 and not sum(hoje['respostas'].values()):
|
||||
licoes.append('Hoje ninguém brincou comigo. Amanhã eu tento coisas diferentes.')
|
||||
# rotina: quando passa a ter um padrão (ou ele muda), conta
|
||||
# sono: a rotina mexe no horário de dormir (antes de contar a rotina, que já usa o novo horário)
|
||||
licoes += self.ajustar_noite(hoje['dia'])
|
||||
# rotina: quando passa a ter um padrão (ou ele muda), conta, separando o que cai na madrugada
|
||||
if self.dias >= 2 and not self.plano:
|
||||
cheias = [h for h in range(24) if self.rotina[h] is not None and self.rotina[h] >= ROTINA_CHEIA]
|
||||
mudou = not self.rotina_avisada or len(set(cheias) ^ set(self.rotina_avisada)) >= 3 or (
|
||||
cheias != self.rotina_avisada and self.dias - self.rotina_avisada_dia >= 7)
|
||||
if cheias and mudou:
|
||||
licoes.append('Percebi que vocês costumam estar por perto %s. Vou estar acordado nessa hora.'
|
||||
% _texto_faixas(_faixas(cheias)))
|
||||
noite = self.noite()
|
||||
dormindo = [h for h in cheias if noite and _na_noite(h, *noite)]
|
||||
acordado = [h for h in cheias if h not in dormindo]
|
||||
quieto = 'é a minha hora de dormir: fico quietinho, a não ser que alguém me chame.'
|
||||
if acordado:
|
||||
texto = ('Percebi que vocês costumam estar por perto %s. Vou estar acordado nessa hora.'
|
||||
% _texto_faixas(_faixas(acordado)))
|
||||
if dormindo:
|
||||
resto = _texto_faixas(_faixas(dormindo)) + ' ' + quieto
|
||||
texto += ' ' + resto[0].upper() + resto[1:]
|
||||
else:
|
||||
texto = 'Percebi gente por perto %s, mas %s' % (_texto_faixas(_faixas(dormindo)), quieto)
|
||||
licoes.append(texto)
|
||||
self.rotina_avisada, self.rotina_avisada_dia = cheias, self.dias
|
||||
# sono: acordou à toa várias vezes na mesma hora
|
||||
piores = sorted(((n, h) for h, n in enumerate(hoje['falsos']) if n >= 2), reverse=True)
|
||||
@@ -463,6 +494,62 @@ class Aprendizado:
|
||||
del self.licoes[MAX_LICOES:]
|
||||
return licoes
|
||||
|
||||
def ajustar_noite(self, dia):
|
||||
"""Muda o horário da madrugada aos poucos pela rotina da casa. Devolve as lições."""
|
||||
noite = self.noite()
|
||||
if not noite or not self.config['ajustar_sono'] or not self.ligado():
|
||||
return []
|
||||
if self.sono_base is None or noite != self.sono_auto:
|
||||
self.sono_base = noite # a pessoa escolheu (ou mudou) o horário: vira a base
|
||||
if self.dias < SONO_MIN_DIAS:
|
||||
return []
|
||||
ini, fim = noite
|
||||
base_ini, base_fim = self.sono_base
|
||||
|
||||
def cheia(h):
|
||||
v = self.rotina[h % 24]
|
||||
return v is not None and v >= ROTINA_CHEIA
|
||||
|
||||
def vazia(h):
|
||||
v = self.rotina[h % 24]
|
||||
return v is not None and v <= ROTINA_VAZIA
|
||||
|
||||
atraso, adianto = (ini - base_ini) % 24, (base_fim - fim) % 24
|
||||
novo_ini, novo_fim = ini, fim
|
||||
if cheia(ini) and atraso < SONO_ATRASO_MAX:
|
||||
novo_ini = (ini + 1) % 24 # a primeira hora da madrugada costuma ter gente
|
||||
elif 0 < atraso <= SONO_ATRASO_MAX and vazia(ini - 1):
|
||||
novo_ini = (ini - 1) % 24 # a casa voltou a dormir cedo: volta para o de antes
|
||||
if cheia(fim - 1) and adianto < SONO_ADIANTO_MAX:
|
||||
novo_fim = (fim - 1) % 24 # a casa acorda antes
|
||||
elif 0 < adianto <= SONO_ADIANTO_MAX and vazia(fim):
|
||||
novo_fim = (fim + 1) % 24
|
||||
if (novo_ini, novo_fim) == tuple(noite) or (novo_fim - novo_ini) % 24 < SONO_MIN_HORAS:
|
||||
return []
|
||||
self.mudar_noite(novo_ini, novo_fim)
|
||||
self.sono_auto = (novo_ini, novo_fim)
|
||||
licoes, motivos = [], []
|
||||
if novo_ini != ini:
|
||||
if novo_ini == (ini + 1) % 24:
|
||||
licoes.append('Vocês têm ficado acordados até mais tarde: agora eu vou dormir às %dh (antes era %dh).'
|
||||
% (novo_ini, ini))
|
||||
motivos.append('às %dh costuma ter gente' % ini)
|
||||
else:
|
||||
licoes.append('A casa voltou a dormir mais cedo: volto a dormir às %dh.' % novo_ini)
|
||||
motivos.append('às %dh a casa costuma estar vazia' % novo_ini)
|
||||
if novo_fim != fim:
|
||||
if novo_fim == (fim - 1) % 24:
|
||||
licoes.append('Vocês têm acordado cedo: agora eu acordo às %dh (antes era %dh).' % (novo_fim, fim))
|
||||
motivos.append('às %dh costuma ter gente' % novo_fim)
|
||||
else:
|
||||
licoes.append('A casa voltou a acordar mais tarde: volto a acordar às %dh.' % novo_fim)
|
||||
motivos.append('às %dh a casa costuma estar vazia' % fim)
|
||||
fim_do_dia = time.mktime(time.strptime(dia + ' 23:59:59', '%Y-%m-%d %H:%M:%S'))
|
||||
self.decisoes.insert(0, dict(
|
||||
horario=fim_do_dia, tipo='sono', texto='Mudou o horário de dormir para %dh às %dh' % (novo_ini, novo_fim),
|
||||
motivo='; '.join(motivos) + ' (o horário escolhido é %dh às %dh)' % tuple(self.sono_base)))
|
||||
return licoes
|
||||
|
||||
# ------------------------------------------------------------------ semana: IA e plano
|
||||
@staticmethod
|
||||
def validar_plano(bruto, origem, criado=None):
|
||||
@@ -691,6 +778,7 @@ class Aprendizado:
|
||||
gosto.sort(key=lambda g: -g['peso'])
|
||||
hoje = self.hoje
|
||||
return dict(config=dict(self.config), dias=self.dias, rotina=list(self.rotina),
|
||||
sono_base=self.sono_base, sono_auto=self.sono_auto,
|
||||
sono=[self._extra(f) if self.ligado() else 0 for f in self.falsos],
|
||||
gosto=gosto, licoes=self.licoes[:12],
|
||||
semana=[[v if self.semana_n[s][h] else None for h, v in enumerate(linha)]
|
||||
|
||||
Reference in New Issue
Block a user