[Architect] #204 design spec — Flutter bootstrap + 18 tables
Phase 1 설계서 작성 완료. docs/design/204-flutter-bootstrap/ 13 개 파일: - README.md (12 섹션 모두 채움, 함수 19 개 명세, AC 16 항) - 01-project-structure.md (feature-first + layer-first 하이브리드) - 02-drift-schema-catalog.md (Catalog 7 테이블 Dart 정의) - 03-drift-schema-user.md (User 11 테이블 + R1~R10 강제 매트릭스) - 04-migrations.md (schemaVersion v1 + 인덱스 17 개) - 05-seed-data.md (assets/seed/*.json + first-run import) - 06-ux-contracts.md (체크인 R8 ≤ 60 초 흐름) - fn-recommend-variant / fn-compute-streak / fn-weekly-minimum-ratio - fn-validate-frame-level / fn-active-habit-quota / fn-seed-importer 핵심 결정: dose_variants 는 별도 habit_dose_variant 테이블로 정규화 (FK 무결성 + recommendVariant SQL 단순성). ADR-0002 승격 권장. Refs #204 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
109
docs/design/204-flutter-bootstrap/fn-active-habit-quota.md
Normal file
109
docs/design/204-flutter-bootstrap/fn-active-habit-quota.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# 함수 설계서: `checkActiveHabitQuota` (#204)
|
||||
|
||||
> 부모 설계서: [README.md](./README.md) · 상태: Draft
|
||||
> 작성: [AI] Architect · 구현: `lib/domain/rules/active_habit_quota.dart::checkActiveHabitQuota` (TBD)
|
||||
> 테스트: `test/domain/rules/active_habit_quota_test.dart` (TBD)
|
||||
|
||||
## 1. 시그니처
|
||||
|
||||
```dart
|
||||
Future<QuotaResult> checkActiveHabitQuota({
|
||||
required String userId,
|
||||
required HabitType type, // build | break
|
||||
required HabitDao habitDao,
|
||||
String? excludeHabitId, // 자기 자신 제외 (status 변경 시)
|
||||
});
|
||||
|
||||
class QuotaResult {
|
||||
final bool ok;
|
||||
final int currentActiveCount;
|
||||
final int max; // build=3, break=1
|
||||
final String? reason; // ok=false 시
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 책임
|
||||
|
||||
R1 (build ≤ 3) 와 R2 (break ≤ 1) 강제. habit insert 또는 status 'paused'→'active' 변경 직전 호출.
|
||||
|
||||
## 3. 입력
|
||||
|
||||
| 파라미터 | 타입 | 제약/검증 | 설명 |
|
||||
|----------|------|-----------|------|
|
||||
| `userId` | `String` | not null | 'u_local_default' default |
|
||||
| `type` | `HabitType` enum | build / break | |
|
||||
| `habitDao` | DAO | | DB read |
|
||||
| `excludeHabitId` | `String?` | nullable | 이 ID 의 habit 은 카운트에서 제외 (자기 자신) |
|
||||
|
||||
## 4. 출력
|
||||
|
||||
- **반환**: `QuotaResult`.
|
||||
- `ok=true` → insert 진행.
|
||||
- `ok=false` → reason 메시지로 UI 차단.
|
||||
- **부수효과**: DB read only.
|
||||
|
||||
## 5. 동작 / 알고리즘
|
||||
|
||||
```
|
||||
1. max = (type == build) ? 3 : 1
|
||||
2. count = habitDao.countActive(userId, type, excludeHabitId)
|
||||
3. if count >= max:
|
||||
return QuotaResult(ok=false, count, max,
|
||||
reason='active ${type} habit 은 최대 ${max} 개입니다. 진행 중인 habit 1 개를 paused/completed/abandoned 로 변경 후 다시 시도하세요.')
|
||||
4. return QuotaResult(ok=true, count, max)
|
||||
```
|
||||
|
||||
### 5.1 SQL
|
||||
|
||||
```sql
|
||||
SELECT COUNT(*) FROM habits
|
||||
WHERE user_id = :userId
|
||||
AND status = 'active'
|
||||
AND type = :type
|
||||
AND (:excludeHabitId IS NULL OR id != :excludeHabitId)
|
||||
```
|
||||
|
||||
`IDX_habits_user_status_type` 인덱스로 < 5 ms.
|
||||
|
||||
## 6. 에러 & 실패 모드
|
||||
|
||||
| 조건 | 처리 | 반환 |
|
||||
|------|------|------|
|
||||
| DAO 예외 | 상위로 throw (transactional rollback) | — |
|
||||
| `excludeHabitId` 가 존재하지 않음 | 무영향 (필터 조건만 적용) | ok=true (조건만 영향) |
|
||||
| 동시성 (다른 트랜잭션이 동시에 insert) | 본 Phase 단일 사용자 = race 사실상 0. 안전 보장은 호출자가 transaction 으로 quota check + insert 묶어 처리. | — |
|
||||
|
||||
## 7. 엣지케이스
|
||||
|
||||
- 사용자가 build 3 개 active 인 상태에서 새 build 시도 → reject.
|
||||
- 사용자가 break 1 개 active 인 상태에서 새 break 시도 → reject.
|
||||
- 사용자가 build 3 개 active 인 상태에서 새 **break** 시도 → ok (별도 quota).
|
||||
- status='paused' 인 habit 은 카운트 제외.
|
||||
- excludeHabitId = 자기 자신 (status 변경 시) → 정상 동작.
|
||||
|
||||
## 8. 복잡도 / 성능
|
||||
|
||||
- 시간: O(log N) (인덱스 lookup) + COUNT.
|
||||
- 호출 빈도: habit 생성/상태변경 시 1 회. 무시.
|
||||
|
||||
## 9. 의존성
|
||||
|
||||
- `HabitDao.countActive(userId, type, excludeHabitId)`.
|
||||
- Riverpod provider 로 호출 가능.
|
||||
|
||||
## 10. 테스트 케이스
|
||||
|
||||
- [ ] 신규 build, active 0 개 → ok, count=0, max=3
|
||||
- [ ] 신규 build, active 3 개 → reject
|
||||
- [ ] 신규 build, active 2 개 → ok, count=2
|
||||
- [ ] 신규 break, active 0 개 → ok
|
||||
- [ ] 신규 break, active 1 개 → reject
|
||||
- [ ] excludeHabitId 적용: active 3 중 1 개를 exclude → ok, count=2
|
||||
- [ ] paused habit 은 카운트 제외 (status='paused' 3 개 있어도 active 0 → ok)
|
||||
- [ ] 빈 DB → count=0, ok
|
||||
|
||||
## 11. 추적성
|
||||
|
||||
- 인수조건: #204 AC-7.
|
||||
- 관련 메모리: `feedback_sustainable_minimal.md` (build ≤ 3), `project_habit_breaking_module.md` (break ≤ 1).
|
||||
- 관련 schema: `habit.schema.json`, data-model.md §3 R1/R2.
|
||||
Reference in New Issue
Block a user