{
  "name": "v-care-flow",
  "schemaVersion": "2.0.0",
  "title": "VCare 라이선스 중심 운영 플로우",
  "language": "ko-KR",
  "updatedAt": "2026-09-17",
  "description": "2026-09-17 회의 답변을 우선하여 기존 플로우를 재작성했습니다. 역할별 화면 흐름과 역할 사이의 인계, 이용 정책, 상태, 검증 시나리오를 포함합니다.",
  "format": {
    "baseCompatibility": "기존 name + roles[].{role,title,description,nodes,edges} 구조 유지",
    "nodeKinds": [
      "screen",
      "action",
      "decision",
      "example"
    ],
    "localEdges": "roles[].edges는 같은 역할 안의 노드만 연결합니다.",
    "crossRoleEdges": "역할 간 인계는 handoffs[]의 source/target을 사용합니다. overview는 인계를 포함한 전체 흐름입니다.",
    "flowGroups": "roles[].flows와 node.flowId로 상세 흐름을 나눠 표시합니다. terminal=true는 처리 종료이며 화면 이후 가능한 새 진입은 별도 플로우입니다.",
    "legacyViewerNote": "추가 필드를 무시하는 기존 뷰어에서도 역할별 기본 그래프를 읽을 수 있도록 구성했습니다. 실제 기존 뷰어 실행 호환성은 별도 확인이 필요합니다."
  },
  "sourcePriority": [
    {
      "priority": 1,
      "source": "사용자의 22개 질문 답변과 추가 가격/승인 답변",
      "role": "확정 요구사항"
    },
    {
      "priority": 2,
      "source": "이번 파일에 명시한 delegated_design / design_default",
      "role": "위임된 운영 규칙과 예외의 구체화. 사용자 확정 발언과 구분"
    },
    {
      "priority": 3,
      "source": "/Users/kimminsu/Desktop/v-care-flow.json",
      "role": "기존 구조·누락 확인용 참고자료"
    },
    {
      "priority": 4,
      "source": "https://www.figma.com/design/Q2yyLdNaSd3kLcv3M1uLj2/V-care-UXUI?node-id=1-4",
      "role": "기존 화면 참고. Figma는 이번 작업에서 수정하지 않음"
    }
  ],
  "terminology": [
    {
      "term": "슈퍼어드민",
      "definition": "운영사 엑시스트의 플랫폼 관리자"
    },
    {
      "term": "디스트리뷰터",
      "definition": "VDX 같은 판매·운영 조직 및 그 관리 역할. 기존 고객 조직·조직 관리자 계층을 통합"
    },
    {
      "term": "시설",
      "definition": "입소자·디바이스·측정을 운영하는 단위. 디스트리뷰터 하나에 소속"
    },
    {
      "term": "시설 관리자",
      "definition": "시설에 초대되어 관리자 PC에서 로그인하는 관리 사용자"
    },
    {
      "term": "케어기버",
      "definition": "측정모드 비밀번호로 현장 운영을 수행하는 사람. 개인 로그인·이름 선택 없음"
    },
    {
      "term": "입소자",
      "definition": "시설에 귀속된 측정 대상 데이터. 로그인 없음"
    },
    {
      "term": "일반 사용자",
      "definition": "거치모드에서 측정하고 선택적으로 입소자 정보를 등록하는 사람"
    },
    {
      "term": "플랜",
      "definition": "Standard·Plus·Enterprise의 기능 등급"
    },
    {
      "term": "라이선스",
      "definition": "하나의 시설에 귀속되고 동시에 디바이스 한 대에 연결되는 월 이용 권한. 개별 가격·승인·기간·코드·3000회 한도"
    },
    {
      "term": "라이선스 코드",
      "definition": "라이선스에 자동 발급되는 시간 제한 없는 연결 자격. 시설과 라이선스를 특정"
    },
    {
      "term": "디바이스",
      "definition": "라이선스에 연결되어 측정모드·거치모드를 실행하는 측정 단말"
    },
    {
      "term": "측정모드",
      "definition": "케어기버가 비밀번호를 입력하여 입소자 운영과 보조 측정을 수행하는 상태"
    },
    {
      "term": "거치모드",
      "definition": "일반 사용자·입소자가 안내·동의 후 직접 측정하는 상태"
    },
    {
      "term": "측정모드 비밀번호",
      "definition": "측정모드 진입을 허용하는 시설 공통 비밀번호. 라이선스 코드와 구분"
    },
    {
      "term": "측정",
      "definition": "디바이스와 라이선스에 귀속되는 한 번의 바이탈 측정 세션. 입소자는 연결 전까지 없을 수 있음"
    },
    {
      "term": "유효 측정",
      "definition": "품질 검증을 통과하고 유효한 지표가 있어 결과와 1회 차감이 함께 확정된 측정"
    },
    {
      "term": "측정 결과",
      "definition": "유효 측정에서 나온 지표와 품질. 품질 미달 숫자는 결과로 저장하지 않음"
    },
    {
      "term": "분석 불가",
      "definition": "유효 결과를 만들지 못한 상태. 재측정 안내·미차감"
    },
    {
      "term": "후속조치",
      "definition": "유효 측정의 이상치가 있는 입소자에 대한 담당자의 확인·재측정 등 기록"
    },
    {
      "term": "개인 기준선",
      "definition": "최근 유효 측정 5회의 지표별 중앙값. 유효 이력이 5회 미만이면 형성 중으로 표시하며 진단 판정과 구분"
    },
    {
      "term": "관리자 PC",
      "definition": "슈퍼어드민·디스트리뷰터·시설 관리자의 관리 화면. 직접 측정 없음"
    }
  ],
  "plans": [
    {
      "id": "standard",
      "label": "Standard",
      "monthlyPriceKrw": 200000,
      "vatIncluded": false,
      "monthlyValidMeasurements": 3000,
      "features": [
        "현재 기획된 전체 기본 화면",
        "측정모드",
        "거치모드",
        "입소자·동의 관리",
        "측정 결과·이력",
        "오늘 운영 표",
        "후속조치",
        "내보내기",
        "라이선스·디바이스 관리",
        "권한 범위별 이용 현황"
      ]
    },
    {
      "id": "plus",
      "label": "Plus",
      "monthlyPriceKrw": 300000,
      "vatIncluded": false,
      "monthlyValidMeasurements": 3000,
      "inherits": "standard",
      "features": [
        "인지 모듈"
      ]
    },
    {
      "id": "enterprise",
      "label": "Enterprise",
      "monthlyPriceKrw": 400000,
      "vatIncluded": false,
      "monthlyValidMeasurements": 3000,
      "inherits": "plus",
      "features": [
        "계약별 커스터마이징"
      ]
    }
  ],
  "policies": {
    "hierarchy": {
      "label": "조직과 역할",
      "basis": "confirmed_with_design_defaults",
      "rules": [
        "운영사 엑시스트 → 디스트리뷰터 → 시설로 통합한다. 별도 고객 조직·조직 관리자 계층을 만들지 않는다.",
        "시설과 시설 관리자는 데이터와 사람으로 구분한다. 시설 관리자는 관리자 PC에 로그인한다.",
        "한 시설은 디스트리뷰터 하나에 소속된다. 다중 디스트리뷰터 거래와 시설 이전은 이번 범위 밖이다.",
        "슈퍼어드민은 디스트리뷰터를 생성·초대하고, 디스트리뷰터는 시설을 생성하고 시설 관리자를 초대한다.",
        "케어기버와 입소자는 로그인 사용자를 만들지 않는다. 디바이스 역시 로그인 사용자가 아니다."
      ]
    },
    "accounts": {
      "label": "관리자 접근",
      "basis": "design_default",
      "rules": [
        "초대된 이메일로 비밀번호를 설정하면 해당 범위의 관리자 접근이 열린다. 별도 가입 승인을 반복하지 않는다.",
        "하나의 디스트리뷰터·시설에 복수 관리자를 초대할 수 있다. 각 사용자는 자신의 소속 범위만 접근한다.",
        "관리자 비밀번호 재설정은 등록 이메일의 재설정 링크를 사용한다. 이것은 디바이스 연결 코드와 별개이다.",
        "잘못된 비밀번호는 입력 화면에 오류를 표시하고, 반복 실패는 일시 제한한다. 재설정·제한 해제 후 재시도한다."
      ]
    },
    "plans": {
      "label": "플랜과 가격",
      "basis": "confirmed",
      "rules": [
        "Standard 월 200000원, Plus 월 300000원, Enterprise 월 400000원. 모두 부가세 별도이다.",
        "Standard는 현재 기획된 모든 기본 화면과 기능을 포함한다. Plus는 Standard와 인지 모듈, Enterprise는 Plus와 계약별 커스터마이징을 포함한다.",
        "모든 플랜은 라이선스 하나당 매월 유효 측정 3000회를 제공한다. 플랜은 기능 차이이며 측정 횟수 차이가 아니다.",
        "디바이스 기능은 연결된 라이선스 하나의 플랜을 따른다. 시설 전체에 상위 플랜 기능을 확장하지 않는다.",
        "커스터마이징 상세는 계약에 연결된 설정이며 모든 Enterprise 계약에 같은 맞춤 기능을 제공한다고 표시하지 않는다.",
        "기본가격 변경은 이후 신규 등록의 비교 기준에만 반영한다. 기존 계약·청구기간·실제 입금 기록의 금액은 바꾸지 않는다."
      ]
    },
    "license_creation": {
      "label": "라이선스 등록",
      "basis": "confirmed",
      "rules": [
        "디스트리뷰터가 시설·플랜·월 이용료·수량을 입력해 라이선스를 생성한다. 선구매 재고와 재고 배분은 없다.",
        "수량을 여러 개 입력해도 개별 라이선스 ID·가격·승인·코드·이용기간·잔여 횟수를 각각 생성한다.",
        "기본가격과 같은 금액은 자동 승인한다. 할인과 할증 모두 기본가격과 다르면 슈퍼어드민 승인이 필요하다.",
        "입력한 가격은 부가세를 제외한 라이선스 1개당 월 이용료이다. 수량 전체 가격으로 입력하지 않는다.",
        "코드는 라이선스 생성 시 발급한다. 신규 승인 대기 중에도 연결은 가능하지만 측정은 불가하다."
      ]
    },
    "price_approval": {
      "label": "가격 승인",
      "basis": "confirmed_with_design_defaults",
      "rules": [
        "승인은 라이선스 하나에 귀속한다. 일괄 요청·승인도 개별 처리 결과와 감사 기록을 남긴다.",
        "신규 요청의 요청가격과 적용가격을 분리한다. 승인 전에는 기본가격을 기준값으로 유지하고 요청가격을 승인된 가격처럼 표시하지 않는다.",
        "사용 중인 라이선스의 변경 요청은 현재 승인가격과 기능으로 계속 이용한다. 기존 이용을 중단하지 않는다.",
        "신규 반려 시 사유를 보여주고 수정·재요청 또는 등록 취소가 가능하다. 기존 변경 반려 시 종전 조건을 유지한다.",
        "기존 라이선스의 승인된 변경가격은 다음 달 1일 적용한다. 이미 확정된 당월 요금은 소급 변경하지 않는다.",
        "승인된 가격은 재변경 또는 해지 전까지 자동 갱신에도 유지한다. 요청 수정 시 요청 버전을 올리고 구버전 승인을 거부한다.",
        "플랜 변경도 변경 후 플랜의 기본가격과 비교하여 승인 필요 여부를 판정하고 다음 달 1일 적용한다.",
        "기본가격 비교는 라이선스 1개의 월 이용료로 한다. 첫 달 일할 계산액·부가세·수량 합계가 기본 월 가격과 다르다는 이유로 승인을 요구하지 않는다."
      ]
    },
    "activation": {
      "label": "외부 계약·입금과 활성화",
      "basis": "confirmed_with_design_defaults",
      "rules": [
        "라이선스 등록·필요한 가격 승인 → 외부 계약 → 외부 입금 확인 → 이용 활성화 순서이다.",
        "화면 이름은 라이선스 등록·라이선스 관리·계약 및 입금 확인을 사용한다. 판매 내역 등록으로 표현하지 않는다.",
        "소속 디스트리뷰터가 계약 완료와 라이선스별 입금일·금액·대상기간을 확인 등록한다. 슈퍼어드민은 지원 수정이 가능하며 사유를 기록한다.",
        "일괄 입금을 확인할 수 있으나 어느 라이선스의 어느 기간을 충족하는지 배분해야 한다. 중복 확인으로 기간이나 횟수가 늘어나지 않는다.",
        "신규 이용은 승인 완료·계약 완료·첫 이용기간 입금 확인을 모두 충족하면 시작한다. 최초 연결일로 시작일을 늦추지 않는다.",
        "첫 이용 시작일은 확인된 최초 입금의 유효일이다. 확인 등록이 늦어져도 기존 시작일과 말일을 다시 연장하지 않는다.",
        "서비스 내 결제와 엑시스트·디스트리뷰터 정산은 이번 범위 밖이다. 추후 결제 완료 이벤트도 같은 입금 확인·활성화 경로에 연결할 수 있다."
      ]
    },
    "billing": {
      "label": "월 이용기간과 일할 계산",
      "basis": "confirmed_with_design_defaults",
      "rules": [
        "기준 시간대는 Asia/Seoul이며 매월 1일 00:00에 새 이용기간을 만든다.",
        "첫 이용기간은 최초 입금 유효일부터 해당 월 말일까지이다. 첫날을 포함한 이용일수 / 해당 월의 총 일수로 월 이용료를 일할 계산한다.",
        "일할 공급가액은 원 단위 반올림한다. 부가세와 총액은 외부 계약·청구 자료에 따라 별도 기록한다.",
        "첫 달에도 측정 한도는 3000회이다. 요금만 일할 계산하며 측정 횟수는 일할 계산하지 않는다.",
        "첫 달 예시: 월 200000원, 2026-09-17 시작, 14일 / 30일 → 공급가액 93333원, 부가세 별도, 한도 3000회.",
        "활성 이용계약은 취소 효력일에 도달하기 전까지 자동 갱신한다. 새달 이용기간과 해당 월 요금을 라이선스별로 생성한다.",
        "외부 입금 방식에서 자동 갱신은 자동 수납을 뜻하지 않는다. 새 이용기간 입금이 미확인인 동안 측정은 대기하고 확인되면 남은 해당 기간을 이용한다."
      ],
      "calculation": {
        "currency": "KRW",
        "monthlyPriceVatIncluded": false,
        "timezone": "Asia/Seoul",
        "initialPeriod": "effective_first_payment_date through last_day_of_same_month inclusive",
        "serviceDays": "days_in_month - start_day + 1",
        "supplyAmount": "round_half_up(monthly_price * service_days / days_in_month)",
        "quotaLimit": 3000,
        "vatAmount": "external_invoice_amount"
      }
    },
    "quota": {
      "label": "측정 횟수",
      "basis": "confirmed_with_design_defaults",
      "rules": [
        "라이선스·월 이용기간별 한도는 3000회이다. 다른 라이선스와 합산·공유·이전하지 않는다.",
        "사용하지 않은 횟수는 기간 종료 시 소멸한다. 다음 기간 한도는 다시 3000회이다.",
        "기간 중 소진하면 측정을 중단하고 다음 갱신일을 표시한다. 추가 횟수 판매·횟수 충전은 이번 범위 밖이다.",
        "디바이스 교체·재연결·코드 재발급·플랜 변경은 이미 사용한 횟수를 초기화하지 않는다.",
        "유효 측정 세션 한 번을 1회 차감한다. 지표 개수와 관계없으며 일반 사용자 측정도 포함한다.",
        "품질 미달·사용자 중단·오류·저장 실패는 차감하지 않는다. 재측정이 유효하게 확정되면 그 성공 세션은 1회 차감한다.",
        "인지 모듈 실행만으로 바이탈 측정 횟수를 별도 차감하지 않는다. 별도 과금 상품은 이번 범위 밖이다."
      ]
    },
    "cancellation": {
      "label": "취소와 이용 종료",
      "basis": "confirmed_with_design_defaults",
      "rules": [
        "디스트리뷰터와 슈퍼어드민이 취소 효력일을 필수로 입력해 예약한다. 시설 관리자는 요청을 보내고 처리 상태를 확인한다.",
        "취소일은 이용이 중단되는 날짜이다. 해당 날짜 00:00부터 측정을 차단하고 이후 자동 갱신을 중지한다.",
        "취소 예정일까지는 기존 유효 조건으로 사용한다. 효력일 전에 취소 예약을 철회할 수 있다. 과거 날짜로 소급 취소하지 않는다.",
        "취소일까지 미납이면 입금 확인 전에는 측정할 수 없다. 취소 효력일 당일 갱신과 겹치면 취소가 우선한다.",
        "월중 취소의 환불·정산은 외부 계약으로 처리하며 시스템이 임의 환불액을 만들거나 자동 환불하지 않는다.",
        "종료된 라이선스는 기존 결과를 삭제하지 않는다. 유효한 시설 관리자 권한으로 과거 결과를 조회한다.",
        "종료 후 재이용은 신규 라이선스 등록으로 처리한다. 기존 ID와 사용량 기록을 재활용해 초기화하지 않는다."
      ]
    },
    "connection": {
      "label": "라이선스 코드와 디바이스 연결",
      "basis": "confirmed_with_design_defaults",
      "rules": [
        "자동 생성된 라이선스 코드 하나로 시설과 라이선스를 특정한다. OTP·유효시간·수동 디바이스 암호는 사용하지 않는다.",
        "디바이스는 라이선스 하나, 라이선스는 동시에 디바이스 한 대에 연결된다. 시설은 여러 디바이스와 라이선스를 보유할 수 있다.",
        "디바이스 등록 자체는 라이선스가 없어도 가능하지만 측정은 연결된 유효 라이선스가 있어야 한다.",
        "연결 상태와 측정 가능 상태를 분리한다. 승인·입금·이용기간·횟수·온라인 조건 때문에 연결되어 있어도 측정이 제한될 수 있다.",
        "같은 디바이스의 같은 코드 재입력은 기존 연결을 복원한다. 다른 디바이스가 이미 연결된 코드는 자동 탈취하지 않고 관리자에게 연결 해제를 요청한다.",
        "시설 관리자·소속 디스트리뷰터·슈퍼어드민이 코드 재발급과 연결 해제를 수행할 수 있다.",
        "재발급 확인 후 기존 코드와 기존 디바이스 연결 자격을 즉시 폐기하고 새 코드를 표시한다. 새 코드로 다시 연결한다.",
        "재발급·해제는 이용기간과 사용량을 보존한다. 기존 시설의 입소자·결과를 다른 시설로 이동시키지 않는다.",
        "코드 원문은 공개 운영 표·감사 로그·이용 현황 다운로드에 노출하지 않는다. 권한 있는 관리 화면에서만 확인한다."
      ]
    },
    "modes": {
      "label": "측정모드와 거치모드",
      "basis": "confirmed_with_design_defaults",
      "rules": [
        "한 디바이스에서 버튼으로 거치모드와 측정모드를 전환한다. 두 모드 모두 동일한 연결 라이선스의 기능과 횟수를 사용한다.",
        "측정모드 진입에는 시설 관리자가 설정하는 측정모드 비밀번호가 필요하다. 라이선스 코드·관리자 로그인 비밀번호와 별개로 표시한다.",
        "편의를 위해 시설 공통 측정모드 비밀번호 하나를 사용한다. 미설정이면 시설 관리자에게 설정을 요청한다.",
        "케어기버는 개인 로그인과 담당자 이름 선택 없이 측정모드를 이용한다. 수행자는 시설·디바이스·세션으로 기록한다.",
        "측정모드에서는 입소자 등록·수정·퇴소 처리·동의 기록·측정·결과 조회·후속조치·내보내기가 가능하다.",
        "가격·계약·입금·취소·관리자 초대·코드 재발급·측정모드 비밀번호 변경은 관리자 PC의 권한 있는 관리자만 수행한다.",
        "거치모드로 돌아갈 때 측정모드 접근 자격과 이전 입소자 화면을 지운다. 다시 측정모드에 들어가면 비밀번호를 재입력한다.",
        "측정모드 비밀번호 변경 시 기존 측정모드 접근을 종료한다. 디바이스 연결 자체는 유지한다.",
        "관리자 PC에는 직접 카메라 측정 시작 기능을 두지 않는다. 측정 시작은 디바이스에서 수행한다."
      ]
    },
    "residents": {
      "label": "입소자와 동의",
      "basis": "delegated_design",
      "rules": [
        "입소자는 시설에 귀속된 측정 대상 데이터이다. 입소자 정보 등록으로 로그인 사용자를 생성하지 않는다.",
        "기본 등록은 이름·생년월일을 필수로 하고 성별·시설 내 위치는 별도 항목으로 둔다. 지표 분석에 필요한 추가 항목은 실제 분석 모듈의 입력 규칙을 따른다.",
        "동명이인·동일 생년월일 후보는 별도 입소자 ID와 시설 내 위치를 함께 확인한다. 이름만으로 자동 병합하지 않는다.",
        "관리자가 측정·저장 동의와 동의 기록의 확인 근거를 등록한다. 생체 식별 동의는 별도이며 거부해도 수동 선택 측정이 가능하다.",
        "측정모드는 입소자를 먼저 선택한다. 유효 동의가 없으면 동의 보완 후 측정하며, 거부하면 측정하지 않고 운영 표로 돌아간다.",
        "거치모드는 측정 안내·동의 확인 후 측정을 시작한다. 얼굴 식별 성공 여부와 측정 진행을 분리한다.",
        "얼굴 식별 실패 시 일반 사용자 상태로 측정을 계속한다. 얼굴이 화면에서 사라져 신호를 수집하지 못하는 상황은 측정 품질 문제로 처리한다.",
        "일반 사용자는 유효 결과 확인 후 입소자 정보 등록과 저장 동의를 선택할 수 있다. 등록하지 않으면 결과 종료 시 임시 개인 데이터를 제거한다.",
        "등록 시 중복 후보가 있으면 공개 입소자 목록을 보여주거나 자동 연결하지 않는다. 측정모드 비밀번호를 통과한 담당자가 기존 입소자 연결 또는 별도 등록을 확정한다.",
        "기존 입소자 얼굴 후보도 결과 연결 전 본인 확인이 필요하다. 잘못 식별됐다고 응답하면 일반 사용자 경로를 따른다.",
        "동의 철회·퇴소 후에는 새 측정과 오늘 필수 측정 대상에서 제외한다. 기존 기록은 권한 있는 관리자에게 보존 상태로 표시한다.",
        "일상 운영의 삭제 버튼 대신 퇴소 처리를 사용한다. 개인정보 삭제 요청은 슈퍼어드민 접수·신원/범위 확인·정책에 따른 처리·완료 통지 경로로 분리하며 자동 영구보존이나 임의 보존기간을 선언하지 않는다.",
        "무조작 종료와 등록 저장이 경합하면 먼저 같은 세션의 서버 연결 확정 여부를 확인한다. 이미 동의에 따라 영구 연결된 결과는 임시 데이터 정리로 삭제하지 않는다."
      ]
    },
    "measurement": {
      "label": "측정과 결과 확정",
      "basis": "confirmed_with_design_defaults",
      "rules": [
        "온라인 상태에서만 연결·권한 검증·측정을 수행한다. 오프라인 측정과 사후 일괄 전송은 지원하지 않는다.",
        "측정 전 연결 자격·시설 활성·신규 가격 승인·계약/입금·기간·잔여 횟수·동의를 확인한다. 제한 사유와 해결 담당자를 표시한다.",
        "품질 검증을 통과하고 적어도 한 지표가 유효한 세션만 유효 측정이다. 유효하지 않은 지표는 숫자로 표시하거나 지난 값을 채워 넣지 않는다.",
        "유효 결과 확정과 차감은 같은 처리로 성공해야 한다. 세션 ID에 대해 한 번만 기록하며 재전송·중복 클릭·등록 연결은 추가 차감하지 않는다.",
        "측정 중 네트워크가 끊기면 진행을 중단한다. 저장 응답이 유실되면 재연결 후 동일 세션의 서버 확정 여부를 확인하고 임의로 새 결과를 만들지 않는다.",
        "시작 시 횟수 사용 가능 여부를 예약하고 실패·중단 시 해제한다. 월경계·취소·정지 등 이용 조건이 확정 전에 바뀌면 저장하지 않고 새 조건으로 재시도한다.",
        "결과 미저장 시 분석 불가·재측정 안내를 표시한다. 첫 재측정도 품질 미달이면 담당자 확인 안내 후 종료하거나 명시적으로 새 측정을 시작한다.",
        "일반 사용자 결과를 나중에 입소자와 연결하는 작업은 같은 측정 결과의 연결 변경이며 두 번째 측정이 아니다.",
        "결과 화면은 완료 또는 30초 무조작 후 초기 대기로 돌아가 이전 개인 결과를 지운다. 이는 라이선스 코드 만료와 무관하다."
      ]
    },
    "followup": {
      "label": "후속조치",
      "basis": "confirmed_with_delegated_design",
      "rules": [
        "유효한 지표에 이상치 플래그가 있는 입소자를 후속조치 대상으로 자동 등록한다. 임상 진단명으로 표시하지 않는다.",
        "이상치 여부는 실제 분석 모듈의 검증된 플래그 또는 승인·버전 관리된 운영 기준으로만 판정한다. 임의 숫자 기준을 새로 만들지 않는다.",
        "판정 기준이나 결과가 없으면 평가 안 됨으로 표시한다. 정상으로 간주하지 않는다. 슈퍼어드민의 기준 연결 화면으로 운영 알림을 보낸다.",
        "품질 미달은 재측정 필요 상태로 표시한다. 이상치 후속조치와 합쳐서 표시하지 않는다.",
        "담당자는 결과 확인·재측정·별도 측정 확인 등의 실제 조치와 메모를 기록하고 수동으로 후속조치를 완료한다. 새 정상값만으로 자동 완료하지 않는다.",
        "같은 결과에 대해 후속조치를 중복 생성하지 않는다. 새 유효 측정에 새 이상치가 있으면 새 결과에 연결된 조치를 생성한다."
      ]
    },
    "operations": {
      "label": "오늘 운영 표",
      "basis": "confirmed_with_delegated_design",
      "rules": [
        "모든 재원 입소자는 매일 측정 대상이다. 당일 유효 측정 1회 이상이면 오늘 측정 완료이며 추가 유효 측정도 실제 횟수만큼 차감한다.",
        "동의 필요·미측정·재측정 필요·측정 완료와 후속조치 대기를 별도 표시한다. 분석 불가만으로 오늘 측정 완료가 되지 않는다.",
        "입소자 한 행의 지표는 마지막 유효 측정 세션 하나에서 가져온다. 지표별 과거 값을 섞지 않는다.",
        "과거 유효값은 실제 측정일시와 함께 보여준다. 오늘 실패했으면 오늘 상태는 재측정 필요로 별도 표시한다.",
        "기본 열은 입소자·위치·오늘 상태·마지막 유효 측정일시·HR·BP·SpO2·피부 온도·측정 품질·후속조치·다음 행동이다.",
        "아직 한 번도 유효 측정이 없거나 제공되지 않은 지표는 값 없음 또는 제공 안 됨으로 표시한다. 미측정을 0으로 채우지 않는다.",
        "HRV 등 추가 지표는 실제 분석 모듈이 유효값을 제공할 때 상세 화면에 표시한다. 현재 표에 숫자를 만들어 넣지 않는다.",
        "오늘 유효 측정이 이미 있으면 이후 품질 미달 시에도 오늘 측정 완료 이력은 유지하고 최근 시도 상태와 재측정 안내를 별도로 표시한다."
      ]
    },
    "analytics": {
      "label": "라이선스·이용 현황",
      "basis": "confirmed_with_delegated_design",
      "rules": [
        "슈퍼어드민은 전체, 디스트리뷰터는 자신의 시설만 집계한다. 디스트리뷰터 → 시설 → 라이선스 → 연결 디바이스 순으로 상세 조회한다.",
        "대표 금액은 활성 계약의 승인된 월 이용료 합계이다. 당월 일할 반영 확정 이용료와 실제 확인 입금액을 별도 지표로 구분한다.",
        "계약 기준 금액과 실제 입금액을 모두 매출이라고 섞어 표시하지 않는다. 부가세 별도 공급가액과 세액·입금 총액을 구분한다.",
        "대표 사용량은 확정된 차감 횟수이다. 유효 측정 수와 일치해야 하며 시도 수·품질 미달 수는 보조 운영 지표이다.",
        "예산이나 횟수를 시설 단위로 공유하는 것처럼 보이지 않도록 합계와 라이선스별 잔여 횟수를 함께 표시한다.",
        "기본가격 변경은 과거 승인가격·기간별 이용료·입금액을 다시 계산하지 않는다. 가격 변경 이력과 적용일을 보존한다.",
        "상위 관리자의 기본 화면은 집계만 제공한다. 입소자 이름과 개인 지표는 시설 관리자·인증된 측정모드에서 본다. 슈퍼어드민의 개인 데이터 지원 접근은 시설 요청·사유 기록을 거쳐 별도 허용한다."
      ]
    },
    "scope": {
      "label": "출시 범위",
      "basis": "confirmed_with_design_defaults",
      "rules": [
        "관리자 PC와 디바이스의 측정모드·거치모드를 포함한다. 별도 케어기버 가입 화면과 입소자 로그인 화면을 만들지 않는다.",
        "서비스 내 결제, 디스트리뷰터 정산, 오프라인 측정, 미리 구매한 라이선스 재고, 횟수 공유·이월·추가 충전은 제외한다.",
        "실제 이상치 기준, 커스터마이징 설정, 개인정보 요청 처리 기준은 운영 설정으로 연결한다. 설정이 없으면 명시된 제한 상태를 보여주고 숨겨진 기본값으로 진행하지 않는다."
      ]
    }
  },
  "designDefaults": [
    {
      "id": "oneDistributorPerFacility",
      "decision": "시설은 디스트리뷰터 하나에 소속",
      "reason": "조직 통합에 맞춘 단일 소속 운영. 다중 판매 조직 거래는 별도 범위로 확장"
    },
    {
      "id": "firstMonthQuota",
      "decision": "첫 달에도 3000회",
      "reason": "요금은 일할 계산하되 모든 플랜의 횟수는 무조건 3000회라는 확정 조건 유지"
    },
    {
      "id": "initialDate",
      "decision": "최초 입금 유효일부터 해당 월 말일까지",
      "reason": "첫 결제 이후 말일까지라는 조건을 외부 입금 확인으로 구현"
    },
    {
      "id": "renewalPayment",
      "decision": "자동 갱신하되 미확인 입금 기간은 측정 대기",
      "reason": "자동 갱신과 서비스 내 자동결제를 혼동하지 않도록 구분"
    },
    {
      "id": "changeEffective",
      "decision": "기존 가격·플랜 변경은 다음 달 1일 적용",
      "reason": "이미 확정된 기간의 요금과 횟수를 소급 변경하지 않음"
    },
    {
      "id": "cancelEffective",
      "decision": "취소일 00:00에 이용 중단",
      "reason": "취소일을 마지막 이용일과 혼용하지 않도록 화면에 명시"
    },
    {
      "id": "sharedModePassword",
      "decision": "시설 공통 측정모드 비밀번호",
      "reason": "케어기버 계정 없이 간단한 진입을 제공하고 라이선스 코드와 역할 분리"
    },
    {
      "id": "noTopup",
      "decision": "소진 시 다음 월까지 중단, 별도 충전 제외",
      "reason": "개별 라이선스 월 3000회 고정 범위 안에서 명확한 종료 경로 제공"
    },
    {
      "id": "anonymousQuota",
      "decision": "일반 사용자 유효 측정도 1회 차감",
      "reason": "디바이스에서 수행된 유효 측정이므로 입소자 등록 여부와 과금 분리"
    },
    {
      "id": "resultReset",
      "decision": "결과 완료 또는 무조작 30초에 대기 복귀",
      "reason": "거치 환경에서 이전 사용자의 결과가 다음 사람에게 남지 않게 함"
    },
    {
      "id": "thresholdSource",
      "decision": "실제 분석 플래그·승인된 기준만 사용",
      "reason": "임의 이상치 수치를 만들지 않으며 미설정이면 평가 안 됨을 노출"
    },
    {
      "id": "historicalRead",
      "decision": "라이선스 종료 후 관리자 과거 결과 조회 유지",
      "reason": "이용권 종료와 입소자 기록 삭제를 분리"
    },
    {
      "id": "privacyScope",
      "decision": "상위 관리자 기본 화면은 시설 집계",
      "reason": "매출·사용량 관심사에 맞추며 개인 결과 지원 접근은 별도 근거로 처리"
    },
    {
      "id": "personalBaseline",
      "decision": "최근 유효 측정 5회의 지표별 중앙값",
      "reason": "기존 화면의 개인 기준선 표시를 유지하되 부족한 이력은 형성 중으로 명시"
    }
  ],
  "states": {
    "priceApproval": [
      "auto_approved",
      "pending_new",
      "approved",
      "rejected_new",
      "pending_change",
      "rejected_change",
      "change_scheduled"
    ],
    "contract": [
      "pending",
      "confirmed",
      "cancelled"
    ],
    "periodPayment": [
      "unconfirmed",
      "confirmed"
    ],
    "subscription": [
      "not_started",
      "active",
      "cancellation_scheduled",
      "ended",
      "suspended"
    ],
    "connection": [
      "unbound",
      "bound",
      "credential_revoked"
    ],
    "quota": [
      "available",
      "exhausted",
      "period_expired"
    ],
    "measurement": [
      "started",
      "capturing",
      "quality_rejected",
      "interrupted",
      "commit_unknown",
      "committed",
      "not_committed"
    ],
    "todayResident": [
      "consent_required",
      "not_measured",
      "retest_required",
      "measured"
    ],
    "followup": [
      "pending",
      "completed"
    ],
    "note": "상태 축은 서로 독립입니다. bound와 active 또는 측정 가능을 동의어로 사용하지 않습니다."
  },
  "measurementEligibility": {
    "allRequired": [
      "online",
      "valid_device_binding",
      "active_facility",
      "new_license_price_approved",
      "contract_confirmed",
      "current_period_payment_confirmed",
      "current_period_open",
      "not_ended_or_suspended",
      "quota_reservation_available",
      "measurement_consent_valid"
    ],
    "explicitlyNotBlocking": [
      "existing_license_price_change_pending",
      "face_identification_failed",
      "biometric_identification_not_consented_when_manual_or_guest_measurement_allowed"
    ],
    "reasonPriority": [
      "offline",
      "invalid_binding",
      "facility_suspended",
      "new_price_pending_or_rejected",
      "contract_pending",
      "license_ended_or_suspended",
      "payment_unconfirmed",
      "outside_period",
      "quota_exhausted",
      "consent_required"
    ],
    "checkAt": [
      "before_capture",
      "before_commit"
    ],
    "alreadyCommittedSession": "이미 확정된 세션은 기존 결과만 반환하고 추가 차감하지 않음",
    "decisionTable": [
      {
        "priority": 1,
        "field": "online",
        "operator": "equals",
        "value": false,
        "reason": "offline"
      },
      {
        "priority": 2,
        "field": "binding_valid",
        "operator": "equals",
        "value": false,
        "reason": "invalid_binding"
      },
      {
        "priority": 3,
        "field": "facility_active",
        "operator": "equals",
        "value": false,
        "reason": "facility_suspended"
      },
      {
        "priority": 4,
        "field": "price_approval",
        "operator": "in",
        "value": [
          "pending_new",
          "rejected_new"
        ],
        "reason": "new_price_pending_or_rejected"
      },
      {
        "priority": 5,
        "field": "contract_confirmed",
        "operator": "equals",
        "value": false,
        "reason": "contract_pending"
      },
      {
        "priority": 6,
        "field": "subscription",
        "operator": "in",
        "value": [
          "ended",
          "suspended"
        ],
        "reason": "license_ended_or_suspended"
      },
      {
        "priority": 7,
        "field": "period_payment_confirmed",
        "operator": "equals",
        "value": false,
        "reason": "payment_unconfirmed"
      },
      {
        "priority": 8,
        "field": "current_period_open",
        "operator": "equals",
        "value": false,
        "reason": "outside_period"
      },
      {
        "priority": 9,
        "field": "quota_available",
        "operator": "equals",
        "value": false,
        "reason": "quota_exhausted"
      },
      {
        "priority": 10,
        "field": "measurement_consent_valid",
        "operator": "equals",
        "value": false,
        "reason": "consent_required"
      }
    ],
    "decisionTableDefault": "allowed"
  },
  "dataContracts": {
    "distributor": {
      "required": [
        "id",
        "name",
        "status"
      ]
    },
    "facility": {
      "required": [
        "id",
        "distributor_id",
        "name",
        "status"
      ],
      "optional": [
        "measurement_mode_password_hash"
      ]
    },
    "adminUser": {
      "required": [
        "id",
        "email",
        "password_hash",
        "role",
        "scope_id",
        "status"
      ],
      "roles": [
        "superadmin",
        "distributor",
        "facility_manager"
      ]
    },
    "license": {
      "required": [
        "id",
        "facility_id",
        "plan_id",
        "base_price_version",
        "current_monthly_price_krw",
        "vat_included_false",
        "price_approval_state",
        "contract_state",
        "subscription_state",
        "code_hash",
        "code_version"
      ],
      "optional": [
        "pending_price_request_id",
        "scheduled_change",
        "cancellation_effective_date"
      ],
      "invariant": "라이선스는 동시에 디바이스 한 대에만 연결"
    },
    "licensePeriod": {
      "required": [
        "id",
        "license_id",
        "starts_at",
        "ends_at_exclusive",
        "monthly_price_snapshot_krw",
        "prorated_amount_krw",
        "quota_limit_3000",
        "used_count",
        "payment_state"
      ],
      "unique": [
        "license_id",
        "period_month"
      ]
    },
    "paymentConfirmation": {
      "required": [
        "id",
        "license_id",
        "period_id",
        "effective_paid_on",
        "confirmed_amount_krw",
        "tax_amount_krw",
        "external_reference",
        "confirmed_by"
      ],
      "note": "실제 외부 계약·청구·입금 자료의 금액을 저장. 플랫폼 내 정산 처리 아님"
    },
    "device": {
      "required": [
        "id",
        "facility_id",
        "label",
        "binding_status"
      ],
      "optional": [
        "license_id",
        "location"
      ],
      "invariant": "케어기버 개인 계정과 수행자 이름 없음"
    },
    "resident": {
      "required": [
        "id",
        "facility_id",
        "name",
        "birth_date",
        "residence_status",
        "consent_status"
      ],
      "optional": [
        "sex",
        "location",
        "biometric_consent_status"
      ]
    },
    "measurementSession": {
      "required": [
        "id",
        "facility_id",
        "device_id",
        "license_id",
        "period_id",
        "mode",
        "status",
        "created_at"
      ],
      "optional": [
        "resident_id"
      ],
      "invariant": "일반 사용자도 허용하며 수행 주체는 디바이스"
    },
    "measurementResult": {
      "required": [
        "id",
        "session_id",
        "measured_at",
        "quality",
        "valid_metrics",
        "evaluation_status"
      ],
      "optional": [
        "resident_id",
        "evaluation_policy_version"
      ],
      "invariant": "유효 측정만 기록. 같은 세션의 차감과 함께 확정"
    },
    "usageLedger": {
      "required": [
        "session_id",
        "license_id",
        "period_id",
        "debit_one",
        "committed_at"
      ],
      "unique": [
        "session_id"
      ],
      "invariant": "차감과 유효 결과 확정은 원자적. 결과 재조회·입소자 연결·코드 재발급은 추가 차감 없음"
    },
    "followup": {
      "required": [
        "id",
        "resident_id",
        "result_id",
        "status",
        "created_at"
      ],
      "optional": [
        "action_type",
        "note",
        "completed_at",
        "actor_type",
        "actor_id"
      ],
      "unique": [
        "resident_id",
        "result_id"
      ]
    },
    "audit": {
      "required": [
        "event_id",
        "event_type",
        "actor_type",
        "actor_id",
        "scope_id",
        "occurred_at"
      ],
      "actorTypes": [
        "admin_user",
        "device",
        "system"
      ],
      "note": "코드·비밀번호 원문을 저장하지 않음"
    }
  },
  "dashboardDefinitions": {
    "facility": {
      "rowEntity": "resident",
      "dailyPopulation": "재원이며 동의 철회 상태가 아닌 입소자 전체. 동의 미완료는 동의 필요 상태로 표시",
      "columns": [
        "입소자",
        "위치",
        "오늘 측정 상태",
        "마지막 유효 측정일시",
        "HR",
        "BP",
        "SpO2",
        "피부 온도",
        "측정 품질",
        "후속조치",
        "다음 행동"
      ],
      "valueSource": "한 행의 모든 지표는 같은 마지막 유효 측정 결과",
      "emptyValues": [
        "측정 이력 없음",
        "유효값 없음",
        "제공 안 됨"
      ],
      "todaySuccess": "당일 유효 측정 1회 이상",
      "readOnlyOnPC": "직접 측정 시작 없음",
      "latestAttemptStatus": "오늘 완료 여부와 별도로 최신 시도의 분석 불가·중단 상태를 표시"
    },
    "commercial": {
      "dimensions": [
        "디스트리뷰터",
        "시설",
        "라이선스",
        "연결 디바이스",
        "기간"
      ],
      "primary": [
        "활성 계약 월 이용료 합계 (부가세 별도)",
        "당월 확정 이용료 (일할 반영, 부가세 별도)",
        "확인 입금액 (세액과 총액 구분)",
        "활성 라이선스 수",
        "연결 디바이스 수",
        "당월 차감 횟수",
        "라이선스별 잔여 횟수"
      ],
      "secondary": [
        "측정 시도 수",
        "품질 미달 수",
        "승인 대기 수",
        "입금 대기 수",
        "취소 예정 수"
      ],
      "reconciliation": "유효 측정 확정 수 = usageLedger 차감 수",
      "historicalPrice": "기간별 가격 스냅샷으로 집계"
    }
  },
  "operationalConfiguration": [
    {
      "id": "evaluationSource",
      "owner": "슈퍼어드민",
      "source": "실제 분석 모듈 또는 승인된 운영 기준",
      "missingBehavior": "평가 안 됨 표시와 설정 알림. 정상 판정으로 대체하지 않음",
      "flowNodeId": "saRule"
    },
    {
      "id": "enterpriseConfiguration",
      "owner": "슈퍼어드민·디스트리뷰터",
      "source": "해당 라이선스 계약의 커스터마이징 범위",
      "missingBehavior": "기본 제공 기능은 사용 가능하며 아직 제공되지 않은 맞춤 기능을 약속하거나 노출하지 않음",
      "flowNodeId": "diContract"
    },
    {
      "id": "privacyRequestPolicy",
      "owner": "슈퍼어드민",
      "source": "확인된 개인정보 처리 기준과 요청 근거",
      "missingBehavior": "보완 요청 상태 유지. 자동 삭제·임의 영구보존을 실행하지 않음",
      "flowNodeId": "saPrivacyCheck"
    }
  ],
  "roles": [
    {
      "role": "overview",
      "title": "전체 흐름",
      "description": "라이선스 생성부터 오늘 운영 표까지 이어지는 전체 흐름. 상세 분기는 각 역할 플로우를 참조합니다.",
      "surface": "전체",
      "entryNodeIds": [
        "ovStart"
      ],
      "flows": [
        {
          "id": "overviewMain",
          "title": "등록부터 측정까지",
          "entryNodeId": "ovStart",
          "purpose": "역할별 인계와 라이선스 중심 구조를 한 장에서 확인"
        }
      ],
      "nodes": [
        {
          "id": "ovStart",
          "label": "디스트리뷰터 생성·초대",
          "kind": "action",
          "note": "슈퍼어드민이 생성하고 초대합니다.",
          "flowId": "overviewMain",
          "policyRefs": [],
          "detailNodeId": "saInvite"
        },
        {
          "id": "ovFacility",
          "label": "시설 생성·관리자 초대",
          "kind": "action",
          "note": "디스트리뷰터가 자신의 시설과 시설 관리자를 등록합니다.",
          "flowId": "overviewMain",
          "policyRefs": [],
          "detailNodeId": "diFacilityCreate"
        },
        {
          "id": "ovLicense",
          "label": "라이선스 등록",
          "kind": "action",
          "note": "시설·플랜·1개당 월 이용료·수량을 입력합니다. 개별 라이선스와 코드를 생성합니다.",
          "flowId": "overviewMain",
          "policyRefs": [],
          "detailNodeId": "diCreateForm"
        },
        {
          "id": "ovPrice",
          "label": "기본가격과 같은가?",
          "kind": "decision",
          "note": "각 라이선스의 플랜 기본가격과 비교합니다.",
          "flowId": "overviewMain",
          "policyRefs": []
        },
        {
          "id": "ovApproval",
          "label": "가격 승인 요청",
          "kind": "action",
          "note": "다른 가격이면 슈퍼어드민에게 요청합니다. 신규 측정은 대기합니다.",
          "flowId": "overviewMain",
          "policyRefs": [],
          "detailNodeId": "saRequests"
        },
        {
          "id": "ovDecision",
          "label": "승인 결과는?",
          "kind": "decision",
          "note": "승인 또는 반려 사유를 라이선스별로 기록합니다.",
          "flowId": "overviewMain",
          "policyRefs": []
        },
        {
          "id": "ovFix",
          "label": "가격 수정·재요청",
          "kind": "action",
          "note": "반려 사유를 확인하고 수정합니다.",
          "flowId": "overviewMain",
          "policyRefs": []
        },
        {
          "id": "ovContract",
          "label": "외부 계약·입금 확인",
          "kind": "action",
          "note": "승인된 조건으로 계약하고 입금 확인을 등록합니다.",
          "flowId": "overviewMain",
          "policyRefs": [],
          "detailNodeId": "diContract"
        },
        {
          "id": "ovConnect",
          "label": "코드로 디바이스 연결",
          "kind": "action",
          "note": "시설과 라이선스를 자동 특정합니다. 승인 대기에도 연결은 가능합니다.",
          "flowId": "overviewMain",
          "policyRefs": [],
          "detailNodeId": "dvCode"
        },
        {
          "id": "ovReady",
          "label": "측정 가능한가?",
          "kind": "decision",
          "note": "온라인·연결·승인·입금·기간·횟수 조건을 확인합니다. 연결만으로 활성화되지 않습니다.",
          "flowId": "overviewMain",
          "policyRefs": [],
          "detailNodeId": "dvServiceGate"
        },
        {
          "id": "ovBlocked",
          "label": "제한 사유·해결 안내",
          "kind": "screen",
          "note": "승인·입금·갱신·연결·온라인 중 해결할 사유와 담당자를 표시합니다.",
          "flowId": "overviewMain",
          "policyRefs": []
        },
        {
          "id": "ovMode",
          "label": "측정모드·거치모드",
          "kind": "screen",
          "note": "같은 디바이스에서 전환합니다. 측정모드 진입은 비밀번호가 필요합니다.",
          "flowId": "overviewMain",
          "policyRefs": [],
          "detailNodeId": "dvStandby"
        },
        {
          "id": "ovScan",
          "label": "측정·품질 확인",
          "kind": "action",
          "note": "동의와 대상자를 확인하고 측정합니다. 얼굴 식별 실패도 일반 사용자로 진행합니다.",
          "flowId": "overviewMain",
          "policyRefs": [],
          "detailNodeId": "dvPreflight"
        },
        {
          "id": "ovValid",
          "label": "유효 측정인가?",
          "kind": "decision",
          "note": "분석 모듈의 품질과 지표 유효성을 검증합니다.",
          "flowId": "overviewMain",
          "policyRefs": []
        },
        {
          "id": "ovRetry",
          "label": "미차감·재측정 안내",
          "kind": "action",
          "note": "결과를 저장하지 않고 차감하지 않습니다.",
          "flowId": "overviewMain",
          "policyRefs": []
        },
        {
          "id": "ovCommit",
          "label": "결과 확정·1회 차감",
          "kind": "action",
          "note": "같은 세션은 한 번만 확정합니다. 라이선스별 당월 횟수를 사용합니다.",
          "flowId": "overviewMain",
          "policyRefs": [],
          "detailNodeId": "syCommitRequest"
        },
        {
          "id": "ovTable",
          "label": "오늘 운영 표·후속조치",
          "kind": "screen",
          "note": "입소자 연결 결과로 오늘 상태와 마지막 유효값을 갱신합니다. 이상치는 후속조치에 표시합니다.",
          "flowId": "overviewMain",
          "policyRefs": [],
          "terminal": true,
          "detailNodeId": "faHome"
        },
        {
          "id": "ovUsage",
          "label": "라이선스·이용 현황",
          "kind": "screen",
          "note": "슈퍼어드민과 디스트리뷰터의 승인 금액·입금·사용량 집계에 반영합니다.",
          "flowId": "overviewMain",
          "policyRefs": [],
          "terminal": true,
          "detailNodeId": "saDashboard"
        }
      ],
      "edges": [
        {
          "id": "overviewEdge001",
          "source": "ovStart",
          "target": "ovFacility",
          "label": "초대 수락",
          "when": "디스트리뷰터 접근 활성화"
        },
        {
          "id": "overviewEdge002",
          "source": "ovFacility",
          "target": "ovLicense",
          "label": "시설 준비",
          "when": "시설 등록 완료"
        },
        {
          "id": "overviewEdge003",
          "source": "ovLicense",
          "target": "ovPrice",
          "label": "가격 비교",
          "when": "개별 라이선스 생성"
        },
        {
          "id": "overviewEdge004",
          "source": "ovPrice",
          "target": "ovContract",
          "label": "같음",
          "when": "기본가격 자동 승인"
        },
        {
          "id": "overviewEdge005",
          "source": "ovPrice",
          "target": "ovApproval",
          "label": "다름",
          "when": "할인·할증 모두 승인 필요"
        },
        {
          "id": "overviewEdge006",
          "source": "ovApproval",
          "target": "ovDecision",
          "label": "검토",
          "when": "슈퍼어드민 판단"
        },
        {
          "id": "overviewEdge007",
          "source": "ovDecision",
          "target": "ovContract",
          "label": "승인",
          "when": "승인가격 확정"
        },
        {
          "id": "overviewEdge008",
          "source": "ovDecision",
          "target": "ovFix",
          "label": "반려",
          "when": "사유 전달"
        },
        {
          "id": "overviewEdge009",
          "source": "ovFix",
          "target": "ovApproval",
          "label": "다시 요청",
          "when": "요청 버전 갱신"
        },
        {
          "id": "overviewEdge010",
          "source": "ovLicense",
          "target": "ovConnect",
          "label": "코드 발급",
          "when": "승인 여부와 별개로 연결 가능"
        },
        {
          "id": "overviewEdge011",
          "source": "ovContract",
          "target": "ovReady",
          "label": "입금 확인",
          "when": "서비스 활성 조건 갱신"
        },
        {
          "id": "overviewEdge012",
          "source": "ovConnect",
          "target": "ovReady",
          "label": "연결 완료",
          "when": "현재 라이선스 상태 검증"
        },
        {
          "id": "overviewEdge013",
          "source": "ovReady",
          "target": "ovBlocked",
          "label": "조건 미충족",
          "when": "제한 사유 존재"
        },
        {
          "id": "overviewEdge014",
          "source": "ovBlocked",
          "target": "ovReady",
          "label": "다시 확인",
          "when": "담당자 처리 또는 연결 회복"
        },
        {
          "id": "overviewEdge015",
          "source": "ovReady",
          "target": "ovMode",
          "label": "조건 충족",
          "when": "이용 가능"
        },
        {
          "id": "overviewEdge016",
          "source": "ovMode",
          "target": "ovScan",
          "label": "측정 시작",
          "when": "동의 확인 완료"
        },
        {
          "id": "overviewEdge017",
          "source": "ovScan",
          "target": "ovValid",
          "label": "분석 완료",
          "when": "검증 결과 도착"
        },
        {
          "id": "overviewEdge018",
          "source": "ovValid",
          "target": "ovRetry",
          "label": "무효",
          "when": "품질 미달·중단·오류"
        },
        {
          "id": "overviewEdge019",
          "source": "ovRetry",
          "target": "ovMode",
          "label": "다시 준비",
          "when": "재측정 또는 종료"
        },
        {
          "id": "overviewEdge020",
          "source": "ovValid",
          "target": "ovCommit",
          "label": "유효",
          "when": "확정 시점 이용 조건도 충족"
        },
        {
          "id": "overviewEdge021",
          "source": "ovCommit",
          "target": "ovTable",
          "label": "입소자 연결",
          "when": "일반 사용자는 선택 등록 후 연결"
        },
        {
          "id": "overviewEdge022",
          "source": "ovCommit",
          "target": "ovUsage",
          "label": "집계 갱신",
          "when": "일반 사용자 포함 모든 차감 집계"
        }
      ]
    },
    {
      "role": "superadmin",
      "title": "슈퍼어드민",
      "description": "엑시스트의 전체 라이선스·이용 현황, 디스트리뷰터, 가격 승인과 전역 설정을 관리합니다.",
      "surface": "관리자 PC",
      "entryNodeIds": [
        "saLogin",
        "saRequests",
        "saSettings"
      ],
      "flows": [
        {
          "id": "saOverview",
          "title": "현황과 디스트리뷰터",
          "entryNodeId": "saLogin",
          "purpose": "전체 현황 조회 및 디스트리뷰터 온보딩"
        },
        {
          "id": "saApproval",
          "title": "가격 승인",
          "entryNodeId": "saRequests",
          "purpose": "라이선스별 변경가격 승인·반려"
        },
        {
          "id": "saSettings",
          "title": "설정과 지원",
          "entryNodeId": "saSettings",
          "purpose": "플랜·지원·이상치 판정 기준 연결"
        }
      ],
      "nodes": [
        {
          "id": "saLogin",
          "label": "관리자 로그인",
          "kind": "screen",
          "note": "슈퍼어드민 권한을 확인합니다. 공통 로그인 실패·복구 흐름을 사용합니다.",
          "flowId": "saOverview",
          "policyRefs": [
            "hierarchy",
            "accounts",
            "analytics"
          ]
        },
        {
          "id": "saDashboard",
          "label": "전체 라이선스·이용 현황",
          "kind": "screen",
          "note": "승인 월 이용료 합계·당월 확정 이용료·입금액·디스트리뷰터별 시설·라이선스·유효 측정과 사용률을 보여줍니다.",
          "flowId": "saOverview",
          "policyRefs": [
            "hierarchy",
            "accounts",
            "analytics"
          ]
        },
        {
          "id": "saDrill",
          "label": "디스트리뷰터·시설 상세",
          "kind": "screen",
          "note": "필터 기간을 유지하고 시설·라이선스·연결 디바이스까지 내려갑니다. 기본 화면은 개인 지표를 포함하지 않습니다.",
          "flowId": "saOverview",
          "policyRefs": [
            "hierarchy",
            "accounts",
            "analytics"
          ]
        },
        {
          "id": "saDistributor",
          "label": "디스트리뷰터 관리",
          "kind": "screen",
          "note": "생성·관리자 초대·재초대·접근 정지를 제공합니다.",
          "flowId": "saOverview",
          "policyRefs": [
            "hierarchy",
            "accounts",
            "analytics"
          ]
        },
        {
          "id": "saInvite",
          "label": "디스트리뷰터 생성·초대",
          "kind": "action",
          "note": "디스트리뷰터명과 관리자 이메일을 입력합니다. 중복 조직·이메일을 확인하고 초대합니다.",
          "flowId": "saOverview",
          "policyRefs": [
            "hierarchy",
            "accounts",
            "analytics"
          ]
        },
        {
          "id": "saInviteState",
          "label": "초대 결과는?",
          "kind": "decision",
          "note": "수락·미수락·거절/취소 상태를 표시합니다.",
          "flowId": "saOverview",
          "policyRefs": [
            "hierarchy",
            "accounts",
            "analytics"
          ]
        },
        {
          "id": "saResend",
          "label": "초대 재발송·정보 수정",
          "kind": "action",
          "note": "미수락이면 재발송, 잘못된 이메일이면 취소 후 수정합니다.",
          "flowId": "saOverview",
          "policyRefs": [
            "hierarchy",
            "accounts",
            "analytics"
          ]
        },
        {
          "id": "saActive",
          "label": "디스트리뷰터 활성",
          "kind": "action",
          "note": "초대 수락 후 소속 시설 관리와 라이선스 등록이 가능합니다.",
          "flowId": "saOverview",
          "policyRefs": [
            "hierarchy",
            "accounts",
            "analytics"
          ]
        },
        {
          "id": "saRequests",
          "label": "가격 승인 요청",
          "kind": "screen",
          "note": "시설·플랜·현재가격·요청가격·변경사유·신규/기존·요청 버전을 라이선스별로 봅니다.",
          "flowId": "saApproval",
          "policyRefs": [
            "price_approval"
          ]
        },
        {
          "id": "saReview",
          "label": "요청이 최신인가?",
          "kind": "decision",
          "note": "자신의 검토 이후 수정·취소된 요청이나 이미 처리된 요청에는 중복 승인하지 않습니다.",
          "flowId": "saApproval",
          "policyRefs": [
            "price_approval"
          ]
        },
        {
          "id": "saStale",
          "label": "최신 요청 다시 보기",
          "kind": "action",
          "note": "변경된 내용과 기존 처리 결과를 표시합니다.",
          "flowId": "saApproval",
          "policyRefs": [
            "price_approval"
          ]
        },
        {
          "id": "saChoice",
          "label": "승인할 것인가?",
          "kind": "decision",
          "note": "기본가격과 차이, 계약 대상과 적용일을 확인합니다.",
          "flowId": "saApproval",
          "policyRefs": [
            "price_approval"
          ]
        },
        {
          "id": "saApprove",
          "label": "가격 승인",
          "kind": "action",
          "note": "승인가격·승인자·시점·적용일을 저장합니다. 신규는 계약 진행, 기존은 다음 달 적용 예약입니다.",
          "flowId": "saApproval",
          "policyRefs": [
            "price_approval"
          ]
        },
        {
          "id": "saReject",
          "label": "사유 입력·반려",
          "kind": "action",
          "note": "반려 사유는 필수입니다. 신규는 측정 대기, 기존은 종전 조건을 유지합니다.",
          "flowId": "saApproval",
          "policyRefs": [
            "price_approval"
          ]
        },
        {
          "id": "saNotified",
          "label": "요청자에게 결과 표시",
          "kind": "action",
          "note": "앱 내 알림과 라이선스 상세 상태를 갱신합니다. 일괄 처리도 개별 결과를 남깁니다.",
          "flowId": "saApproval",
          "policyRefs": [
            "price_approval"
          ]
        },
        {
          "id": "saSettings",
          "label": "전역 설정·지원",
          "kind": "screen",
          "note": "플랜 기본가격·기능, 기준 연결, 디바이스 연결 지원, 개인정보 요청을 관리합니다.",
          "flowId": "saSettings",
          "policyRefs": [
            "plans",
            "followup",
            "residents",
            "connection"
          ]
        },
        {
          "id": "saPlans",
          "label": "플랜 기본가격·기능",
          "kind": "screen",
          "note": "Standard 20만 원·Plus 30만 원·Enterprise 40만 원, 부가세 별도. 모든 플랜 월 3000회입니다.",
          "flowId": "saSettings",
          "policyRefs": [
            "plans",
            "followup",
            "residents",
            "connection"
          ]
        },
        {
          "id": "saPlanSave",
          "label": "새 가격 버전 저장",
          "kind": "action",
          "note": "변경 이후 신규 등록에만 적용합니다. 과거 계약·이용기간 금액을 수정하지 않습니다.",
          "flowId": "saSettings",
          "policyRefs": [
            "plans",
            "followup",
            "residents",
            "connection"
          ]
        },
        {
          "id": "saRule",
          "label": "이상치 기준 연결",
          "kind": "screen",
          "note": "실제 분석 모듈의 판정 출력이나 승인된 기준 버전을 연결합니다. 미연결이면 평가 안 됨과 운영 알림을 유지합니다.",
          "flowId": "saSettings",
          "policyRefs": [
            "plans",
            "followup",
            "residents",
            "connection"
          ]
        },
        {
          "id": "saSupport",
          "label": "라이선스 연결 지원",
          "kind": "action",
          "note": "권한과 대상 시설을 확인하여 연결 해제·코드 재발급 공통 흐름을 실행합니다.",
          "flowId": "saSettings",
          "policyRefs": [
            "plans",
            "followup",
            "residents",
            "connection"
          ]
        },
        {
          "id": "saPrivacy",
          "label": "개인정보 요청 접수",
          "kind": "screen",
          "note": "시설에서 들어온 철회 이후 삭제 등 요청의 본인·권한·대상·적용 처리 기준을 확인합니다.",
          "flowId": "saSettings",
          "policyRefs": [
            "plans",
            "followup",
            "residents",
            "connection"
          ]
        },
        {
          "id": "saPrivacyCheck",
          "label": "처리 근거가 확인됐는가?",
          "kind": "decision",
          "note": "확인 없이 데이터를 자동 삭제하거나 영구보존으로 확정하지 않습니다.",
          "flowId": "saSettings",
          "policyRefs": [
            "plans",
            "followup",
            "residents",
            "connection"
          ]
        },
        {
          "id": "saPrivacyMore",
          "label": "보완 요청",
          "kind": "action",
          "note": "필요 정보를 요청하고 접수 상태를 유지합니다.",
          "flowId": "saSettings",
          "policyRefs": [
            "plans",
            "followup",
            "residents",
            "connection"
          ]
        },
        {
          "id": "saPrivacyApply",
          "label": "정책에 따른 처리·통지",
          "kind": "action",
          "note": "확인된 범위의 처리 결과·사유·완료 시점을 기록하고 시설에 통지합니다. 측정 차감 집계는 비식별 원장으로 정합성을 유지합니다.",
          "flowId": "saSettings",
          "policyRefs": [
            "plans",
            "followup",
            "residents",
            "connection"
          ]
        }
      ],
      "edges": [
        {
          "id": "superadminEdge001",
          "source": "saLogin",
          "target": "saDashboard",
          "label": "로그인 완료",
          "when": "권한 확인 성공"
        },
        {
          "id": "superadminEdge002",
          "source": "saDashboard",
          "target": "saDrill",
          "label": "상세 보기",
          "when": "행 선택"
        },
        {
          "id": "superadminEdge003",
          "source": "saDrill",
          "target": "saDashboard",
          "label": "목록으로",
          "when": "상세 닫기"
        },
        {
          "id": "superadminEdge004",
          "source": "saDashboard",
          "target": "saDistributor",
          "label": "디스트리뷰터",
          "when": "메뉴 선택"
        },
        {
          "id": "superadminEdge005",
          "source": "saDistributor",
          "target": "saInvite",
          "label": "생성·초대",
          "when": "필수 정보 유효"
        },
        {
          "id": "superadminEdge006",
          "source": "saInvite",
          "target": "saInviteState",
          "label": "초대 발송",
          "when": "초대 상태 확인"
        },
        {
          "id": "superadminEdge007",
          "source": "saInviteState",
          "target": "saActive",
          "label": "수락",
          "when": "비밀번호 설정 완료"
        },
        {
          "id": "superadminEdge008",
          "source": "saInviteState",
          "target": "saResend",
          "label": "미수락",
          "when": "재발송 또는 수정 선택"
        },
        {
          "id": "superadminEdge009",
          "source": "saInviteState",
          "target": "saDistributor",
          "label": "거절·취소",
          "when": "접근 권한 미발급"
        },
        {
          "id": "superadminEdge010",
          "source": "saResend",
          "target": "saInviteState",
          "label": "다시 발송",
          "when": "정정된 초대 확인"
        },
        {
          "id": "superadminEdge011",
          "source": "saActive",
          "target": "saDistributor",
          "label": "완료",
          "when": "목록 갱신"
        },
        {
          "id": "superadminEdge012",
          "source": "saRequests",
          "target": "saReview",
          "label": "요청 선택",
          "when": "한 개 또는 여러 개 선택"
        },
        {
          "id": "superadminEdge013",
          "source": "saReview",
          "target": "saStale",
          "label": "아니오",
          "when": "버전 불일치·처리 완료"
        },
        {
          "id": "superadminEdge014",
          "source": "saStale",
          "target": "saRequests",
          "label": "새로고침",
          "when": "현재 요청 다시 조회"
        },
        {
          "id": "superadminEdge015",
          "source": "saReview",
          "target": "saChoice",
          "label": "예",
          "when": "처리 가능한 최신 요청"
        },
        {
          "id": "superadminEdge016",
          "source": "saChoice",
          "target": "saApprove",
          "label": "승인",
          "when": "개별 승인 기록"
        },
        {
          "id": "superadminEdge017",
          "source": "saChoice",
          "target": "saReject",
          "label": "반려",
          "when": "사유 입력"
        },
        {
          "id": "superadminEdge018",
          "source": "saApprove",
          "target": "saNotified",
          "label": "완료",
          "when": "승인 결과 확정"
        },
        {
          "id": "superadminEdge019",
          "source": "saReject",
          "target": "saNotified",
          "label": "완료",
          "when": "반려 결과 확정"
        },
        {
          "id": "superadminEdge020",
          "source": "saNotified",
          "target": "saRequests",
          "label": "목록으로",
          "when": "다음 요청 확인"
        },
        {
          "id": "superadminEdge021",
          "source": "saDashboard",
          "target": "saRequests",
          "label": "가격 승인",
          "when": "메뉴 또는 대기 알림"
        },
        {
          "id": "superadminEdge022",
          "source": "saDashboard",
          "target": "saSettings",
          "label": "설정·지원",
          "when": "메뉴 선택"
        },
        {
          "id": "superadminEdge023",
          "source": "saSettings",
          "target": "saPlans",
          "label": "플랜",
          "when": "플랜 관리"
        },
        {
          "id": "superadminEdge024",
          "source": "saPlans",
          "target": "saPlanSave",
          "label": "저장",
          "when": "유효 금액·기능 입력"
        },
        {
          "id": "superadminEdge025",
          "source": "saPlanSave",
          "target": "saSettings",
          "label": "완료",
          "when": "새 버전 적용"
        },
        {
          "id": "superadminEdge026",
          "source": "saSettings",
          "target": "saRule",
          "label": "기준 연결",
          "when": "이상치 평가 설정"
        },
        {
          "id": "superadminEdge027",
          "source": "saRule",
          "target": "saSettings",
          "label": "저장·돌아가기",
          "when": "검증된 설정만 반영"
        },
        {
          "id": "superadminEdge028",
          "source": "saSettings",
          "target": "saSupport",
          "label": "연결 지원",
          "when": "관리 대상 라이선스 선택"
        },
        {
          "id": "superadminEdge029",
          "source": "saSupport",
          "target": "saSettings",
          "label": "처리 완료",
          "when": "공통 재발급 완료"
        },
        {
          "id": "superadminEdge030",
          "source": "saSettings",
          "target": "saPrivacy",
          "label": "개인정보 요청",
          "when": "대기 요청 선택"
        },
        {
          "id": "superadminEdge031",
          "source": "saPrivacy",
          "target": "saPrivacyCheck",
          "label": "검토",
          "when": "근거·범위 확인"
        },
        {
          "id": "superadminEdge032",
          "source": "saPrivacyCheck",
          "target": "saPrivacyMore",
          "label": "미충족",
          "when": "보완 필요"
        },
        {
          "id": "superadminEdge033",
          "source": "saPrivacyMore",
          "target": "saPrivacy",
          "label": "보완 수신",
          "when": "재검토"
        },
        {
          "id": "superadminEdge034",
          "source": "saPrivacyCheck",
          "target": "saPrivacyApply",
          "label": "충족",
          "when": "확인된 처리 정책 존재"
        },
        {
          "id": "superadminEdge035",
          "source": "saPrivacyApply",
          "target": "saSettings",
          "label": "완료",
          "when": "시설에 결과 통지"
        }
      ]
    },
    {
      "role": "distributor",
      "title": "디스트리뷰터",
      "description": "자신의 시설과 시설 관리자, 라이선스 등록·계약·입금·변경·취소 및 이용 현황을 관리합니다.",
      "surface": "관리자 PC",
      "entryNodeIds": [
        "diInviteAccept",
        "diLicenses",
        "diContract",
        "diDetail"
      ],
      "flows": [
        {
          "id": "diOnboarding",
          "title": "시설과 초대",
          "entryNodeId": "diInviteAccept",
          "purpose": "디스트리뷰터 초대 수락과 시설 관리자 초대"
        },
        {
          "id": "diCreate",
          "title": "라이선스 등록",
          "entryNodeId": "diLicenses",
          "purpose": "수량 입력과 라이선스별 승인·코드 생성"
        },
        {
          "id": "diActivate",
          "title": "계약과 입금 확인",
          "entryNodeId": "diContract",
          "purpose": "승인 이후 외부 계약·입금과 이용 시작"
        },
        {
          "id": "diLifecycle",
          "title": "이용 중 변경과 취소",
          "entryNodeId": "diDetail",
          "purpose": "기존 라이선스의 가격 변경·갱신·취소·연결 지원"
        }
      ],
      "nodes": [
        {
          "id": "diInviteAccept",
          "label": "초대 수락·비밀번호 설정",
          "kind": "screen",
          "note": "슈퍼어드민이 만든 디스트리뷰터에 합류합니다. 별도 조직 계층을 생성하지 않습니다.",
          "flowId": "diOnboarding",
          "policyRefs": [
            "hierarchy",
            "accounts",
            "analytics"
          ]
        },
        {
          "id": "diDashboard",
          "label": "시설별 라이선스·이용 현황",
          "kind": "screen",
          "note": "자신의 시설별 월 이용료·당월 확정 이용료·확인 입금액·활성 라이선스·유효 측정과 사용률을 보여줍니다.",
          "flowId": "diOnboarding",
          "policyRefs": [
            "hierarchy",
            "accounts",
            "analytics"
          ]
        },
        {
          "id": "diFacilities",
          "label": "시설 관리",
          "kind": "screen",
          "note": "자신의 시설과 시설 관리자 초대 상태를 표시합니다.",
          "flowId": "diOnboarding",
          "policyRefs": [
            "hierarchy",
            "accounts",
            "analytics"
          ]
        },
        {
          "id": "diFacilityCreate",
          "label": "시설 생성·관리자 초대",
          "kind": "action",
          "note": "시설명·위치·관리자 이메일을 입력합니다. 시설은 현재 디스트리뷰터에 귀속합니다.",
          "flowId": "diOnboarding",
          "policyRefs": [
            "hierarchy",
            "accounts",
            "analytics"
          ]
        },
        {
          "id": "diFacilityInviteState",
          "label": "시설 관리자 초대 상태는?",
          "kind": "decision",
          "note": "수락·미수락·거절/취소를 구분합니다.",
          "flowId": "diOnboarding",
          "policyRefs": [
            "hierarchy",
            "accounts",
            "analytics"
          ]
        },
        {
          "id": "diFacilityResend",
          "label": "재초대·이메일 수정",
          "kind": "action",
          "note": "기존 초대를 취소하거나 재발송합니다. 별도 가입 승인 단계는 없습니다.",
          "flowId": "diOnboarding",
          "policyRefs": [
            "hierarchy",
            "accounts",
            "analytics"
          ]
        },
        {
          "id": "diLicenses",
          "label": "라이선스 관리",
          "kind": "screen",
          "note": "시설·플랜·가격·승인·계약/입금·이용기간·연결·당월 잔여를 구분해 봅니다.",
          "flowId": "diCreate",
          "policyRefs": [
            "license_creation",
            "price_approval",
            "connection"
          ]
        },
        {
          "id": "diCreateForm",
          "label": "라이선스 등록",
          "kind": "screen",
          "note": "시설·플랜·1개당 월 이용료·수량을 입력합니다. 기본가격을 자동 채우며 합계와 부가세 별도 표시를 제공합니다.",
          "flowId": "diCreate",
          "policyRefs": [
            "license_creation",
            "price_approval",
            "connection"
          ]
        },
        {
          "id": "diFormValid",
          "label": "입력값이 유효한가?",
          "kind": "decision",
          "note": "소속 시설·판매 가능 플랜·0원 이상 월 이용료·양의 정수 수량을 검증합니다. 0원 등 기본가격 외 금액은 승인 대상입니다.",
          "flowId": "diCreate",
          "policyRefs": [
            "license_creation",
            "price_approval",
            "connection"
          ]
        },
        {
          "id": "diFormError",
          "label": "입력 오류 수정",
          "kind": "screen",
          "note": "오류 필드를 표시하고 입력값을 보존합니다. 저장 재시도로 중복 라이선스를 생성하지 않습니다.",
          "flowId": "diCreate",
          "policyRefs": [
            "license_creation",
            "price_approval",
            "connection"
          ]
        },
        {
          "id": "diCreateEach",
          "label": "개별 라이선스·코드 생성",
          "kind": "action",
          "note": "수량만큼 독립 ID·코드·가격·승인 상태를 만듭니다. 미리 구매한 재고는 없습니다.",
          "flowId": "diCreate",
          "policyRefs": [
            "license_creation",
            "price_approval",
            "connection"
          ]
        },
        {
          "id": "diBasePrice",
          "label": "기본가격과 같은가?",
          "kind": "decision",
          "note": "등록 시점 플랜 가격 버전과 개별 입력 월 이용료를 비교합니다.",
          "flowId": "diCreate",
          "policyRefs": [
            "license_creation",
            "price_approval",
            "connection"
          ]
        },
        {
          "id": "diAutoApprove",
          "label": "기본가격 자동 승인",
          "kind": "action",
          "note": "슈퍼어드민 추가 승인 없이 계약 진행 상태로 전환합니다.",
          "flowId": "diCreate",
          "policyRefs": [
            "license_creation",
            "price_approval",
            "connection"
          ]
        },
        {
          "id": "diRequestApproval",
          "label": "가격 승인 요청",
          "kind": "action",
          "note": "사유와 개별 요청가격을 슈퍼어드민에게 전달합니다. 신규는 연결 가능·측정 대기입니다.",
          "flowId": "diCreate",
          "policyRefs": [
            "license_creation",
            "price_approval",
            "connection"
          ]
        },
        {
          "id": "diApprovalResult",
          "label": "신규 승인 결과는?",
          "kind": "decision",
          "note": "승인·반려·대기·요청 취소를 구분합니다.",
          "flowId": "diCreate",
          "policyRefs": [
            "license_creation",
            "price_approval",
            "connection"
          ]
        },
        {
          "id": "diApprovalWait",
          "label": "승인 대기",
          "kind": "screen",
          "note": "현재 요청 상태와 코드를 확인할 수 있습니다. 계약·입금 완료로 오인하지 않도록 구분합니다.",
          "flowId": "diCreate",
          "policyRefs": [
            "license_creation",
            "price_approval",
            "connection"
          ]
        },
        {
          "id": "diRejected",
          "label": "반려 사유·가격 수정",
          "kind": "screen",
          "note": "가격과 사유를 수정하여 재요청하거나 등록을 취소합니다.",
          "flowId": "diCreate",
          "policyRefs": [
            "license_creation",
            "price_approval",
            "connection"
          ]
        },
        {
          "id": "diDraftCancel",
          "label": "미활성 등록 취소",
          "kind": "action",
          "note": "미활성 라이선스를 취소하고 코드를 폐기합니다. 기록은 보존합니다.",
          "flowId": "diCreate",
          "policyRefs": [
            "license_creation",
            "price_approval",
            "connection"
          ]
        },
        {
          "id": "diContract",
          "label": "외부 계약 확인",
          "kind": "screen",
          "note": "승인된 라이선스별 월 이용료·플랜으로 외부 계약을 진행하고 계약 정보와 완료 여부를 기록합니다.",
          "flowId": "diActivate",
          "policyRefs": [
            "activation",
            "billing",
            "quota"
          ]
        },
        {
          "id": "diContractState",
          "label": "계약이 완료됐는가?",
          "kind": "decision",
          "note": "계약 미완료는 연결만 허용하며 측정하지 않습니다.",
          "flowId": "diActivate",
          "policyRefs": [
            "activation",
            "billing",
            "quota"
          ]
        },
        {
          "id": "diContractWait",
          "label": "계약 대기",
          "kind": "screen",
          "note": "계약 정보 보완 또는 계약 취소가 가능합니다.",
          "flowId": "diActivate",
          "policyRefs": [
            "activation",
            "billing",
            "quota"
          ]
        },
        {
          "id": "diReceipt",
          "label": "입금 확인",
          "kind": "screen",
          "note": "라이선스별 대상기간·유효 입금일·금액·확인 근거를 등록합니다. 수량 일괄 확인도 개별 배분합니다.",
          "flowId": "diActivate",
          "policyRefs": [
            "activation",
            "billing",
            "quota"
          ]
        },
        {
          "id": "diReceiptValid",
          "label": "대상기간 입금이 확인됐는가?",
          "kind": "decision",
          "note": "해당 기간을 충족하는 입금과 계약을 확인합니다. 중복·부족·대상 불명확은 활성화하지 않습니다.",
          "flowId": "diActivate",
          "policyRefs": [
            "activation",
            "billing",
            "quota"
          ]
        },
        {
          "id": "diReceiptWait",
          "label": "입금 대기·보완",
          "kind": "screen",
          "note": "외부 입금과 대상기간을 확인합니다. 자동 수납 완료로 표시하지 않습니다.",
          "flowId": "diActivate",
          "policyRefs": [
            "activation",
            "billing",
            "quota"
          ]
        },
        {
          "id": "diActivate",
          "label": "이용 활성화",
          "kind": "action",
          "note": "최초 입금 유효일부터 말일까지 기간·일할 이용료·3000회 한도를 한 번 생성합니다. 코드 연결 상태는 유지합니다.",
          "flowId": "diActivate",
          "policyRefs": [
            "activation",
            "billing",
            "quota"
          ]
        },
        {
          "id": "diDetail",
          "label": "라이선스 상세",
          "kind": "screen",
          "note": "현재 적용 조건과 다음 달 변경 예약을 나눠 표시합니다. 시설·코드·연결·기간·잔여·입금 상태를 확인합니다.",
          "flowId": "diLifecycle",
          "policyRefs": [
            "price_approval",
            "cancellation",
            "billing",
            "connection"
          ]
        },
        {
          "id": "diChange",
          "label": "가격·플랜 변경 요청",
          "kind": "screen",
          "note": "변경 후 플랜과 월 이용료를 입력합니다. 기존 측정은 승인된 종전 조건으로 계속합니다.",
          "flowId": "diLifecycle",
          "policyRefs": [
            "price_approval",
            "cancellation",
            "billing",
            "connection"
          ]
        },
        {
          "id": "diChangeBase",
          "label": "변경가격이 기본가격인가?",
          "kind": "decision",
          "note": "변경 후 플랜의 현재 기본가격과 비교합니다.",
          "flowId": "diLifecycle",
          "policyRefs": [
            "price_approval",
            "cancellation",
            "billing",
            "connection"
          ]
        },
        {
          "id": "diChangeReview",
          "label": "변경 승인 결과는?",
          "kind": "decision",
          "note": "비기본가격은 슈퍼어드민 승인·반려·대기를 기다립니다.",
          "flowId": "diLifecycle",
          "policyRefs": [
            "price_approval",
            "cancellation",
            "billing",
            "connection"
          ]
        },
        {
          "id": "diChangeWait",
          "label": "종전 조건으로 이용",
          "kind": "screen",
          "note": "요청가격과 현재가격을 함께 표시합니다. 승인 대기만으로 기존 측정을 차단하지 않습니다.",
          "flowId": "diLifecycle",
          "policyRefs": [
            "price_approval",
            "cancellation",
            "billing",
            "connection"
          ]
        },
        {
          "id": "diChangeRejected",
          "label": "반려·종전 조건 유지",
          "kind": "screen",
          "note": "사유를 보고 수정·재요청하거나 요청을 철회합니다.",
          "flowId": "diLifecycle",
          "policyRefs": [
            "price_approval",
            "cancellation",
            "billing",
            "connection"
          ]
        },
        {
          "id": "diScheduleChange",
          "label": "다음 달 변경 예약",
          "kind": "action",
          "note": "승인·자동 승인된 플랜과 가격을 다음 달 1일부터 적용합니다.",
          "flowId": "diLifecycle",
          "policyRefs": [
            "price_approval",
            "cancellation",
            "billing",
            "connection"
          ]
        },
        {
          "id": "diCancel",
          "label": "취소일 입력",
          "kind": "screen",
          "note": "취소 효력일·사유를 입력합니다. 당일부터 사용 중단할 날짜임을 확인합니다.",
          "flowId": "diLifecycle",
          "policyRefs": [
            "price_approval",
            "cancellation",
            "billing",
            "connection"
          ]
        },
        {
          "id": "diCancelValid",
          "label": "취소일이 유효한가?",
          "kind": "decision",
          "note": "필수 날짜·오늘 이후·현재 이용 상태를 검증합니다. 과거 소급 취소는 불가합니다.",
          "flowId": "diLifecycle",
          "policyRefs": [
            "price_approval",
            "cancellation",
            "billing",
            "connection"
          ]
        },
        {
          "id": "diCancelScheduled",
          "label": "취소 예약",
          "kind": "action",
          "note": "효력일까지 사용 후 해당 날짜 00:00부터 중단합니다. 이후 자동 갱신을 중지합니다.",
          "flowId": "diLifecycle",
          "policyRefs": [
            "price_approval",
            "cancellation",
            "billing",
            "connection"
          ]
        },
        {
          "id": "diCancelWithdraw",
          "label": "취소 예약 철회",
          "kind": "action",
          "note": "효력일 도래 전만 철회하고 자동 갱신을 유지합니다.",
          "flowId": "diLifecycle",
          "policyRefs": [
            "price_approval",
            "cancellation",
            "billing",
            "connection"
          ]
        },
        {
          "id": "diConnectionSupport",
          "label": "연결 해제·코드 재발급",
          "kind": "action",
          "note": "자신의 시설 라이선스만 공통 재발급 흐름으로 처리합니다.",
          "flowId": "diLifecycle",
          "policyRefs": [
            "price_approval",
            "cancellation",
            "billing",
            "connection"
          ]
        },
        {
          "id": "diNewPeriodReceipt",
          "label": "갱신기간 입금 확인",
          "kind": "screen",
          "note": "자동 생성된 새달 대상기간의 외부 입금을 확인합니다. 입금 확인 전에는 측정 대기입니다.",
          "flowId": "diLifecycle",
          "policyRefs": [
            "price_approval",
            "cancellation",
            "billing",
            "connection"
          ]
        },
        {
          "id": "diPeriodKind",
          "label": "최초 이용기간인가?",
          "kind": "decision",
          "note": "입금 확인은 새 이용 시작과 기존 갱신기간의 재개를 구분합니다. 갱신 입금으로 첫 이용기간이나 3000회를 다시 생성하지 않습니다.",
          "flowId": "diActivate",
          "policyRefs": [
            "activation",
            "billing",
            "quota"
          ]
        },
        {
          "id": "diResumePeriod",
          "label": "기존 기간 입금 상태 갱신",
          "kind": "action",
          "note": "확인한 대상기간의 입금 상태만 갱신합니다. 기존 사용량·기간·한도를 보존하며 종료·취소된 기간을 되살리지 않습니다.",
          "flowId": "diActivate",
          "policyRefs": [
            "activation",
            "billing",
            "quota"
          ]
        }
      ],
      "edges": [
        {
          "id": "distributorEdge001",
          "source": "diInviteAccept",
          "target": "diDashboard",
          "label": "수락 완료",
          "when": "로그인 접근 활성화"
        },
        {
          "id": "distributorEdge002",
          "source": "diDashboard",
          "target": "diFacilities",
          "label": "시설 관리",
          "when": "메뉴 선택"
        },
        {
          "id": "distributorEdge003",
          "source": "diFacilities",
          "target": "diFacilityCreate",
          "label": "시설 추가",
          "when": "생성 또는 관리자 추가 초대"
        },
        {
          "id": "distributorEdge004",
          "source": "diFacilityCreate",
          "target": "diFacilityInviteState",
          "label": "초대 발송",
          "when": "시설 레코드 생성 완료"
        },
        {
          "id": "distributorEdge005",
          "source": "diFacilityInviteState",
          "target": "diFacilities",
          "label": "수락",
          "when": "시설 관리자 접근 활성화"
        },
        {
          "id": "distributorEdge006",
          "source": "diFacilityInviteState",
          "target": "diFacilityResend",
          "label": "미수락",
          "when": "재발송·정정 필요"
        },
        {
          "id": "distributorEdge007",
          "source": "diFacilityInviteState",
          "target": "diFacilities",
          "label": "거절·취소",
          "when": "시설 보존·관리자 접근 미발급"
        },
        {
          "id": "distributorEdge008",
          "source": "diFacilityResend",
          "target": "diFacilityInviteState",
          "label": "다시 초대",
          "when": "수정된 정보로 초대"
        },
        {
          "id": "distributorEdge009",
          "source": "diDashboard",
          "target": "diLicenses",
          "label": "라이선스",
          "when": "메뉴 선택"
        },
        {
          "id": "distributorEdge010",
          "source": "diLicenses",
          "target": "diCreateForm",
          "label": "등록",
          "when": "새 라이선스 생성"
        },
        {
          "id": "distributorEdge011",
          "source": "diCreateForm",
          "target": "diFormValid",
          "label": "등록 요청",
          "when": "폼 제출"
        },
        {
          "id": "distributorEdge012",
          "source": "diFormValid",
          "target": "diFormError",
          "label": "아니오",
          "when": "필드 검증 실패"
        },
        {
          "id": "distributorEdge013",
          "source": "diFormError",
          "target": "diCreateForm",
          "label": "다시 입력",
          "when": "기존 입력 유지"
        },
        {
          "id": "distributorEdge014",
          "source": "diFormValid",
          "target": "diCreateEach",
          "label": "예",
          "when": "중복 요청 아님"
        },
        {
          "id": "distributorEdge015",
          "source": "diCreateEach",
          "target": "diBasePrice",
          "label": "가격 확인",
          "when": "라이선스별 판단"
        },
        {
          "id": "distributorEdge016",
          "source": "diBasePrice",
          "target": "diAutoApprove",
          "label": "같음",
          "when": "표준 월 이용료"
        },
        {
          "id": "distributorEdge017",
          "source": "diBasePrice",
          "target": "diRequestApproval",
          "label": "다름",
          "when": "할인·할증"
        },
        {
          "id": "distributorEdge018",
          "source": "diRequestApproval",
          "target": "diApprovalResult",
          "label": "요청 완료",
          "when": "슈퍼어드민 검토"
        },
        {
          "id": "distributorEdge019",
          "source": "diApprovalResult",
          "target": "diApprovalWait",
          "label": "대기",
          "when": "아직 처리 안 됨"
        },
        {
          "id": "distributorEdge020",
          "source": "diApprovalWait",
          "target": "diApprovalResult",
          "label": "결과 갱신",
          "when": "처리 이벤트 또는 재조회"
        },
        {
          "id": "distributorEdge021",
          "source": "diApprovalResult",
          "target": "diRejected",
          "label": "반려",
          "when": "사유 표시"
        },
        {
          "id": "distributorEdge022",
          "source": "diRejected",
          "target": "diRequestApproval",
          "label": "수정·재요청",
          "when": "새 요청 버전"
        },
        {
          "id": "distributorEdge023",
          "source": "diRejected",
          "target": "diDraftCancel",
          "label": "등록 취소",
          "when": "신규 계약 미활성"
        },
        {
          "id": "distributorEdge024",
          "source": "diApprovalResult",
          "target": "diDraftCancel",
          "label": "요청 취소",
          "when": "진행 철회"
        },
        {
          "id": "distributorEdge025",
          "source": "diDraftCancel",
          "target": "diLicenses",
          "label": "완료",
          "when": "취소 상태 표시"
        },
        {
          "id": "distributorEdge026",
          "source": "diAutoApprove",
          "target": "diContract",
          "label": "계약 진행",
          "when": "자동 승인 완료"
        },
        {
          "id": "distributorEdge027",
          "source": "diApprovalResult",
          "target": "diContract",
          "label": "승인",
          "when": "변경가격 승인 완료"
        },
        {
          "id": "distributorEdge028",
          "source": "diContract",
          "target": "diContractState",
          "label": "확인",
          "when": "계약 정보 입력"
        },
        {
          "id": "distributorEdge029",
          "source": "diContractState",
          "target": "diContractWait",
          "label": "아니오",
          "when": "계약 진행 중"
        },
        {
          "id": "distributorEdge030",
          "source": "diContractWait",
          "target": "diContract",
          "label": "보완",
          "when": "계약 완료 후 확인"
        },
        {
          "id": "distributorEdge031",
          "source": "diContractWait",
          "target": "diDraftCancel",
          "label": "계약 취소",
          "when": "첫 이용 미활성"
        },
        {
          "id": "distributorEdge032",
          "source": "diContractState",
          "target": "diReceipt",
          "label": "예",
          "when": "외부 입금 확인으로"
        },
        {
          "id": "distributorEdge033",
          "source": "diReceipt",
          "target": "diReceiptValid",
          "label": "확인 등록",
          "when": "중복·대상기간 검증"
        },
        {
          "id": "distributorEdge034",
          "source": "diReceiptValid",
          "target": "diReceiptWait",
          "label": "아니오",
          "when": "미입금 또는 보완 필요"
        },
        {
          "id": "distributorEdge035",
          "source": "diReceiptWait",
          "target": "diReceipt",
          "label": "다시 확인",
          "when": "정보·입금 보완"
        },
        {
          "id": "distributorEdge036",
          "source": "diActivate",
          "target": "diLicenses",
          "label": "완료",
          "when": "측정 가능 조건 갱신"
        },
        {
          "id": "distributorEdge037",
          "source": "diLicenses",
          "target": "diDetail",
          "label": "상세",
          "when": "라이선스 선택"
        },
        {
          "id": "distributorEdge038",
          "source": "diDetail",
          "target": "diLicenses",
          "label": "목록으로",
          "when": "상세 닫기"
        },
        {
          "id": "distributorEdge039",
          "source": "diDetail",
          "target": "diChange",
          "label": "조건 변경",
          "when": "기존 라이선스"
        },
        {
          "id": "distributorEdge040",
          "source": "diChange",
          "target": "diChangeBase",
          "label": "요청",
          "when": "새 플랜 가격 비교"
        },
        {
          "id": "distributorEdge041",
          "source": "diChangeBase",
          "target": "diScheduleChange",
          "label": "같음",
          "when": "자동 승인"
        },
        {
          "id": "distributorEdge042",
          "source": "diChangeBase",
          "target": "diChangeReview",
          "label": "다름",
          "when": "슈퍼어드민 요청"
        },
        {
          "id": "distributorEdge043",
          "source": "diChangeReview",
          "target": "diChangeWait",
          "label": "대기",
          "when": "종전 조건 유지"
        },
        {
          "id": "distributorEdge044",
          "source": "diChangeWait",
          "target": "diChangeReview",
          "label": "결과 갱신",
          "when": "승인 알림 수신"
        },
        {
          "id": "distributorEdge045",
          "source": "diChangeReview",
          "target": "diChangeRejected",
          "label": "반려",
          "when": "사유 기록"
        },
        {
          "id": "distributorEdge046",
          "source": "diChangeRejected",
          "target": "diChange",
          "label": "수정·재요청",
          "when": "변경 의사 유지"
        },
        {
          "id": "distributorEdge047",
          "source": "diChangeRejected",
          "target": "diDetail",
          "label": "철회",
          "when": "현재 조건 유지"
        },
        {
          "id": "distributorEdge048",
          "source": "diChangeReview",
          "target": "diScheduleChange",
          "label": "승인",
          "when": "다음 달 적용"
        },
        {
          "id": "distributorEdge049",
          "source": "diScheduleChange",
          "target": "diDetail",
          "label": "완료",
          "when": "변경 예정 표시"
        },
        {
          "id": "distributorEdge050",
          "source": "diDetail",
          "target": "diCancel",
          "label": "이용 취소",
          "when": "취소 예약 선택"
        },
        {
          "id": "distributorEdge051",
          "source": "diCancel",
          "target": "diCancelValid",
          "label": "저장",
          "when": "효력일 검증"
        },
        {
          "id": "distributorEdge052",
          "source": "diCancelValid",
          "target": "diCancel",
          "label": "아니오",
          "when": "오류 표시"
        },
        {
          "id": "distributorEdge053",
          "source": "diCancelValid",
          "target": "diCancelScheduled",
          "label": "예",
          "when": "취소 예약 확정"
        },
        {
          "id": "distributorEdge054",
          "source": "diCancelScheduled",
          "target": "diDetail",
          "label": "완료",
          "when": "취소 예정 표시"
        },
        {
          "id": "distributorEdge055",
          "source": "diDetail",
          "target": "diCancelWithdraw",
          "label": "취소 철회",
          "when": "효력일 이전"
        },
        {
          "id": "distributorEdge056",
          "source": "diCancelWithdraw",
          "target": "diDetail",
          "label": "완료",
          "when": "갱신 유지"
        },
        {
          "id": "distributorEdge057",
          "source": "diDetail",
          "target": "diConnectionSupport",
          "label": "연결 지원",
          "when": "대상·권한 확인"
        },
        {
          "id": "distributorEdge058",
          "source": "diConnectionSupport",
          "target": "diDetail",
          "label": "완료",
          "when": "코드/연결 상태 갱신"
        },
        {
          "id": "distributorEdge059",
          "source": "diDetail",
          "target": "diNewPeriodReceipt",
          "label": "입금 확인",
          "when": "갱신기간 미확인"
        },
        {
          "id": "distributorEdge060",
          "source": "diNewPeriodReceipt",
          "target": "diReceipt",
          "label": "확인 등록",
          "when": "선택한 갱신기간을 그대로 전달"
        },
        {
          "id": "distributorEdge061",
          "source": "diReceiptValid",
          "target": "diPeriodKind",
          "label": "예",
          "when": "승인·계약·대상기간 입금 충족"
        },
        {
          "id": "distributorEdge062",
          "source": "diPeriodKind",
          "target": "diActivate",
          "label": "예",
          "when": "아직 첫 이용을 시작하지 않은 라이선스"
        },
        {
          "id": "distributorEdge063",
          "source": "diPeriodKind",
          "target": "diResumePeriod",
          "label": "아니오",
          "when": "이미 생성된 갱신 또는 과거 이용기간"
        },
        {
          "id": "distributorEdge064",
          "source": "diResumePeriod",
          "target": "diLicenses",
          "label": "완료",
          "when": "기존 기간 상태만 갱신"
        }
      ]
    },
    {
      "role": "facility",
      "title": "시설 관리자",
      "description": "시설 운영 표, 입소자·동의·결과·후속조치, 디바이스와 라이선스를 관리합니다. 관리자 PC에서 측정하지 않습니다.",
      "surface": "관리자 PC",
      "entryNodeIds": [
        "faInviteAccept",
        "faResidents",
        "faFollowups"
      ],
      "flows": [
        {
          "id": "faOnboarding",
          "title": "시설 시작",
          "entryNodeId": "faInviteAccept",
          "purpose": "시설 관리자 접근과 디바이스 운영 준비"
        },
        {
          "id": "faResidents",
          "title": "입소자와 동의",
          "entryNodeId": "faResidents",
          "purpose": "입소자 등록·정보 수정·철회·퇴소"
        },
        {
          "id": "faFollowup",
          "title": "후속조치",
          "entryNodeId": "faFollowups",
          "purpose": "이상치 후속조치와 재측정 필요 상태를 구분"
        }
      ],
      "nodes": [
        {
          "id": "faInviteAccept",
          "label": "시설 초대 수락",
          "kind": "screen",
          "note": "디스트리뷰터가 초대한 시설에 비밀번호를 설정해 합류합니다.",
          "flowId": "faOnboarding",
          "policyRefs": [
            "accounts",
            "modes",
            "operations",
            "connection"
          ]
        },
        {
          "id": "faHome",
          "label": "오늘 운영 표",
          "kind": "screen",
          "note": "모든 재원 입소자의 오늘 상태·마지막 유효 측정일시와 지표·후속조치를 한 표에서 봅니다.",
          "flowId": "faOnboarding",
          "policyRefs": [
            "accounts",
            "modes",
            "operations",
            "connection"
          ]
        },
        {
          "id": "faSetup",
          "label": "측정모드 비밀번호 설정",
          "kind": "screen",
          "note": "시설 공통 비밀번호를 설정·변경합니다. 라이선스 코드와 별개입니다. 변경 시 기존 측정모드 접근을 잠급니다.",
          "flowId": "faOnboarding",
          "policyRefs": [
            "accounts",
            "modes",
            "operations",
            "connection"
          ]
        },
        {
          "id": "faLicenses",
          "label": "시설 라이선스",
          "kind": "screen",
          "note": "플랜·현재가격·기간·연결·잔여 횟수·승인/입금 상태를 라이선스별로 봅니다. 가격·계약 수정은 디스트리뷰터가 수행합니다.",
          "flowId": "faOnboarding",
          "policyRefs": [
            "accounts",
            "modes",
            "operations",
            "connection"
          ]
        },
        {
          "id": "faDevices",
          "label": "디바이스 관리",
          "kind": "screen",
          "note": "시설의 디바이스명·위치·연결 라이선스·연결 여부·측정 가능 여부를 표시합니다.",
          "flowId": "faOnboarding",
          "policyRefs": [
            "accounts",
            "modes",
            "operations",
            "connection"
          ]
        },
        {
          "id": "faAddDevice",
          "label": "디바이스 추가·코드 확인",
          "kind": "screen",
          "note": "이름·위치를 입력하거나 첫 연결 시 자동 생성합니다. 미연결 라이선스 코드를 디바이스에서 입력하도록 안내합니다.",
          "flowId": "faOnboarding",
          "policyRefs": [
            "accounts",
            "modes",
            "operations",
            "connection"
          ]
        },
        {
          "id": "faDeviceAction",
          "label": "연결 해제·코드 재발급",
          "kind": "action",
          "note": "기존 연결이 해제됨을 확인한 뒤 공통 재발급 흐름을 실행합니다. 횟수와 기간은 보존합니다.",
          "flowId": "faOnboarding",
          "policyRefs": [
            "accounts",
            "modes",
            "operations",
            "connection"
          ]
        },
        {
          "id": "faDistributorRequest",
          "label": "디스트리뷰터에게 요청",
          "kind": "screen",
          "note": "새 라이선스·취소일·입금 확인·요금 문의를 소속 디스트리뷰터에게 요청하고 처리 상태를 표시합니다.",
          "flowId": "faOnboarding",
          "policyRefs": [
            "accounts",
            "modes",
            "operations",
            "connection"
          ]
        },
        {
          "id": "faResidents",
          "label": "입소자 관리",
          "kind": "screen",
          "note": "재원·퇴소·동의 상태로 구분합니다. 오늘 표와 같은 입소자 ID를 사용합니다.",
          "flowId": "faResidents",
          "policyRefs": [
            "residents",
            "operations"
          ]
        },
        {
          "id": "faResidentForm",
          "label": "입소자 등록·정보 수정",
          "kind": "screen",
          "note": "이름·생년월일·성별·위치와 동의를 입력합니다. 입소자 로그인은 만들지 않습니다.",
          "flowId": "faResidents",
          "policyRefs": [
            "residents",
            "operations"
          ]
        },
        {
          "id": "faDuplicate",
          "label": "중복 후보가 있는가?",
          "kind": "decision",
          "note": "이름·생년월일이 같아도 자동 병합하지 않습니다.",
          "flowId": "faResidents",
          "policyRefs": [
            "residents",
            "operations"
          ]
        },
        {
          "id": "faDuplicateReview",
          "label": "기존 입소자 확인",
          "kind": "screen",
          "note": "위치·식별정보를 확인해 기존 입소자를 선택하거나 별도 입소자로 등록합니다.",
          "flowId": "faResidents",
          "policyRefs": [
            "residents",
            "operations"
          ]
        },
        {
          "id": "faConsent",
          "label": "동의 기록",
          "kind": "screen",
          "note": "측정·저장 동의와 선택 생체 식별 동의를 구분합니다. 동의 미완료도 입소자 기본정보 등록은 가능합니다.",
          "flowId": "faResidents",
          "policyRefs": [
            "residents",
            "operations"
          ]
        },
        {
          "id": "faResidentSaved",
          "label": "입소자 저장",
          "kind": "action",
          "note": "동의 완료는 측정 가능, 미완료는 오늘 표의 동의 필요 상태로 표시합니다.",
          "flowId": "faResidents",
          "policyRefs": [
            "residents",
            "operations"
          ]
        },
        {
          "id": "faProfile",
          "label": "입소자 상세",
          "kind": "screen",
          "note": "마지막 유효 결과·과거 이력·동의·후속조치를 확인합니다. 관리자 PC에는 측정 시작 버튼이 없습니다.",
          "flowId": "faResidents",
          "policyRefs": [
            "residents",
            "operations"
          ]
        },
        {
          "id": "faResidentExit",
          "label": "동의 철회·퇴소 처리",
          "kind": "screen",
          "note": "확인 후 새 측정과 오늘 대상에서 제외합니다. 기록을 즉시 삭제하지 않습니다.",
          "flowId": "faResidents",
          "policyRefs": [
            "residents",
            "operations"
          ]
        },
        {
          "id": "faPrivacyRequest",
          "label": "개인정보 처리 요청",
          "kind": "action",
          "note": "별도 삭제 요청이 있으면 슈퍼어드민에 근거·범위를 전달하고 완료 상태를 표시합니다.",
          "flowId": "faResidents",
          "policyRefs": [
            "residents",
            "operations"
          ]
        },
        {
          "id": "faExport",
          "label": "현재 범위 내보내기",
          "kind": "action",
          "note": "선택한 시설·입소자·기간에 대한 실제 결과와 측정일시를 내보냅니다. 코드·비밀번호는 포함하지 않습니다.",
          "flowId": "faResidents",
          "policyRefs": [
            "residents",
            "operations"
          ]
        },
        {
          "id": "faFollowups",
          "label": "후속조치 대상",
          "kind": "screen",
          "note": "유효 측정의 이상치가 있는 입소자와 해당 결과·일시를 보여줍니다. 품질 미달 목록과 구분합니다.",
          "flowId": "faFollowup",
          "policyRefs": [
            "followup",
            "operations"
          ]
        },
        {
          "id": "faFollowupDetail",
          "label": "측정 결과·조치 확인",
          "kind": "screen",
          "note": "발생한 지표와 판정 근거 버전을 확인합니다. 담당자는 실제 조치를 선택하고 기록합니다.",
          "flowId": "faFollowup",
          "policyRefs": [
            "followup",
            "operations"
          ]
        },
        {
          "id": "faFollowupAction",
          "label": "후속조치 기록",
          "kind": "screen",
          "note": "결과 확인·디바이스 재측정·별도 측정 확인 등의 조치와 메모를 입력합니다.",
          "flowId": "faFollowup",
          "policyRefs": [
            "followup",
            "operations"
          ]
        },
        {
          "id": "faFollowupDone",
          "label": "후속조치를 완료했는가?",
          "kind": "decision",
          "note": "단순히 화면을 열거나 새 정상값이 생겼다고 자동 완료하지 않습니다.",
          "flowId": "faFollowup",
          "policyRefs": [
            "followup",
            "operations"
          ]
        },
        {
          "id": "faFollowupClosed",
          "label": "완료 기록",
          "kind": "action",
          "note": "조치·시점·입력 주체를 기록하고 대기 목록에서 제외합니다.",
          "flowId": "faFollowup",
          "policyRefs": [
            "followup",
            "operations"
          ]
        },
        {
          "id": "faRetestList",
          "label": "재측정 필요",
          "kind": "screen",
          "note": "품질 미달·분석 불가 입소자를 표시합니다. 실제 재측정은 디바이스에서 수행합니다.",
          "flowId": "faFollowup",
          "policyRefs": [
            "followup",
            "operations"
          ]
        }
      ],
      "edges": [
        {
          "id": "facilityEdge001",
          "source": "faInviteAccept",
          "target": "faHome",
          "label": "수락 완료",
          "when": "시설 접근 활성화"
        },
        {
          "id": "facilityEdge002",
          "source": "faHome",
          "target": "faSetup",
          "label": "운영 준비",
          "when": "최초 설정 또는 변경"
        },
        {
          "id": "facilityEdge003",
          "source": "faSetup",
          "target": "faHome",
          "label": "저장",
          "when": "비밀번호 설정 완료"
        },
        {
          "id": "facilityEdge004",
          "source": "faHome",
          "target": "faLicenses",
          "label": "라이선스",
          "when": "메뉴 선택"
        },
        {
          "id": "facilityEdge005",
          "source": "faLicenses",
          "target": "faHome",
          "label": "운영 표로",
          "when": "목록 복귀"
        },
        {
          "id": "facilityEdge006",
          "source": "faHome",
          "target": "faDevices",
          "label": "디바이스",
          "when": "메뉴 선택"
        },
        {
          "id": "facilityEdge007",
          "source": "faDevices",
          "target": "faAddDevice",
          "label": "추가·연결",
          "when": "미연결 디바이스 준비"
        },
        {
          "id": "facilityEdge008",
          "source": "faAddDevice",
          "target": "faDevices",
          "label": "연결 상태 확인",
          "when": "디바이스에서 코드 입력"
        },
        {
          "id": "facilityEdge009",
          "source": "faDevices",
          "target": "faDeviceAction",
          "label": "해제·재발급",
          "when": "관리자 작업 선택"
        },
        {
          "id": "facilityEdge010",
          "source": "faDeviceAction",
          "target": "faDevices",
          "label": "완료",
          "when": "연결 상태 갱신"
        },
        {
          "id": "facilityEdge011",
          "source": "faDevices",
          "target": "faHome",
          "label": "운영 표로",
          "when": "관리 종료"
        },
        {
          "id": "facilityEdge012",
          "source": "faLicenses",
          "target": "faDistributorRequest",
          "label": "요청",
          "when": "추가·변경·취소 문의"
        },
        {
          "id": "facilityEdge013",
          "source": "faDistributorRequest",
          "target": "faLicenses",
          "label": "접수·상태 확인",
          "when": "처리 알림 수신"
        },
        {
          "id": "facilityEdge014",
          "source": "faHome",
          "target": "faResidents",
          "label": "입소자",
          "when": "메뉴 선택"
        },
        {
          "id": "facilityEdge015",
          "source": "faResidents",
          "target": "faResidentForm",
          "label": "등록·수정",
          "when": "입소자 정보 입력"
        },
        {
          "id": "facilityEdge016",
          "source": "faResidentForm",
          "target": "faDuplicate",
          "label": "저장 요청",
          "when": "필수정보 검증 후"
        },
        {
          "id": "facilityEdge017",
          "source": "faDuplicate",
          "target": "faDuplicateReview",
          "label": "있음",
          "when": "후보 확인 필요"
        },
        {
          "id": "facilityEdge018",
          "source": "faDuplicateReview",
          "target": "faConsent",
          "label": "확정",
          "when": "기존 선택 또는 별도 등록"
        },
        {
          "id": "facilityEdge019",
          "source": "faDuplicate",
          "target": "faConsent",
          "label": "없음",
          "when": "새 입소자"
        },
        {
          "id": "facilityEdge020",
          "source": "faConsent",
          "target": "faResidentSaved",
          "label": "동의 상태 저장",
          "when": "완료·미완료 모두 상태 보존"
        },
        {
          "id": "facilityEdge021",
          "source": "faResidentSaved",
          "target": "faHome",
          "label": "완료",
          "when": "운영 표 갱신"
        },
        {
          "id": "facilityEdge022",
          "source": "faResidents",
          "target": "faProfile",
          "label": "상세",
          "when": "입소자 선택"
        },
        {
          "id": "facilityEdge023",
          "source": "faHome",
          "target": "faProfile",
          "label": "행 선택",
          "when": "오늘 표에서 상세"
        },
        {
          "id": "facilityEdge024",
          "source": "faProfile",
          "target": "faResidentForm",
          "label": "정보·동의 수정",
          "when": "관리 작업"
        },
        {
          "id": "facilityEdge025",
          "source": "faProfile",
          "target": "faResidentExit",
          "label": "철회·퇴소",
          "when": "확인 절차"
        },
        {
          "id": "facilityEdge026",
          "source": "faResidentExit",
          "target": "faResidents",
          "label": "처리 완료",
          "when": "재원 목록에서 제외"
        },
        {
          "id": "facilityEdge027",
          "source": "faResidentExit",
          "target": "faPrivacyRequest",
          "label": "삭제 요청",
          "when": "별도 요청이 있는 경우"
        },
        {
          "id": "facilityEdge028",
          "source": "faPrivacyRequest",
          "target": "faResidents",
          "label": "접수·상태 확인",
          "when": "슈퍼어드민 처리"
        },
        {
          "id": "facilityEdge029",
          "source": "faProfile",
          "target": "faResidents",
          "label": "목록으로",
          "when": "상세 닫기"
        },
        {
          "id": "facilityEdge030",
          "source": "faResidents",
          "target": "faExport",
          "label": "내보내기",
          "when": "범위 확인"
        },
        {
          "id": "facilityEdge031",
          "source": "faExport",
          "target": "faResidents",
          "label": "완료",
          "when": "파일 내려받기"
        },
        {
          "id": "facilityEdge032",
          "source": "faHome",
          "target": "faFollowups",
          "label": "후속조치",
          "when": "대기 상태 선택"
        },
        {
          "id": "facilityEdge033",
          "source": "faFollowups",
          "target": "faFollowupDetail",
          "label": "대상 선택",
          "when": "해당 유효 결과 확인"
        },
        {
          "id": "facilityEdge034",
          "source": "faFollowupDetail",
          "target": "faFollowupAction",
          "label": "조치 기록",
          "when": "수행한 행동 입력"
        },
        {
          "id": "facilityEdge035",
          "source": "faFollowupAction",
          "target": "faFollowupDone",
          "label": "저장",
          "when": "완료 여부 확인"
        },
        {
          "id": "facilityEdge036",
          "source": "faFollowupDone",
          "target": "faFollowupClosed",
          "label": "완료",
          "when": "실제 조치·메모 확정"
        },
        {
          "id": "facilityEdge037",
          "source": "faFollowupDone",
          "target": "faFollowups",
          "label": "대기 유지",
          "when": "추가 조치 필요"
        },
        {
          "id": "facilityEdge038",
          "source": "faFollowupClosed",
          "target": "faHome",
          "label": "완료",
          "when": "운영 표 갱신"
        },
        {
          "id": "facilityEdge039",
          "source": "faHome",
          "target": "faRetestList",
          "label": "재측정 필요",
          "when": "오늘 품질 미달 선택"
        },
        {
          "id": "facilityEdge040",
          "source": "faRetestList",
          "target": "faHome",
          "label": "운영 표로",
          "when": "디바이스에서 재측정 안내"
        }
      ]
    },
    {
      "role": "device",
      "title": "디바이스",
      "description": "라이선스 코드로 연결한 하나의 디바이스에서 측정모드와 거치모드를 제공합니다. 케어기버·입소자 개인 로그인은 없습니다.",
      "surface": "디바이스",
      "entryNodeIds": [
        "dvBoot",
        "dvModePassword",
        "dvGuestConsent",
        "dvPreflight",
        "dvGuestResult"
      ],
      "flows": [
        {
          "id": "dvConnection",
          "title": "디바이스 연결",
          "entryNodeId": "dvBoot",
          "purpose": "코드 입력·단일 연결·측정 가능 상태 판정"
        },
        {
          "id": "dvMode",
          "title": "모드 전환과 오늘 운영",
          "entryNodeId": "dvModePassword",
          "purpose": "측정모드 인증과 케어기버 운영"
        },
        {
          "id": "dvGuest",
          "title": "거치모드 측정 준비",
          "entryNodeId": "dvGuestConsent",
          "purpose": "일반 사용자 동의·얼굴 식별 실패의 비차단 처리"
        },
        {
          "id": "dvMeasure",
          "title": "공통 측정과 확정",
          "entryNodeId": "dvPreflight",
          "purpose": "두 모드의 공통 측정·품질·정확히 한 번 차감"
        },
        {
          "id": "dvGuestResult",
          "title": "일반 사용자 결과와 등록",
          "entryNodeId": "dvGuestResult",
          "purpose": "결과 확인 후 입소자 연결·등록·종료"
        }
      ],
      "nodes": [
        {
          "id": "dvBoot",
          "label": "디바이스 시작",
          "kind": "action",
          "note": "저장된 연결 자격을 서버에서 검증합니다. 처음 실행하면 코드 입력으로 이동합니다.",
          "flowId": "dvConnection",
          "policyRefs": [
            "connection",
            "measurement",
            "modes"
          ]
        },
        {
          "id": "dvOnline",
          "label": "온라인인가?",
          "kind": "decision",
          "note": "네트워크와 서비스 접속을 확인합니다.",
          "flowId": "dvConnection",
          "policyRefs": [
            "connection",
            "measurement",
            "modes"
          ]
        },
        {
          "id": "dvOffline",
          "label": "연결 확인 안내",
          "kind": "screen",
          "note": "온라인에서만 사용할 수 있습니다. 네트워크 설정 후 다시 확인합니다.",
          "flowId": "dvConnection",
          "policyRefs": [
            "connection",
            "measurement",
            "modes"
          ]
        },
        {
          "id": "dvBound",
          "label": "연결 자격이 유효한가?",
          "kind": "decision",
          "note": "재발급·해제·정지로 폐기된 자격은 다시 사용하지 않습니다.",
          "flowId": "dvConnection",
          "policyRefs": [
            "connection",
            "measurement",
            "modes"
          ]
        },
        {
          "id": "dvCode",
          "label": "라이선스 코드 입력",
          "kind": "screen",
          "note": "코드를 입력하면 시설과 라이선스를 자동 특정합니다. OTP나 시간 제한 안내를 표시하지 않습니다.",
          "flowId": "dvConnection",
          "policyRefs": [
            "connection",
            "measurement",
            "modes"
          ]
        },
        {
          "id": "dvCodeValid",
          "label": "코드가 유효한가?",
          "kind": "decision",
          "note": "존재·폐기 여부·연결 허용 상태를 확인합니다. 신규 승인·입금 대기는 코드 오류가 아닙니다.",
          "flowId": "dvConnection",
          "policyRefs": [
            "connection",
            "measurement",
            "modes"
          ]
        },
        {
          "id": "dvCodeError",
          "label": "코드 확인 안내",
          "kind": "screen",
          "note": "잘못되었거나 폐기된 코드입니다. 관리자에게 현재 코드를 확인하거나 재발급을 요청합니다.",
          "flowId": "dvConnection",
          "policyRefs": [
            "connection",
            "measurement",
            "modes"
          ]
        },
        {
          "id": "dvOccupied",
          "label": "다른 디바이스가 연결됐는가?",
          "kind": "decision",
          "note": "같은 디바이스는 복원합니다. 다른 디바이스면 기존 연결을 자동 해제하지 않습니다.",
          "flowId": "dvConnection",
          "policyRefs": [
            "connection",
            "measurement",
            "modes"
          ]
        },
        {
          "id": "dvOccupiedHelp",
          "label": "관리자 연결 해제 요청",
          "kind": "screen",
          "note": "시설 관리자·디스트리뷰터·슈퍼어드민이 기존 연결을 해제하거나 코드를 재발급할 수 있습니다.",
          "flowId": "dvConnection",
          "policyRefs": [
            "connection",
            "measurement",
            "modes"
          ]
        },
        {
          "id": "dvBind",
          "label": "시설·라이선스 연결",
          "kind": "action",
          "note": "디바이스 ID·시설·라이선스를 원자적으로 연결하고 해당 플랜 기능을 받습니다.",
          "flowId": "dvConnection",
          "policyRefs": [
            "connection",
            "measurement",
            "modes"
          ]
        },
        {
          "id": "dvServiceGate",
          "label": "측정 조건이 충족됐는가?",
          "kind": "decision",
          "note": "온라인·연결·시설 활성·신규 승인·계약·당월 입금·이용기간·잔여 횟수를 확인합니다. 기존 가격 변경 대기는 차단 사유가 아닙니다.",
          "flowId": "dvConnection",
          "policyRefs": [
            "connection",
            "measurement",
            "modes"
          ]
        },
        {
          "id": "dvRestriction",
          "label": "제한 사유 안내",
          "kind": "screen",
          "note": "승인 대기·계약/입금 대기·이용 종료·횟수 소진·정지·연결 오류별 해결 담당자와 다음 행동을 표시합니다.",
          "flowId": "dvConnection",
          "policyRefs": [
            "connection",
            "measurement",
            "modes"
          ]
        },
        {
          "id": "dvStandby",
          "label": "거치모드 대기",
          "kind": "screen",
          "note": "일반 사용자 측정 시작과 측정모드 전환 버튼을 제공합니다. 이전 개인 화면은 남기지 않습니다.",
          "flowId": "dvConnection",
          "policyRefs": [
            "connection",
            "measurement",
            "modes"
          ]
        },
        {
          "id": "dvModePassword",
          "label": "측정모드 비밀번호",
          "kind": "screen",
          "note": "시설 공통 측정모드 비밀번호를 입력합니다. 개인 이름·이메일은 묻지 않습니다.",
          "flowId": "dvMode",
          "policyRefs": [
            "modes",
            "residents",
            "operations"
          ]
        },
        {
          "id": "dvPasswordValid",
          "label": "비밀번호가 맞는가?",
          "kind": "decision",
          "note": "잘못된 입력·미설정·반복 실패 제한을 구분합니다.",
          "flowId": "dvMode",
          "policyRefs": [
            "modes",
            "residents",
            "operations"
          ]
        },
        {
          "id": "dvPasswordHelp",
          "label": "비밀번호 확인·설정 안내",
          "kind": "screen",
          "note": "오입력은 재입력, 미설정·분실은 시설 관리자에게 설정·변경을 요청합니다.",
          "flowId": "dvMode",
          "policyRefs": [
            "modes",
            "residents",
            "operations"
          ]
        },
        {
          "id": "dvWorkTable",
          "label": "측정모드 오늘 운영 표",
          "kind": "screen",
          "note": "입소자 등록·수정·동의·결과·후속조치·퇴소·내보내기를 제공합니다. 라이선스 조건 변경·코드 재발급은 관리자 PC에서 처리합니다.",
          "flowId": "dvMode",
          "policyRefs": [
            "modes",
            "residents",
            "operations"
          ]
        },
        {
          "id": "dvResidentOps",
          "label": "입소자·후속조치 관리",
          "kind": "screen",
          "note": "시설 관리자 운영과 같은 규칙을 사용합니다. 수행자는 로그인 사용자 대신 인증된 디바이스로 기록합니다.",
          "flowId": "dvMode",
          "policyRefs": [
            "modes",
            "residents",
            "operations"
          ]
        },
        {
          "id": "dvSelectResident",
          "label": "입소자 선택",
          "kind": "screen",
          "note": "이름·생년월일·위치로 대상을 확정합니다. 다른 시설의 입소자를 선택하지 않습니다.",
          "flowId": "dvMode",
          "policyRefs": [
            "modes",
            "residents",
            "operations"
          ]
        },
        {
          "id": "dvResidentConsent",
          "label": "유효 동의가 있는가?",
          "kind": "decision",
          "note": "동의와 재원 상태를 확인합니다. 생체 식별 동의가 없어도 수동 선택한 대상은 측정할 수 있습니다.",
          "flowId": "dvMode",
          "policyRefs": [
            "modes",
            "residents",
            "operations"
          ]
        },
        {
          "id": "dvConsentFix",
          "label": "동의 보완",
          "kind": "screen",
          "note": "적절한 동의 확인 근거를 등록합니다. 동의 거부·철회·퇴소는 측정 없이 운영 표로 돌아갑니다.",
          "flowId": "dvMode",
          "policyRefs": [
            "modes",
            "residents",
            "operations"
          ]
        },
        {
          "id": "dvLock",
          "label": "거치모드로 전환",
          "kind": "action",
          "note": "진행 중 세션은 먼저 종료하고 측정모드 접근 자격·입소자 화면을 지웁니다.",
          "flowId": "dvMode",
          "policyRefs": [
            "modes",
            "residents",
            "operations"
          ]
        },
        {
          "id": "dvGuestConsent",
          "label": "측정 안내·동의",
          "kind": "screen",
          "note": "측정과 임시 결과 표시를 안내합니다. 얼굴 식별을 사용할 경우 별도 동의를 확인합니다. 영구 저장은 결과 후 선택 등록 시 확인합니다.",
          "flowId": "dvGuest",
          "policyRefs": [
            "residents",
            "measurement"
          ]
        },
        {
          "id": "dvGuestConsentChoice",
          "label": "측정에 동의하는가?",
          "kind": "decision",
          "note": "측정 동의가 없으면 카메라 측정을 시작하지 않습니다.",
          "flowId": "dvGuest",
          "policyRefs": [
            "residents",
            "measurement"
          ]
        },
        {
          "id": "dvIdentify",
          "label": "얼굴 식별을 병행",
          "kind": "action",
          "note": "허용된 경우 기존 입소자 후보를 찾습니다. 실패·미동의여도 일반 사용자로 측정을 계속하며 지연시키지 않습니다.",
          "flowId": "dvGuest",
          "policyRefs": [
            "residents",
            "measurement"
          ]
        },
        {
          "id": "dvPreflight",
          "label": "측정 시작 조건 재확인",
          "kind": "decision",
          "note": "온라인·이용 상태·동의를 다시 검증하고 이번 세션의 횟수 예약을 확보합니다.",
          "flowId": "dvMeasure",
          "policyRefs": [
            "measurement",
            "quota",
            "followup"
          ]
        },
        {
          "id": "dvCapture",
          "label": "얼굴 위치 안내·측정",
          "kind": "screen",
          "note": "유효한 신호를 수집합니다. 식별 실패와 얼굴 미검출·움직임 등 신호 품질 문제를 구분합니다.",
          "flowId": "dvMeasure",
          "policyRefs": [
            "measurement",
            "quota",
            "followup"
          ]
        },
        {
          "id": "dvCaptureState",
          "label": "측정 진행 결과는?",
          "kind": "decision",
          "note": "분석 완료·사용자 중단·네트워크 단절·수집 오류를 분기합니다.",
          "flowId": "dvMeasure",
          "policyRefs": [
            "measurement",
            "quota",
            "followup"
          ]
        },
        {
          "id": "dvStop",
          "label": "측정 중단·예약 해제",
          "kind": "action",
          "note": "결과 미저장·미차감으로 종료합니다.",
          "flowId": "dvMeasure",
          "policyRefs": [
            "measurement",
            "quota",
            "followup"
          ]
        },
        {
          "id": "dvQuality",
          "label": "유효 측정인가?",
          "kind": "decision",
          "note": "품질 통과·유효 지표 존재를 분석 모듈 결과로 판정합니다.",
          "flowId": "dvMeasure",
          "policyRefs": [
            "measurement",
            "quota",
            "followup"
          ]
        },
        {
          "id": "dvInvalid",
          "label": "분석 불가·미차감",
          "kind": "screen",
          "note": "지표 숫자를 저장하지 않습니다. 환경을 확인하고 재측정을 안내합니다. 선택된 입소자는 재측정 필요 상태입니다.",
          "flowId": "dvMeasure",
          "policyRefs": [
            "measurement",
            "quota",
            "followup"
          ]
        },
        {
          "id": "dvRetryChoice",
          "label": "재측정할 것인가?",
          "kind": "decision",
          "note": "한 번 재측정도 실패하면 담당자 확인을 안내합니다. 이후 추가 시도는 새 시작 의사를 확인합니다.",
          "flowId": "dvMeasure",
          "policyRefs": [
            "measurement",
            "quota",
            "followup"
          ]
        },
        {
          "id": "dvCommit",
          "label": "결과 확정 요청",
          "kind": "action",
          "note": "같은 세션 ID로 유효 결과 확정과 1회 차감을 함께 요청합니다.",
          "flowId": "dvMeasure",
          "policyRefs": [
            "measurement",
            "quota",
            "followup"
          ]
        },
        {
          "id": "dvCommitState",
          "label": "확정 결과는?",
          "kind": "decision",
          "note": "확정 성공·처리 거부·응답 미확인을 구분합니다.",
          "flowId": "dvMeasure",
          "policyRefs": [
            "measurement",
            "quota",
            "followup"
          ]
        },
        {
          "id": "dvUnknown",
          "label": "서버 확정 여부 확인",
          "kind": "screen",
          "note": "응답 유실이면 네트워크 복구 후 같은 세션 ID로 조회합니다. 결과 확인 전 새 세션으로 중복 전송하지 않습니다.",
          "flowId": "dvMeasure",
          "policyRefs": [
            "measurement",
            "quota",
            "followup"
          ]
        },
        {
          "id": "dvNotSaved",
          "label": "결과 미저장 안내",
          "kind": "screen",
          "note": "이용 조건 변경·저장 오류 등으로 미확정이면 미차감입니다. 조건을 확인한 뒤 새 측정을 시작합니다.",
          "flowId": "dvMeasure",
          "policyRefs": [
            "measurement",
            "quota",
            "followup"
          ]
        },
        {
          "id": "dvResultRoute",
          "label": "측정모드인가?",
          "kind": "decision",
          "note": "같은 확정 결과를 현재 모드에 맞게 표시합니다.",
          "flowId": "dvMeasure",
          "policyRefs": [
            "measurement",
            "quota",
            "followup"
          ]
        },
        {
          "id": "dvCareResult",
          "label": "입소자 측정 결과",
          "kind": "screen",
          "note": "유효한 지표와 측정 품질을 보여줍니다. 이상치가 있으면 후속조치를 표시합니다.",
          "flowId": "dvMeasure",
          "policyRefs": [
            "measurement",
            "quota",
            "followup"
          ]
        },
        {
          "id": "dvReturn",
          "label": "현재 모드 대기로",
          "kind": "action",
          "note": "확정 여부에 따라 오늘 상태와 잔여 횟수를 갱신하고 개인 결과 화면을 정리합니다.",
          "flowId": "dvMeasure",
          "policyRefs": [
            "measurement",
            "quota",
            "followup"
          ]
        },
        {
          "id": "dvGuestResult",
          "label": "측정 결과",
          "kind": "screen",
          "note": "유효 측정은 이미 1회 차감했습니다. 얼굴 후보 확인·입소자 정보 등록·등록 없이 종료를 제공합니다.",
          "flowId": "dvGuestResult",
          "policyRefs": [
            "residents",
            "measurement",
            "quota"
          ]
        },
        {
          "id": "dvIdentityChoice",
          "label": "기존 입소자 확인 여부는?",
          "kind": "decision",
          "note": "얼굴 후보가 있어도 본인 확인 후 연결합니다. 후보 없음·잘못된 후보·확인 거부는 일반 사용자 경로입니다.",
          "flowId": "dvGuestResult",
          "policyRefs": [
            "residents",
            "measurement",
            "quota"
          ]
        },
        {
          "id": "dvAttachExisting",
          "label": "기존 입소자에 결과 연결",
          "kind": "action",
          "note": "동일 시설·유효 저장 동의·본인 확인을 검증해 같은 결과를 연결합니다. 추가 차감하지 않습니다.",
          "flowId": "dvGuestResult",
          "policyRefs": [
            "residents",
            "measurement",
            "quota"
          ]
        },
        {
          "id": "dvRegisterChoice",
          "label": "입소자 정보를 등록할까?",
          "kind": "decision",
          "note": "로그인 사용자 가입이 아닌 입소자 정보 등록입니다.",
          "flowId": "dvGuestResult",
          "policyRefs": [
            "residents",
            "measurement",
            "quota"
          ]
        },
        {
          "id": "dvRegister",
          "label": "입소자 정보·저장 동의",
          "kind": "screen",
          "note": "이름·생년월일 등과 저장 동의를 입력합니다. 생체 식별 등록은 별도 선택입니다.",
          "flowId": "dvGuestResult",
          "policyRefs": [
            "residents",
            "measurement",
            "quota"
          ]
        },
        {
          "id": "dvRegistrationValid",
          "label": "등록 가능한가?",
          "kind": "decision",
          "note": "필수정보·저장 동의·중복 후보를 검증합니다.",
          "flowId": "dvGuestResult",
          "policyRefs": [
            "residents",
            "measurement",
            "quota"
          ]
        },
        {
          "id": "dvDuplicateStaff",
          "label": "담당자 확인 요청",
          "kind": "screen",
          "note": "중복 후보는 공개 목록으로 노출하지 않습니다. 측정모드 비밀번호 인증 후 기존 연결 또는 별도 등록을 확정합니다.",
          "flowId": "dvGuestResult",
          "policyRefs": [
            "residents",
            "measurement",
            "quota"
          ]
        },
        {
          "id": "dvAttachNew",
          "label": "입소자 등록·결과 연결",
          "kind": "action",
          "note": "동일 측정 결과에 입소자 ID를 연결하고 오늘 상태·후속조치를 갱신합니다. 다시 차감하지 않습니다.",
          "flowId": "dvGuestResult",
          "policyRefs": [
            "residents",
            "measurement",
            "quota"
          ]
        },
        {
          "id": "dvDetailedResult",
          "label": "입소자 측정 결과",
          "kind": "screen",
          "note": "동의와 연결이 완료된 결과를 보여줍니다. 현재 세션 기준 지표·실제 이력·개인 기준선을 구분합니다.",
          "flowId": "dvGuestResult",
          "policyRefs": [
            "residents",
            "measurement",
            "quota"
          ]
        },
        {
          "id": "dvGuestExit",
          "label": "등록 없이 종료",
          "kind": "action",
          "note": "임시 얼굴·이름 후보·개인 결과를 제거합니다. 비식별 세션·차감 기록은 중복 방지와 이용 현황을 위해 유지합니다.",
          "flowId": "dvGuestResult",
          "policyRefs": [
            "residents",
            "measurement",
            "quota"
          ]
        },
        {
          "id": "dvAttachState",
          "label": "입소자 연결이 저장됐는가?",
          "kind": "decision",
          "note": "기존·신규 입소자 연결 작업 결과를 확인합니다. 측정 결과와 차감은 이미 확정되어 있습니다.",
          "flowId": "dvGuestResult",
          "policyRefs": [
            "residents",
            "measurement"
          ]
        },
        {
          "id": "dvAttachError",
          "label": "등록·연결 실패 안내",
          "kind": "screen",
          "note": "동의·대상 확인 또는 네트워크를 보완한 뒤 같은 결과 연결을 다시 시도하거나 등록 없이 종료합니다. 재측정과 추가 차감은 하지 않습니다.",
          "flowId": "dvGuestResult",
          "policyRefs": [
            "residents",
            "measurement"
          ]
        }
      ],
      "edges": [
        {
          "id": "deviceEdge001",
          "source": "dvBoot",
          "target": "dvOnline",
          "label": "접속 확인",
          "when": "시작 또는 재시작"
        },
        {
          "id": "deviceEdge002",
          "source": "dvOnline",
          "target": "dvOffline",
          "label": "아니오",
          "when": "오프라인·서버 접속 실패"
        },
        {
          "id": "deviceEdge003",
          "source": "dvOffline",
          "target": "dvOnline",
          "label": "다시 확인",
          "when": "연결 복구"
        },
        {
          "id": "deviceEdge004",
          "source": "dvOnline",
          "target": "dvBound",
          "label": "예",
          "when": "서버 접근 가능"
        },
        {
          "id": "deviceEdge005",
          "source": "dvBound",
          "target": "dvCode",
          "label": "아니오",
          "when": "미연결·자격 폐기"
        },
        {
          "id": "deviceEdge006",
          "source": "dvBound",
          "target": "dvServiceGate",
          "label": "예",
          "when": "현재 자격 확인"
        },
        {
          "id": "deviceEdge007",
          "source": "dvCode",
          "target": "dvCodeValid",
          "label": "연결",
          "when": "코드 제출"
        },
        {
          "id": "deviceEdge008",
          "source": "dvCodeValid",
          "target": "dvCodeError",
          "label": "아니오",
          "when": "오류 코드"
        },
        {
          "id": "deviceEdge009",
          "source": "dvCodeError",
          "target": "dvCode",
          "label": "다시 입력",
          "when": "현재 코드 확인"
        },
        {
          "id": "deviceEdge010",
          "source": "dvCodeValid",
          "target": "dvOccupied",
          "label": "예",
          "when": "코드의 연결 상태 확인"
        },
        {
          "id": "deviceEdge011",
          "source": "dvOccupied",
          "target": "dvOccupiedHelp",
          "label": "예",
          "when": "다른 디바이스에 연결됨"
        },
        {
          "id": "deviceEdge012",
          "source": "dvOccupiedHelp",
          "target": "dvCode",
          "label": "처리 후 재입력",
          "when": "관리자가 해제·재발급"
        },
        {
          "id": "deviceEdge013",
          "source": "dvOccupied",
          "target": "dvBind",
          "label": "아니오",
          "when": "미연결 또는 동일 디바이스"
        },
        {
          "id": "deviceEdge014",
          "source": "dvBind",
          "target": "dvServiceGate",
          "label": "연결 완료",
          "when": "연결 상태와 서비스 상태 분리"
        },
        {
          "id": "deviceEdge015",
          "source": "dvServiceGate",
          "target": "dvRestriction",
          "label": "아니오",
          "when": "하나 이상의 제한 사유"
        },
        {
          "id": "deviceEdge016",
          "source": "dvRestriction",
          "target": "dvServiceGate",
          "label": "다시 확인",
          "when": "사유 해결 후"
        },
        {
          "id": "deviceEdge017",
          "source": "dvRestriction",
          "target": "dvCode",
          "label": "코드 다시 입력",
          "when": "연결 자격이 폐기된 경우"
        },
        {
          "id": "deviceEdge018",
          "source": "dvServiceGate",
          "target": "dvStandby",
          "label": "예",
          "when": "이용 가능"
        },
        {
          "id": "deviceEdge019",
          "source": "dvStandby",
          "target": "dvModePassword",
          "label": "측정모드",
          "when": "전환 버튼"
        },
        {
          "id": "deviceEdge020",
          "source": "dvModePassword",
          "target": "dvPasswordValid",
          "label": "입력",
          "when": "비밀번호 제출"
        },
        {
          "id": "deviceEdge021",
          "source": "dvPasswordValid",
          "target": "dvPasswordHelp",
          "label": "아니오",
          "when": "오입력·미설정·일시 제한"
        },
        {
          "id": "deviceEdge022",
          "source": "dvPasswordHelp",
          "target": "dvModePassword",
          "label": "다시 입력",
          "when": "설정 또는 제한 해제"
        },
        {
          "id": "deviceEdge023",
          "source": "dvModePassword",
          "target": "dvStandby",
          "label": "취소",
          "when": "거치모드 유지"
        },
        {
          "id": "deviceEdge024",
          "source": "dvPasswordValid",
          "target": "dvWorkTable",
          "label": "예",
          "when": "시설 운영 접근 허용"
        },
        {
          "id": "deviceEdge025",
          "source": "dvWorkTable",
          "target": "dvResidentOps",
          "label": "입소자·후속조치",
          "when": "운영 작업 선택"
        },
        {
          "id": "deviceEdge026",
          "source": "dvResidentOps",
          "target": "dvWorkTable",
          "label": "완료",
          "when": "같은 시설 운영 표 갱신"
        },
        {
          "id": "deviceEdge027",
          "source": "dvWorkTable",
          "target": "dvSelectResident",
          "label": "측정 시작",
          "when": "입소자 행 선택"
        },
        {
          "id": "deviceEdge028",
          "source": "dvSelectResident",
          "target": "dvResidentConsent",
          "label": "대상 확정",
          "when": "재원 입소자"
        },
        {
          "id": "deviceEdge029",
          "source": "dvResidentConsent",
          "target": "dvConsentFix",
          "label": "아니오",
          "when": "동의 필요·철회·퇴소"
        },
        {
          "id": "deviceEdge030",
          "source": "dvConsentFix",
          "target": "dvResidentConsent",
          "label": "동의 보완 완료",
          "when": "유효 동의 확인"
        },
        {
          "id": "deviceEdge031",
          "source": "dvConsentFix",
          "target": "dvWorkTable",
          "label": "취소·동의 거부",
          "when": "측정하지 않음"
        },
        {
          "id": "deviceEdge032",
          "source": "dvWorkTable",
          "target": "dvLock",
          "label": "거치모드",
          "when": "전환 버튼"
        },
        {
          "id": "deviceEdge033",
          "source": "dvLock",
          "target": "dvStandby",
          "label": "전환 완료",
          "when": "개인 화면 초기화"
        },
        {
          "id": "deviceEdge034",
          "source": "dvStandby",
          "target": "dvGuestConsent",
          "label": "측정 시작",
          "when": "일반 사용자 시작"
        },
        {
          "id": "deviceEdge035",
          "source": "dvGuestConsent",
          "target": "dvGuestConsentChoice",
          "label": "확인",
          "when": "동의 선택"
        },
        {
          "id": "deviceEdge036",
          "source": "dvGuestConsentChoice",
          "target": "dvStandby",
          "label": "동의 안 함",
          "when": "측정하지 않음"
        },
        {
          "id": "deviceEdge037",
          "source": "dvGuestConsentChoice",
          "target": "dvIdentify",
          "label": "동의함",
          "when": "측정 준비"
        },
        {
          "id": "deviceEdge038",
          "source": "dvResidentConsent",
          "target": "dvPreflight",
          "label": "예",
          "when": "측정모드 대상과 동의 확정"
        },
        {
          "id": "deviceEdge039",
          "source": "dvIdentify",
          "target": "dvPreflight",
          "label": "계속",
          "when": "성공·실패·생체 식별 미동의 모두 진행"
        },
        {
          "id": "deviceEdge040",
          "source": "dvPreflight",
          "target": "dvRestriction",
          "label": "미충족",
          "when": "제한 사유 표시"
        },
        {
          "id": "deviceEdge041",
          "source": "dvPreflight",
          "target": "dvCapture",
          "label": "충족",
          "when": "세션 생성·횟수 예약"
        },
        {
          "id": "deviceEdge042",
          "source": "dvCapture",
          "target": "dvCaptureState",
          "label": "진행 결과",
          "when": "카메라 수집"
        },
        {
          "id": "deviceEdge043",
          "source": "dvCaptureState",
          "target": "dvQuality",
          "label": "분석 완료",
          "when": "검증 가능한 분석 결과"
        },
        {
          "id": "deviceEdge044",
          "source": "dvCaptureState",
          "target": "dvStop",
          "label": "사용자 중단",
          "when": "명시적 중지"
        },
        {
          "id": "deviceEdge045",
          "source": "dvCaptureState",
          "target": "dvStop",
          "label": "네트워크 단절",
          "when": "온라인 측정 중단"
        },
        {
          "id": "deviceEdge046",
          "source": "dvCaptureState",
          "target": "dvInvalid",
          "label": "수집 오류",
          "when": "품질 미달로 종료·예약 해제"
        },
        {
          "id": "deviceEdge047",
          "source": "dvStop",
          "target": "dvReturn",
          "label": "종료",
          "when": "미차감"
        },
        {
          "id": "deviceEdge048",
          "source": "dvQuality",
          "target": "dvInvalid",
          "label": "아니오",
          "when": "미유효·예약 해제"
        },
        {
          "id": "deviceEdge049",
          "source": "dvInvalid",
          "target": "dvRetryChoice",
          "label": "안내 확인",
          "when": "실패 횟수와 환경 안내"
        },
        {
          "id": "deviceEdge050",
          "source": "dvRetryChoice",
          "target": "dvPreflight",
          "label": "재측정",
          "when": "동의·이용 조건 재검증"
        },
        {
          "id": "deviceEdge051",
          "source": "dvRetryChoice",
          "target": "dvReturn",
          "label": "종료",
          "when": "담당자 확인 또는 나중에 시도"
        },
        {
          "id": "deviceEdge052",
          "source": "dvQuality",
          "target": "dvCommit",
          "label": "예",
          "when": "유효 세션"
        },
        {
          "id": "deviceEdge053",
          "source": "dvCommit",
          "target": "dvCommitState",
          "label": "확정 응답",
          "when": "결과+차감 처리"
        },
        {
          "id": "deviceEdge054",
          "source": "dvCommitState",
          "target": "dvUnknown",
          "label": "응답 미확인",
          "when": "타임아웃·응답 유실"
        },
        {
          "id": "deviceEdge055",
          "source": "dvUnknown",
          "target": "dvCommitState",
          "label": "동일 세션 조회",
          "when": "서버의 기존 처리 상태 확인"
        },
        {
          "id": "deviceEdge056",
          "source": "dvCommitState",
          "target": "dvNotSaved",
          "label": "처리 거부",
          "when": "미확정·미차감"
        },
        {
          "id": "deviceEdge057",
          "source": "dvNotSaved",
          "target": "dvReturn",
          "label": "확인",
          "when": "다음 시도 전 조건 해결"
        },
        {
          "id": "deviceEdge058",
          "source": "dvCommitState",
          "target": "dvResultRoute",
          "label": "확정 성공",
          "when": "1회 차감 완료"
        },
        {
          "id": "deviceEdge059",
          "source": "dvResultRoute",
          "target": "dvCareResult",
          "label": "예",
          "when": "선택 입소자에 결과 연결"
        },
        {
          "id": "deviceEdge060",
          "source": "dvCareResult",
          "target": "dvReturn",
          "label": "완료",
          "when": "결과 확인 또는 무조작 30초"
        },
        {
          "id": "deviceEdge061",
          "source": "dvReturn",
          "target": "dvWorkTable",
          "label": "측정모드",
          "when": "인증 유지 중인 경우"
        },
        {
          "id": "deviceEdge062",
          "source": "dvReturn",
          "target": "dvStandby",
          "label": "거치모드",
          "when": "개인 임시 데이터 정리"
        },
        {
          "id": "deviceEdge063",
          "source": "dvResultRoute",
          "target": "dvGuestResult",
          "label": "아니오",
          "when": "거치모드"
        },
        {
          "id": "deviceEdge064",
          "source": "dvGuestResult",
          "target": "dvIdentityChoice",
          "label": "결과 연결 선택",
          "when": "얼굴 후보 상태 확인"
        },
        {
          "id": "deviceEdge065",
          "source": "dvIdentityChoice",
          "target": "dvAttachExisting",
          "label": "본인·동의 확인",
          "when": "저장 동의 유효한 기존 입소자"
        },
        {
          "id": "deviceEdge066",
          "source": "dvIdentityChoice",
          "target": "dvRegisterChoice",
          "label": "미확인·후보 없음",
          "when": "일반 사용자로 계속"
        },
        {
          "id": "deviceEdge067",
          "source": "dvRegisterChoice",
          "target": "dvRegister",
          "label": "등록",
          "when": "입소자 정보 등록 선택"
        },
        {
          "id": "deviceEdge068",
          "source": "dvRegisterChoice",
          "target": "dvGuestExit",
          "label": "등록 안 함",
          "when": "저장하지 않음"
        },
        {
          "id": "deviceEdge069",
          "source": "dvRegister",
          "target": "dvRegistrationValid",
          "label": "등록 요청",
          "when": "필수정보와 동의 확인"
        },
        {
          "id": "deviceEdge070",
          "source": "dvRegister",
          "target": "dvGuestResult",
          "label": "취소",
          "when": "임시 결과로 복귀"
        },
        {
          "id": "deviceEdge071",
          "source": "dvRegistrationValid",
          "target": "dvRegister",
          "label": "입력·동의 보완",
          "when": "필수정보 또는 동의 미충족"
        },
        {
          "id": "deviceEdge072",
          "source": "dvRegistrationValid",
          "target": "dvDuplicateStaff",
          "label": "중복 후보",
          "when": "수동 확인 필요"
        },
        {
          "id": "deviceEdge073",
          "source": "dvDuplicateStaff",
          "target": "dvAttachNew",
          "label": "담당자 확정",
          "when": "비밀번호 인증 후 기존 연결 또는 별도 등록"
        },
        {
          "id": "deviceEdge074",
          "source": "dvDuplicateStaff",
          "target": "dvGuestExit",
          "label": "확인 취소",
          "when": "영구 저장하지 않음"
        },
        {
          "id": "deviceEdge075",
          "source": "dvRegistrationValid",
          "target": "dvAttachNew",
          "label": "등록 가능",
          "when": "중복 없음·유효 동의"
        },
        {
          "id": "deviceEdge076",
          "source": "dvDetailedResult",
          "target": "dvReturn",
          "label": "완료",
          "when": "확인 또는 무조작 30초"
        },
        {
          "id": "deviceEdge077",
          "source": "dvGuestResult",
          "target": "dvGuestExit",
          "label": "종료·시간 경과",
          "when": "완료 또는 무조작 30초"
        },
        {
          "id": "deviceEdge078",
          "source": "dvRegisterChoice",
          "target": "dvGuestExit",
          "label": "시간 경과",
          "when": "무조작 30초"
        },
        {
          "id": "deviceEdge079",
          "source": "dvGuestExit",
          "target": "dvStandby",
          "label": "다음 대기",
          "when": "개인 데이터 초기화"
        },
        {
          "id": "deviceEdge080",
          "source": "dvAttachExisting",
          "target": "dvAttachState",
          "label": "저장 결과",
          "when": "동일 결과에 기존 입소자 연결 요청"
        },
        {
          "id": "deviceEdge081",
          "source": "dvAttachNew",
          "target": "dvAttachState",
          "label": "저장 결과",
          "when": "입소자 등록 또는 담당자 확정 후 연결"
        },
        {
          "id": "deviceEdge082",
          "source": "dvAttachState",
          "target": "dvDetailedResult",
          "label": "성공",
          "when": "동의·시설·대상과 저장 확인"
        },
        {
          "id": "deviceEdge083",
          "source": "dvAttachState",
          "target": "dvAttachError",
          "label": "실패",
          "when": "필수 확인 또는 연결 저장 실패"
        },
        {
          "id": "deviceEdge084",
          "source": "dvAttachError",
          "target": "dvIdentityChoice",
          "label": "다시 확인",
          "when": "동일 세션 결과를 보존한 상태에서 보완"
        },
        {
          "id": "deviceEdge085",
          "source": "dvAttachError",
          "target": "dvGuestExit",
          "label": "종료",
          "when": "입소자 영구 연결 없이 종료"
        },
        {
          "id": "deviceEdge086",
          "source": "dvIdentityChoice",
          "target": "dvGuestExit",
          "label": "시간 경과",
          "when": "무조작 30초. 진행 중 저장은 서버 확정 여부를 먼저 확인하고 영구 연결이 없으면 임시 개인 데이터를 정리"
        },
        {
          "id": "deviceEdge087",
          "source": "dvRegister",
          "target": "dvGuestExit",
          "label": "시간 경과",
          "when": "무조작 30초. 진행 중 저장은 서버 확정 여부를 먼저 확인하고 영구 연결이 없으면 임시 개인 데이터를 정리"
        },
        {
          "id": "deviceEdge088",
          "source": "dvRegistrationValid",
          "target": "dvGuestExit",
          "label": "시간 경과",
          "when": "무조작 30초. 진행 중 저장은 서버 확정 여부를 먼저 확인하고 영구 연결이 없으면 임시 개인 데이터를 정리"
        },
        {
          "id": "deviceEdge089",
          "source": "dvDuplicateStaff",
          "target": "dvGuestExit",
          "label": "시간 경과",
          "when": "무조작 30초. 진행 중 저장은 서버 확정 여부를 먼저 확인하고 영구 연결이 없으면 임시 개인 데이터를 정리"
        },
        {
          "id": "deviceEdge090",
          "source": "dvAttachError",
          "target": "dvGuestExit",
          "label": "시간 경과",
          "when": "무조작 30초. 진행 중 저장은 서버 확정 여부를 먼저 확인하고 영구 연결이 없으면 임시 개인 데이터를 정리"
        }
      ]
    },
    {
      "role": "system",
      "title": "자동 처리",
      "description": "권한·차감·갱신·취소·연결 변경을 처리합니다. 각 화면에서 호출하는 공통 동작입니다.",
      "surface": "서버·공통",
      "entryNodeIds": [
        "syLogin",
        "syCommitRequest",
        "syEvaluate",
        "syClock",
        "syCodeRequest"
      ],
      "flows": [
        {
          "id": "sysAuth",
          "title": "관리자 로그인과 복구",
          "entryNodeId": "syLogin",
          "purpose": "슈퍼어드민·디스트리뷰터·시설 관리자 공통 인증"
        },
        {
          "id": "sysCommit",
          "title": "유효 측정 확정",
          "entryNodeId": "syCommitRequest",
          "purpose": "중복·월경계·저장 오류에도 정확히 한 번 차감"
        },
        {
          "id": "sysFollowup",
          "title": "이상치 후속조치 생성",
          "entryNodeId": "syEvaluate",
          "purpose": "유효 결과와 입소자 연결 이후 후속조치 평가"
        },
        {
          "id": "sysRenewal",
          "title": "갱신·취소·오늘 상태",
          "entryNodeId": "syClock",
          "purpose": "월·일 경계와 예약 취소 처리"
        },
        {
          "id": "sysReissue",
          "title": "연결 해제·코드 재발급",
          "entryNodeId": "syCodeRequest",
          "purpose": "공통 관리자 연결 작업"
        }
      ],
      "nodes": [
        {
          "id": "syLogin",
          "label": "이메일·비밀번호 입력",
          "kind": "screen",
          "note": "초대 수락한 관리자만 로그인합니다. 케어기버·입소자 로그인은 없습니다.",
          "flowId": "sysAuth",
          "policyRefs": [
            "accounts",
            "hierarchy"
          ]
        },
        {
          "id": "syAuth",
          "label": "접근 권한이 유효한가?",
          "kind": "decision",
          "note": "비밀번호와 사용자·소속의 활성 상태를 검증합니다.",
          "flowId": "sysAuth",
          "policyRefs": [
            "accounts",
            "hierarchy"
          ]
        },
        {
          "id": "syAuthError",
          "label": "오류·접근 제한 안내",
          "kind": "screen",
          "note": "잘못된 정보·초대 미완료·정지·반복 실패를 구분하고 가능한 복구 행동을 표시합니다.",
          "flowId": "sysAuth",
          "policyRefs": [
            "accounts",
            "hierarchy"
          ]
        },
        {
          "id": "syReset",
          "label": "비밀번호 재설정",
          "kind": "screen",
          "note": "등록 이메일의 재설정 링크로 비밀번호를 변경합니다. 디바이스 코드는 변경하지 않습니다.",
          "flowId": "sysAuth",
          "policyRefs": [
            "accounts",
            "hierarchy"
          ]
        },
        {
          "id": "syRoute",
          "label": "권한별 홈 진입",
          "kind": "action",
          "note": "슈퍼어드민·디스트리뷰터·시설 관리자의 허용 범위로 이동합니다.",
          "flowId": "sysAuth",
          "policyRefs": [
            "accounts",
            "hierarchy"
          ],
          "terminal": true
        },
        {
          "id": "syCommitRequest",
          "label": "세션 확정 요청",
          "kind": "action",
          "note": "세션 ID·디바이스·라이선스·이용기간·분석 결과와 동의를 검증합니다.",
          "flowId": "sysCommit",
          "policyRefs": [
            "measurement",
            "quota",
            "operations"
          ]
        },
        {
          "id": "syCommitted",
          "label": "이미 확정됐는가?",
          "kind": "decision",
          "note": "같은 세션 ID의 성공 기록을 조회합니다.",
          "flowId": "sysCommit",
          "policyRefs": [
            "measurement",
            "quota",
            "operations"
          ]
        },
        {
          "id": "syExisting",
          "label": "기존 확정 결과 반환",
          "kind": "action",
          "note": "새 저장·차감 없이 기존 결과를 돌려줍니다.",
          "flowId": "sysCommit",
          "policyRefs": [
            "measurement",
            "quota",
            "operations"
          ]
        },
        {
          "id": "syCommitGate",
          "label": "확정 조건이 유효한가?",
          "kind": "decision",
          "note": "품질·지표·연결·시설·동의·기간·입금·잔여 예약·취소/정지를 마지막으로 확인합니다. 이미 확정된 세션 조회는 새 조건으로 재차감하지 않습니다.",
          "flowId": "sysCommit",
          "policyRefs": [
            "measurement",
            "quota",
            "operations"
          ]
        },
        {
          "id": "syRollback",
          "label": "미저장·예약 해제",
          "kind": "action",
          "note": "조건 불충족이면 유효 결과와 차감을 기록하지 않습니다. 시도 상태와 실패 사유만 보존합니다.",
          "flowId": "sysCommit",
          "policyRefs": [
            "measurement",
            "quota",
            "operations"
          ]
        },
        {
          "id": "syAtomic",
          "label": "결과·차감 동시 확정",
          "kind": "action",
          "note": "유효 결과와 사용 원장을 하나의 처리로 저장합니다. 둘 중 하나만 성공하면 전체를 취소합니다.",
          "flowId": "sysCommit",
          "policyRefs": [
            "measurement",
            "quota",
            "operations"
          ]
        },
        {
          "id": "syAtomicState",
          "label": "저장이 성공했는가?",
          "kind": "decision",
          "note": "처리 성공과 실패를 분리합니다.",
          "flowId": "sysCommit",
          "policyRefs": [
            "measurement",
            "quota",
            "operations"
          ]
        },
        {
          "id": "syUpdate",
          "label": "운영 표·이용 현황 갱신",
          "kind": "action",
          "note": "연결된 입소자의 오늘 상태·마지막 유효값과 라이선스 사용량을 갱신합니다. 일반 사용자는 비식별 사용량만 반영합니다.",
          "flowId": "sysCommit",
          "policyRefs": [
            "measurement",
            "quota",
            "operations"
          ]
        },
        {
          "id": "syReturn",
          "label": "확정 상태 반환",
          "kind": "action",
          "note": "동일 세션 상태 조회에도 같은 결과를 반환합니다.",
          "flowId": "sysCommit",
          "policyRefs": [
            "measurement",
            "quota",
            "operations"
          ],
          "terminal": true
        },
        {
          "id": "syEvaluate",
          "label": "유효 결과 평가",
          "kind": "action",
          "note": "유효한 지표만 실제 분석 플래그 또는 승인된 기준 버전으로 평가합니다.",
          "flowId": "sysFollowup",
          "policyRefs": [
            "followup"
          ]
        },
        {
          "id": "syEvaluationState",
          "label": "평가 결과는?",
          "kind": "decision",
          "note": "이상치·기준 내·평가 안 됨을 구분합니다.",
          "flowId": "sysFollowup",
          "policyRefs": [
            "followup"
          ]
        },
        {
          "id": "syFlag",
          "label": "이상치 결과 보존",
          "kind": "action",
          "note": "해당 결과와 지표·판정 근거를 연결합니다. 일반 사용자는 본인 결과에 안내만 표시합니다.",
          "flowId": "sysFollowup",
          "policyRefs": [
            "followup"
          ]
        },
        {
          "id": "syResidentLinked",
          "label": "입소자가 연결됐는가?",
          "kind": "decision",
          "note": "익명 측정은 특정 입소자의 후속조치 목록에 넣지 않습니다.",
          "flowId": "sysFollowup",
          "policyRefs": [
            "followup"
          ]
        },
        {
          "id": "syFollowupCreate",
          "label": "후속조치 생성",
          "kind": "action",
          "note": "입소자와 결과별로 중복 없이 대기 조치를 만듭니다. 이후 입소자 연결에도 같은 규칙을 적용합니다.",
          "flowId": "sysFollowup",
          "policyRefs": [
            "followup"
          ]
        },
        {
          "id": "syNotEvaluated",
          "label": "평가 안 됨·설정 알림",
          "kind": "action",
          "note": "정상으로 표시하지 않습니다. 슈퍼어드민에게 판정 기준 연결 필요를 알립니다.",
          "flowId": "sysFollowup",
          "policyRefs": [
            "followup"
          ]
        },
        {
          "id": "syEvaluationDone",
          "label": "평가 상태 반영",
          "kind": "action",
          "note": "결과의 평가 상태를 표시합니다. 기존 후속조치는 수동 완료 전까지 유지합니다.",
          "flowId": "sysFollowup",
          "policyRefs": [
            "followup"
          ],
          "terminal": true
        },
        {
          "id": "syClock",
          "label": "일자·기간 경계 도래",
          "kind": "action",
          "note": "Asia/Seoul의 날짜 변경과 취소 효력일을 확인합니다.",
          "flowId": "sysRenewal",
          "policyRefs": [
            "billing",
            "cancellation",
            "quota",
            "operations"
          ]
        },
        {
          "id": "syCancelDue",
          "label": "취소 효력일에 도달했는가?",
          "kind": "decision",
          "note": "갱신보다 취소를 먼저 적용합니다.",
          "flowId": "sysRenewal",
          "policyRefs": [
            "billing",
            "cancellation",
            "quota",
            "operations"
          ]
        },
        {
          "id": "syEnd",
          "label": "이용 종료·갱신 중지",
          "kind": "action",
          "note": "측정을 중지하고 이후 기간을 만들지 않습니다. 기존 결과·사용 기록은 보존합니다.",
          "flowId": "sysRenewal",
          "policyRefs": [
            "billing",
            "cancellation",
            "quota",
            "operations"
          ]
        },
        {
          "id": "syMonth",
          "label": "새달 자동 갱신 대상인가?",
          "kind": "decision",
          "note": "이미 첫 이용을 시작한 유효 계약이며 새달인지 확인합니다. 최초 승인·입금 대기에는 기간을 임의 생성하지 않습니다.",
          "flowId": "sysRenewal",
          "policyRefs": [
            "billing",
            "cancellation",
            "quota",
            "operations"
          ]
        },
        {
          "id": "syExpire",
          "label": "지난달 잔여 소멸",
          "kind": "action",
          "note": "지난달 사용 기록을 확정하고 남은 횟수를 넘기지 않습니다.",
          "flowId": "sysRenewal",
          "policyRefs": [
            "billing",
            "cancellation",
            "quota",
            "operations"
          ]
        },
        {
          "id": "syNewMonth",
          "label": "새달 기간·3000회 생성",
          "kind": "action",
          "note": "라이선스별 한 기간만 생성합니다. 승인된 변경 예약이 있으면 새 가격·플랜을 적용하고 해당 월 이용료를 확정합니다.",
          "flowId": "sysRenewal",
          "policyRefs": [
            "billing",
            "cancellation",
            "quota",
            "operations"
          ]
        },
        {
          "id": "syPaid",
          "label": "새달 입금이 확인됐는가?",
          "kind": "decision",
          "note": "외부 입금 확인 또는 이후 결제 완료 이벤트를 확인합니다.",
          "flowId": "sysRenewal",
          "policyRefs": [
            "billing",
            "cancellation",
            "quota",
            "operations"
          ]
        },
        {
          "id": "syPaymentHold",
          "label": "입금 대기·측정 제한",
          "kind": "action",
          "note": "계약 갱신과 실제 수납 상태를 분리합니다. 입금 확인 후 같은 기간을 활성화합니다.",
          "flowId": "sysRenewal",
          "policyRefs": [
            "billing",
            "cancellation",
            "quota",
            "operations"
          ]
        },
        {
          "id": "syPeriodActive",
          "label": "당월 측정 가능",
          "kind": "action",
          "note": "다른 조건 충족 시 이용을 허용합니다. 입금 확인이 늦어도 말일과 횟수를 추가 연장하지 않습니다.",
          "flowId": "sysRenewal",
          "policyRefs": [
            "billing",
            "cancellation",
            "quota",
            "operations"
          ]
        },
        {
          "id": "syDaily",
          "label": "오늘 측정 상태 갱신",
          "kind": "action",
          "note": "동의 철회·퇴소를 제외한 모든 재원 입소자를 새 날짜의 일일 대상에 넣습니다. 동의 미완료는 동의 필요로 표시하며 과거 유효값은 실제 측정일시와 함께 유지합니다.",
          "flowId": "sysRenewal",
          "policyRefs": [
            "billing",
            "cancellation",
            "quota",
            "operations"
          ],
          "terminal": true
        },
        {
          "id": "syCodeRequest",
          "label": "연결 작업 요청",
          "kind": "action",
          "note": "시설 관리자·소속 디스트리뷰터·슈퍼어드민만 요청합니다. 케어기버 측정모드에서는 수행하지 않습니다.",
          "flowId": "sysReissue",
          "policyRefs": [
            "connection",
            "modes"
          ]
        },
        {
          "id": "syCodePermission",
          "label": "권한과 대상이 유효한가?",
          "kind": "decision",
          "note": "현재 라이선스·시설 범위와 요청자 권한을 검증합니다.",
          "flowId": "sysReissue",
          "policyRefs": [
            "connection",
            "modes"
          ]
        },
        {
          "id": "syCodeDenied",
          "label": "처리 불가 안내",
          "kind": "action",
          "note": "권한 없음·대상 상태 변경을 안내합니다. 기존 코드를 노출하거나 연결을 변경하지 않습니다.",
          "flowId": "sysReissue",
          "policyRefs": [
            "connection",
            "modes"
          ],
          "terminal": true
        },
        {
          "id": "syCodeConfirm",
          "label": "기존 연결 해제 확인",
          "kind": "screen",
          "note": "기존 디바이스에서 측정이 중단되며 횟수·기간은 유지됨을 확인합니다.",
          "flowId": "sysReissue",
          "policyRefs": [
            "connection",
            "modes"
          ]
        },
        {
          "id": "syCodeChoice",
          "label": "어떤 작업인가?",
          "kind": "decision",
          "note": "연결 해제와 코드 재발급을 구분합니다.",
          "flowId": "sysReissue",
          "policyRefs": [
            "connection",
            "modes"
          ]
        },
        {
          "id": "syUnbind",
          "label": "디바이스 연결 해제",
          "kind": "action",
          "note": "기존 연결 자격을 폐기합니다. 현재 코드는 유지하여 다른 디바이스에 연결할 수 있습니다.",
          "flowId": "sysReissue",
          "policyRefs": [
            "connection",
            "modes"
          ]
        },
        {
          "id": "syReissue",
          "label": "기존 코드 폐기·새 코드 발급",
          "kind": "action",
          "note": "코드와 기존 디바이스 연결 자격을 폐기하고 새 코드를 발급합니다. 유효시간은 없습니다.",
          "flowId": "sysReissue",
          "policyRefs": [
            "connection",
            "modes"
          ]
        },
        {
          "id": "syCodeDone",
          "label": "관리 화면 갱신",
          "kind": "action",
          "note": "횟수·기간 보존을 확인하고 작업자·시각·대상을 기록합니다. 디바이스는 다시 연결해야 합니다.",
          "flowId": "sysReissue",
          "policyRefs": [
            "connection",
            "modes"
          ],
          "terminal": true
        },
        {
          "id": "syPaidCurrent",
          "label": "현재 이용할 수 있는 기간인가?",
          "kind": "decision",
          "note": "입금 확인된 기간이 현재 열려 있고 취소·정지되지 않았는지 확인합니다. 과거 기간 입금 확인은 과거 기록만 보완합니다.",
          "flowId": "sysRenewal",
          "policyRefs": [
            "billing",
            "cancellation"
          ]
        },
        {
          "id": "syNoReactivate",
          "label": "입금 기록만 보완",
          "kind": "action",
          "note": "종료·취소·과거 기간은 활성화하거나 횟수를 새로 부여하지 않습니다. 현재 기간과 제한 사유를 별도로 표시합니다.",
          "flowId": "sysRenewal",
          "policyRefs": [
            "billing",
            "cancellation"
          ],
          "terminal": true
        }
      ],
      "edges": [
        {
          "id": "systemEdge001",
          "source": "syLogin",
          "target": "syAuth",
          "label": "로그인",
          "when": "입력 제출"
        },
        {
          "id": "systemEdge002",
          "source": "syAuth",
          "target": "syAuthError",
          "label": "실패",
          "when": "인증·접근 조건 미충족"
        },
        {
          "id": "systemEdge003",
          "source": "syAuthError",
          "target": "syLogin",
          "label": "다시 입력",
          "when": "입력 오류 해결"
        },
        {
          "id": "systemEdge004",
          "source": "syAuthError",
          "target": "syReset",
          "label": "비밀번호 재설정",
          "when": "등록 이메일 사용"
        },
        {
          "id": "systemEdge005",
          "source": "syReset",
          "target": "syLogin",
          "label": "변경 완료",
          "when": "다시 로그인"
        },
        {
          "id": "systemEdge006",
          "source": "syAuth",
          "target": "syRoute",
          "label": "성공",
          "when": "유효 관리자와 소속"
        },
        {
          "id": "systemEdge007",
          "source": "syCommitRequest",
          "target": "syCommitted",
          "label": "조회",
          "when": "세션 ID 확인"
        },
        {
          "id": "systemEdge008",
          "source": "syCommitted",
          "target": "syExisting",
          "label": "예",
          "when": "기존 확정 기록 존재"
        },
        {
          "id": "systemEdge009",
          "source": "syExisting",
          "target": "syReturn",
          "label": "기존 결과",
          "when": "중복 차감 없음"
        },
        {
          "id": "systemEdge010",
          "source": "syCommitted",
          "target": "syCommitGate",
          "label": "아니오",
          "when": "새 확정 처리"
        },
        {
          "id": "systemEdge011",
          "source": "syCommitGate",
          "target": "syRollback",
          "label": "아니오",
          "when": "조건 변경 또는 무효"
        },
        {
          "id": "systemEdge012",
          "source": "syCommitGate",
          "target": "syAtomic",
          "label": "예",
          "when": "모든 검증 통과"
        },
        {
          "id": "systemEdge013",
          "source": "syAtomic",
          "target": "syAtomicState",
          "label": "처리",
          "when": "원자적 저장"
        },
        {
          "id": "systemEdge014",
          "source": "syAtomicState",
          "target": "syRollback",
          "label": "실패",
          "when": "부분 저장 취소"
        },
        {
          "id": "systemEdge015",
          "source": "syAtomicState",
          "target": "syUpdate",
          "label": "성공",
          "when": "1회 확정"
        },
        {
          "id": "systemEdge016",
          "source": "syUpdate",
          "target": "syReturn",
          "label": "완료",
          "when": "결과·사용량 반영"
        },
        {
          "id": "systemEdge017",
          "source": "syRollback",
          "target": "syReturn",
          "label": "미확정",
          "when": "미차감 사유 반환"
        },
        {
          "id": "systemEdge018",
          "source": "syEvaluate",
          "target": "syEvaluationState",
          "label": "평가",
          "when": "검증된 플래그 또는 기준"
        },
        {
          "id": "systemEdge019",
          "source": "syEvaluationState",
          "target": "syFlag",
          "label": "이상치",
          "when": "유효 지표에 플래그 존재"
        },
        {
          "id": "systemEdge020",
          "source": "syFlag",
          "target": "syResidentLinked",
          "label": "대상 확인",
          "when": "결과와 대상 관계 확인"
        },
        {
          "id": "systemEdge021",
          "source": "syResidentLinked",
          "target": "syFollowupCreate",
          "label": "예",
          "when": "같은 시설의 입소자"
        },
        {
          "id": "systemEdge022",
          "source": "syResidentLinked",
          "target": "syEvaluationDone",
          "label": "아니오",
          "when": "일반 사용자 결과 안내만"
        },
        {
          "id": "systemEdge023",
          "source": "syFollowupCreate",
          "target": "syEvaluationDone",
          "label": "완료",
          "when": "후속조치 대기 반영"
        },
        {
          "id": "systemEdge024",
          "source": "syEvaluationState",
          "target": "syNotEvaluated",
          "label": "평가 안 됨",
          "when": "기준·판정 출력 없음"
        },
        {
          "id": "systemEdge025",
          "source": "syNotEvaluated",
          "target": "syEvaluationDone",
          "label": "상태 반영",
          "when": "이상치 없음으로 치환하지 않음"
        },
        {
          "id": "systemEdge026",
          "source": "syEvaluationState",
          "target": "syEvaluationDone",
          "label": "기준 내",
          "when": "새 후속조치 없음"
        },
        {
          "id": "systemEdge027",
          "source": "syClock",
          "target": "syCancelDue",
          "label": "확인",
          "when": "예약 취소 우선"
        },
        {
          "id": "systemEdge028",
          "source": "syCancelDue",
          "target": "syEnd",
          "label": "예",
          "when": "효력일 00:00 이상"
        },
        {
          "id": "systemEdge029",
          "source": "syEnd",
          "target": "syDaily",
          "label": "기록 유지",
          "when": "시설 입소자 기록 보존"
        },
        {
          "id": "systemEdge030",
          "source": "syCancelDue",
          "target": "syMonth",
          "label": "아니오",
          "when": "취소 아직 미도래 또는 없음"
        },
        {
          "id": "systemEdge031",
          "source": "syMonth",
          "target": "syDaily",
          "label": "아니오",
          "when": "일일 상태만 갱신"
        },
        {
          "id": "systemEdge032",
          "source": "syMonth",
          "target": "syExpire",
          "label": "예",
          "when": "새 이용기간 필요"
        },
        {
          "id": "systemEdge033",
          "source": "syExpire",
          "target": "syNewMonth",
          "label": "이월 없음",
          "when": "지난달 확정"
        },
        {
          "id": "systemEdge034",
          "source": "syNewMonth",
          "target": "syPaid",
          "label": "입금 상태 확인",
          "when": "자동 수납으로 가정하지 않음"
        },
        {
          "id": "systemEdge035",
          "source": "syPaid",
          "target": "syPaymentHold",
          "label": "아니오",
          "when": "미확인 입금"
        },
        {
          "id": "systemEdge036",
          "source": "syPaymentHold",
          "target": "syPaid",
          "label": "입금 확인 이벤트",
          "when": "기간·취소 조건 재확인"
        },
        {
          "id": "systemEdge037",
          "source": "syPeriodActive",
          "target": "syDaily",
          "label": "완료",
          "when": "현재 이용 상태 갱신"
        },
        {
          "id": "systemEdge038",
          "source": "syCodeRequest",
          "target": "syCodePermission",
          "label": "검증",
          "when": "대상과 권한 확인"
        },
        {
          "id": "systemEdge039",
          "source": "syCodePermission",
          "target": "syCodeDenied",
          "label": "아니오",
          "when": "불일치·권한 없음"
        },
        {
          "id": "systemEdge040",
          "source": "syCodePermission",
          "target": "syCodeConfirm",
          "label": "예",
          "when": "허용된 관리자"
        },
        {
          "id": "systemEdge041",
          "source": "syCodeConfirm",
          "target": "syCodeDone",
          "label": "취소",
          "when": "연결 상태 변경 없이 반환"
        },
        {
          "id": "systemEdge042",
          "source": "syCodeConfirm",
          "target": "syCodeChoice",
          "label": "확인",
          "when": "기존 연결 중단 동의"
        },
        {
          "id": "systemEdge043",
          "source": "syCodeChoice",
          "target": "syUnbind",
          "label": "연결 해제",
          "when": "코드 유지"
        },
        {
          "id": "systemEdge044",
          "source": "syCodeChoice",
          "target": "syReissue",
          "label": "코드 재발급",
          "when": "기존 코드 폐기"
        },
        {
          "id": "systemEdge045",
          "source": "syUnbind",
          "target": "syCodeDone",
          "label": "완료",
          "when": "미연결 상태"
        },
        {
          "id": "systemEdge046",
          "source": "syReissue",
          "target": "syCodeDone",
          "label": "완료",
          "when": "새 코드로 재연결"
        },
        {
          "id": "systemEdge047",
          "source": "syPaid",
          "target": "syPaidCurrent",
          "label": "예",
          "when": "대상기간 입금 확인됨"
        },
        {
          "id": "systemEdge048",
          "source": "syPaidCurrent",
          "target": "syPeriodActive",
          "label": "예",
          "when": "현재 이용 가능한 기간"
        },
        {
          "id": "systemEdge049",
          "source": "syPaidCurrent",
          "target": "syNoReactivate",
          "label": "아니오",
          "when": "과거 기간·취소·정지"
        }
      ]
    }
  ],
  "handoffs": [
    {
      "id": "inviteDistributor",
      "source": "saInvite",
      "target": "diInviteAccept",
      "event": "distributor.invited",
      "payload": [
        "distributor_id",
        "invitation_id"
      ],
      "note": "초대된 디스트리뷰터 관리자가 수락합니다.",
      "asynchronous": true
    },
    {
      "id": "inviteFacility",
      "source": "diFacilityCreate",
      "target": "faInviteAccept",
      "event": "facility.manager_invited",
      "payload": [
        "facility_id",
        "invitation_id"
      ],
      "note": "시설 관리자 수락을 기존 시설에 연결합니다.",
      "asynchronous": true
    },
    {
      "id": "requestNewPrice",
      "source": "diRequestApproval",
      "target": "saRequests",
      "event": "license.price_requested",
      "payload": [
        "license_id",
        "request_id",
        "version"
      ],
      "note": "신규 비기본가격 승인 요청입니다.",
      "asynchronous": true
    },
    {
      "id": "requestExistingPrice",
      "source": "diChangeReview",
      "target": "saRequests",
      "event": "license.price_change_requested",
      "payload": [
        "license_id",
        "request_id",
        "version"
      ],
      "note": "기존 이용은 종전 조건으로 유지합니다.",
      "asynchronous": true
    },
    {
      "id": "newApprovalResult",
      "source": "saNotified",
      "target": "diApprovalResult",
      "event": "license.new_price_resolved",
      "payload": [
        "license_id",
        "request_id",
        "decision"
      ],
      "note": "신규 결과를 승인·반려 상태로 반영합니다.",
      "asynchronous": true
    },
    {
      "id": "existingApprovalResult",
      "source": "saNotified",
      "target": "diChangeReview",
      "event": "license.existing_price_resolved",
      "payload": [
        "license_id",
        "request_id",
        "decision",
        "effective_on"
      ],
      "note": "기존 요청의 다음 달 변경 예약 여부를 반영합니다.",
      "asynchronous": true
    },
    {
      "id": "licenseVisible",
      "source": "diCreateEach",
      "target": "faLicenses",
      "event": "license.created",
      "payload": [
        "facility_id",
        "license_ids"
      ],
      "note": "각 코드·승인·연결 상태를 시설 관리자에게 표시합니다.",
      "asynchronous": true
    },
    {
      "id": "activationVisible",
      "source": "diActivate",
      "target": "dvServiceGate",
      "event": "license.activated",
      "payload": [
        "license_id",
        "period_id"
      ],
      "note": "이미 연결된 디바이스의 측정 제한을 새 상태로 판정합니다.",
      "asynchronous": true
    },
    {
      "id": "managerCodeToDevice",
      "source": "faAddDevice",
      "target": "dvCode",
      "event": "device.connection_prepared",
      "payload": [
        "facility_id",
        "license_id"
      ],
      "note": "권한 있는 화면에서 코드를 확인하여 디바이스에 입력합니다.",
      "asynchronous": true
    },
    {
      "id": "facilityRequest",
      "source": "faDistributorRequest",
      "target": "diDetail",
      "event": "facility.license_request",
      "payload": [
        "facility_id",
        "license_id",
        "request_type"
      ],
      "note": "입금·취소·변경 문의를 소속 디스트리뷰터가 처리합니다.",
      "asynchronous": true
    },
    {
      "id": "facilityReissue",
      "source": "faDeviceAction",
      "target": "syCodeRequest",
      "event": "license.connection_action",
      "payload": [
        "license_id",
        "actor_id"
      ],
      "note": "시설 관리자의 연결 변경 요청입니다.",
      "asynchronous": true
    },
    {
      "id": "distributorReissue",
      "source": "diConnectionSupport",
      "target": "syCodeRequest",
      "event": "license.connection_action",
      "payload": [
        "license_id",
        "actor_id"
      ],
      "note": "소속 디스트리뷰터의 연결 변경 요청입니다.",
      "asynchronous": true
    },
    {
      "id": "superadminReissue",
      "source": "saSupport",
      "target": "syCodeRequest",
      "event": "license.connection_action",
      "payload": [
        "license_id",
        "actor_id"
      ],
      "note": "슈퍼어드민 지원 요청입니다.",
      "asynchronous": true
    },
    {
      "id": "revokedDevice",
      "source": "syCodeDone",
      "target": "dvBoot",
      "event": "device.credential_revoked",
      "payload": [
        "license_id",
        "device_id"
      ],
      "note": "실제 해제·재발급 시에만 기존 디바이스를 재검증합니다. 확인 취소는 알리지 않습니다.",
      "asynchronous": true
    },
    {
      "id": "measurementCommit",
      "source": "dvCommit",
      "target": "syCommitRequest",
      "event": "measurement.commit_requested",
      "payload": [
        "session_id",
        "license_id",
        "period_id",
        "device_id"
      ],
      "note": "결과 확정과 차감을 공통 처리합니다.",
      "asynchronous": true
    },
    {
      "id": "measurementCommitReturn",
      "source": "syReturn",
      "target": "dvCommitState",
      "event": "measurement.commit_resolved",
      "payload": [
        "session_id",
        "status"
      ],
      "note": "성공·미확정·기존 결과를 반환합니다.",
      "asynchronous": true
    },
    {
      "id": "refreshFacility",
      "source": "syUpdate",
      "target": "faHome",
      "event": "measurement.committed",
      "payload": [
        "facility_id",
        "resident_id",
        "session_id"
      ],
      "note": "입소자 연결이 있는 경우 오늘 운영 표를 갱신합니다.",
      "asynchronous": true
    },
    {
      "id": "refreshDevice",
      "source": "syUpdate",
      "target": "dvWorkTable",
      "event": "measurement.committed",
      "payload": [
        "facility_id",
        "resident_id",
        "session_id"
      ],
      "note": "인증된 측정모드의 운영 표를 갱신합니다.",
      "asynchronous": true
    },
    {
      "id": "refreshDistributor",
      "source": "syUpdate",
      "target": "diDashboard",
      "event": "usage.updated",
      "payload": [
        "license_id",
        "period_id"
      ],
      "note": "비식별 이용 현황 집계를 갱신합니다.",
      "asynchronous": true
    },
    {
      "id": "refreshSuperadmin",
      "source": "syUpdate",
      "target": "saDashboard",
      "event": "usage.updated",
      "payload": [
        "license_id",
        "period_id"
      ],
      "note": "전역 집계를 갱신합니다.",
      "asynchronous": true
    },
    {
      "id": "evaluateNewResult",
      "source": "syUpdate",
      "target": "syEvaluate",
      "event": "measurement.valid_result",
      "payload": [
        "session_id",
        "resident_id"
      ],
      "note": "동일 결과의 평가 상태를 생성합니다.",
      "asynchronous": true
    },
    {
      "id": "evaluateResidentAttach",
      "source": "dvAttachState",
      "target": "syEvaluate",
      "event": "measurement.resident_linked",
      "payload": [
        "session_id",
        "resident_id"
      ],
      "note": "입소자 연결 저장이 성공한 경우에만 실행합니다. 같은 결과의 차감을 반복하지 않고 후속조치 대상을 연결합니다.",
      "asynchronous": true
    },
    {
      "id": "followupVisible",
      "source": "syFollowupCreate",
      "target": "faFollowups",
      "event": "followup.created",
      "payload": [
        "resident_id",
        "session_id",
        "followup_id"
      ],
      "note": "입소자와 실제 유효 결과를 함께 표시합니다.",
      "asynchronous": true
    },
    {
      "id": "missingEvaluation",
      "source": "syNotEvaluated",
      "target": "saRule",
      "event": "evaluation.configuration_missing",
      "payload": [
        "module_id",
        "policy_version"
      ],
      "note": "평가 미설정을 정상으로 숨기지 않고 담당자에게 알립니다.",
      "asynchronous": true
    },
    {
      "id": "renewalReceipt",
      "source": "syPaymentHold",
      "target": "diNewPeriodReceipt",
      "event": "period.payment_pending",
      "payload": [
        "license_id",
        "period_id"
      ],
      "note": "외부 입금 확인이 필요한 갱신기간입니다.",
      "asynchronous": true
    },
    {
      "id": "cancelApplied",
      "source": "syEnd",
      "target": "dvServiceGate",
      "event": "license.ended",
      "payload": [
        "license_id",
        "effective_at"
      ],
      "note": "현재 연결을 서비스 활성으로 오인하지 않고 측정을 차단합니다.",
      "asynchronous": true
    },
    {
      "id": "privacyRequest",
      "source": "faPrivacyRequest",
      "target": "saPrivacy",
      "event": "privacy.requested",
      "payload": [
        "facility_id",
        "resident_id",
        "request_id"
      ],
      "note": "개인정보 처리 요청을 검토합니다.",
      "asynchronous": true
    },
    {
      "id": "caregiverResidentOperations",
      "source": "dvResidentOps",
      "target": "faResidents",
      "event": "reference.same_operational_flow",
      "payload": [
        "facility_id",
        "device_id"
      ],
      "note": "화면 이동이 아닌 재사용 규칙 참조입니다. 디바이스에서 동일 등록·동의·퇴소 흐름을 제공합니다.",
      "asynchronous": true
    },
    {
      "id": "caregiverFollowupOperations",
      "source": "dvResidentOps",
      "target": "faFollowups",
      "event": "reference.same_operational_flow",
      "payload": [
        "facility_id",
        "device_id"
      ],
      "note": "화면 이동이 아닌 재사용 규칙 참조입니다. 디바이스에서 동일 후속조치 흐름을 제공합니다.",
      "asynchronous": true
    },
    {
      "id": "authSuperadmin",
      "source": "syRoute",
      "target": "saDashboard",
      "event": "auth.superadmin",
      "payload": [
        "user_id"
      ],
      "note": "유효 슈퍼어드민 홈입니다.",
      "asynchronous": true
    },
    {
      "id": "authDistributor",
      "source": "syRoute",
      "target": "diDashboard",
      "event": "auth.distributor",
      "payload": [
        "user_id",
        "distributor_id"
      ],
      "note": "유효 디스트리뷰터 홈입니다.",
      "asynchronous": true
    },
    {
      "id": "authFacility",
      "source": "syRoute",
      "target": "faHome",
      "event": "auth.facility_manager",
      "payload": [
        "user_id",
        "facility_id"
      ],
      "note": "유효 시설 관리자 홈입니다.",
      "asynchronous": true
    },
    {
      "id": "renewalReceiptConfirmed",
      "source": "diResumePeriod",
      "target": "syPaid",
      "event": "period.payment_confirmed",
      "payload": [
        "license_id",
        "period_id"
      ],
      "note": "과거 기간의 입금도 기록하지만 유효하지 않은 기간을 다시 활성화하지 않습니다.",
      "asynchronous": true
    }
  ],
  "acceptanceScenarios": [
    {
      "id": "AC01",
      "given": "Standard 기본가격 200000원으로 수량 3개 등록",
      "expect": "승인 대기 없이 라이선스 3개·코드 3개 생성. 각 월 3000회. 월 공급가액 합계 600000원. 연결·이용 상태는 개별"
    },
    {
      "id": "AC02",
      "given": "신규 Standard 180000원 등록 후 코드 연결",
      "expect": "슈퍼어드민 승인 요청. 연결 가능하지만 승인·계약·입금 전 측정 불가"
    },
    {
      "id": "AC03",
      "given": "사용 중인 Standard 200000원을 180000원으로 변경 요청",
      "expect": "승인 대기에도 현재 가격 200000원과 기능으로 계속 측정. 승인 시 다음 달 1일 적용"
    },
    {
      "id": "AC04",
      "given": "신규 요청 반려",
      "expect": "사유 확인 후 수정·재요청 또는 등록 취소. 임의 측정 활성화 없음"
    },
    {
      "id": "AC05",
      "given": "Standard 첫 입금 유효일 2026-09-17",
      "expect": "첫 기간 9월17일~9월30일. 공급가액 93333원·부가세 별도. 첫 기간 한도 3000회"
    },
    {
      "id": "AC06",
      "given": "9월 잔여 2000회, 10월1일 자동 갱신",
      "expect": "9월 잔여 소멸. 10월 한도 3000회. 10월 입금 미확인 동안 측정 대기"
    },
    {
      "id": "AC07",
      "given": "한 라이선스 소진, 같은 시설 다른 라이선스 잔여 1000회",
      "expect": "소진한 라이선스는 측정 불가. 다른 라이선스의 횟수를 공유·이전하지 않음"
    },
    {
      "id": "AC08",
      "given": "300회 사용 후 코드 재발급·디바이스 교체",
      "expect": "기존 코드와 연결 자격 폐기. 새 연결의 잔여는 2700회이며 이용기간 유지"
    },
    {
      "id": "AC09",
      "given": "이미 다른 디바이스가 연결된 코드 입력",
      "expect": "자동 연결 탈취 없음. 권한 있는 관리자의 해제·재발급 안내"
    },
    {
      "id": "AC10",
      "given": "품질 미달 후 재측정 성공",
      "expect": "품질 미달 미저장·미차감. 성공 세션 1회만 차감"
    },
    {
      "id": "AC11",
      "given": "결과 저장 성공 후 응답 유실, 동일 세션 재조회",
      "expect": "기존 결과 반환. 추가 차감 없음"
    },
    {
      "id": "AC12",
      "given": "얼굴 식별 실패한 일반 사용자 유효 측정 후 등록",
      "expect": "측정 계속 진행. 유효 확정 시 1회 차감. 입소자 등록·연결 시 추가 차감 없음"
    },
    {
      "id": "AC13",
      "given": "거치모드에서 등록하지 않고 종료",
      "expect": "임시 개인 결과 제거. 이미 확정된 비식별 차감 기록 1회 유지"
    },
    {
      "id": "AC14",
      "given": "오늘 분석 불가, 어제 유효 측정 있음",
      "expect": "오늘은 재측정 필요. 표 지표는 어제의 동일 세션 값과 실제 일시 표시"
    },
    {
      "id": "AC15",
      "given": "유효 측정의 이상치, 이후 새 기준 내 결과",
      "expect": "이상치 후속조치 자동 생성. 담당자 완료 기록 전까지 이전 후속조치 유지"
    },
    {
      "id": "AC16",
      "given": "이상치 판정 기준 또는 모듈 출력 없음",
      "expect": "평가 안 됨. 정상으로 대체하지 않으며 슈퍼어드민 설정 알림"
    },
    {
      "id": "AC17",
      "given": "취소 효력일이 2026-10-01",
      "expect": "10월1일 00:00부터 중단. 그날 새달 갱신보다 취소 우선. 기존 결과 조회 유지"
    },
    {
      "id": "AC18",
      "given": "Standard·Plus·Enterprise가 같은 시설에서 각각 연결",
      "expect": "각 디바이스에 해당 플랜 기능만 제공. 모든 기본 화면은 Standard에도 존재"
    },
    {
      "id": "AC19",
      "given": "거치모드에서 측정모드 진입 후 다시 거치모드로 전환",
      "expect": "진입 시 측정모드 비밀번호 필요. 개인 로그인 없음. 복귀 시 운영 접근 자격과 이전 결과 초기화"
    },
    {
      "id": "AC20",
      "given": "측정 중 네트워크 단절",
      "expect": "측정 중단·미차감. 이미 전송한 확정 응답만 불명확하면 같은 세션의 서버 상태 확인"
    },
    {
      "id": "AC21",
      "given": "두 확정 요청이 잔여 마지막 1회를 동시에 사용하려 함",
      "expect": "예약과 원자적 확정으로 초과 사용 불가. 중복 세션 ID는 한 번만 차감"
    },
    {
      "id": "AC22",
      "given": "슈퍼어드민이 Standard 기본가격을 변경",
      "expect": "새 등록의 기준가격만 변경. 기존 계약·기간 요금·과거 입금·누적 금액은 유지"
    },
    {
      "id": "AC23",
      "given": "입소자 퇴소 또는 동의 철회",
      "expect": "새 측정과 오늘 필수 대상 제외. 기록 즉시 삭제 없음. 삭제 요청은 별도 검토 흐름"
    },
    {
      "id": "AC24",
      "given": "월경계 또는 취소 효력일을 넘겨 미확정 세션 제출",
      "expect": "확정 직전 기간·취소 재검증 후 미저장·미차감. 새 조건으로 다시 시작"
    },
    {
      "id": "AC25",
      "given": "갱신기간에서 100회 사용 후 입금 확인 수정 이벤트 재전송",
      "expect": "해당 기간 used_count=100·잔여 2900 유지. 새 기간·3000회 재부여 없음"
    },
    {
      "id": "AC26",
      "given": "이용 종료 후 과거 미확인 입금을 확인",
      "expect": "과거 입금 기록만 보완. 측정 재활성화·취소 철회·한도 재부여 없음"
    },
    {
      "id": "AC27",
      "given": "일반 사용자 결과 확정 후 입소자 연결 저장 실패",
      "expect": "유효 측정의 1회 차감 유지. 같은 결과 연결만 재시도하며 추가 측정·차감 없음"
    },
    {
      "id": "AC28",
      "given": "오늘 유효 측정 후 추가 시도가 품질 미달",
      "expect": "오늘 측정 완료 유지. 지표는 오늘 마지막 유효 결과, 최신 시도는 재측정 안내를 별도로 표시"
    }
  ],
  "requirementCoverage": {
    "Q01": [
      "hierarchy"
    ],
    "Q02": [
      "terminology"
    ],
    "Q03": [
      "hierarchy",
      "accounts"
    ],
    "Q04": [
      "license_creation"
    ],
    "Q05": [
      "plans"
    ],
    "Q06": [
      "activation"
    ],
    "Q07": [
      "license_creation",
      "price_approval"
    ],
    "Q08": [
      "price_approval",
      "connection"
    ],
    "Q09": [
      "billing"
    ],
    "Q10": [
      "quota"
    ],
    "Q11": [
      "billing",
      "cancellation"
    ],
    "Q12": [
      "connection"
    ],
    "Q13": [
      "connection"
    ],
    "Q14": [
      "plans"
    ],
    "Q15": [
      "quota",
      "measurement"
    ],
    "Q16": [
      "modes"
    ],
    "Q17": [
      "modes",
      "residents"
    ],
    "Q18": [
      "residents",
      "measurement"
    ],
    "Q19": [
      "operations",
      "followup"
    ],
    "Q20": [
      "operations",
      "dashboardDefinitions"
    ],
    "Q21": [
      "analytics",
      "dashboardDefinitions"
    ],
    "Q22": [
      "measurement"
    ],
    "AdditionalPricing": [
      "plans"
    ],
    "AdditionalExistingApproval": [
      "price_approval"
    ]
  },
  "reviewNotes": [
    "현재 Figma 화면을 수정한 산출물이 아니라 최종 플로우 데이터입니다. 기존 Figma의 6자리 인증번호·케어기버 계정·재고·400회 예시는 폐기 대상으로 봅니다.",
    "모든 질문 답변과 추가 가격 답변을 반영했습니다. 사용자에게 위임받아 선택한 규칙은 designDefaults와 policy.basis에 따로 표시했습니다.",
    "개별 인지 모듈 과제와 계약별 커스터마이징 내부 화면은 이번 기본 운영 흐름의 범위 밖입니다. 플랜별 제공 권한과 진입 조건만 정의했습니다."
  ],
  "graphSummary": {
    "roles": 6,
    "flows": 21,
    "nodes": 198,
    "localEdges": 300,
    "handoffs": 33
  }
}
