From bcfc6b2402c378ebda9f146f065e4bafb892932e Mon Sep 17 00:00:00 2001 From: joungmin Date: Thu, 11 Jun 2026 16:37:35 +0900 Subject: [PATCH] =?UTF-8?q?[Architect]=20Refs=20#204=20=E2=80=94=20adopt?= =?UTF-8?q?=20dose=5Fvariants=20design=20(ADR-0001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves the long-pending dose variant decision before Phase 1 schema work: - habit.dose_variants[] added (id/label/dose_text/context_tags/condition_tags/is_minimum), no cardinality cap. - tracker_entry gains variant_id + context_snapshot{location,condition}. - reflection gains weekly minimum_ratio (hint-only). - data-model.md: R9 (≤4) retired; new R9 (no cap, UX-side enforcement) + R10 (weekly minimum_ratio hint, no threshold). - docs/adr/0001-dose-variants.md captures rationale + alternatives. Why now: Phase 1 (#204) needs the final schema shape before Drift DDL is drawn. Co-Authored-By: Claude Opus 4.6 --- data-model.md | 2 + docs/adr/0001-dose-variants.md | 66 ++++++++++++++++++++++++++++++++ schema/habit.schema.json | 33 +++++++++++++++- schema/reflection.schema.json | 6 +++ schema/tracker_entry.schema.json | 15 +++++++- 5 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 docs/adr/0001-dose-variants.md diff --git a/data-model.md b/data-model.md index 4a3b3f9..5e794ab 100644 --- a/data-model.md +++ b/data-model.md @@ -80,6 +80,8 @@ methodology ──< methodology_reference >── reference | R6 | `phase.duration_weeks` = 6 (default), 6주 전 anchor 변동 X | 변경 시 warning | 6 가드레일 #5 | | R7 | `if_then_rule.then_action` 회피 키워드 감지 시 warning | 클라이언트 검증 | 코끼리 회피 UX | | R8 | `habit` 일일 운영 ≤ 2분 (tracker UI 30~60초) | UX 제약 (DB X) | 6 가드레일 #2 | +| R9 | `habit.dose_variants[]` 개수 제한 없음. 단 체크인 화면은 "장소 1탭 + 컨디션 1탭 → 추천 + override" 흐름으로 R8 유지. `is_minimum=true` variant는 0개 이상 허용 (강제 X). | application + UI | `docs/adr/0001-dose-variants.md` | +| R10 | `reflection.scope='weekly'`은 `minimum_ratio` (해당 주 done 중 is_minimum 비율) 표시. 강제 임계값 없음 — hint 용도. | application 계산 | `docs/adr/0001-dose-variants.md` | --- diff --git a/docs/adr/0001-dose-variants.md b/docs/adr/0001-dose-variants.md new file mode 100644 index 0000000..1b52f38 --- /dev/null +++ b/docs/adr/0001-dose-variants.md @@ -0,0 +1,66 @@ +# ADR-0001: Dose Variants (상황·컨디션별 도즈 옵션) + +> **상태**: Accepted +> **날짜**: 2026-06-11 · **결정자**: 사용자 (joungmin) + AI 합의 · **관련 이슈**: #204 + +## 맥락 (Context) + +기존 데이터 모델은 `tracker_entry.value` 가 `done` / `blank` 2값(R5)이고, +`habit.min_dose` / `target_dose` 가 단일 문자열이었다. 사용자가 컨디션이 나쁘거나 +환경(짐 vs 집, 출장 등)이 풀 도즈와 안 맞을 때, "할 수 없으면 0(blank)"이라는 +이분법이 강요됐다. 결과: blank → lapse → 자책 → abandonment 사이클 위험. + +BJ Fogg *Tiny Habits* 의 "If tired, do tiny" + Lally 2010 의 자동화 곡선 모두 +"빈도 > 강도" 를 가리킨다. 본 시스템의 "Never miss twice" 가드레일과도 맞물려, +*상황별 가변 도즈*가 본질적으로 필요하다. + +## 결정 (Decision) + +`habit` 에 `dose_variants[]` 배열을 추가한다. 각 variant 는 +`{ id, label, dose_text, context_tags?, condition_tags?, is_minimum? }` 구조. +`tracker_entry` 에 `variant_id` 와 `context_snapshot { location, condition }` 추가. +체크인 UX 는 **장소 1탭 + 컨디션 1탭 → 매칭 variant 추천 + override** 흐름. + +운영 규칙: +- **R9 신규**: `dose_variants[]` 개수 제한 없음 (기존 R9 ≤4 폐기). `is_minimum=true` 권장이나 강제 X. +- **R10 신규**: 주간 `reflection.minimum_ratio` 표시 — `is_minimum=true` variant 선택 비율. + 강제 임계값 없음 (SDT autonomy 존중), hint 만. + +기존 단일 `min_dose` / `target_dose` 필드는 호환성을 위해 보존하되 +`dose_variants` 사용 시 deprecated. + +## 근거 (Rationale) + +- **다양성 + 상황 매칭** 둘 다 잡음 (사용자 명시 요구). 옵션 A (3단계 enum) 는 + "다양"이 약함, 옵션 C (매트릭스) 는 가드레일 #2 위반 위험. +- **체크인 ≤ 60초 (R8) 유지**: "1탭 × 2" 입력 후 자동 추천이므로 마찰 작음. +- **자유 태그**: 사용자별 컨텍스트(예: '출장-호텔', '주말-새벽') 무한 확장. +- **is_minimum 플래그**: tiny 가드 계산을 위한 최소한의 메타데이터. enum 강제 안 함. +- **R10 hint-only**: 강제 임계값은 결정 피로 + 자율성 침해. 데이터만 제공. + +## 결과 (Consequences) + +- **긍정**: + - Never miss twice 가드 강화 (tiny variant 로도 스트릭 유지). + - 5-Tier Reward T1 (3회 스트릭) 진입 확률 ↑. + - 컨텍스트 스냅샷 누적으로 향후 자동 추천 학습 가능. +- **부정 / 비용**: + - 체크인 UI 가 2-step (상황 입력 + variant 선택) 으로 늘어남. 데이터 1탭 추가. + - `tracker_entry` 통계가 done/blank 단순 카운트가 아닌, variant level 분석 필요. + - 사용자가 처음 habit 만들 때 variant 1개 이상 정의해야 하는 onboarding 부담. +- **후속 작업**: + - Phase 1 schema 작업 (`#204`) 에 dose_variants 반영. + - 4개 SoT 마크다운 (huberman / methodologies / breaking / nutrition) 에 + "도즈 변동" 가이드 짧게 추가 (별도 후속). + - `seed/` 카탈로그 데이터에 권장 variant 예시 포함. + +## 검토한 대안 (Alternatives Considered) + +- **옵션 A — 3단계 enum (tiny/normal/strong) + context_tags** + - 기각 사유: "다양"이라는 사용자 요구가 3단계로 제한됨. +- **옵션 C — condition × context 2축 매트릭스** + - 기각 사유: 입력 마찰 큼, 가드레일 #2 (≤2분/일) 위반 위험. +- **Tiny 가드 — Streak 가중치 (tiny=0.5x)** + - 기각 사유: "점수화" 느낌이 5-Tier Reward Ladder 의 "행동 단순 카운트" 원칙과 충돌. +- **Tiny 가드 — 장치 없음** + - 부분 채택: 강제는 안 함. 다만 reflection 비율 표시는 유지 (정보 제공만). diff --git a/schema/habit.schema.json b/schema/habit.schema.json index e22e092..b766d1b 100644 --- a/schema/habit.schema.json +++ b/schema/habit.schema.json @@ -43,8 +43,37 @@ "type": "integer", "minimum": 1, "description": "Atomic Habits #5 habit stacking 순서" }, - "min_dose": { "type": "string", "description": "시작 doseo (Tiny Habits)" }, - "target_dose": { "type": "string" }, + "min_dose": { "type": "string", "description": "시작 dose (Tiny Habits). dose_variants 사용 시 deprecated 가능" }, + "target_dose": { "type": "string", "description": "dose_variants 사용 시 deprecated 가능" }, + "dose_variants": { + "type": "array", + "description": "상황·컨디션별 도즈 옵션. 개수 제한 없음 (R9 폐기). 매일 체크인 시 context_tags/condition_tags 매칭으로 추천. 최소 1개 항목의 is_minimum=true 권장 (없어도 됨).", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["id", "label", "dose_text"], + "properties": { + "id": { "type": "string", "description": "habit 안에서 unique. tracker_entry.variant_id 참조" }, + "label": { "type": "string", "description": "사용자가 보는 짧은 이름 (예: '짐-메인', '집-아침')" }, + "dose_text": { "type": "string", "description": "실제 도즈 표현 (예: '데드 리프트 60kg 5x5', '푸시업 5회')" }, + "context_tags": { + "type": "array", + "items": { "type": "string" }, + "description": "장소·상황 태그 (예: ['짐'], ['집','아침'], ['출장'])" + }, + "condition_tags": { + "type": "array", + "items": { "type": "string" }, + "description": "컨디션 태그 (예: ['좋음'], ['피곤','수면부족'])" + }, + "is_minimum": { + "type": "boolean", + "default": false, + "description": "최소 도즈(tiny) 여부. 주간 reflection의 'minimum 비율' 가드 계산에 사용" + } + } + } + }, "started_at": { "$ref": "enums.schema.json#/$defs/DateString" }, "ended_at": { "$ref": "enums.schema.json#/$defs/DateString" }, "tags": { "type": "array", "items": { "type": "string" } }, diff --git a/schema/reflection.schema.json b/schema/reflection.schema.json index 7b0aa3c..7ae1ac7 100644 --- a/schema/reflection.schema.json +++ b/schema/reflection.schema.json @@ -17,6 +17,12 @@ "missed": { "type": "string", "description": "결석/lapse 패턴 — 자책 아닌 데이터 처리" }, "adjust": { "type": "string", "description": "다음 주기에 바꿀 것 1개만" }, "identity_note": { "type": "string", "description": "L3 정체성 변화 감각 (옵션)" }, + "minimum_ratio": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "scope=weekly에 한해 집계. 해당 기간 done 체크인 중 is_minimum=true variant 비율. application layer 계산. UI: 'tiny가 자주 고정' 감지 hint 용 (강제 X)." + }, "created_at": { "$ref": "enums.schema.json#/$defs/DateTimeString" } } } diff --git a/schema/tracker_entry.schema.json b/schema/tracker_entry.schema.json index 55171ab..c52a2c8 100644 --- a/schema/tracker_entry.schema.json +++ b/schema/tracker_entry.schema.json @@ -12,6 +12,19 @@ "date": { "$ref": "enums.schema.json#/$defs/DateString" }, "value": { "$ref": "enums.schema.json#/$defs/TrackerValue" }, "logged_at": { "$ref": "enums.schema.json#/$defs/DateTimeString" }, - "note": { "type": "string", "maxLength": 200, "description": "선택. 길게 쓰지 말 것" } + "note": { "type": "string", "maxLength": 200, "description": "선택. 길게 쓰지 말 것" }, + "variant_id": { + "type": "string", + "description": "선택된 habit.dose_variants[].id. value=done일 때만 의미 있음. value=blank면 무시." + }, + "context_snapshot": { + "type": "object", + "additionalProperties": false, + "description": "체크인 시점 컨텍스트 (variant 추천에 쓰인 입력). 회고·집계용 스냅샷.", + "properties": { + "location": { "type": "string", "description": "예: '짐', '집', '출장'" }, + "condition": { "type": "string", "description": "예: '좋음', '보통', '나쁨'" } + } + } } }