공용 데모 세계 — 한달품 데모 (cocode#1276)

앱(/app/ — 데스크 태블릿 키오스크)과 콘솔(/console/)은 서로 다른 Flutter 웹 빌드지만 같은 출처다. 둘은 package/postpartum_world 의 PostpartumWorld 하나를 같은 브라우저 저장소 키로 읽고 쓴다 — 콘솔이 발급한 코드를 키오스크가 받고, 키오스크가 제출하면 콘솔에 새로고침 없이 뜬다. 값은 모두 가상이다.

이 문서는 설계 단계의 world-schema(W-1)를 옮긴 세계 계약이다. 기능 번들은 여기 적힌 컬렉션 · 필드만 읽고 쓰고, 숫자는 §5 의 집계 식으로만 센다.

0. 한눈에

항목 값
저장 키 demo-healthcare-postpartum/v1/world-v1 — JSON 스냅샷 하나. 이 접두사 밖은 읽지도 쓰지도 지우지도 않는다
쓰기 PostpartumWorld.transact — Web Locks 로 직렬화한 배타 구간에서 최신 스냅샷의 초안을 바꾸고, 던지면 쓰지 않는다. 끝난 트랜잭션에는 쓸 수 없다
신호 실제로 바뀐 쓰기 · 다른 탭 갱신(storage 이벤트 · 포커스 · 가시성)에서만. 읽기는 신호 0
초기화 로그인 화면의 「데이터 초기화」 = PostpartumRuntime.reset() — 접두사 키만 지우고 seed 1274 로 복원
판 PostpartumWorldSeed.schema(지금 1)가 다르거나 깨진 저장본은 버리고 시드에서 시작
시계 스냅샷 안의 now = 2026-01-15 09:00 서울(목) 고정. 진행 UI 없음 — advanceClock · resetClock 은 시험 · 코드 전용. AppClock.now() 가 이 값을 읽는다
금액 원 단위 정수. 통화(KRW) · 시간대(Asia/Seoul)는 화면 언어와 무관
시각 · 날짜 순간은 ISO-8601 UTC 문자열(PostpartumDates.instant), 날짜는 서울 날짜 YYYY-MM-DD, 달은 YYYY-MM
값 JSON 값만 — 문자열 · 유한한 수 · 참거짓 · null · 목록 · 문자열 키 맵(중첩 허용 · 재귀 검사). 시드는 null 필드를 넣지 않는다(없는 필드 = null)
데이터 분류 A 업무 데이터는 세계, B 표시 데이터(사람 · 병원 이름 · 메모 예문)는 PostpartumDisplayFields 투영, C 사용자 입력은 세계에 그대로(사람이 친 고객 이름은 투영 overlay 로도 — §6)

1. 패키지

패키지 계층 하는 일
postpartum_rules L1 shared(순수 Dart) 업무 규칙 정본 — 시계 · 역할 · 메뉴 권한 · 요금 · 해지 환불 · 회차 · 임신 주수 · 체크인 단계 · 신생아 입실 기준
postpartum_world L1 shared(순수 Dart) 공용 세계 — 스냅샷 · 트랜잭션 · 시드 · 업무 코드(enum) · 신원/세션 · 키오스크 코드 판정 · 집계 식 · 표시(B) 키. 규칙을 다시 내보낸다
postpartum_binding L6 flutter 호스트 바인딩 — registerPostpartumDemo · PostpartumRuntime(실행 판정 · 로그인 · 기기 · 코드 · 지점 스위처 · 초기화). 셸 · 호스트 전용 — 기능 번들은 의존하지 않는다

기능 저장소의 기본 모양 — 기능 번들(L5)은 postpartum_world 만 의존한다. postpartum_binding(L6)의 PostpartumRuntime 은 셸(console_router · app_router) · 호스트 전용이다(계층 래칫 — daycare 기능도 daycare_world 만 의존). 인스턴스는 서비스 로케이터로 받는다:

final locator = ServiceLocator.instance;          // package:core
if (!locator.isRegistered<PostpartumWorld>()) {   // 서버 모드 · 등록 전 — S-05(조용한 폴백 금지)
  return;
}
final world = locator.get<PostpartumWorld>();
final session = locator.get<PostpartumSession>();
final actor = session.actor;                      // 콘솔 로그인 행위자(로그인 전 null)
final scope = session.branchScope;                // 지점 범위(FR-02) — null = 전체(대표의 「전체」)
final rows = world.view.where(                    // 읽기 — 신호 0
  PostpartumCollections.reservations,
  (r) => scope == null || r.text('branchId') == scope,
);
await world.transact((tx) {                       // 쓰기 — 한 트랜잭션이 원자적
  final r = tx.get(PostpartumCollections.reservations, id);
  tx.update(PostpartumCollections.reservations, id, {
    'status': ReservationStatus.booked.name,
    'history': [...r.list('history'), {'at': PostpartumDates.instant(tx.now), 'status': 'booked', 'staffId': actor!.staffId}],
  });
});
// 화면은 mergeSignals([world.changes, session.changes]) 를 듣고 다시 읽는다(읽기는 신호 0 — 루프 없음).

교차 쓰기는 한 트랜잭션 안에서 한다(예: 퇴실 = reservations.status + stays.dischargedAt + rooms.status + aftercareCalls 생성). 다른 번들의 컬렉션을 쓸 때는 그 번들 시드 · 화면이 기대는 필드 이름을 바꾸지 않는다.

2. 신원 · 세션

세션은 탭의 메모리다(탭마다 따로 — 세계는 공유). 콘솔은 로그인 전, 앱은 기기 선택 전 상태로 시작한다.

콘솔 데모 계정(로그인 패널 순서)

계정 id 역할 직원 id 이름(B · 11개 언어 ko 고정) 지점
acct_counselor 상담실장 staff_counselor 윤채원 물결점(mul) — 투어 시작 계정
acct_director 원장 staff_director 문하린 물결점
acct_nurse 간호사 staff_nurse 임소율 물결점
acct_newborn 신생아실 staff_newborn 정다온 물결점(D 근무조)
acct_aesthetic 에스테틱 staff_aesthetic 백서아 물결점
acct_lactation 모유실장 staff_lactation 오가람 물결점
acct_ceo 대표 staff_ceo 서지원 본사(hq — 전체 지점)

행위자 PostpartumActor = accountId · staffId · role(PostpartumRole) · branchId · scope(postpartum.<역할>) · canAccessBranch · canSee(ConsoleMenu) · showsAmounts. 퇴사(staff.status = retired)한 직원의 계정은 패널에서 빠진다.

세션 인터페이스 PostpartumSession (기능 번들이 보는 것)

멤버 뜻
PostpartumActor? actor 콘솔 로그인 행위자(로그인 전 null)
PostpartumKioskDevice? device · String? passId 키오스크 기기 · 지금 쓰는 코드(kioskPasses 행 id)
String? branchScope 지점 범위(FR-02) — 대표: 지점 스위처 값(로그인하면 null = 전체에서 시작) · 지점 역할: 늘 자기 지점 · 키오스크(행위자 없이 기기만): 기기의 지점 · 로그인 · 기기 선택 전: null
void selectBranch(String? branchId) 대표의 지점 스위처(null = 전체). 대표가 아니거나 로그인 전이면 StateError, 업무 지점(PostpartumBranches.all)이 아니면(hq 포함) ArgumentError — 둘 다 아무것도 바꾸지 않는다. 행위자가 바뀌거나 로그아웃하면 전체로 돌아가고, 초기화는 그대로 둔다
void endPass() 키오스크 제출 뒤 코드를 놓는다(기기는 그대로) — 셸의 PostpartumRuntime.endPass() 와 같은 일
Stream<void> changes 행위자 · 기기 · 코드 · 지점 범위 · 미리보기가 실제로 바뀔 때만 신호
preview · resetPreview() 상태 미리보기(?fixture=)

셸은 같은 동작을 PostpartumRuntime(signIn · signOut · selectDevice · beginPass · endPass · branchScope · selectBranch · reset)로 부른다 — 세션을 바꾸는 메서드는 세션에 위임한다.

키오스크 기기 · 코드 흐름 (BRIEF-contract §7)

기기 id 목적 지점
tablet_consult 상담신청서(K-02) 물결점
tablet_admission 입실 서류(K-03 · K-04) 물결점
  1. 로그인 패널에서 기기 — PostpartumRuntime.selectDevice(deviceId).
  2. K-01 이 4자리 코드를 받아 KioskPassCheck.byCode(view, code) → KioskPassCheck.verdict(pass, device:, now:) 로 이유를 가린다(없음 · 만료 · 제출됨 · 다른 기기/목적 · 다른 기기에서 작성 중).
  3. 통과면 PostpartumRuntime.beginPass(passId)(셸) — 같은 판정을 트랜잭션 안에서 다시 하고 행을 inUse(inUseDeviceId · inUseAt)로 바꾼 뒤 세션에 싣는다.
  4. 제출 = 한 트랜잭션(기능 컬렉션 쓰기 + kioskPasses status: submitted · submittedAt) → 세션의 endPass()(기능은 locator.get<PostpartumSession>().endPass() — 셸의 PostpartumRuntime.endPass() 와 같다) → /done. 목적별 화면은 session.passId 가 없으면 /code 로 돌려보낸다(world.pass(passId) 로 행을 읽는다).

만료는 저장 상태가 아니라 시각으로 판정한다(expiresAt 이 지났고 제출 전이면 만료). 데모 시계가 09:00 고정이라 08:5x 에 발급한 코드는 데모 내내 유효하다.

3. 컬렉션

필드 이름은 camelCase, 열거값은 postpartum_rules · postpartum_world 의 enum name 이다. 「더한 필드」는 world-schema 에 없던 것을 A1 이 시드 · 집계를 위해 더한 것이다(모두 선택 — 없으면 null).

컬렉션 핵심 필드 더한 필드 시드 주인 · 쓰는 번들
branches id(sol · mul · nol) · capacity(40) · deluxeRooms(30) · vipRooms(10) · isMain order A1 · —
staff id · branchId(hq 가능) · role · employment(regular/part) · status(active/leave/nightFixed/retired) · healthCertExpiresOn? licenseKey · licenseExpiresOn · leaveUntil · status retired A1 · operations
accounts id · staffId · role · branchId — 행 순서 = 패널 순서 — A1 · 호스트
kioskDevices id · branchId · purpose(consult/admission) — A1 · 호스트
kioskPasses id · code(4자리) · purpose · deviceId?(QR 은 없음) · issuedAt · expiresAt(+10분) · status(issued/inUse/submitted/expired) · reservationId? · customerId? branchId · issuedBy · inUseDeviceId · inUseAt · submittedAt · 체크인 ① 이 발급한 입실 코드 id pass_admission_<nnnn>(일련번호 kioskPasses — AdmissionPaperwork.issuePass, 같은 예약의 안 쓴 앞 코드는 expired) A1 · consultation · stay(발급) · 앱 셸 · consult_intake · consent_record
customers id · branchId · birthYear · phoneLast4 · referralChannel · revisitCount · privacyAgreed · marketingAgreed · guardianRelation guardianPhoneLast4 · (상담 WP — 사람이 친 글 C) nameText? · guardianNameText? · addressText? · memos[{at, staffId, text}] A1 · consultation · consult_intake
consultations id · customerId · branchId · stage(first/second/third/booked/hold/dropped) · method · dueDate · hospitalKey · deliveryPlan · birthOrder · wishRoomType · wishPlan · nextContactOn? · counselorId · tagKey? · history[{at, stage, staffId, memo?}] · createdAt wishAdmissionOn · history[].method · history[].memoKey · (상담 WP — C) hospitalText? · babyNicknameText? A1 · consultation · reservation(stage booked)
intakes id · passId · deviceId · submittedAt · status · payload{C} · customerId? branchId · personaId?(시드 접수의 이름 B 키 — payload.motherName 이 없을 때 customerName(personaId)) · inquiryKey?(문의 메모 예문 키) · linkedAt · linkedBy 상담 WP
waitlist id · customerId · branchId · fromOn · toOn · roomType · suggestedBranchId? · status createdAt · createdBy · memo?(C — 안내한 대안) · referredAt · referredBy 상담 WP
reservations id · customerId · branchId · roomType · plan · status(consult/hold/booked/inHouse/checkedOut/canceled/terminated) · admissionOn · dischargeOn · birthStatus · bornOn? · roomId? · discounts[] · multiples · guardianMeals · extras[{key, amount}] · total · deposit · contractAt? · history[] consultationId · depositDueOn · counselorId · terminationRequestedAt · terminationRequestedBy A1 · reservation · stay · settlement
payments id · reservationId · purpose(deposit/balance/extra/aesthetic) · status · lines[{method, amount, status, approval?, cashReceipt?}] · paidAt? customerId · branchId · createdAt · note · lines[].approvedAt · lines[].installment · installments A1 · reservation · settlement · aesthetic
refunds id · reservationId · requestedAt · requestedBy · timing · fault · reasonKey · methodKey · resultRow · resultAmount · status(requested/calculated/confirmed/paidMock) · confirmedBy? · confirmedAt? 제안: branchId · paidAt(매출 차감 시각) 정산 WP
sessionBuckets id · customerId · source · total · used · expiresOn? reservationId · branchId · menuKey(코스) A1 · reservation · aesthetic
rooms id(room_<지점>_<호실>) · branchId · number · roomType · status · lastDisinfectedAt — A1 · stay · settlement
stays id · reservationId · customerId · roomId · admittedAt? · steps{orientation/feedingPlan/newbornIntake/motherCheck/done: {at, by}} · feedingPlan · education[6 bool] · educationSignedAt? · dischargedAt? branchId · steps.orientation.passId(K-04 제출이 남긴 ①) · steps.feedingPlan.pumpRentals(0~2) · pumpExplained · steps.motherCheck.vitalId(첫 motherVitals 행) · weightKg(초기 체중) · note(C) — WP-STAY A1 · stay · settlement
newborns id · customerId · stayId? · nicknameKey · sex · bornAt · gestationWeeks · gestationDays · birthWeightGrams · deliveryType · hospitalKey · tests{5} · assessment{8} · noteKey? branchId · reservationId · note(C — 체크인 ③ 인수 메모, WP-STAY) A1 · stay · care_chart
newbornEvents · newbornShiftChecks · newbornWeights · motherVitals · careNotes world-schema 그대로(필드 전체는 §4.6) recordedAt · confirmedAt · updatedBy · updatedAt · 점검 observations[] · memo(C) · 일지 newbornId 케어 차트 WP · stay(체크인 ④ 바이탈) · 홈(숫자 — CareChartFacts)
aestheticSessions id · customerId · therapistId · startAt · menuKey · minutes · bucketId? · status — 에스테틱 WP
messageTemplates · messageDrips · messageSends · aftercareCalls world-schema 그대로 — 메시지 WP(tpl_reservation_confirmed 는 예약 WP)
journals world-schema 그대로 — 일지 WP
monthlyLedgers id · branchId · month(YYYY-MM) · bookings · cancellations · consultations · conversions · admissions · revenue 새 컬렉션 A1 · journal · dashboard(읽기)
shiftAssignments · inventory · inspections · auditLogs world-schema 그대로 — 운영 WP
documents id · kind(DocumentKind) · targetId · signerKey · signedAt · signature?(C) · deviceId? reservationId · branchId · passId · items(동의 항목 키 목록) · 입실신청서만 payload(C — K-03 입력) · step · savedAt · completedAt · supplemented, 다른 입실 서류는 signatureDocId(서명 이미지를 가진 입실신청서 행) — 입실 서류(K-03 · K-04)는 AdmissionPaperwork(WP-STAY), id doc_<체류 꼬리>_<kind 스네이크> 각 WP(자기 kind)

customers.nameText · guardianNameText(선택 · C): 상담 등록 · 접수 연결이 새 고객을 만들 때 사람이 친 이름을 그대로 넣는다(시드에는 없다 — 시드 고객의 이름은 B). 화면은 이 필드를 직접 읽지 않고 B 와 같은 projector.text(PostpartumDisplayKeys.customerName(id)) 로 읽는다 — 바인딩이 overlay 로 맞춘다(§6).

코드 값: newborns.tests 키 = congenitalMetabolic · hearing · hepatitisB1 · vitaminK · transcutaneousJaundice(NewbornTest), assessment 키 = skinJaundice · genitals · fontanelle · hip · eyesMouth · primitiveReflex · umbilical · breathingCry (NewbornAssessmentArea). documents.kind = DocumentKind(consultForm · privacyRequired · marketingOptional · reservationConfirmation · admissionForm · infectionSafety · newbornAssessment · dischargeEducation · procedureConsent · refundForm · stayCertificate). 입실 서류의 동의 항목 키(documents.items) = admissionRules · privacy · healthInfo · infectionSafety(필수) · aesthetic · messaging(선택) — K-04 ConsentItem 이름과 같다. 세 enum 의 주인은 WP-STAY. staff.licenseKey = nurse · nurseAide · ibclc · esthetician. reservations.extras[].key 예: pumpFunnelSet. sessionBuckets.menuKey 예: postnatalBody60 · breastCare40 · swellingCare40. 결제 note = partialDuringStay (재실 중 지자체 지원 승인) · balanceAtAdmission(잔금을 입실일에 냈다).

4. 시드 (seed 1274 · 기준 2026-01-15 09:00 서울)

부품 목록 PostpartumWorldSeed._steps: seedOrganization → seedOccupancy → seedConsultations → seedKioskPasses → seedMonthlyLedgers(→ 기능 WP 의 단계: seedStay — 물결점 재실 체류가 입실 때 서명한 입실 서류 행 · 서명 이미지 · 산모 입력은 싣지 않는다). 빈 세계(reset(empty: true))에는 조직 · 기기 · 객실(모두 사용 가능)만 있다. VM 과 웹(dart2js)이 같은 seed 로 같은 바이트를 만든다(확인함). 크기는 약 1.14M 자.

4.1 숫자 (seed_facts_test 가 §5 의 식으로 단언)

숫자 물결 솔빛 노을 전체
현입실자(가동률) 34(85%) 35(88%) 32(80%) 101/120(84%)
신생아(쌍둥이) 35(1쌍) 36(1쌍) 33(1쌍) 104
오늘 입실 / 퇴실 1 / 3 2 / 3 2 / 1 5 / 7
내일(1/16) 입실 / 퇴실 1 / 2 — — —
당월입실 · 당월입실예정 15 · 11 (14 · 8) (9 · 7) —
이번 달 신규 예약 12 14 10 36
이번 달 해지(요청 기준) 2 1 1 4
이번 달 상담 · 전환 22 · 9(41%) 25 · 11(44%) 19 · 7(37%) 66 · 27(41%)
진행 중 상담(1차 · 2차 · 3차) 18(7 · 6 · 5) (16: 6 · 5 · 5) (14: 5 · 5 · 4) —
이번 달 매출 108,200,000 118,600,000 85,600,000 312,400,000
월별 예약자(입실 예정 월 · 예약완료) 2~6월 24 · 23 · 19 · 12 · 6 — — —
정리 · 소독 필요 객실 2(308 · 706) 0 0 2

괄호 값은 시험이 단언하지 않는 생성 결과다(기능 WP 가 기대지 말 것).

4.2 물결점 이름 있는 행 (Figma 와 같게)

객실 고객 id 상태 · 날짜
VIP 701 cust_yunseo 하윤서 재실 D+6 — 1/9 입실 · 13박 → 1/22 퇴실, 다음 cust_eunsol 나은솔 1/24
VIP 702 cust_eunbi 고은비 재실 D+3 — 1/12 14:00 입실 → 1/25, 아기 baby_eunbi 꼬물이
VIP 703 cust_bomi 서보미 배정됨 · 오늘 입실 예정(체류 stay_bomi 는 입실 전 — 단계 비어 있음), 아기 baby_bomi 튼튼이
VIP 704 cust_eungyeol 조은결 재실 D+10 — 1/5 → 1/18, 다음 cust_harin 마하린 1/21
VIP 705 — 비어 있음(권나윤 1/12 퇴실), 다음 cust_soi_byeon 변소이 1/20(출산 확인 전)
VIP 706 생성 소독 — 오늘 08:40 퇴실(계좌이체)
디럭스 301 · 302 · 303 cust_dain 표다인 D+5 · cust_arin 설아린 D+1 · cust_seula 구슬아 D+2(6박) 재실
디럭스 304 — 비어 있음, 다음 cust_raon 임라온 1/22
디럭스 305 cust_soi_nam 남소이 배정됨 · 내일 입실(제왕절개 · 출산 완료)
디럭스 306 · 307 · 309 · 310 cust_hana 노하나 D+7 · cust_yewon 탁예원 D+4(9박 — 다음 cust_eunbyeol 차은별 1/23) · cust_sua 봉수아 D+9 · cust_dayeon 석다연 D+3 재실
디럭스 308 생성 소독 — 오늘 08:20 퇴실(카드)
디럭스 312 cust_dasom 강다솜 재실 — 1/2 → 오늘 퇴실 예정(09:00 엔 아직 재실), 퇴실 교육 6/6 서명 08:50

id 규칙: 고객 cust_<꼬리> → 예약 res_<꼬리> · 체류 stay_<꼬리> · 신생아 baby_<꼬리>(쌍둥이 _2) · 상담 cons_<꼬리> · 결제 pay_<꼬리>_<deposit|balance|course|support> · 버킷 bkt_<꼬리>_<prenatal|postnatal|course>. 생성 레코드는 cust_<지점>_<nnn>(상담만 있는 고객은 cust_<지점>_c<nn>), 직원 staff_<지점>_<nn>. PostpartumTourIds 가 모든 이름 있는 id 와 파생 함수를 가진다.

4.3 키오스크 코드 (데모 방문자가 앱에서 넣는다)

코드 행 id 목적 · 기기 발급 → 만료 상태
3812 pass_consult_today 상담 · 상담 태블릿 08:59 → 09:09 발급
6427 pass_admission_bomi 입실 · 입실 태블릿(서보미 예약 res_bomi) 08:58 → 09:08 발급
1594 pass_intake_yena 상담 · 상담 태블릿 08:34 → 08:44 08:42 제출(접수함 — 상담 WP 의 intakes.passId)
8826 pass_intake_haram 상담 · QR(휴대폰 — 기기 없음) 08:48 → 08:58 08:57 제출(접수함)

4.4 생성 규칙 (숫자를 맞추는 방법)

4.5 마감 월 장부 (monthlyLedgers — 솔빛 / 물결 / 노을)

예약: 8월 24/22/19 · 9월 26/21/20 · 10월 25/23/18 · 11월 27/24/21 · 12월 23/22/19. 해지 · 상담 · 전환 · 입실 · 매출 칸은 가상 값이다(seed_monthly_ledgers.dart). 이번 달(1월)은 장부가 아니라 행에서 센다.

4.6 케어 차트 (seed_care.dart — WP-CARE)

물결점 재실자만 시드한다. 솔빛 · 노을은 행이 없다(그 지점의 차트 계정이 없다 — 그 지점 숫자는 「재실 전원 미기록」이 된다). 기준시각 뒤의 「일어난 일」은 넣지 않는다 — 오늘(1/15)은 오전 바이탈 · 07:00 뒤 첫 칸 · D 근무조 점검(꼬물이 · 하윤서 아기)까지.

컬렉션 행 · 크기 무엇을 필드(world-schema + 더한 것)
motherVitals 132 · 40.3K 자 고은비 6회차(1/12 오후 ~ 1/15 오전 — Figma C-08a 값) · 이름 있는 산모 10명 1/12(입실이 늦으면 입실 회차)부터 · 생성 산모 23명 1/14 오전부터. 오늘은 오전 회차만 id · customerId · stayId · date · slot · temperature · systolic · diastolic · bleeding · breastPain · edema · mood · recordedBy · recordedAt · confirmedBy? · confirmedAt? · updatedBy? · updatedAt?(고친 사람 — 고치면 확인 서명이 지워진다)
newbornEvents 122 · 22.8K 자 꼬물이 1/14 하루(Figma 10:30 · 12:00 칸 포함) + 1/15 07:30 · 09:00 · 이름 있는 재실자의 아기 1/14 3시간 간격 8칸 + 오늘 아침 한 칸 · 생성 재실자의 아기는 오늘 아침 한 칸 id · newbornId · slotAt · directMinutes? · bottleMl? · formulaPreparedMl? · formulaTakenMl? · urine? · stool? · temperature? · vomit? · shift · recordedBy · recordedAt — urine · stool · vomit 은 있으면 1(없으면 필드가 없다)
newbornShiftChecks 109 · 23.6K 자 꼬물이 1/12 E ~ 1/15 D(D 는 Figma 「정다온 → 미서명 · 황달 9.1 / 8.4」) · 그 밖의 아기 1/14 D · E · N(입실 뒤 근무조) · 하윤서 아기 1/15 D id · newbornId · date(차트 날짜) · shift · jaundiceForehead · jaundiceChest · findingsKey(none 「특이 소견 없음」 / noted) · observations[](breathing · eyes · umbilicalSkin · other) · memo?(C) · recordedBy · recordedAt · confirmedBy? · confirmedAt? · updatedBy? · updatedAt?
newbornWeights 134 · 11.8K 자 꼬물이 1/9 3,420 g ~ 1/14 3,380 g(Figma) · 이름 있는 재실자의 아기 1/9(또는 출생)부터 · 생성 아기 1/12 ~ 1/14 — 15:00 목욕 체중 id · newbornId · date · grams · recordedBy? · recordedAt?(화면에서 기록한 행)
careNotes 6 · 1.0K 자 예문 키(CareNoteExample — 피로 호소 · 낮잠 시간 안내 / 유방이 단단해짐 / 정기검진 외출 / 제대가 떨어짐 / 수유 뒤 딸꾹질 / 입실 뒤 첫 목욕 — 문구는 번들 카탈로그 note_example) — 고은비 2 · 노하나 · 하윤서 · 표다인 · 설아린 id · stayId · target · newbornId?(아기 일지) · at · noteKey(시드) 또는 text(C — 직원 입력) · authorId

합계 ≈ 99,500 자(스냅샷 전체 1,242,127 자 — 척추 + 케어 차트만 올린 때). id 규칙은 CareChartIds: 바이탈 mv_<체류 꼬리>_<YYYYMMDD>_<am|pm>, 칸 nbe_<아기 꼬리>_<YYYYMMDDHHmm>, 점검 nsc_<아기 꼬리>_<YYYYMMDD>_<D|E|N>, 체중 nbw_<아기 꼬리>_<YYYYMMDD>, 직원 일지 note_<일련번호>(시드 일지는 note_<고객 꼬리>_<n>). 한 칸 · 회차 · 근무조 · 하루에 행이 하나라 저장은 같은 id 를 덮어쓴다 — 체크인 ④(WP-STAY)의 첫 바이탈도 같은 id 로 쓴다.

차트 하루 · 근무조(CareChartDay · CareShift): 차트 날짜는 서울 07:00 ~ 다음 날 07:00(칸은 정시 · 반시 48개), 근무조 D 07–15 · E 15–23 · N 23–07(N 은 시작한 차트 날짜에 속한다). 바이탈 회차는 13시 전 오전 · 뒤 오후. 체중은 15:00(목욕) 기준 — 그 전이면 어제가 「마지막 측정일」이다.

숫자(물결 · 09:00 — seed_care_test 가 CareChartFacts 로 단언)

숫자 값 식 맞춘 행
재실 산모 · 신생아 34 · 35 inHouseStays · inHouseNewborns(§5 현입실자 · 신생아 수와 같다) 척추
바이탈 미기록 산모 4 mothersMissingVitals — 재실 산모 중 오늘 지금 회차(오전) 행이 없는 수 cust_mul_003 · 009 · 016 · 021 오늘 오전 없음
바이탈 확인 대기 3 vitalsAwaitingConfirmation — 오늘(지금 회차까지) 확인 서명이 없는 회차 원장 확인(08:50) 뒤에 적은 고은비 08:55 · 탁예원 08:52 · 석다연 08:57
지난 근무조 점검 없음 2 shiftChecksMissing — 마지막으로 끝난 근무조(1/14 N)의 점검이 없는 재실 아기(그 뒤 입실은 빼고) baby_mul_004 · baby_mul_012
근무조 확인 대기 2 shiftChecksAwaitingConfirmation — (1/14 N · 1/15 D) 점검 중 확인 서명이 없는 것 꼬물이 · 하윤서 아기의 1/15 D
근무조 점검 미완료 4 없음 2 + 확인 대기 2(Figma 사이드바 「케어 차트 4」) —
기록 확인 필요(원장 홈) 6 바이탈 미기록 4 + 근무조 점검 없음 2 —
체중 미기록 1 newbornsMissingWeight — weighDateAt(09:00 이면 1/14)의 체중이 없는 재실 아기 baby_mul_018 1/14 없음
오늘 수유 기록 33/35 newbornsFedToday — 지금 차트 하루(07:00 뒤)에 수유 칸이 있는 아기 baby_mul_007 · baby_mul_019 오늘 칸 없음

홈(C-01) · 사이드바 배지가 위 숫자를 쓰려면 CareChartFacts 를 부른다(식 정의는 이 표 하나).

5. 집계 식 (PostpartumFacts)

기준 순간 at(없으면 세계의 지금 — 09:00), 「오늘」 = at 의 서울 날짜, 「이번 달」 = [월초 0시, at](끝 포함 — 데모 중 쓰기는 모두 09:00 에 찍힌다). branchId 가 null 이면 전체 지점. 비율은 반올림 정수(87.5 → 88).

칸 식 함수
현입실자 체류 중 admittedAt ≤ at 이고 (dischargedAt 없음 또는 > at) inHouse · inHouseStays
수용 지점 capacity 합(40 × 지점 수) capacity
가동률 현입실자 ÷ 수용 occupancyPercent
신생아 수 newborns.stayId 가 현입실 체류인 행(쌍둥이 둘) newborns
오늘(날짜 D) 입실 예약 중 admissionOn = D 이고 상태 ∈ {예약완료 · 입실중 · 퇴실완료} admissionsToday · admissionsOn
오늘(D) 퇴실 예약 중 dischargeOn = D 이고 상태 ∈ {입실중 · 퇴실완료} dischargesToday · dischargesOn
당월입실 체류 중 admittedAt ∈ 이번 달(이미 퇴실한 사람 포함) admittedInMonth
당월입실예정 상태 예약완료이고 admissionOn ∈ [오늘, 말일](오늘 입실 예정 포함) upcomingAdmissionsInMonth
당월예약(신규 예약) 예약 중 contractAt ∈ 이번 달(계약금 승인으로 예약완료가 된 때 — 뒤에 해지돼도 센다) bookingsInMonth
당월해약(해지) 예약 중 terminationRequestedAt ∈ 이번 달 terminationsInMonth
당월상담 상담 중 createdAt ∈ 이번 달 consultationsInMonth
전환 당월상담 중 stage = booked conversionsInMonth · conversionPercent
진행 중 상담 stage ∈ {first, second, third}(시작 달 무관) inProgress · inProgressByStage
당월 매출 누계 Σ payments.lines[].amount(status ∈ {approved, refunded} · approvedAt ∈ 이번 달) − Σ refunds.resultAmount(status paidMock · paidAt(없으면 confirmedAt) ∈ 이번 달) revenueInMonth
월별 예약자(C-13) 상태 예약완료이고 admissionOn 이 그 달 bookedByAdmissionMonth
정리 · 소독 필요 객실 상태 ∈ {cleaning · disinfect} roomsToClean
월별(C-02 · C-13) 지난 달은 monthlyLedgers(지점 합), 이번 달은 위 식으로 monthFacts → PostpartumMonthFacts

환불은 refunds 행으로만 매출에서 뺀다 — 원결제 줄을 refunded 로 바꿔도 그 줄은 승인 당시의 매출로 남는다(이중 차감 금지). 정산 WP 는 지급(모의) 때 paidAt(과 가능하면 branchId)을 refunds 행에 적는다.

6. 표시(B) 데이터

PostpartumDisplayFields.of(() => world.view) 를 두 호스트의 표시 필드 목록(demoDisplayFields · consoleDemoDisplayFields)이 싣는다 — 같은 설정(PostpartumWorldSpec.config)이라 같은 사람이 앱 · 콘솔 어디서나 같은 이름이다. 화면은 DisplayProjector.text(PostpartumDisplayKeys.…) 로만 읽는다.

키 값
customerName(id) · staffName(id) · newbornNickname(id) 투어 인물(PostpartumPersonas)은 11개 언어 모두 ko 원문(LocaleFieldGap(languageNeutral)). 시드에 이름이 적힌 사람(PostpartumSeedNames — 하윤서 · 조은결 … · 보호자 김도윤 · 직원 나혜린 · 정하윤)은 ko 에서만 그 이름, 다른 언어는 co_faker. 나머지는 모든 언어 co_faker(ko 에서 고정 이름과 겹치면 다시 뽑는다)
guardianName(customerId) 보호자 관계(A)가 배우자면 남성 이름
hospitalName(hospitalKey) co_faker 의원 접두어 + 언어별 틀(ko …여성병원 · …산부인과, en … Women's Hospital …) — 실제 병원 이름을 쓰지 않는다. 키 hosp_01hosp_18(물결 0106 · 솔빛 0712 · 노을 1318)
branchName(id) 솔빛점 · 물결점 · 노을점 — ko 고정 라벨(「본점」 표시는 화면 번역이 isMain 으로)
memoText(memoKey) 상담 이력 메모 예문 6종(PostpartumMemoKeys) — 11개 언어 표

사람이 친 이름(C): 고객 행에 nameText · guardianNameText 가 있으면 postpartum_binding 이 부팅 때와 world.changes 때(이 탭의 쓰기 · 다른 탭의 갱신) DisplayProjector.overlay 의 customerName(id) · guardianName(id) 로 맞춘다. 그래서 예약 · 재실 · 정산 등 다른 화면은 고칠 것 없이 projector.text(customerName(id)) 로 친 이름을 본다.

메모 예문 · 병원 틀 · 고정 이름의 글자는 번들 글꼴(Pretendard ∪ LocaleFallback)이 덮는다 — postpartum_display_glyph_test.dart 가 지킨다(글꼴 게이트는 slang 카탈로그 · co_faker 만 본다).

7. 시드 더하기 (기능 WP)

  1. 자기 컬렉션 상수는 이미 PostpartumCollections 에 있다(새 컬렉션이면 상수 + all 에 한 줄).
  2. package/postpartum_world/lib/src/seed/seed_<도메인>.dart 에 void seed<Domain>(PostpartumSeedBuilder builder) 를 쓴다.
    • 업무 행은 if (builder.empty) return; 뒤에.
    • 난수는 builder.faker('<도메인>/<키>') 로만(단계마다 독립 스트림 — 다른 단계 값을 밀지 않는다).
    • 시각은 builder.at(월, 일, 시, 분)(서울 → UTC 문자열), 날짜는 builder.date(월, 일). 기준시각(09:00) 이후의 「일어난 일」을 넣지 않는다 — 앞으로의 일정(예약된 마사지 10:00 등)은 된다.
    • 척추 행은 builder.rowsOf(collection) · builder.find(collection, id) 로 읽고, id 는 PostpartumTourIds 로 만든다.
    • 한국어 문자열을 넣지 않는다(이름 · 병원 · 메모는 B 키 — 시드 시험이 한글을 막는다). null 필드는 빌더가 뺀다.
  3. PostpartumWorldSeed._steps 의 🔧 줄 아래에 한 줄을 더하고 schema 를 올린다(옛 저장본은 버려진다).
  4. 시험: 자기 화면 숫자를 단언하는 시험을 더하고, seed_facts_test · postpartum_world_seed_test(참조 무결성 · 객실 겹침 · 기준시각 · 한글 · 크기)가 그대로 통과해야 한다. 척추 행을 더해도 이미 단언된 숫자를 깨지 않는다.
  5. 크기: 스냅샷 전체 2,000,000 자 안(척추 ≈ 1.14M 자 — 기능 WP 합계 ≈ 0.85M 자). 같은 출처 localStorage 를 앱 · 콘솔이 함께 쓰고 브라우저 한도가 약 5MB(UTF-16)다. 격자 기록(신생아 24시간 등)은 이름 있는 재실자 · 최근 며칠만.

8. Figma 와 다른 값

화면 Figma 세계 이유
C-11 해지 요청 1/15 10:10 08:40 기준시각(09:00) 전이어야 「일어난 일」이다
K-01 상담 코드 「09:02 발급」 08:59 발급 · 09:09 만료 같은 이유 — 시계가 09:00 고정이라 데모 내내 유효
C-15 최온유 VIP 701 · 1/10 퇴실 VIP 702 701 은 하윤서가 1/9 부터 재실(D+6). 702 는 고은비 1/12 입실 전 비어 있었다
C-13 금일 퇴실 「외 2건 VIP 704 · 디럭스 308」 VIP 706 · 디럭스 308 704 는 조은결 재실(1/18 퇴실 — C-06)
C-06 「오늘 · 내일」 입실/공실 31/9 · 오늘 2·3 · 내일 1·2 · 정리·소독 2 34/6 · 1·3 · 1·2 · 2 world-schema · C-02 · C-13 의 재실 34 · 오늘 입실 1 을 따른다
C-06 「공실 예정」 퇴실일(701 1/23 · 704 1/19 · 303 1/20) 1/22 · 1/18 · 1/19 타임라인 막대 · 입실일 + 박 수와 같게(패널 날짜가 하루 뒤였다)
C-07 머리말 「오늘 입실 2건」 1건(서보미) C-02 · C-13 과 같게
C-05 머리말 「상담 2차」(이하늘) 1차에서 시작 Figma C-03 열(1차 이하늘)을 따른다 — 투어 4 가 1차 카드를 예약으로 옮긴다
C-09 14:00 백시아(산전 40분 · 서비스 1/2) 진새벽(bkt_saebyeok_prenatal 2회 · 0회 사용 · 2/16 소멸) 백시아는 3차 상담(계약 전)이라 산전 서비스 회차가 없다 — 플랜에 산전 서비스가 든 예약완료 고객으로 옮겼다. 관리 예약은 예약 상태로 시드하며, 사용자가 완료할 때 차감
C-01 홍윤슬 계약금 기한 「오늘」 1/16 C-03 · world-schema
C-01 「출산 임박 해피콜 · 백시아 외 4명(예정일 ±7일)」 백시아 예정일 2/26(3차 상담 — 예약 없음) world-schema 를 따랐다 — 대상 정의는 대시보드 WP 몫
C-13 월별 예약자 1월 15 · 합계 99 1월 11 · 합계 95 식 = 예약완료 상태로 남은 그 달 입실 예정(이미 입실한 1월 15명은 입실중 · 퇴실완료) — 2~6월 24/23/19/12/6 은 같다
C-02 「월별 예약 추이 · 예정 입실 기준」 계약(예약완료가 된) 월 기준 1월 막대가 KPI 「이번 달 예약 36」과 같으려면 계약 월 기준이어야 한다. 마감 월은 장부
C-02 「전월 같은 날 +5」 · 「가동률 전일 +2%p」 시드가 맞추지 않는다 장부는 월 단위다 — 대시보드 WP 가 식으로 셀 때 다른 값일 수 있다
C-05 · C-07 · C-08b 병원 「한빛여성병원」 · 「새봄산부인과」 co_faker 접두어 + 틀로 만든 가상 이름 실제 병원 이름을 만들지 않는다. 고은비 · 서보미는 같은 병원 키
C-09 10:00 하윤서 · 11:00 조은결 「완료」 회차는 완료 전 값(하윤서 산후 1/2 · 조은결 코스 1/10 사용) 09:00 에는 아직 일어나지 않았다 — 관리 예약은 예약 상태로 시드하며, 사용자가 완료할 때 차감
C-08b 1/15 10:30 · 12:00 기록 1/14 같은 시각으로 옮겼다 — 1/15 은 07:30 · 09:00 두 칸(분유 먹은 양 60 · 소변 2) 09:00 뒤라 「일어난 일」이 아니다. 투어 7 뒤 합계는 분유 먹은 양 130ml · 소변 3
C-08a 1/12 오전 바이탈(36.8 · 118/76 …) 없음 — 「입실 전」 칸 고은비는 1/12 14:00 입실이라 첫 기록은 1/12 오후(입실 체크 ④). 나머지 회차는 Figma 값 그대로
C-08b 근무조 점검 「이상 소견 없음」 「특이 소견 없음」(findingsKey none) 판정 단어 「이상」을 쓰지 않는다 — 관찰해 적은 것이 없다는 기록이다
C-08b 투어 7 「체중 추이 갱신」 14:00 칸 저장 → 오늘 체중(목욕) 3,410 g 기록 → D 확인 서명 수유 기록은 체중을 바꾸지 않는다 — 체중 추이는 체중을 기록할 때 갱신된다
C-08b (하루 경계 표기 없음) 차트 하루 = 07:00 ~ 다음 날 07:00, 근무조 D 07–15 · E 15–23 · N 23–07 격자가 07:00 에서 시작한다. 시계가 09:00 에 멈춰 있어 오늘 차트 하루 안의 칸(투어의 14:00)은 저장을 허용하고, 시작 전 근무조 점검만 막는다
C-08a · C-08b 「작성 · 확인」(누가 서명하는지 표기 없음) 산모 바이탈 확인 = 원장 · 근무조 점검 확인 = 원장 · 간호사 · 신생아실(작성자 본인 포함) Figma C-08a 「… / 문하린(원장)」, 투어 7 은 정다온이 적은 D 점검에 정다온이 서명한다
C-08b 근무 전 근무조 담당(임채윤 · 서민아) 운영 근무표(shiftAssignments)에서 읽고, 없으면 「—」 담당은 운영 WP 시드가 정한다
C-14 · C-09 · C-13 하채린 휴직 ~1/31 이면서 오늘 근무 · 관리 휴직(leave · leaveUntil 1/31) world-schema — 시간표 처리는 에스테틱 · 운영 WP
C-07 · C-04 서보미 체크인 진행(14:20 · 14:35) · 이하늘 이력(09:12 · 09:30 · 09:48) 09:00 기준 이전 상태 투어가 만드는 뒤 상태다
전체 (인상) 대부분 2주 계약 물결 재실 34 중 20명은 12월 입실 3 · 4주 계약 C-13 당월입실 15 를 식으로 맞추려면 1월 입실이 15명이어야 한다

9. 같은 출처의 다른 데모와 겹치지 않기

docs.cocode.im/<저장소>/ 의 다른 데모는 자기 접두사를 쓴다. 이 세계의 초기화는 demo-healthcare-postpartum/v1/ 로 시작하는 키만 지운다(시험: 「접두사 밖의 키는 읽지도 쓰지도 지우지도 않는다」). 포털과 공유하는 언어 · 테마 값 (co_demo_prefs)은 이 세계의 저장 키가 아니다.