feat: perfiles de jitter intercambiables desde Telegram

Cinco perfiles con distinto punto en el compromiso entre variedad visible y
minutos de presencia de mas. Todos dan saldo 0. Se cambian en caliente con
/perfil, con botones, y al cambiar se vuelven a tirar los dados de todos los
dias del horizonte que no se hayan fichado ya.

                 min/dia  madrugon  entrada 09:00
    discreto       2,8      1,3     2 minutos, moda 75%
    natural        4,1      1,8     5 minutos, moda 51%   <- por defecto
    camuflado      5,5      2,1     4 minutos, moda 43%
    disperso       7,9      3,0     6 minutos, moda 29%
    sin_madrugar   5,7      1,3     2 minutos, moda 77%
    (historico real del usuario:    4 minutos, moda 38%)

La distincion util no es solo el coste total sino DONDE cae. Adelantar la
entrada de la mañana alarga el dia; adelantar la vuelta de comer solo acorta la
comida y se sale a la misma hora. De ahi "sin_madrugar": entrada de la mañana lo
mas pegada posible a las 9 (madrugon 1,26 min/dia, el mas bajo de todos, por
debajo incluso de "discreto") y toda la variedad en la pausa y las salidas.

Para eso los perfiles admiten afinar por tramo con sufijo _w<n>:
entry_median_w0 gana a entry_median para el tramo de la mañana.

tools/perfiles.py mide cada perfil sobre los 226 dias laborables del calendario
real; las cifras de arriba salen de ahi y van guardadas como metadatos para que
/perfil las muestre sin recalcular.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-24 19:41:50 +02:00
parent a496b1c672
commit a1eef6472f
6 changed files with 339 additions and 30 deletions

View File

@@ -56,6 +56,7 @@ Se registran solos en el menú del chat al arrancar.
| `/hoy` | plan y estado de hoy, con lo que Timenet ha registrado de verdad |
| `/saldo` | saldo del mes y acumulado |
| `/plan` | plan de hoy, con botón para re-tirar los dados |
| `/perfil [nombre]` | ver y cambiar el perfil de jitter |
| `/proximos [n]` | previsión de los próximos días laborables y sus rangos |
| `/fichar` `/desfichar` | fichaje manual inmediato |
| `/off [hoy\|dd/mm\|dd/mm-dd/mm]` | no fichar esos días |
@@ -72,23 +73,38 @@ Doce minutos antes de cada fichaje llega un aviso con killswitch:
El bot sólo atiende al `chat_id` de `config.json`; cualquier otro chat se ignora.
Perfiles de jitter
------------------
Timenet sólo cuenta dentro de la ventana teórica, así que las horas de fichaje
sólo pueden variar hacia el lado seguro, y alejarse cuesta presencia real. Más
variedad visible = más minutos ahí. Cada perfil elige un punto de ese compromiso.
Los cinco dan **saldo 0**; se cambian en caliente con `/perfil`.
| perfil | min/día | madrugón | entrada 09:00 |
|---|---|---|---|
| discreto | 2,8 | 1,3 | 2 minutos, moda 75 % |
| **natural** (por defecto) | 4,1 | 1,8 | 5 minutos, moda 51 % |
| camuflado | 5,5 | 2,1 | 4 minutos, moda 43 % |
| disperso | 7,9 | 3,0 | 6 minutos, moda 29 % |
| sin madrugar | 5,7 | **1,3** | 2 minutos, moda 77 % |
| *histórico real* | — | — | *4 minutos, moda 38 %* |
`min/día` es la presencia total de más. `madrugón` es la parte de esos minutos
que te levantas antes por la entrada de la mañana — el resto se lo come la pausa
de comer y la salida, que no alargan el día. Por eso existe *sin madrugar*:
entrada de la mañana pegada a las 9 y toda la variedad en la comida y la salida.
Medido sobre los 226 días laborables del año con `tools/perfiles.py`. Un perfil
puede afinar por tramo con sufijo `_w<n>`: `entry_median_w0` gana a `entry_median`
para el tramo de la mañana.
Configuración
-------------
Lo esencial va en `config.json` (ver `sample.config.json`). Los valores por defecto
del jitter están en `app/settings.py`:
| perfil | `entry_before` / `exit_after` (s) | presencia extra | colchón mínimo |
|---|---|---|---|
| holgado | 40-150 / 30-130 | 5,67 min/día | 30 s |
| **equilibrado** (por defecto) | 30-100 / 20-70 | 3,55 min/día | 20 s |
| ajustado | 20-70 / 15-45 | 2,70 min/día | 15 s |
Los tres dan saldo 0; cambia cuánta presencia real cuestan y cuánto retraso del
daemon aguantan antes de perder un minuto.
`geo_jitter_meters` desplaza la posición dentro de un disco de ese radio. Sin él,
las coordenadas salen idénticas al sexto decimal en todos los fichajes.
Lo esencial va en `config.json` (ver `sample.config.json`). `geo_jitter_meters`
desplaza la posición dentro de un disco de ese radio; sin él, las coordenadas
salen idénticas al sexto decimal en todos los fichajes.
Herramientas
------------
@@ -97,6 +113,7 @@ Herramientas
./venv/bin/python3 tools/tn_inspect.py # calendario y saldo, contra la API real
./venv/bin/python3 tools/dryrun.py # simula el año y verifica que no pierde minutos
./venv/bin/python3 tools/test_daemon.py # 19 pruebas con reloj simulado, sin red
./venv/bin/python3 tools/perfiles.py # mide cada perfil sobre el calendario real
```
Modo antiguo (CLI de un disparo)