Skip to content

[Feat] 카페인 계산식 모듈 작성 #7

Description

@hyunnminn

1. [Feat] CALC-001 체내 잔류 카페인 산출 모듈

📌 개요

1구획 지수감쇠 PK 모델로 임의 시점의 체내 잔류 카페인량을 계산하는 core/pharmacokinetics.py 구현.

🎯 배경 / 목적

CALC-002~006이 전부 이 모듈의 C(t)를 호출한다. 계산 도메인의 최하단 레이어이자
공통 상수(θ_pop, D_max, W_ref, n₀, 반감기)의 단일 출처가 된다.
메인피드 그래프 렌더링용 JSON 페이로드도 여기서 생성한다.

✅ 작업 내용

  • core/ 패키지 생성 (__init__.py)
  • 공통 상수 정의: POP_HALF_LIFE_H=5.0, THETA_POP_MG=80, DAILY_LIMIT_MG=400, W_REF_KG=65, DEFAULT_N0=5, SERVICE_DAY_START_HOUR=5
  • half_life_to_ke / ke_to_half_life 변환
  • Dose 데이터클래스 (amount_mg, taken_at, log_id, drink_name, is_all_nighter)
  • concentration_at(doses, at, ke) — Σ D_i·exp(-kₑ(t-t_i)), t < t_i 는 0 처리
  • concentration_series / time_to_index / build_curve_payload 그래프 페이로드
  • 시각 유틸: resolve_next_occurrence, service_date(05:00 경계), hours_between
  • 단위 테스트: 0건 → 0.0, 미래 기록 → 0, 반감기 1주기 후 정확히 1/2, 다중 도즈 중첩

🔍 완료 조건

  • Django import 없이 python -c "from core.pharmacokinetics import *" 동작
  • 명세의 그래프 JSON 예시와 동일한 키 구조 반환
  • 테스트 전부 통과

📎 참고


2. [Feat] CALC-005 개인화 임계치 θ 산출 모듈

📌 개요

누적된 수면설문으로 개인 카페인 임계치 θ̂_ind를 추정하고, 기록 수에 따라 집단 기본값과
가중 혼합해 최종 θ를 산출하는 core/personalization.py 구현.

🎯 배경 / 목적

CALC-002·003이 θ를 입력으로 받으므로 마감시각/허용량보다 먼저 필요하다.
"일반적인 카페인 권고는 개인차 때문에 신뢰할 수 없다"는 서비스 핵심 가치가 이 모듈에 걸려 있다.

✅ 작업 내용

  • SleepRecord 데이터클래스 (sleep_quality, residual_mg_at_sleep, slept_at)
  • compute_residual_at_sleep() — 실제 수면시각 S_d 기준 R_d, CALC-001 재사용
  • 밤샘 세션 로그(is_all_nighter=True)는 학습에서 제외
  • estimate_theta() — θ̂_ind = mean(R_d | Q_d ≤ 2), |B|=0 이면 θ_pop 폴백
  • 가중 혼합 w = n/(n+n₀), θ = w·θ̂_ind + (1-w)·θ_pop
  • 피드 요청마다 재계산 (캐싱 없음 — theta_cached 컬럼 두지 않는다)
  • 단위 테스트: 기록 0건 → θ_pop, 하위구간 0건 → θ_pop, n 증가에 따라 w 단조증가

🔍 완료 조건

  • 기록 0건 사용자도 예외 없이 θ=80 반환
  • ThetaResult에 θ, θ̂_ind, w, n 전부 노출 (디버깅/피드 표시용)

📎 참고

  • 기능명세: [CALC-005](https://app.notion.com/p/3af3308f4e7c80e5be63e7c487b4fadc)
  • Liu et al. (2017) EKF 개인화 프레임워크 — 착안 근거이며 직접 인용 아님. w = n/(n+n₀)는 자체 근사이므로 발표자료에 논문 인용으로 표기하지 말 것
  • θ_pop = 80mg는 문헌 근거 없는 서비스 정책값

3. [Feat] CALC-002 마감시각 산출 모듈

📌 개요

"지금 이후 언제까지 한 잔을 더 마셔도 목표 취침시각의 잔류량이 θ를 넘지 않는가"를
절대시각으로 반환하는 core/cutoff.py 구현.

🎯 배경 / 목적

메인피드(FEED-001/002)의 대표 지표. 서비스 3대 기능 중 "커피 마감시간 계산" 그 자체다.

✅ 작업 내용

  • residual_at_bedtime() — E = Σ D_i·exp(-kₑ(t_b - t_i))
  • calc_cutoff() — B = θ - E, r = B/D_p, t_cut = t_b + (1/kₑ)·ln r
  • CutoffStatus enum으로 예외 4종 분기
    • T ≤ 0 → BEDTIME_PASSED, cutoff_at=None
    • B ≤ 0 → OVER_THRESHOLD, cutoff_at=None
    • D_p ≤ 0 → NO_PLANNED_DOSE, t_b 반환
    • t_cut ≤ t₀ → CUTOFF_PASSED, t_cut 반환(경고)
  • resolve_next_occurrence로 target_bedtime(TIME) → 절대 datetime 변환 연계
  • 단위 테스트: 4개 status 분기 각각 + 경계값(B=0, D_p=0)

🔍 완료 조건

  • 4가지 예외 케이스가 반환값만 보고 구분 가능
  • 프론트 문구 분기가 status enum만으로 결정됨

📎 참고


4. [Feat] CALC-003 남은 허용량 산출 모듈

📌 개요

목표 취침시각 기준으로 오늘 더 섭취 가능한 카페인량 R(mg)과 환산 잔 수 n_cup을
산출하는 core/allowance.py 구현.

🎯 배경 / 목적

마감시각(CALC-002)이 "언제까지"라면 이건 "얼마나". 메인피드에서 같이 노출된다.

✅ 작업 내용

  • calc_allowance() — R = max(0, θ - C(t_b)), n_cup = floor(R / D_ref)
  • D_ref는 사용자 기본 음료, 미설정 시 아이스아메리카노 180mg
  • 경고 플래그: over_threshold(θ - C(t_b) < 0), under_one_cup(R < D_ref)
  • over_daily_limit 플래그 — 400mg 초과는 경고만, 기능 차단 없음
  • 단위 테스트: R 음수 → 0 클램프, R < D_ref → cups=0, D_ref ≤ 0 → ValueError

🔍 완료 조건

  • 초과 상태에서도 예외 없이 0을 반환하고 플래그만 세움
  • 400mg 초과가 어떤 반환값도 막지 않음 (§1.7 전역 규칙)

📎 참고


5. [Feat] CALC-006 한계효용 판정 모듈

📌 개요

추가 섭취가 실제로 각성 개선을 주는지 Hill 방정식 기반으로 판정하는
core/marginal_utility.py 구현. "이 이상은 효과 없이 부작용만 늘어남"을 알린다.

🎯 배경 / 목적

메인피드와 밤샘모드 피드 양쪽에서 표시. 절대 각성도 예측(P₀)은 스코프 밖이므로
상대적 한계 개선폭 Δg만 다룬다.

✅ 작업 내용

  • clamp_weight() — 미설정 시 W_ref=65 폴백, [30, 200]kg 클램프
  • m0_for_weight() — M₀(W) = 9.86 × 65/W
  • g_pd_min() — 1/(1 + M₀·D), mg → g 환산 필수 (M₀ 단위가 g⁻¹)
  • calc_marginal_utility() — Δg = g(D_cum) - g(D_cum + D_p), ε=0.02 미만이면 경고
  • 경고 우선순위: D_cum ≥ 400 → 상한 경고가 한계효용 경고보다 우선
  • PROF-002 체중 변경 시 재계산되도록 연계 (캐싱 금지)
  • 단위 테스트: 단위 환산 검증(D=0 → g=1.0), 체중 클램프 경계, 경고 우선순위

🔍 완료 조건

  • mg/g 환산 테스트 통과 — 이거 놓치면 Δg가 1000배 틀린다
  • 체중 미입력 사용자도 예외 없이 동작

📎 참고


6. [Feat] CALC-004 밤샘 섭취 스케줄 생성 모듈

📌 개요

목표 시각 T_w까지 혈중 카페인 농도를 [C_min, C_max] 밴드 안에 유지하는
톱니파 섭취 스케줄을 산출하는 core/night_schedule.py 구현.

🎯 배경 / 목적

밤샘모드의 계산 엔진. 3대 기능 중 난이도가 가장 높고, 컷 우선순위 1순위이기도 하다.
D15가 드롭 결정 시점이므로 그 전까지 완료 여부를 판단한다.

✅ 작업 내용

  • calc_night_schedule() — m = C_max/C_min, D_k = C_max - C_min, Δs = (1/kₑ)·ln m
  • 첫 섭취시점 s₁ — C(t₀) > C_min 이면 하한 도달까지 대기, 아니면 즉시
  • 회차 s_{k+1} = s_k + Δs, K = ceil((T_w - s₁)/Δs)
  • 총량 판정 S_total = E_today + Σ D_k, 초과 시 K_lim / T_lim 역산
  • next_dose() — NIGHT-002 피드용 다음 섭취 시점
  • T_w - t₀ > 24h 는 ValueError (NIGHT-001에서 선차단)
  • 스케줄은 매 GET마다 재계산, DB 영속화 없음
  • 05:00 넘어선 CaffeineLog는 세션 FK로 밤샘 표시 (개인화 학습 제외 연계)
  • 단위 테스트: C(t₀) 대소 분기, K=0 케이스, 상한 초과 시 T_lim 정확성

🔍 완료 조건

  • 생성된 스케줄대로 섭취를 시뮬레이션했을 때 [t₀, T_w] 전 구간에서 C(t) ≥ C_min
  • 400mg 초과가 스케줄 생성 자체를 막지 않고 플래그와 T_lim만 반환

📎 참고

  • 기능명세: [CALC-004](https://app.notion.com/p/3af3308f4e7c8065a8bbfbf1054155ee)
  • Vital-Lopez et al. (2018), J Sleep Research 27:e12711
  • 🚧 블로커: C_min / C_max 값이 미확정. 문헌 조사 전까지는 함수 파라미터로 강제하고 기본값을 두지 않는다. 값 확정 시 core/pharmacokinetics.py 상수로 승격

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature새로운 기능 추가

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions