diff --git a/CLAUDE.md b/CLAUDE.md index e23a826..164e2b4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,7 +34,7 @@ design. Public repo: **github.com/serpeigd/TrainFitter**. [`docs/decisiones.md`](docs/decisiones.md) — read it only if you need the *why* behind a past call; don't load it by default (it's long). - [`docs/highlights.md`](docs/highlights.md) is the condensed, interview-ready - version of the same log (18 decisions, ~2 pages) — update it when a change adds + version of the same log (22 decisions, ~2 pages) — update it when a change adds a genuinely new "defensible decision," not for routine work. - **Scheduled documentation-sync runs (added 2026-08-07, explicit decision in chat): standing authorization to merge doc-only PRs from that recurring task @@ -54,9 +54,12 @@ design. Public repo: **github.com/serpeigd/TrainFitter**. Client intake JSON → routine_agent + diet_agent (motor="reglas"|"llm", same output schema) → validator_agent (ALWAYS rule-based, re-derives risk from raw profile) → verdict: aprobado_automatico | revision_reforzada - → aprobado_automatico: sent automatically (mcp/gmail_client.py's enviar_plan(), - as of 2026-08-19 — see below) - → revision_reforzada: human review, ALWAYS, no auto-send (unchanged) + → aprobado_automatico: sent automatically, zero clicks (mcp/gmail_client.py's + enviar_plan(), as of 2026-08-19 — see below) + → revision_reforzada: human review, ALWAYS, before anything sends — but once + approved, "send" now means enviar_plan() directly too (a real send, no draft + in between); a Gmail draft is a secondary, explicit fallback button, not the + default path (see below) ``` - **Two interchangeable engines** per generation agent: `"reglas"` (default, diff --git a/README.md b/README.md index b2018f5..4554555 100644 --- a/README.md +++ b/README.md @@ -138,19 +138,25 @@ This repository is built phase by phase, as a learning project. Right now: [`docs/base_conocimiento/sinergias_nutrientes.md`](docs/base_conocimiento/sinergias_nutrientes.md) — not just listed as a separate tip. Renders as a styled table in the diet PDF and in the trainer's on-screen review, in whichever language the UI - is set to. A client can **like a meal from their own portal**, and it - biases (never forces — same "prefer, don't lock" philosophy as the - synergy pairing above) toward reappearing in their next generated week, - dropped automatically if it's no longer a safe/valid pick (e.g. a new - allergy) — verified against the real workspace, not just mocked: a real - liked meal reappeared in ~55% of 15 regenerations, matching the ~60% - design target. **Liking an exercise works exactly the same way** on the - routine side (see [`agents/rutina_reglas.py`](agents/rutina_reglas.py)'s + is set to. A client can **like or dislike a meal from their own portal** + (both undoable — a like/dislike toggles back off the same way it was + set), and a like biases (never forces — same "prefer, don't lock" + philosophy as the synergy pairing above) toward reappearing in their next + generated week, while a dislike biases the same meal combination away + from a future week; either is dropped automatically if it's no longer a + safe/valid pick (e.g. a new allergy) — verified against the real + workspace, not just mocked: a real liked meal reappeared in ~55% of 15 + regenerations, matching the ~60% design target. **The same bias mechanism + exists on the routine side** (see + [`agents/rutina_reglas.py`](agents/rutina_reglas.py)'s `_sesgar_por_favoritos()`): a liked exercise is preferred, never pinned, and is dropped silently the moment a new injury makes it unsafe — the safety cross-check always outranks the preference. Verified live against a real client record: a liked exercise came back in ~74% of 30 - regenerations. + regenerations. Unlike meals, exercise-liking has no portal UI anymore — + it was removed there by direct request, so today it's only ever set on a + client's stored profile directly; the bias itself still runs whenever + `ejercicios_favoritos` is present. - Both rule engines use most of what the intake form actually collects: training level and session length shape routine volume/complexity, disliked foods and lifestyle signals (stress/sleep, job type) bias diet @@ -358,11 +364,6 @@ actually been verified: - **A magic link can be revoked early, but only by hand in Notion** — clearing the client's "Portal Reference" property invalidates it immediately. There's no "revoke" button in the app itself; the default is still its own expiry (7 days). -- **A liked meal/exercise in the client portal can't be un-liked from the portal - itself.** The 🤍/❤️ button is one-way by design (see `ui/app.py`'s - `_vista_portal_cliente()`) — `agregar_comida_favorita()`/`agregar_ejercicio_favorito()` - only ever append; removing one means editing "Liked Meals (JSON)"/"Liked Exercises - (JSON)" in Notion directly. - **The Streamlit Community Cloud free tier sleeps after inactivity.** The first request after a period of no traffic triggers a cold start (can take up to a minute); this is a hosting trade-off, not an application bug. diff --git a/docs/highlights.md b/docs/highlights.md index ff47a48..03a7177 100644 --- a/docs/highlights.md +++ b/docs/highlights.md @@ -273,5 +273,28 @@ what caught it, not testing harder. --- +## 22. A second reversal of the same guarantee — scoped narrower, not looser + +#19 kept `revision_reforzada` completely untouched when `aprobado_automatico` +plans started sending themselves: still a mandatory draft, still a human +send, no exceptions. That held right up until the trainer's own review moved +*into* the panel itself — the new edit controls (#21) plus a +password-gated Approve click already are the review, so a second look inside +Gmail afterward added friction, not safety. `_panel_aprobacion()`'s primary +action is now "Enviar el plan por email," calling `enviar_plan()` directly; +"Crear borrador en Gmail en su lugar" stays as an explicit secondary button +for a trainer who wants a second look anyway, and as the real fallback if +the send itself fails. `revision_reforzada` is now the *only* category left +in this project that gates a real, no-draft-in-between send behind a +password — the same reasoning as #19, applied one step further once the +thing that reasoning depended on (a human already looked) became true here +too. **Why it matters:** the guarantee didn't get weaker twice by +coincidence — each reversal was justified by a *specific* mechanism (the +validator's own verdict, then the panel's own review) actually satisfying +what the removed step used to provide, not by "we already crossed this line +once." + +--- + *For the full "why," including things that were tried and reverted, see* [`decisiones.md`](decisiones.md).