EVENT_ROBOT_FLOW_DESIGN - zero1ne-26/doc GitHub Wiki

์ด๋ฒคํŠธ ๋กœ๋ด‡ ํ”Œ๋กœ์šฐ (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: ํƒœ์Šคํฌ ์™„๋ฃŒ ์ฒ˜๋ฆฌ

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 ํฌํ•จ) ๋’ค์— ๊ฑธ๋ฆฐ๋‹ค