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 <noreply@anthropic.com>
87 lines
4.3 KiB
JSON
87 lines
4.3 KiB
JSON
{
|
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
"$id": "https://life-helper.local/schema/habit.schema.json",
|
|
"title": "Habit (사용자 습관 — build 또는 break)",
|
|
"description": "active 상태에서 build ≤ 3, break ≤ 1 (R1/R2). frame.level은 L2/L3만 허용 (R3).",
|
|
"type": "object",
|
|
"required": ["id", "user_id", "type", "status", "title", "frame", "anchor", "started_at"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"id": { "type": "string" },
|
|
"user_id": { "type": "string" },
|
|
"phase_id": { "type": "string", "description": "소속 phase. null이면 phase 외 운영" },
|
|
"type": { "$ref": "enums.schema.json#/$defs/HabitType" },
|
|
"status": { "$ref": "enums.schema.json#/$defs/HabitStatus" },
|
|
"title": { "type": "string", "description": "사용자가 보는 짧은 제목 (≤ 20자 권장)" },
|
|
"protocol_id": { "type": ["string", "null"], "description": "catalog protocol 참조 (type=build)" },
|
|
"break_protocol_id": { "type": ["string", "null"], "description": "catalog break_protocol 참조 (type=break)" },
|
|
"common_frame_ids": {
|
|
"type": "array",
|
|
"items": { "$ref": "enums.schema.json#/$defs/CommonFrameId" },
|
|
"description": "type=break 시 활성화할 공통 프레임 ID"
|
|
},
|
|
"frame": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"required": ["level", "framed_text"],
|
|
"properties": {
|
|
"level": { "$ref": "enums.schema.json#/$defs/FrameLevelAllowed" },
|
|
"original_text": { "type": "string", "description": "사용자의 최초 입력 (L0일 수 있음, 감사 목적)" },
|
|
"framed_text": { "type": "string", "description": "L2 또는 L3로 변환된 최종 문장" }
|
|
}
|
|
},
|
|
"anchor": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"when": { "$ref": "enums.schema.json#/$defs/TimeOfDay" },
|
|
"after_what": { "type": "string", "description": "Tiny Habits anchor — 직전 행동" },
|
|
"where": { "type": "string" }
|
|
}
|
|
},
|
|
"stack_position": {
|
|
"type": "integer", "minimum": 1,
|
|
"description": "Atomic Habits #5 habit stacking 순서"
|
|
},
|
|
"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" } },
|
|
"x_constraint": {
|
|
"description": "참고용 메타 — 검증은 application layer에서 수행 (XOR protocol_id vs break_protocol_id)",
|
|
"type": "string",
|
|
"const": "type=build → protocol_id 채움 / type=break → break_protocol_id 채움 (반대쪽은 null)"
|
|
}
|
|
}
|
|
}
|