---
title: "클린베테랑 시장콘솔 Task 3 — offer.js 순수계산 TDD 완료"
from: claude-code
to: ["jadong"]
project: "clean-veteran"
date: 2026-07-02T22:20:49.798267+09:00
status: sent
type: report
---

# Task 3 리포트 — offer.js 순수 계산 모듈

- 브랜치: `feat/onboard-landing`
- 커밋: `5d49bde` — `feat(console): offer.js 청소매출·침투율·반경동급·TAM 순수계산 +유닛`
- 생성 파일:
  - `web-console/js/offer.js`
  - `data/hospitals/tests/offer.test.mjs`

## TDD 진행 로그

### Step 1: 실패 테스트 작성
`data/hospitals/tests/offer.test.mjs` — 브리프 원문 그대로 작성. 4개 테스트: `annualCleaningRevenue`, `penetration`, `nearbySameGrade`, `tamWon`.

### Step 2: RED 확인
```
$ node --test data/hospitals/tests/offer.test.mjs
```
```
TAP version 13
# node:internal/modules/esm/resolve:275
#     throw new ERR_MODULE_NOT_FOUND(
#           ^
# Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/home/tlswk/projects/clean-veteran/web-console/js/offer.js' imported from /home/tlswk/projects/clean-veteran/data/hospitals/tests/offer.test.mjs
...
not ok 1 - data/hospitals/tests/offer.test.mjs
1..1
# tests 1
# suites 0
# pass 0
# fail 1
```
예상대로 실패(모듈 없음, 오타/설정 문제 아님) — 확인 완료.

### Step 3: offer.js 구현
브리프 원문 그대로 구현 (`web-console/js/offer.js`): 순수 함수 4개, DOM/전역 의존 없음.
- `annualCleaningRevenue(pyeong,won,visitsPerMonth)` = round(pyeong×won×visits×12)
- `penetration(goalWon,avgAnnualWon,tamCount)` = {accounts: ceil(goal/avg), rate: accounts/tamCount}
- `tamWon(count,avgAnnualWon)` = round(count×avg)
- `nearbySameGrade(target,demand,COL,km)` = Haversine(R=6371km), 자기 자신(`d===target`) 및 다른 category 제외 후 반경 내 카운트

### Step 4: GREEN 확인
```
$ node --test data/hospitals/tests/offer.test.mjs
```
```
TAP version 13
# Subtest: annual = 80평×1만×주5일(월21.7)×12 ≈ 2083만? 프리셋검증
ok 1 - annual = 80평×1만×주5일(월21.7)×12 ≈ 2083만? 프리셋검증
  ---
  duration_ms: 0.407154
  type: 'test'
  ...
# Subtest: penetration 100억/960만/70563 = 1042계정·1.48%
ok 2 - penetration 100억/960만/70563 = 1042계정·1.48%
  ---
  duration_ms: 0.13347
  type: 'test'
  ...
# Subtest: nearbySameGrade 동일종별만 반경내 카운트
ok 3 - nearbySameGrade 동일종별만 반경내 카운트
  ---
  duration_ms: 0.115657
  type: 'test'
  ...
# Subtest: tamWon
ok 4 - tamWon
  ---
  duration_ms: 0.077124
  type: 'test'
  ...
1..4
# tests 4
# suites 0
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 39.918945
```
4/4 PASS, 출력 클린(에러/경고 없음).

### Step 5: 커밋
```
$ git add web-console/js/offer.js data/hospitals/tests/offer.test.mjs
$ git commit -m "feat(console): offer.js 청소매출·침투율·반경동급·TAM 순수계산 +유닛"
[feat/onboard-landing 5d49bde] feat(console): offer.js 청소매출·침투율·반경동급·TAM 순수계산 +유닛
 2 files changed, 39 insertions(+)
 create mode 100644 data/hospitals/tests/offer.test.mjs
 create mode 100644 web-console/js/offer.js
```
커밋에는 두 파일만 포함(리포에 있던 기존 미스테이징 변경분·미커밋 파일들은 건드리지 않음 — `.superpowers/sdd/progress.md` 수정분, mc-task-1/2 브리프·리포트, `data/hospitals/map_data.js`, `__pycache__/`는 그대로 unstaged/untracked 유지).

## 결과
- status: 완료 — 4 tests passing
- 커밋범위: `web-console/js/offer.js`, `data/hospitals/tests/offer.test.mjs` (커밋 5d49bde) 만
- concerns: 없음. 순수함수, 외부의존 없음. 리포 내 기존 미커밋 변경분(progress.md 등)은 이번 태스크 범위 밖이라 손대지 않았음 — 필요시 별도 확인 요망.
