Files
autoficher/tools/perfiles.py
omar a1eef6472f 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>
2026-09-24 19:41:50 +02:00

87 lines
3.4 KiB
Python

#!/usr/bin/env python3
"""Mide cada perfil de jitter sobre el calendario real del año. No ficha nada."""
import json, os, random, sys, tempfile
from collections import Counter
from datetime import timedelta
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app import profiles, schedule, settings
from app.planner import ENTRADA, Planner
from app.store import Store
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
CAL = os.path.join(ROOT, "data", "calendar_cache.json")
def medir(nombre, cal, cfg, semilla=11):
"""Devuelve (saldo, presencia_extra_min_dia, histograma_entrada, histograma_salida)."""
st = Store(os.path.join(tempfile.mkdtemp(), "m.db"))
st.put("perfil", nombre)
pl = Planner(cfg, st)
rng = random.Random(semilla)
lo, hi = cal.span
ent, sal = Counter(), Counter()
P = W = pres = dias = 0
peor = 10 ** 9
madrugon = 0 # minutos de mas en la ENTRADA DE LA MAÑANA (la que alarga el dia)
d = lo
while d <= hi:
if cal.is_workday(d):
plan = pl.plan_day(cal, d, rng)
dias += 1
for wi, w in enumerate(cal.windows(d)):
e = [x for x in plan if x.window_idx == wi and x.typ == ENTRADA][0]
s = [x for x in plan if x.window_idx == wi and x.typ != ENTRADA][0]
em = e.planned_at.hour * 60 + e.planned_at.minute
sm = s.planned_at.hour * 60 + s.planned_at.minute
W += min(sm, w.end) - max(em, w.start)
P += w.minutes
pres += sm - em
if wi == 0:
madrugon += w.start - em
if w.start == 540:
ent[e.planned_at.strftime("%H:%M")] += 1
if w.end == 1110:
sal[s.planned_at.strftime("%H:%M")] += 1
peor = min(peor, min(x.offset_seconds for x in plan))
d += timedelta(days=1)
st.close()
return dict(saldo=W - P, coste=(pres - P) / dias, madrugon=madrugon / dias,
peor=peor, ent=ent, sal=sal, dias=dias)
def barra(c, ancho=40):
t = sum(c.values())
vis = {m: n / t for m, n in sorted(c.items()) if n / t >= 0.03}
return vis, " ".join("%s %d%%" % (m, round(100 * p)) for m, p in vis.items())
def main():
cfg = settings.load()
cal = schedule.Calendar(json.load(open(CAL)))
print("%-14s %6s %9s %10s %6s %6s" %
("perfil", "saldo", "min/día", "madrugón", "mins", "moda"))
print("-" * 62)
for n in profiles.nombres():
r = medir(n, cal, cfg)
ve, _ = barra(r["ent"])
marca = " ←" if n == profiles.DEFECTO else ""
print("%-14s %+5d %8.2f %9.2f %6d %5d%%%s" %
(n, r["saldo"], r["coste"], r["madrugon"], len(ve),
round(100 * max(ve.values())), marca))
print("\nmadrugón = minutos/día que te levantas antes por la entrada de la mañana")
print("min/día = presencia total de más (mañana + comida + salidas)\n")
for n in profiles.nombres():
r = medir(n, cal, cfg)
_, se = barra(r["ent"])
_, ss = barra(r["sal"])
print("=== %s === %s" % (profiles.titulo(n), profiles.resumen(n)))
print(" entrada 09:00 %s" % se)
print(" salida 18:30 %s" % ss)
print("tu histórico real: entrada 09:00 23% 09:01 8% 09:02 31% 09:03 38%")
if __name__ == "__main__":
main()