feat(vida): aprendizado com o dia a dia (gosto, rotina da casa e sono)
O Carinha passa a melhorar as interações com o que vive em casa, sem IA e sem internet (aprendizado.py): - gosto: gesto ou atividade feito com alguém por perto é uma tentativa; carinho, brincadeira ou conversa até 60 s depois contam como "gostaram" (cutucada vale meio). A chance de cada ato vai de 0,5x a 2x, relativa à média, e só depois de 3 tentativas; - rotina: fração de minutos com gente em cada hora (média móvel entre dias). Antes de uma hora movimentada, acorda do cochilo 15 min antes; sono pesado e madrugada continuam respeitados; - sono: PIR acordou e ninguém ficou nem interagiu em 3 min = à toa. Se repete na mesma hora, ali pede 1 ou 2 disparos a mais para acordar; - à meia-noite fecha o dia: lições em primeira pessoa no diário e o passado pesa 15% menos a cada dia (o gosto muda); - rotas /aprendizado/status|config|esquecer e SSE 'aprendizado'; - painel: cartão "O que ele aprendeu" no Ao vivo (rotina em 24 barras, faz mais / faz menos, lições, esquecer com confirmação) e tópico no guia. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,283 @@
|
||||
<script lang="ts">
|
||||
import { GraduationCap } from '@lucide/svelte';
|
||||
import { configurarAprendizado, esquecerAprendizado } from '../aprendizado.svelte';
|
||||
import { estado } from '../estado.svelte';
|
||||
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.
|
||||
const a = $derived(estado.aprendizado);
|
||||
let confirmar = $state(false);
|
||||
|
||||
const horaAgora = $derived(new Date(estado.agora).getHours());
|
||||
const temRotina = $derived(!!a && a.rotina.some((v) => v !== null));
|
||||
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 detalhe = $derived.by(() => {
|
||||
if (!a) return '';
|
||||
if (!a.config.ligado) return 'Desligado: ele faz tudo como veio de fábrica';
|
||||
const dias = a.dias === 0 ? 'primeiro dia observando' : a.dias === 1 ? '1 dia observado' : `${a.dias} dias observados`;
|
||||
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 dia(iso: string) {
|
||||
const [, m, d] = iso.split('-');
|
||||
return `${d}/${m}`;
|
||||
}
|
||||
function vezes(peso: number) {
|
||||
return '×' + peso.toLocaleString('pt-BR', { maximumFractionDigits: 1 });
|
||||
}
|
||||
</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."
|
||||
>
|
||||
{#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)} />
|
||||
{/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}
|
||||
</span>
|
||||
{/each}
|
||||
</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>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
{#if faz_mais.length || faz_menos.length}
|
||||
<section class="bloco gosto" aria-labelledby="ap-gosto">
|
||||
<h3 id="ap-gosto">Do que vocês gostam</h3>
|
||||
<div class="colunas">
|
||||
<ul>
|
||||
<li class="titulo">Faz mais</li>
|
||||
{#each faz_mais as g (g.id)}
|
||||
<li class="mais"><span>{g.nome}</span><b class="mono">{vezes(g.peso)}</b></li>
|
||||
{:else}
|
||||
<li class="nada">Nada se destacou ainda</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<ul>
|
||||
<li class="titulo">Faz menos</li>
|
||||
{#each faz_menos as g (g.id)}
|
||||
<li class="menos"><span>{g.nome}</span><b class="mono">{vezes(g.peso)}</b></li>
|
||||
{:else}
|
||||
<li class="nada">Nada foi ignorado</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<section class="bloco" aria-labelledby="ap-licoes">
|
||||
<h3 id="ap-licoes">Lições</h3>
|
||||
{#if a.licoes.length}
|
||||
<ol class="licoes">
|
||||
{#each a.licoes.slice(0, 5) as l (l.dia + l.texto)}
|
||||
<li><time class="mono">{dia(l.dia)}</time> <span>{l.texto}</span></li>
|
||||
{/each}
|
||||
</ol>
|
||||
{:else}
|
||||
<p class="nota">A primeira lição chega à meia-noite. Faça carinho ou converse logo depois de um gesto que você gostou.</p>
|
||||
{/if}
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<div class="esquecer">
|
||||
{#if confirmar}
|
||||
<span>Apagar rotina, 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}>
|
||||
Esquecer o que aprendeu
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</Grupo>
|
||||
|
||||
<style>
|
||||
.vazio,
|
||||
.nota {
|
||||
margin: 0;
|
||||
color: var(--texto-3);
|
||||
font-size: var(--t-xs);
|
||||
}
|
||||
.vazio {
|
||||
padding: var(--e-3) var(--e-4);
|
||||
}
|
||||
.bloco {
|
||||
display: grid;
|
||||
gap: var(--e-2);
|
||||
padding: var(--e-3) var(--e-4);
|
||||
border-top: 1px solid var(--borda);
|
||||
}
|
||||
h3 {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: var(--e-2);
|
||||
margin: 0;
|
||||
font-size: var(--t-xs);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: var(--texto-3);
|
||||
}
|
||||
h3 span {
|
||||
font-weight: 400;
|
||||
letter-spacing: 0;
|
||||
text-transform: none;
|
||||
}
|
||||
.rotina {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(24, minmax(0, 1fr));
|
||||
align-items: end;
|
||||
gap: 2px;
|
||||
height: 44px;
|
||||
}
|
||||
.hora {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
height: 100%;
|
||||
border-radius: 3px;
|
||||
background: var(--superficie-2);
|
||||
}
|
||||
.barra {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: calc(var(--v) * 100%);
|
||||
min-height: 2px;
|
||||
border-radius: 3px;
|
||||
background: #30b0c7;
|
||||
transition: height 600ms ease;
|
||||
}
|
||||
.sem .barra {
|
||||
background: transparent;
|
||||
}
|
||||
.agora {
|
||||
outline: 1.5px solid var(--texto-2);
|
||||
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;
|
||||
color: var(--texto-3);
|
||||
font-size: 10px;
|
||||
}
|
||||
.colunas {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
|
||||
gap: var(--e-2) var(--e-4);
|
||||
}
|
||||
.colunas ul {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
font-size: var(--t-sm);
|
||||
}
|
||||
.colunas li {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: var(--e-2);
|
||||
color: var(--texto-2);
|
||||
}
|
||||
.colunas b {
|
||||
font-weight: 600;
|
||||
font-size: var(--t-xs);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.mais b {
|
||||
color: var(--ok, #34c759);
|
||||
}
|
||||
.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);
|
||||
}
|
||||
.licoes {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
font-size: var(--t-sm);
|
||||
}
|
||||
.licoes li {
|
||||
display: flex;
|
||||
gap: var(--e-2);
|
||||
color: var(--texto);
|
||||
}
|
||||
.licoes time {
|
||||
flex: none;
|
||||
padding-top: 2px;
|
||||
color: var(--texto-3);
|
||||
font-size: var(--t-xs);
|
||||
}
|
||||
.esquecer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: var(--e-2);
|
||||
padding: var(--e-2) var(--e-3) var(--e-3);
|
||||
border-top: 1px solid var(--borda);
|
||||
font-size: var(--t-sm);
|
||||
color: var(--texto-2);
|
||||
}
|
||||
.esquecer span {
|
||||
padding-left: var(--e-1);
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.barra {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -296,6 +296,9 @@
|
||||
.diario li.emoji {
|
||||
color: #af52de;
|
||||
}
|
||||
.diario li.aprendizado {
|
||||
color: #30b0c7;
|
||||
}
|
||||
.diario li.cuidado {
|
||||
color: var(--texto);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user