feat(api): align routine context windows with recent skin history

This commit is contained in:
Piotr Oleszczyk 2026-03-08 11:53:59 +01:00
parent 1c457d62a3
commit fecfa0b9e4
3 changed files with 273 additions and 71 deletions

View file

@ -49,6 +49,9 @@ from innercontext.validators.routine_validator import RoutineValidationContext
logger = logging.getLogger(__name__)
HISTORY_WINDOW_DAYS = 5
SNAPSHOT_FALLBACK_DAYS = 14
def _build_response_metadata(session: Session, log_id: Any) -> ResponseMetadata | None:
"""Build ResponseMetadata from AICallLog for Phase 3 observability."""
@ -284,12 +287,58 @@ def _ev(v: object) -> str:
return str(v)
def _build_skin_context(session: Session) -> str:
def _get_recent_skin_snapshot(
session: Session,
reference_date: date,
window_days: int = HISTORY_WINDOW_DAYS,
fallback_days: int = SNAPSHOT_FALLBACK_DAYS,
) -> SkinConditionSnapshot | None:
window_cutoff = reference_date - timedelta(days=window_days)
fallback_cutoff = reference_date - timedelta(days=fallback_days)
snapshot = session.exec(
select(SkinConditionSnapshot).order_by(
col(SkinConditionSnapshot.snapshot_date).desc()
)
select(SkinConditionSnapshot)
.where(SkinConditionSnapshot.snapshot_date <= reference_date)
.where(SkinConditionSnapshot.snapshot_date >= window_cutoff)
.order_by(col(SkinConditionSnapshot.snapshot_date).desc())
).first()
if snapshot is not None:
return snapshot
return session.exec(
select(SkinConditionSnapshot)
.where(SkinConditionSnapshot.snapshot_date <= reference_date)
.where(SkinConditionSnapshot.snapshot_date >= fallback_cutoff)
.order_by(col(SkinConditionSnapshot.snapshot_date).desc())
).first()
def _get_latest_skin_snapshot_within_days(
session: Session,
reference_date: date,
max_age_days: int = SNAPSHOT_FALLBACK_DAYS,
) -> SkinConditionSnapshot | None:
cutoff = reference_date - timedelta(days=max_age_days)
return session.exec(
select(SkinConditionSnapshot)
.where(SkinConditionSnapshot.snapshot_date <= reference_date)
.where(SkinConditionSnapshot.snapshot_date >= cutoff)
.order_by(col(SkinConditionSnapshot.snapshot_date).desc())
).first()
def _build_skin_context(
session: Session,
reference_date: date,
window_days: int = HISTORY_WINDOW_DAYS,
fallback_days: int = SNAPSHOT_FALLBACK_DAYS,
) -> str:
snapshot = _get_recent_skin_snapshot(
session,
reference_date=reference_date,
window_days=window_days,
fallback_days=fallback_days,
)
if snapshot is None:
return "SKIN CONDITION: no data\n"
ev = _ev
@ -369,10 +418,15 @@ def _build_upcoming_grooming_context(
return "\n".join(lines) + "\n"
def _build_recent_history(session: Session) -> str:
cutoff = date.today() - timedelta(days=7)
def _build_recent_history(
session: Session,
reference_date: date,
window_days: int = HISTORY_WINDOW_DAYS,
) -> str:
cutoff = reference_date - timedelta(days=window_days)
routines = session.exec(
select(Routine)
.where(Routine.routine_date <= reference_date)
.where(Routine.routine_date >= cutoff)
.order_by(col(Routine.routine_date).desc())
).all()
@ -672,14 +726,14 @@ def suggest_routine(
session: Session = Depends(get_session),
):
weekday = data.routine_date.weekday()
skin_ctx = _build_skin_context(session)
skin_ctx = _build_skin_context(session, reference_date=data.routine_date)
profile_ctx = build_user_profile_context(session, reference_date=data.routine_date)
upcoming_grooming_ctx = _build_upcoming_grooming_context(
session,
start_date=data.routine_date,
days=7,
)
history_ctx = _build_recent_history(session)
history_ctx = _build_recent_history(session, reference_date=data.routine_date)
day_ctx = _build_day_context(data.leaving_home)
available_products = _get_available_products(
session,
@ -848,10 +902,10 @@ def suggest_routine(
)
# Get skin snapshot for barrier state
stmt = select(SkinConditionSnapshot).order_by(
col(SkinConditionSnapshot.snapshot_date).desc()
skin_snapshot = _get_latest_skin_snapshot_within_days(
session,
reference_date=data.routine_date,
)
skin_snapshot = session.exec(stmt).first()
# Build validation context
products_by_id = {p.id: p for p in available_products}
@ -923,9 +977,9 @@ def suggest_batch(
{(data.from_date + timedelta(days=i)).weekday() for i in range(delta)}
)
profile_ctx = build_user_profile_context(session, reference_date=data.from_date)
skin_ctx = _build_skin_context(session)
skin_ctx = _build_skin_context(session, reference_date=data.from_date)
grooming_ctx = _build_grooming_context(session, weekdays=weekdays)
history_ctx = _build_recent_history(session)
history_ctx = _build_recent_history(session, reference_date=data.from_date)
batch_products = _get_available_products(
session,
include_minoxidil=data.include_minoxidil_beard,
@ -1030,10 +1084,10 @@ def suggest_batch(
)
# Get skin snapshot for barrier state
stmt = select(SkinConditionSnapshot).order_by(
col(SkinConditionSnapshot.snapshot_date).desc()
skin_snapshot = _get_latest_skin_snapshot_within_days(
session,
reference_date=data.from_date,
)
skin_snapshot = session.exec(stmt).first()
# Build validation context
products_by_id = {p.id: p for p in batch_products}