feat(vida): semana da casa com análise por IA e rotina contada

O aprendizado passa a entender a logística da semana, não só a hora:
- presença por dia da semana × hora (7×24, média móvel por dia) e
  histórico dos últimos 42 dias;
- depois da meia-noite, com 7 dias observados, a IA escolhida (Gemini,
  IA local ou Qwen do S10, via Criacao.chamar) cruza os dias e devolve
  padrões em frases e um plano da semana (casa / fora / dormindo);
- o plano da IA só é adotado se conferir com 70% das horas medidas;
  senão fica como sugestão ("Usar este plano");
- "Conte sua rotina": texto livre vira plano na hora e vale sobre o da
  IA; o painel mostra o quanto confere com o que os sensores veem;
- o plano guia o comportamento: acorda do cochilo 15 min antes da
  chegada naquele dia da semana e, com a casa vazia, tira cochilos
  longos para guardar energia;
- lição da rotina só quando muda 3+ horas ou depois de uma semana, e
  some quando já existe plano da semana;
- painel: quadro da semana (o que eu vi / plano), a rotina entendida,
  sugestão, contar rotina, analisar agora e a IA da análise diária.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
julio
2026-10-09 10:36:32 -03:00
co-authored by Claude Opus 5.5
parent 24625453f7
commit 935bf8d586
8 changed files with 804 additions and 103 deletions
+16 -7
View File
@@ -1,25 +1,34 @@
/** Aprendizado com o dia a dia: ligar/desligar e esquecer. O estado chega pelo SSE 'aprendizado'. */
/** Aprendizado com o dia a dia: ajustes, análise da semana com IA e a rotina contada por você.
* O estado chega pelo SSE 'aprendizado'. */
import { postar } from './api';
import { avisos } from './avisos.svelte';
import { estado } from './estado.svelte';
import type { Aprendizado } from './tipos';
export async function configurarAprendizado(ligado: boolean) {
export async function configurarAprendizado(parcial: Partial<Aprendizado['config']>) {
const antes = estado.aprendizado;
if (antes) estado.aprendizado = { ...antes, config: { ...antes.config, ligado } };
if (antes) estado.aprendizado = { ...antes, config: { ...antes.config, ...parcial } };
try {
estado.aprendizado = await postar<Aprendizado>('/aprendizado/config', { ligado });
estado.aprendizado = await postar<Aprendizado>('/aprendizado/config', parcial);
} catch (e) {
estado.aprendizado = antes;
avisos.erro(e);
}
}
export async function esquecerAprendizado() {
async function pedir(rota: string, corpo: object, sucesso?: string) {
try {
estado.aprendizado = await postar<Aprendizado>('/aprendizado/esquecer', {});
avisos.ok('Ele esqueceu o que tinha aprendido e começa do zero');
estado.aprendizado = await postar<Aprendizado>(rota, corpo);
if (sucesso) avisos.ok(sucesso);
return true;
} catch (e) {
avisos.erro(e);
return false;
}
}
export const esquecerAprendizado = () => pedir('/aprendizado/esquecer', {}, 'Ele esqueceu o que tinha aprendido e começa do zero');
export const analisarSemana = () => pedir('/aprendizado/analisar', {});
export const contarRotina = (texto: string) => pedir('/aprendizado/rotina', { texto });
export const usarSugestao = () => pedir('/aprendizado/plano', { acao: 'usar_sugestao' }, 'Plano da IA em uso');
export const apagarPlano = () => pedir('/aprendizado/plano', { acao: 'apagar' }, 'Plano apagado: ele volta a seguir só o que mede');
+332 -82
View File
@@ -1,20 +1,45 @@
<script lang="ts">
import { GraduationCap } from '@lucide/svelte';
import { configurarAprendizado, esquecerAprendizado } from '../aprendizado.svelte';
import { CalendarClock, GraduationCap } from '@lucide/svelte';
import {
analisarSemana,
apagarPlano,
configurarAprendizado,
contarRotina,
esquecerAprendizado,
usarSugestao,
} from '../aprendizado.svelte';
import { estado } from '../estado.svelte';
import type { PlanoSemana, TipoPeriodo } from '../tipos';
import Grupo from './Grupo.svelte';
import Interruptor from './Interruptor.svelte';
import Linha from './Linha.svelte';
// O que ele aprendeu com o dia a dia: rotina da casa (24 h), do que gostam, lições e o sono.
// O que ele aprendeu com o dia a dia: a semana da casa (medida e planejada), do que gostam,
// as lições e o sono. A IA cruza a semana; você também pode contar a rotina.
const a = $derived(estado.aprendizado);
let confirmar = $state(false);
let vista = $state<'visto' | 'plano'>('visto');
let rotina = $state('');
const horaAgora = $derived(new Date(estado.agora).getHours());
const temRotina = $derived(!!a && a.rotina.some((v) => v !== null));
const DIAS = ['seg', 'ter', 'qua', 'qui', 'sex', 'sáb', 'dom'];
const NOME_TIPO: Record<TipoPeriodo, string> = { casa: 'em casa', fora: 'casa vazia', dormindo: 'dormindo' };
const IAS = [
{ valor: 'gemini', nome: 'Gemini' },
{ valor: 'local', nome: 'IA local' },
{ valor: 's10', nome: 'Qwen do S10' },
] as const;
const agora = $derived(new Date(estado.agora));
const hojeSemana = $derived((agora.getDay() + 6) % 7); // 0 = segunda, como na placa
const horaAgora = $derived(agora.getHours());
const temDados = $derived(!!a && a.semana.some((l) => l.some((v) => v !== null)));
const plano = $derived(a?.plano ?? null);
const verPlano = $derived(vista === 'plano' && !!plano);
const faz_mais = $derived(a ? a.gosto.filter((g) => g.peso > 1.05).slice(0, 4) : []);
const faz_menos = $derived(a ? [...a.gosto].reverse().filter((g) => g.peso < 0.95).slice(0, 3) : []);
const respostas = $derived(a ? Object.values(a.hoje.respostas).reduce((s, n) => s + n, 0) : 0);
const ocupado = $derived(!!a?.analise.ocupado);
const faltamDias = $derived(a ? Math.max(0, a.analise.minimo - a.historico_dias) : 0);
const detalhe = $derived.by(() => {
if (!a) return '';
@@ -23,12 +48,24 @@
return `${dias} · hoje: ${a.hoje.atos} ${a.hoje.atos === 1 ? 'gesto' : 'gestos'} com alguém perto, ${respostas} ${respostas === 1 ? 'carinho ou conversa' : 'carinhos ou conversas'}`;
});
const resumoRotina = $derived.by(() => {
if (!a) return '';
const cheias = a.rotina.flatMap((v, h) => (v !== null && v >= 0.5 ? [h] : []));
return cheias.length ? 'Costuma ter gente às ' + cheias.map((h) => h + 'h').join(', ') : 'Ainda sem um horário marcado';
});
function tipoNoPlano(p: PlanoSemana, dia: number, h: number): TipoPeriodo | null {
return p.dias[dia]?.find((x) => x.ini <= h && h < x.fim)?.tipo ?? null;
}
function rotuloCelula(dia: number, h: number) {
if (verPlano && plano) {
const t = tipoNoPlano(plano, dia, h);
return `${DIAS[dia]} ${h}h: ${t ? NOME_TIPO[t] : 'sem plano'}`;
}
const v = a?.semana[dia][h];
return `${DIAS[dia]} ${h}h: ${v == null ? 'sem dados' : Math.round(v * 100) + '% do tempo com gente'}`;
}
function confere(p: PlanoSemana) {
return p.confere === null ? 'ainda conferindo com o que eu vejo' : `confere ${Math.round(p.confere * 100)}% com o que eu vi`;
}
function quando(s: number | null) {
if (!s) return '';
return new Date(s * 1000).toLocaleString('pt-BR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
}
function dia(iso: string) {
const [, m, d] = iso.split('-');
return `${d}/${m}`;
@@ -36,46 +73,145 @@
function vezes(peso: number) {
return '×' + peso.toLocaleString('pt-BR', { maximumFractionDigits: 1 });
}
async function enviarRotina(e: SubmitEvent) {
e.preventDefault();
if (await contarRotina(rotina.trim())) {
rotina = '';
vista = 'plano';
}
}
</script>
<Grupo
titulo="O que ele aprendeu"
ajuda="aprendizado"
rodape="Ele conta gestos feitos com alguém por perto e o que veio logo depois (carinho, conversa). À meia-noite junta o dia e escreve as lições."
rodape="Ele conta o que acontece em casa: quando tem gente, o que agrada e quando acorda à toa. À meia-noite junta o dia, escreve as lições e a IA cruza a semana."
>
{#if !a}
<p class="vazio">Carregando…</p>
{:else}
<Linha icone={GraduationCap} cor="#30b0c7" rotulo="Aprender com o dia a dia" {detalhe}>
{#snippet acessorio()}
<Interruptor rotulo="Aprender com o dia a dia" checked={a.config.ligado} onchange={(l) => configurarAprendizado(l)} />
<Interruptor rotulo="Aprender com o dia a dia" checked={a.config.ligado} onchange={(l) => configurarAprendizado({ ligado: l })} />
{/snippet}
</Linha>
{#if a.config.ligado}
<section class="bloco" aria-labelledby="ap-rotina">
<h3 id="ap-rotina">Rotina da casa <span>{temRotina ? resumoRotina : 'aparece depois da primeira meia-noite'}</span></h3>
<div class="rotina" role="img" aria-label={resumoRotina}>
{#each a.rotina as v, h (h)}
<span
class="hora"
class:agora={h === horaAgora}
class:sem={v === null}
title={`${h}h: ${v === null ? 'sem dados' : Math.round(v * 100) + '% do tempo com gente'}${a.sono[h] ? ' · acordou à toa aqui' : ''}`}
>
<span class="barra" style="--v: {v ?? 0}"></span>
{#if a.sono[h]}<span class="lua" aria-hidden="true">☾</span>{/if}
<section class="bloco" aria-labelledby="ap-semana">
<div class="cabeca">
<h3 id="ap-semana">Semana da casa</h3>
{#if plano}
<span class="segmentos" role="radiogroup" aria-label="Mostrar">
<button role="radio" aria-checked={vista === 'visto'} class:ativo={vista === 'visto'} onclick={() => (vista = 'visto')}>O que eu vi</button>
<button role="radio" aria-checked={vista === 'plano'} class:ativo={vista === 'plano'} onclick={() => (vista = 'plano')}>Plano</button>
</span>
{/each}
{/if}
</div>
<div class="eixo mono" aria-hidden="true"><span>0h</span><span>6h</span><span>12h</span><span>18h</span><span>24h</span></div>
{#if a.sono.some((n) => n > 0)}
<p class="nota"><span aria-hidden="true">☾</span> Horas em que acordou à toa: ali o sensor precisa de mais movimento para acordá-lo.</p>
<div class="quadro-rolagem">
<div class="quadro" class:modo-plano={verPlano} role="img" aria-label={verPlano ? 'Plano da semana' : 'Presença medida por dia da semana e hora'}>
{#each DIAS as nomeDia, d (d)}
<span class="dia" class:hoje={d === hojeSemana}>{nomeDia}</span>
{#each a.semana[d] as v, h (h)}
{@const t = verPlano && plano ? tipoNoPlano(plano, d, h) : null}
<span
class="celula {t ?? ''}"
class:sem={!verPlano && v === null}
class:agora={d === hojeSemana && h === horaAgora}
style={!verPlano && v !== null ? `--v: ${v}` : ''}
title={rotuloCelula(d, h)}
></span>
{/each}
{/each}
<span></span>
{#each Array(24) as _, h (h)}
<span class="hora mono" aria-hidden="true">{h % 6 === 0 ? h + 'h' : ''}</span>
{/each}
</div>
</div>
{#if verPlano}
<p class="legenda">
<span class="ponto casa"></span> em casa <span class="ponto fora"></span> casa vazia <span class="ponto dormindo"></span> dormindo
</p>
{:else if !temDados}
<p class="nota">Cada dia aparece aqui depois da meia-noite. Com 2 ou 3 semanas ele separa bem um dia do outro.</p>
{:else if a.sono.some((n) => n > 0)}
<p class="nota">☾ Em algumas horas ele acordou à toa: ali o sensor precisa de mais movimento para acordá-lo.</p>
{/if}
</section>
<section class="bloco" aria-labelledby="ap-rotina">
<h3 id="ap-rotina"><CalendarClock size={13} /> A rotina que ele entendeu</h3>
{#if plano}
<div class="plano">
<span class="selo" class:voce={plano.origem === 'voce'}>{plano.origem === 'voce' ? 'Você contou' : 'A IA percebeu'}</span>
<span class="confere">{confere(plano)}</span>
{#if plano.padroes.length}
<ul class="padroes">
{#each plano.padroes as p (p)}<li>{p}</li>{/each}
</ul>
{/if}
<button class="btn fantasma pequeno" onclick={apagarPlano}>Apagar plano</button>
</div>
{:else}
<p class="nota">Sem plano ainda: ele segue só o que mede em cada dia da semana.</p>
{/if}
{#if a.sugestao}
<div class="plano sugestao">
<span class="selo">Sugestão da IA</span>
<span class="confere">{confere(a.sugestao)}</span>
<ul class="padroes">
{#each a.sugestao.padroes as p (p)}<li>{p}</li>{/each}
</ul>
<button class="btn pequeno" onclick={usarSugestao}>Usar este plano</button>
</div>
{/if}
<form class="contar" onsubmit={enviarRotina}>
<label for="ap-contar">Conte sua rotina</label>
<textarea
id="ap-contar"
rows="2"
maxlength="600"
placeholder="Ex.: fico fora de terça a quinta, das 8h às 18h. Segunda, sexta e fim de semana fico em casa."
bind:value={rotina}
></textarea>
<div class="acoes">
<button class="btn pequeno" type="submit" disabled={ocupado || rotina.trim().length < 5 || !a.analise.pode}>Ensinar</button>
<button
class="btn fantasma pequeno"
type="button"
onclick={analisarSemana}
disabled={ocupado || faltamDias > 0 || !a.analise.pode}
title={faltamDias > 0 ? `Faltam ${faltamDias} dias observados` : ''}
>
{ocupado && a.analise.tipo === 'analise' ? 'Analisando…' : 'Analisar a semana agora'}
</button>
{#if ocupado && a.analise.tipo === 'rotina'}<span class="nota">Entendendo sua rotina…</span>{/if}
</div>
{#if a.analise.erro}
<p class="erro" role="alert">{a.analise.erro}</p>
{:else if !a.analise.pode}
<p class="nota">A IA escolhida não está pronta. Confira a chave do Gemini ou a IA local nos Ajustes.</p>
{:else if faltamDias > 0}
<p class="nota">A análise da IA libera com {a.analise.minimo} dias observados (faltam {faltamDias}). Contar a rotina já funciona.</p>
{:else if a.analise.em}
<p class="nota">Última análise: {quando(a.analise.em)}</p>
{/if}
</form>
<div class="ajustes-ia">
<Interruptor rotulo="Analisar todo dia com IA" checked={a.config.analise_ia} onchange={(l) => configurarAprendizado({ analise_ia: l })} />
<span>Analisar todo dia com</span>
<select aria-label="IA da análise" value={a.config.ia} onchange={(e) => configurarAprendizado({ ia: e.currentTarget.value as any })}>
{#each IAS as ia (ia.valor)}<option value={ia.valor}>{ia.nome}</option>{/each}
</select>
</div>
</section>
{#if faz_mais.length || faz_menos.length}
<section class="bloco gosto" aria-labelledby="ap-gosto">
<section class="bloco" aria-labelledby="ap-gosto">
<h3 id="ap-gosto">Do que vocês gostam</h3>
<div class="colunas">
<ul>
@@ -114,11 +250,11 @@
<div class="esquecer">
{#if confirmar}
<span>Apagar rotina, gostos e lições?</span>
<span>Apagar semana, plano, gostos e lições?</span>
<button class="btn pequeno perigo" onclick={() => { confirmar = false; esquecerAprendizado(); }}>Esquecer tudo</button>
<button class="btn fantasma pequeno" onclick={() => (confirmar = false)}>Cancelar</button>
{:else}
<button class="btn fantasma pequeno" onclick={() => (confirmar = true)} disabled={!a.dias && !a.gosto.length && !a.licoes.length}>
<button class="btn fantasma pequeno" onclick={() => (confirmar = true)} disabled={!a.dias && !a.gosto.length && !a.licoes.length && !plano}>
Esquecer o que aprendeu
</button>
{/if}
@@ -136,17 +272,28 @@
.vazio {
padding: var(--e-3) var(--e-4);
}
.erro {
margin: 0;
color: var(--perigo);
font-size: var(--t-xs);
}
.bloco {
display: grid;
gap: var(--e-2);
padding: var(--e-3) var(--e-4);
border-top: 1px solid var(--borda);
}
h3 {
.cabeca {
display: flex;
flex-wrap: wrap;
align-items: baseline;
align-items: center;
justify-content: space-between;
gap: var(--e-2);
}
h3 {
display: flex;
align-items: center;
gap: 6px;
margin: 0;
font-size: var(--t-xs);
font-weight: 600;
@@ -154,56 +301,164 @@
text-transform: uppercase;
color: var(--texto-3);
}
h3 span {
font-weight: 400;
letter-spacing: 0;
text-transform: none;
.segmentos {
display: inline-flex;
padding: 2px;
border-radius: var(--raio-sm);
background: var(--superficie-3);
}
.rotina {
.segmentos button {
min-height: 26px;
padding: 0 var(--e-2);
border: 0;
border-radius: calc(var(--raio-sm) - 2px);
background: transparent;
color: var(--texto-2);
font: 500 var(--t-xs) var(--fonte);
cursor: pointer;
}
.segmentos button.ativo {
background: var(--superficie);
color: var(--texto);
box-shadow: var(--sombra);
}
.quadro-rolagem {
overflow-x: auto;
}
.quadro {
display: grid;
grid-template-columns: repeat(24, minmax(0, 1fr));
align-items: end;
grid-template-columns: 2.2em repeat(24, minmax(9px, 1fr));
gap: 2px;
height: 44px;
min-width: 280px;
align-items: center;
}
.hora {
position: relative;
display: flex;
align-items: flex-end;
height: 100%;
border-radius: 3px;
.dia {
font-size: 10px;
color: var(--texto-3);
}
.dia.hoje {
color: var(--texto);
font-weight: 600;
}
.celula {
height: 14px;
border-radius: 2px;
background: color-mix(in srgb, #30b0c7 calc(var(--v, 0) * 100%), var(--superficie-2));
}
.celula.sem {
background: var(--superficie-2);
opacity: 0.5;
}
.modo-plano .celula {
background: var(--superficie-2);
}
.barra {
display: block;
width: 100%;
height: calc(var(--v) * 100%);
min-height: 2px;
border-radius: 3px;
.modo-plano .celula.casa,
.ponto.casa {
background: #30b0c7;
transition: height 600ms ease;
}
.sem .barra {
background: transparent;
.modo-plano .celula.fora,
.ponto.fora {
background: color-mix(in srgb, var(--texto-3) 35%, var(--superficie-2));
}
.agora {
outline: 1.5px solid var(--texto-2);
.modo-plano .celula.dormindo,
.ponto.dormindo {
background: #5e5ce6;
}
.celula.agora {
outline: 1.5px solid var(--texto);
outline-offset: 1px;
}
.lua {
position: absolute;
top: 1px;
left: 50%;
transform: translateX(-50%);
font-size: 10px;
line-height: 1;
color: #5e5ce6;
}
.eixo {
display: flex;
justify-content: space-between;
.hora {
font-size: 9px;
color: var(--texto-3);
font-size: 10px;
white-space: nowrap;
}
.legenda {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px var(--e-2);
margin: 0;
font-size: var(--t-xs);
color: var(--texto-3);
}
.ponto {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 2px;
}
.plano {
display: grid;
gap: 6px;
padding: var(--e-2) var(--e-3);
border-radius: var(--raio-sm);
background: var(--superficie-2);
justify-items: start;
}
.plano.sugestao {
background: transparent;
border: 1px dashed var(--borda);
}
.selo {
font-size: var(--t-xs);
font-weight: 600;
color: #30b0c7;
}
.selo.voce {
color: var(--texto);
}
.confere {
font-size: var(--t-xs);
color: var(--texto-3);
}
.padroes {
display: grid;
gap: 4px;
margin: 0;
padding-left: 1.1em;
font-size: var(--t-sm);
color: var(--texto);
}
.contar {
display: grid;
gap: 6px;
}
.contar label {
font-size: var(--t-xs);
color: var(--texto-2);
}
.contar textarea {
width: 100%;
box-sizing: border-box;
padding: var(--e-2);
border: 1px solid var(--borda);
border-radius: var(--raio-sm);
background: var(--superficie);
color: var(--texto);
font: var(--t-sm) var(--fonte);
resize: vertical;
}
.acoes,
.ajustes-ia {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--e-2);
}
.ajustes-ia {
font-size: var(--t-sm);
color: var(--texto-2);
}
.ajustes-ia select {
width: auto;
flex: none;
min-height: 28px;
border: 1px solid var(--borda);
border-radius: var(--raio-sm);
background: var(--superficie);
color: var(--texto);
font: var(--t-sm) var(--fonte);
}
.colunas {
display: grid;
@@ -224,21 +479,21 @@
gap: var(--e-2);
color: var(--texto-2);
}
.colunas .titulo {
color: var(--texto-3);
font-size: var(--t-xs);
}
.colunas b {
font-weight: 600;
font-size: var(--t-xs);
font-variant-numeric: tabular-nums;
}
.mais b {
color: var(--ok, #34c759);
color: var(--ok);
}
.menos b {
color: var(--texto-3);
}
.colunas .titulo {
color: var(--texto-3);
font-size: var(--t-xs);
}
.nada {
color: var(--texto-3);
font-size: var(--t-xs);
@@ -275,9 +530,4 @@
.esquecer span {
padding-left: var(--e-1);
}
@media (prefers-reduced-motion: reduce) {
.barra {
transition: none;
}
}
</style>
+6 -3
View File
@@ -60,13 +60,16 @@ export const TOPICOS: Record<string, TopicoGuia> = {
emoji: '🎓',
onde: 'Ao vivo › O que ele aprendeu (com o modo vida ligado)',
resumo:
'Ele observa o próprio dia e melhora aos poucos, sem IA e sem internet: conta o que acontece em casa e ajusta o modo vida. À meia-noite fecha o dia, escreve as lições no diário e o que ficou para trás passa a pesar menos, porque o gosto das pessoas muda.',
'Ele observa o próprio dia e melhora aos poucos: conta o que acontece em casa, ajusta o modo vida e, uma vez por dia, uma IA cruza a semana para entender a rotina. À meia-noite fecha o dia, escreve as lições no diário e o que ficou para trás passa a pesar menos, porque o gosto das pessoas muda.',
itens: [
{ emoji: '❤️', nome: 'Do que vocês gostam', texto: 'Cada gesto ou atividade que ele faz com alguém por perto é uma tentativa. Carinho, brincadeira ou conversa até 1 minuto depois contam como "gostaram" (cutucada conta meio). Os que agradam passam a sair até 2 vezes mais; os ignorados, até a metade. Precisa de pelo menos 3 tentativas para ele formar opinião.' },
{ emoji: '🏠', nome: 'Rotina da casa', texto: 'Em que horas costuma ter gente por perto (pelo PIR e pela câmera). As 24 barras mostram cada hora. Quando a próxima hora costuma ser movimentada e a atual vazia, ele acorda do cochilo 15 minutos antes para estar de pé quando você chegar. Sono pesado e madrugada continuam respeitados.' },
{ emoji: '🗓️', nome: 'Semana da casa', texto: 'Quanto do tempo tem gente por perto (pelo PIR e pela câmera) em cada dia da semana e hora: quanto mais forte a cor, mais gente. Cada dia da semana aparece uma vez por semana, então com 2 ou 3 semanas ele separa bem uma segunda de uma terça.' },
{ emoji: '🧠', nome: 'Análise com IA', texto: 'Depois da meia-noite, com uma semana observada, a IA escolhida (Gemini, IA local ou o Qwen do S10) cruza os dias: quais são parecidos, quando a casa fica vazia, que dia foge da regra. Ela escreve o que percebeu e propõe um plano da semana. O plano só é usado se conferir com pelo menos 70% do que os sensores mediram; senão fica como sugestão, e você decide.' },
{ emoji: '✍️', nome: 'Conte sua rotina', texto: 'Escreva do seu jeito ("fico fora de terça a quinta, das 8h às 18h") e a IA transforma em plano na hora, sem esperar semanas. O que você contou vale sobre o que a IA acha, e o cartão mostra o quanto confere com o que ele vê.' },
{ emoji: '🏠', nome: 'O que muda', texto: 'Quando a próxima hora é de gente em casa e a atual não, ele acorda do cochilo 15 minutos antes, para estar de pé quando você chegar. Com a casa vazia, aproveita para tirar cochilos longos e guardar energia. Sono pesado e madrugada continuam respeitados.' },
{ emoji: '☾', nome: 'Acordar à toa', texto: 'Se o PIR acorda o rosto e ninguém fica nem interage em 3 minutos, foi à toa. Quando isso se repete na mesma hora em dias diferentes, ali ele passa a precisar de 1 ou 2 disparos a mais para acordar.' },
{ emoji: '📖', nome: 'Lições', texto: 'O que ele concluiu em cada dia, em primeira pessoa. Também aparecem no diário do cartão Vida.' },
{ emoji: '🧽', nome: 'Esquecer', texto: 'Apaga rotina, gostos e lições, e ele começa do zero. Desligar o aprendizado só para de usar o que aprendeu, sem apagar.' },
{ emoji: '🧽', nome: 'Esquecer', texto: 'Apaga a semana, o plano, os gostos e as lições, e ele começa do zero. Desligar o aprendizado só para de usar o que aprendeu, sem apagar.' },
],
dica: 'Para ensinar rápido: quando ele fizer algo que você gostou, toque na tela do celular (vale como carinho) ou fale com ele logo em seguida.',
},
+19 -1
View File
@@ -108,8 +108,26 @@ export interface Vida {
}
/** O que ele aprendeu com o dia a dia (aprendizado.py). */
export type TipoPeriodo = 'casa' | 'fora' | 'dormindo';
/** Plano da semana: 7 dias (0 = segunda) com períodos. confere = quanto bate com o medido (null = poucos dados). */
export interface PlanoSemana {
origem: 'ia' | 'voce';
criado: number;
padroes: string[];
dias: { ini: number; fim: number; tipo: TipoPeriodo }[][];
confere: number | null;
}
export interface Aprendizado {
config: { ligado: boolean };
config: { ligado: boolean; analise_ia: boolean; ia: 'gemini' | 'local' | 's10' };
/** 7 dias × 24 horas: chance de ter gente por perto (null = sem dados). */
semana: (number | null)[][];
historico_dias: number;
plano: PlanoSemana | null;
/** Plano da IA que não bateu com os dados (ou que não substitui o que você contou). */
sugestao: PlanoSemana | null;
analise: { ocupado: boolean; erro: string; em: number | null; tipo: '' | 'analise' | 'rotina'; pode: boolean; minimo: number };
/** Dias inteiros observados. */
dias: number;
/** 24 horas: chance de ter gente por perto (null = ainda sem dados nessa hora). */