Skip to content

EVENT_ROBOT_FLOW_DESIGN

Joo edited this page Oct 4, 2026 · 8 revisions

이벤트 로봇 플로우 (Event Robot Flow) 설계

작성일: 2026-09-12 · 상태: 1차 구현 완료 (orchestration-server 마이그레이션 118) — 아래 §7 "구현 결정" 참고. §1~§6 은 원안. · 관련: LOCATION_AUTOMATION_DESIGN, sequence, MQTT_SCHEMA-SPOT, MQTT_SCHEMA-ROBOT_ARM

목표 플로우 (마트 적재):

sequenceDiagram
    oc_server-->>MQTT: mqtt.pub run_scenario market_load
    MQTT-->>Spot: sub robot/spot-01/command
    Spot->>Spot: 마트로 이동 · 자세 변경
    Spot-->>MQTT: pub ack SUCCESS
    MQTT-->>oc_server: SPOT 작업 완료
    oc_server-->>MQTT: mqtt.pub pick_basket
    MQTT-->>로봇팔: sub robot/arm-01/command
    로봇팔->>로봇팔: 물건 집기
    로봇팔-->>MQTT: pub ack SUCCESS
    MQTT-->>oc_server: 로봇팔 작업 완료
    oc_server-->>MQTT: mqtt.pub navigate_to (다음 장소)
    MQTT-->>Spot: sub navigate_to
    Spot-->>MQTT: pub ack SUCCESS
    MQTT-->>oc_server: 태스크 완료 처리
Loading

1. 결론 먼저

새 실행 엔진을 만들지 않는다. 위 플로우는 이미 있는 자동화 규칙(arrival_automations) 의 액션 체인 그대로다 — 규칙 하나에 액션을 spot run_scenario → robot_arm pick_basket → spot navigate_to 순서로 넣으면 디바이스 큐가 앞 액션의 종결 ack(SUCCESS/FAULT)를 기다린 뒤 다음 액션을 발행한다(§4.3 디바이스 명령 큐). sequence 정책 2·3·5(도착 완료 후 지시, 적재 중 SPOT 정지)는 이 체인 구조에서 자동으로 지켜진다.

이벤트 도메인에서 빠진 것은 네 가지뿐이고, 이 설계는 그 네 가지만 추가한다.

# 빠진 것 추가
A 이벤트 규칙의 트리거가 장소 도착뿐 — 팝업이 뜨거나 관람객이 선택지를 고른 순간에 플로우를 시작할 수 없음 트리거 event_published, option_selected 추가
B 플로우가 끝나도 태스크 완료로 이어지지 않음 (태스크 소유 규칙만 user_task_commands 로 추적해 자동 완료) 규칙에 "완료 처리할 태스크" 지정 → 체인 전체를 그 태스크 명령으로 추적, 전부 종결되면 자동 completed
C 앞 액션이 실패해도 다음 액션이 나감 (SPOT 이동 실패 → 로봇팔이 빈자리에서 작업, 정책 3 위반) 액션별 stop_on_failure (실패 시 체인 중단)
D 이벤트 선택지의 로봇 연동이 단일 SPOT 명령 + 반려로봇 + LG 전원뿐이라 다단계 표현 불가 선택지는 그대로 두고, 다단계는 option_selected 규칙으로 옮긴다 (선택지 편집기에 "플로우로 연결" 안내)

2. 데이터 모델 변경 (마이그레이션 118)

-- 트리거 확장
ALTER TABLE arrival_automations DROP CONSTRAINT arrival_automations_trigger_check;
ALTER TABLE arrival_automations ADD CONSTRAINT arrival_automations_trigger_check
  CHECK (trigger IN ('arrive','leave','wrong_item','right_item','event_published','option_selected'));

-- B. 완료 처리할 태스크
ALTER TABLE arrival_automations ADD COLUMN complete_task_mode TEXT NOT NULL DEFAULT 'none'
  CHECK (complete_task_mode IN ('none','task_id','location_task'));
ALTER TABLE arrival_automations ADD COLUMN complete_task_id TEXT;          -- mode='task_id' 일 때 tasks.id

-- C. 실패 시 체인 중단
ALTER TABLE arrival_automation_actions ADD COLUMN stop_on_failure BIGINT NOT NULL DEFAULT 0 CHECK (stop_on_failure IN (0,1));
  • event_published / option_selected 는 이벤트 소유 규칙 전용(어드민 검증). option_selected 는 condition_json.option_ids: [] 로 어느 선택지에 반응할지 고른다(비면 모든 선택지).
  • complete_task_mode
    • task_id: 지정 태스크(complete_task_id). 마트 이벤트 플로우가 "마트 장보기" 태스크를 완료시키는 기본형.
    • location_task: 규칙이 실행되는 장소(이벤트가 매핑된 user_location)에서 이 세션의 미완료 태스크 전부. 태스크 ID 를 하드코딩하지 않아도 되지만 장소에 미완료 태스크가 여럿이면 다 완료돼 버리므로 기본값은 task_id.
    • 태스크 소유 규칙은 지금처럼 자기 태스크를 추적한다(기존 동작 유지, 컬럼은 none).
  • 기존 행은 전부 기본값이라 동작이 바뀌지 않는다. stop_on_failure=0 이 현재 동작(실패해도 계속).

3. 서버 실행 흐름

3.1 트리거 훅 (신규 2개)

event_timing.go  publishSelectedEvent(...) 직후           → onEventPublished(userID, eventID, locationID)
event_option_select.go  선택지 처리 직후                    → onOptionSelected(userID, eventID, optionID, locationID)
                                                              (locationID = GetUserLocationByUserAndEvent)

둘 다 ArrivalAutomationEngine.Run(RunArrivalParams{Trigger, TriggerEventID, TriggerOptionID, LocationID}) 를 goroutine 으로 호출한다. 후보 규칙은 owner_type='event' AND owner_id=eventID AND trigger=? (신규 쿼리 ListActiveAutomationsByEventTrigger). option_selected 는 evaluateRule 에서 condition.option_ids 를 검사한다.

발화 정책·조건·쿨다운·실행 로그(automation_runs)는 도착 규칙과 동일. 시험 발화(백오피스)와 이벤트 테스트 패널의 "자동화 강제 실행"은 트리거와 무관하게 규칙을 지정 실행한다(기존 Manual 경로).

3.2 액션 체인 = 플로우

dispatchActions 는 지금처럼 액션을 Next 체인으로 묶는다. 목표 플로우는 규칙 하나:

순서 device action params stop_on_failure
1 spot run_scenario {scenario_id:"market_load"} 1
2 robot_arm pick_basket {basket:1} 1
3 spot navigate_to {destination_mode:"next_location"} 0
  • 1→2: SPOT 종결 ack(SUCCESS) 를 큐가 기다린 뒤 로봇팔 발행 — sequence 정책 2.
  • 2 진행 중 SPOT 큐는 비어 있고 서버는 SPOT 에 아무것도 보내지 않는다 — 정책 5.
  • 3: next_location 은 매핑 장소 기준 다음 방문 장소(GetNextUserLocationAfter). 이벤트 규칙의 LocationID 는 매핑 장소이므로 그대로 동작.
  • 로봇팔 타임아웃은 이미 120초 기본, SPOT run_scenario 는 REJECTED 도 종결로 온다는 스키마 예외(MQTT_SCHEMA-SPOT §4)를 isTerminalSpotStatus 에 반영해야 한다 — 현재 REJECTED 는 비종결로 취급해 run_scenario 가 스텝 실패로 REJECTED 를 보내면 큐가 타임아웃까지 기다린다. ACCEPTED 이후의 REJECTED 만 종결로 보도록 수정(§6-1).

3.3 실패 시 중단 (C)

device_queue handle() 종료 시 finalActionStatus(res) 가 failed/timeout 이고 job.StopOnFailure 면 enqueueNext 를 건너뛰고, 남은 체인(Next/NextGroup 재귀)의 액션 결과를 skipped / chain_stopped 로 automation_runs 에 기록, 추적 중인 태스크 명령도 failed / chain_stopped 로 닫는다(태스크가 자동 완료되지 않도록 — 아래 3.4).

3.4 태스크 완료 (B)

  • 규칙 실행 시 complete_task_mode 에 따라 추적 태스크 ID 목록을 정하고, 체인의 모든 잡을 recordTaskCommands 로 남긴다 (태스크 소유 규칙과 같은 경로, rule_id 로 어떤 규칙이 만든 명령인지 구분).
  • 모든 명령이 종결되면 finishTaskCommand 가 ProcessTaskCompletion 을 호출 — 결정 필요: 지금은 실패 명령이 섞여도 "전부 종결"이면 완료 처리한다. 플로우에서는 stop_on_failure 로 중단된 경우 완료시키지 않는 편이 맞으므로, chain_stopped 로 닫힌 명령이 하나라도 있으면 완료 대신 상태를 유지하고 백오피스 사용자 상세에 "플로우 중단" 배지를 띄운다.
  • 완료되면 기존대로 대시보드 재발행 · 포인트 · 순차 실행 후속(117) · 이벤트 타이밍 판단이 이어진다.

3.5 선택지와의 관계 (D)

  • 선택지의 기존 단일 명령(spot_action/companion/lg)은 유지한다(호환).
  • 같은 선택지에 option_selected 규칙이 있으면 선택지의 spot_action 은 비워 두라고 편집기가 경고한다(이중 발행 방지). 서버도 규칙이 매칭되면 선택지의 spot_action 을 건너뛰도록 옵션화할 수 있으나, 1차는 경고만.
  • 반려로봇 발화(선택 직후 "실어 드릴게요")는 규칙의 첫 액션을 companion say(parallel) 로 넣으면 SPOT 과 동시에 나간다(100 병렬 그룹).

4. 백오피스

  • 이벤트 상세 → 자동화 편집기: 트리거 드롭다운에 📣 팝업이 발행됐을 때, ☝️ 선택지를 골랐을 때(선택지 다중 선택 칩) 추가. 규칙 폼에 "완료 처리할 태스크" (없음 / 태스크 선택 / 매핑 장소의 태스크) 추가. 액션 카드에 "실패 시 체인 중단" 체크.
  • 액션 순서 위에 시퀀스 다이어그램 미리보기(AutomationSequenceDiagram 재사용)로 위 mermaid 와 같은 그림을 보여준다.
  • 이벤트 테스트 패널(오늘 추가한 것)에 "선택지 선택 시뮬레이션" 버튼: 태블릿의 event/option/select 와 같은 경로를 태워 option_selected 플로우를 끝까지 검증한다. 결과 표에는 규칙별 액션 상태와 태스크 완료 여부가 이어서 보인다.
  • 실행 로그(/automations/runs)와 사용자 상세 명령 표는 그대로 쓰되 chain_stopped 사유 라벨 추가.

5. 구현 순서 (예상 4단계, 서버 → 백오피스)

  1. 마이그레이션 118 + 쿼리(수기 sqlc) + 어드민 검증(트리거/complete_task/stop_on_failure)
  2. 엔진: 트리거 훅 2개, option_ids 조건, complete_task 추적, stop_on_failure 중단 + chain_stopped 기록, run_scenario REJECTED 종결 처리
  3. 백오피스 편집기(트리거·완료 태스크·중단 체크·다이어그램) + 테스트 패널 선택지 시뮬레이션
  4. 위키: 이 문서를 "구현"으로 갱신, MQTT_SCHEMA-ORCHESTRATION_SERVER 24번에 트리거 추가

6. 결정이 필요한 것

  1. run_scenario 의 REJECTED 종결 처리 — SPOT 팀 스키마대로 "ACCEPTED 뒤에 온 REJECTED 는 종결"로 바꾼다. 다른 액션에는 영향 없음(ACCEPTED 전 REJECTED 는 여전히 완료 응답 없음). 반대 의견 없으면 진행.
  2. 실패 섞인 플로우의 태스크 완료 — 3.4 제안(중단되면 완료 안 함)으로 갈지, 지금처럼 전부 종결이면 완료로 볼지.
  3. 트리거 기본값 — 마트 플로우를 option_selected(관람객이 "실어줘" 선택) 로 할지 event_published(팝업 뜨자마자) 로 할지. 시연 통제 측면에서는 option_selected 를 권장한다(사람이 시작 시점을 쥔다).
  4. location_task 모드가 필요한지 — 없으면 task_id 만 두고 단순화.

7. 구현 결정 (2026-09-12)

원안 A~D 중 사용자 결정에 따라 다음과 같이 구현했다.

원안 결정 구현
A 트리거 팝업 버튼 클릭만 trigger='option_selected' (이벤트 소유 규칙 전용). event_published 는 만들지 않음
B 태스크 완료 없음 — 이벤트 도메인에는 완료 개념이 없다 complete_task_* 컬럼 없음. 이벤트 규칙은 user_task_commands 로 추적하지 않는다
C 실패 시 중단 보류 — 실패하면 최근 이력으로 확인 stop_on_failure 없음(실패해도 다음 단계 진행, 기존 동작). 이벤트 상세 파이프라인 카드의 "최근 이력"에서 단계별 상태·에러 확인
D 선택지 다단계 새 이벤트 타입 pipeline 팝업은 choice 처럼 버튼 표시. 버튼마다 파이프라인(규칙 1개 = condition.option_ids=[버튼], 액션 = 단계)을 "단계 추가"로 쌓는다. pipeline 이벤트는 선택지의 단일 SPOT/반려로봇/LG 명령을 쓰지 않는다(편집 모달에서 숨김, 서버도 건너뜀)
  • 서버: ProcessEventOptionSelect(MQTT 핸들러·테스트 패널 공용) → pipeline 이벤트면 onOptionSelected → Run(Trigger=option_selected, TriggerEventID, TriggerOptionID) → 규칙의 액션 체인을 디바이스 큐에 적재(앞 단계 종결 ack 대기). 응답 {ok:true, status:"pipeline_queued"}.
  • 백오피스: 이벤트 타입 "로봇 파이프라인" → "버튼(선택지)" 카드 + "🔁 파이프라인" 카드(버튼별 단계 편집, 시퀀스 다이어그램, 최근 이력). 이벤트 테스트 패널에 "버튼 선택 시뮬레이션"(가상 세션으로 종단 검증).
  • 미해결(§6-1) 해결(2026-09-21): SPOT REJECTED·BUSY 를 종결(실패)로 처리 — 완료 응답이 오지 않는 ①단계 거절이라 즉시 실패로 끝내고 다음 명령으로 넘어간다(종전엔 30초 타임아웃을 채우며 큐가 밀렸음). fault_detail 이 실패 사유(예: CHARGING)로 남는다.

8. 실패 시 멈춤(기본) + 어드민 수동 재시도 (2026-09-19, 마이그레이션 126·127, 기본화 128)

§7-C 에서 보류했던 실패 정책의 첫 조각. "로봇팔 명령이 실패해도 SPOT 이 빈자리로 가 버리는" 문제를 막는다. 재시도는 자동 반복이 아니라 어드민이 백오피스에서 직접 누른다 (126 에서 잠깐 넣었던 자동 반복·automation_retry_max_attempts 는 127 에서 폐기).

항목 내용
기본 동작 (128, 2026-09-19) 모든 액션이 실패 시 멈춤. 옵트인 체크(_hold_on_failure)는 실제 운영에서 로봇팔 FAULT 뒤 SPOT 이 그대로 출발한 사고로 폐기. 예외적으로 "실패해도 다음 단계 진행"이 필요한 액션(연출용 발화 등)만 카드의 "▶ 실패해도 다음 단계 진행" 체크 → params_json._continue_on_failure=true. 디바이스로 나가는 params 에서는 서버가 제거(popFailurePolicy)
기다리지 않는 단계 (2026-10-04) 카드의 "뒤 단계가 기다리지 않음" 체크 → params_json._no_wait=true. 같이 출발하되 뒤 단계가 완료를 기다리지 않는다(드론 배송 등). 체인에 매달려 있지 않으므로 실패해도 멈출 것이 없다 — hold_on_failure=false, awaiting_retry 를 달지 않는다. 자세한 규칙은 LOCATION_AUTOMATION_DESIGN 9-1
동작 큐 소비자(device_queue.go handle)가 종결 결과가 완료가 아니면(거절·FAULT_*·timeout·dispatch 오류) 결과를 failed/timeout 으로 남기되 Next 체인을 적재하지 않는다 — 뒤 단계(SPOT)는 queued 인 채 멈춤. 병렬 그룹의 한 잡이 멈추면 그룹 전체의 다음 단계도 나가지 않는다(heldGroups). _continue_on_failure 액션만 실패해도 다음 단계 진행
실행 로그 action_results[].hold_on_failure, awaiting_retry=true(멈춰서 클릭을 기다리는 단계), attempts(수동 재시도 횟수). command_id 는 최신 시도 것
수동 재시도 POST /api/admin/automations/runs/{runId}/retry {action_id} → RetryRunFrom: 그 단계부터 규칙의 나머지 액션 전부를 새 command_id 로 다시 체인 적재(같은 run 에 결과 덮어쓰기, attempts 이어 세기). 단, _no_wait 단계는 그 단계만 다시 보낸다 — 뒤 단계는 애초에 이 단계를 기다린 적이 없다 (2026-10-04). 실패/타임아웃 단계에만 허용(아니면 409). 태스크 추적 실행이면 되살아난 단계의 옛 user_task_commands 행을 superseded 로 닫아 자동 완료 판정이 새 행만 보게 한다
버튼 위치 이벤트 상세 파이프라인 카드 실행 이력(단계별 로그 옆) · 자동화 실행 로그 페이지 · 사용자 상세 태스크 카드의 실행 로그 · 태스크/이벤트 테스트 패널. 실패/타임아웃 단계에만 나타난다
종결 조건 SPOT REJECTED·BUSY 도 종결(실패, 2026-09-21). 멈춤은 종결(timeout 포함) 뒤에 걸린다

Clone this wiki locally