LOCATION_AUTOMATION_DESIGN - zero1ne-26/doc GitHub Wiki

์žฅ์†Œ ๋„์ฐฉ ์ž๋™ํ™” (Location Automation) ์„ค๊ณ„

์ž‘์„ฑ์ผ: 2026-08-22 ยท ์ƒํƒœ: 1์ฐจ ๊ตฌํ˜„ ์™„๋ฃŒ (orchestration-server 6c0c7e0, ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ 085)

๊ตฌํ˜„ ๋ฒ”์œ„: ยง3 ํ…Œ์ด๋ธ” 3๊ฐœ ยท ยง4 ์‹คํ–‰ ์—”์ง„(internal/mqttx/arrival_automation.go) ยท Admin REST (/admin/{tasks|events|locations}/:id/automations, /admin/automations[/runs|/:id/fire]) ยท ๋ฐฑ์˜คํ”ผ์Šค(ํƒœ์Šคํฌ/์ด๋ฒคํŠธ/์žฅ์†Œ ์ƒ์„ธ ํŽธ์ง‘๊ธฐ, ๋กœ๋ด‡ ์—ฐ๋™ โ†’ ๊ทœ์น™ ๋ชฉ๋กยท์‹คํ–‰ ๋กœ๊ทธ). ๋ฏธ๊ตฌํ˜„: ยง4.4 automation/fired ํ† ํ”ฝ(FE ์ˆ˜์š” ๋ฏธํ™•์ธ), ยง6-5 ์„ธ์…˜ ์ข…๋ฃŒ ์‹œ ๋Œ€๊ธฐ ์•ก์…˜ ์ทจ์†Œ, leave/dwell ํŠธ๋ฆฌ๊ฑฐ, ยง6-1 ์ˆ˜๋™>์ž๋™ ์šฐ์„ ์ˆœ์œ„. ๋กœ๋ด‡ํŒ”ยท๋ฐ˜๋ ค๋กœ๋ด‡์€ ์Šคํ‚ค๋งˆ ํ™•์ • ์ „๊นŒ์ง€ params.topic + payload JSON ์ž์œ  ์ž…๋ ฅ. 2026-08-22 ์ถ”๊ฐ€: ยง4.3 ๋””๋ฐ”์ด์Šค ๋ช…๋ น ํ(SQS FIFO, ์™„๋ฃŒ ๋Œ€๊ธฐยทํƒ€์ž„์•„์›ƒยท์•ก์…˜ ์ฒด์ธ) ๊ตฌํ˜„. 2026-09-12 ์ถ”๊ฐ€: leave(์žฅ์†Œ ์ดํƒˆ) ํŠธ๋ฆฌ๊ฑฐ ๊ตฌํ˜„ (๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ 114, ยง4 ์ฐธ๊ณ ). Vision์ด ์„ธ์…˜ ์œ„์น˜๋ฅผ ๋‹ค๋ฅธ ์žฅ์†Œ๋กœ ๋ฐ”๊ฟ” ๊ธฐ๋กํ•˜๋Š” ์ˆœ๊ฐ„ ์ง์ „ ์žฅ์†Œ์˜ ๊ทœ์น™์„ trigger='leave'๋กœ ํ‰๊ฐ€ํ•œ๋‹ค. ๊ทœ์น™ ๋ชจ๋ธยท์กฐ๊ฑดยท ๋ฐœํ™” ์ •์ฑ…ยท์•ก์…˜ยท์‹คํ–‰ ๋กœ๊ทธ๋Š” ๋„์ฐฉ๊ณผ ๋™์ผํ•˜๋ฉฐ ๋ชจ๋“  owner(ํƒœ์Šคํฌ/์ด๋ฒคํŠธ/์žฅ์†Œ)์— ๋ถ™์ผ ์ˆ˜ ์žˆ๋‹ค. ํ›„์ฒ˜๋ฆฌ ์—ฐ์ถœ์ด๋ฏ€๋กœ ์•ก์…˜์„ user_task_commands๋กœ ์ถ”์ ํ•˜์ง€ ์•Š๋Š”๋‹ค(ํƒœ์Šคํฌ ์ž๋™ ์™„๋ฃŒ ๋ฌด๊ด€). dwell์€ ์—ฌ์ „ํžˆ ๋ฏธ๊ตฌํ˜„. 2026-09-12 ์ถ”๊ฐ€ (117): ํƒœ์Šคํฌ ์‹คํ–‰ ๋ฐฉ์‹ tasks.execution_mode = parallel(๊ธฐ๋ณธ, ๋™์‹œ ์ง„ํ–‰) | sequential(์ˆœ์ฐจ). ์ˆœ์ฐจ ํƒœ์Šคํฌ์˜ ๋„์ฐฉ ๊ทœ์น™ยท์ž๋™ ์‹œ์ž‘์€ ๊ฐ™์€ ์žฅ์†Œ์˜ ์•ž ์ˆœ์„œ ํƒœ์Šคํฌ๊ฐ€ ์ „๋ถ€ ์ข…๊ฒฐ๋  ๋•Œ๊นŒ์ง€ ๋ณด๋ฅ˜(skip_reason=waiting_predecessor)๋˜๊ณ , ์•ž ํƒœ์Šคํฌ ์ข…๊ฒฐ ์‹œ OnUserTaskFinished๊ฐ€ ์‚ฌ์šฉ์ž๊ฐ€ ์•„์ง ๊ทธ ์žฅ์†Œ์— ์žˆ์œผ๋ฉด ๋Œ€๊ธฐ๊ฐ€ ํ’€๋ฆฐ ํƒœ์Šคํฌ์˜ ๊ทœ์น™๋งŒ ์žฌํ‰๊ฐ€ํ•œ๋‹ค(RunArrivalParams.OnlyTaskIDs). ์‹œํ—˜ ๋ฐœํ™”/๊ฐ•์ œ ์‹คํ–‰์€ ํ†ต๊ณผ. 2026-09-12 ์ถ”๊ฐ€ (119): device_command_logs โ€” ํ๊ฐ€ ๋ณด๋‚ธ ๋ช…๋ น(pub)๊ณผ ๋กœ๋ด‡ ์‘๋‹ต(sub: ackยทplanยทhub response)์„ command_id ๋กœ ๋ฌถ์–ด ์›๋ฌธ ์ €์žฅ. GET /admin/automations/runs/:runId/logs ๋กœ ์‹คํ–‰ 1๊ฑด์˜ ์†ก์ˆ˜์‹  ๋กœ๊ทธ๋ฅผ ๋ฐ›์•„ ํŒŒ์ดํ”„๋ผ์ธ ์‹คํ–‰ ์ด๋ ฅ์—์„œ ๋‹จ๊ณ„๋ณ„๋กœ ๋ณธ๋‹ค. 2026-09-12 ์ถ”๊ฐ€: ํƒœ์Šคํฌ ์ƒ์„ธ ํ…Œ์ŠคํŠธ ํŒจ๋„ โ€” ์„ธ์…˜์„ ๊ณจ๋ผ ์ „์ œ์กฐ๊ฑด(์„ธ์…˜ ํ™œ์„ฑยท์ฝ”์Šค ํฌํ•จยท๋ฐฐ์ •ยท์ƒํƒœยทํ˜„์žฌ ์œ„์น˜ยท ์ˆœ์ฐจ ์•ž ํƒœ์Šคํฌยท๊ทœ์น™ยท์•„์ดํ…œยทSPOT/๋กœ๋ด‡ํŒ” ๊ฐ€์šฉ์„ฑยทํƒ€์ž…๋ณ„)์„ ์ ๊ฒ€ํ•˜๊ณ  ๋ฒ„ํŠผ์œผ๋กœ ๋ฐ”๋กœ ์กฐ์น˜ํ•œ ๋’ค ๋„์ฐฉ ์‹œ๋ฎฌ๋ ˆ์ด์…˜(Vision ๊ฒฝ๋กœ ๋™์ผ) ๋˜๋Š” ๊ฐ•์ œ ์‹คํ–‰. GET/POST /admin/tasks/:id/test/{preflight,fix} (internal/handlers/task_preflight.go).

์‚ฌ์šฉ์ž๊ฐ€ ํŠน์ • ์žฅ์†Œ์— ๋„์ฐฉํ•œ ๊ฒƒ์„ Vision ์œ„์น˜ ์ •๋ณด๋กœ ๊ฐ์ง€ํ–ˆ์„ ๋•Œ, ๊ทธ ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ทธ ์žฅ์†Œ์— ๋ฐฐ์ •๋œ ํƒœ์Šคํฌ/์ด๋ฒคํŠธ์™€ ๋ณด์œ  ์•„์ดํ…œ์— ๋”ฐ๋ผ SPOT ยท ๋กœ๋ด‡ํŒ” ยท ๋ฐ˜๋ ค๋กœ๋ด‡ ยท Hub ๋“ฑ์— ์–ด๋“œ๋ฏผ์ด ๋ฏธ๋ฆฌ ์ •์˜ํ•œ ๋ช…๋ น์„ ์ž๋™์œผ๋กœ ๋‚ด๋ฆฌ๋Š” ๊ธฐ๋Šฅ์˜ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ ยท ์„œ๋ฒ„ ์‹คํ–‰ ํ๋ฆ„ ยท ๋ฐฑ์˜คํ”ผ์Šค ํ™”๋ฉด ๊ตฌ์กฐ ์ œ์•ˆ.

๋ชฉ์ฐจ

  1. ๋ฐฐ๊ฒฝ๊ณผ ๋ชฉํ‘œ
  2. ํ˜„์žฌ ์žˆ๋Š” ๊ฒƒ (์žฌํ™œ์šฉ)
  3. ๋ฐ์ดํ„ฐ ๋ชจ๋ธ
  4. ์„œ๋ฒ„ ์‹คํ–‰ ํ๋ฆ„
  5. ๋ฐฑ์˜คํ”ผ์Šค ํ™”๋ฉด ๊ตฌ์กฐ
  6. ์ •์ฑ… ๊ฒฐ์ • ์‚ฌํ•ญ
  7. ๋‹จ๊ณ„๋ณ„ ๊ตฌํ˜„ ์ˆœ์„œ
  8. ๋ฏธ๊ฒฐ ์‚ฌํ•ญ

1. ๋ฐฐ๊ฒฝ๊ณผ ๋ชฉํ‘œ

  • Vision(์นด๋ฉ”๋ผ ํŠธ๋ž˜ํ‚น)์€ vision/position/person์œผ๋กœ ๋ฐฉ๋ฌธ๊ฐ ์ขŒํ‘œยทzone_id๋ฅผ ๊ณ„์† ๋ฐœํ–‰ํ•œ๋‹ค.
  • ์„œ๋ฒ„๋Š” ์ด๋ฅผ ์žฅ์†Œ(locations)๋กœ ๋งคํ•‘ํ•ด orchestration/{sessionId}/position์œผ๋กœ ์žฌ๋ฐœํ–‰ํ•˜๊ณ , ์žฅ์†Œ๊ฐ€ ๋ฐ”๋€ ์ˆœ๊ฐ„๋งŒ user_location_history์— ๊ธฐ๋กํ•œ๋‹ค.
  • ํ˜„์žฌ "์žฅ์†Œ ๋„์ฐฉ ์‹œ ์ž๋™์œผ๋กœ ์ผ์–ด๋‚˜๋Š” ์ผ"์€ ๋Œ๋ฐœ ์ด๋ฒคํŠธ ๋ฐœํ–‰ 1๊ฐ€์ง€๋ฟ์ด๋‹ค.
  • ๋ชฉํ‘œ: ๋„์ฐฉ ์‹œ ์—ฌ๋Ÿฌ ๋””๋ฐ”์ด์Šค(SPOT, ๋กœ๋ด‡ํŒ”, ๋ฐ˜๋ ค๋กœ๋ด‡, Hub)์— ๋ช…๋ น์„ ์ˆœ์„œ๋Œ€๋กœ ์ž๋™ ๋ฐœํ–‰ํ•˜๊ณ , ๊ทธ ๊ทœ์น™์„ ์–ด๋“œ๋ฏผ์ด ๋ฐฑ์˜คํ”ผ์Šค์—์„œ ์ •์˜ยทํ™•์ธยทํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•œ๋‹ค.
  • ํ•ต์‹ฌ ์ „์ œ: "๋ฌด์—‡์„ ์ž๋™ ์ˆ˜ํ–‰ํ• ์ง€"๋Š” ์žฅ์†Œ ์ž์ฒด๊ฐ€ ์•„๋‹ˆ๋ผ (์‚ฌ์šฉ์ž, ์žฅ์†Œ, ํƒœ์Šคํฌ or ์ด๋ฒคํŠธ) ๋งคํ•‘์ด ๊ฒฐ์ •ํ•œ๋‹ค. ๊ฐ™์€ "์ง‘"์ด๋ผ๋„ ์„ธ์…˜๋งˆ๋‹ค ๋ฐฐ์ •๋œ ํƒœ์Šคํฌยท์ด๋ฒคํŠธ๊ฐ€ ๋‹ค๋ฅด๊ณ (user_tasks, user_locations.event_id), ๊ฑฐ๊ธฐ์— ๋ณด์œ  ์•„์ดํ…œ ์œ ๋ฌด๊ฐ€ ๋”ํ•ด์ ธ ์ˆ˜ํ–‰ ์—ฌ๋ถ€๊ฐ€ ๊ฐˆ๋ฆฐ๋‹ค.

1.1 ํŒ์ • ์ž…๋ ฅ ์š”์•ฝ

๋„์ฐฉ(์„ธ์…˜ S, ์žฅ์†Œ L)
  โ”œโ”€ S์—๊ฒŒ L์— ๋ฐฐ์ •๋œ ํƒœ์Šคํฌ   : user_tasks โ‹ˆ tasks(location_id = L)   โ€” 0..N๊ฐœ
  โ”œโ”€ S์—๊ฒŒ L์— ๋ฐฐ์ •๋œ ์ด๋ฒคํŠธ   : user_locations(S, L).event_id          โ€” 0..1๊ฐœ
  โ”œโ”€ (๋ณด์กฐ) L ์ž์ฒด ๊ทœ์น™        : ํƒœ์Šคํฌยท์ด๋ฒคํŠธ์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ์žฅ์†Œ์— ์˜ค๋ฉด ํ•ญ์ƒ
  โ””โ”€ S์˜ ๋ณด์œ  ์•„์ดํ…œ           : store_purchases(S)  (= orchestration/{S}/store/owned)
        โ†“
  ์œ„ ํƒœ์Šคํฌ/์ด๋ฒคํŠธ/์žฅ์†Œ์— ๋ถ™์€ ์ž๋™ํ™” ๊ทœ์น™ ์ค‘ ์กฐ๊ฑด(์•„์ดํ…œ ๋“ฑ)์„ ๋งŒ์กฑํ•˜๋Š” ๊ฒƒ๋งŒ ๋ฐœํ™”

2. ํ˜„์žฌ ์žˆ๋Š” ๊ฒƒ (์žฌํ™œ์šฉ)

์กฐ๊ฐ ์œ„์น˜ ์žฌํ™œ์šฉ ํฌ์ธํŠธ
๋„์ฐฉ ๊ฐ์ง€ internal/mqttx/vision.go recordLocationChange() โ†’ maybePublishArrivalEvent() Vision์˜ ์—ฐ์† ์ขŒํ‘œ๋ฅผ "์žฅ์†Œ๊ฐ€ ๋ฐ”๋€ ์ˆœ๊ฐ„ 1ํšŒ"๋กœ ์ด๋ฏธ ๋””๋ฐ”์šด์Šค. ์ž๋™ํ™” ํ›… ํฌ์ธํŠธ๋Š” ์—ฌ๊ธฐ ํ•œ ๊ณณ
์‚ฌ์šฉ์ž โ†” ์žฅ์†Œ โ†” ํƒœ์Šคํฌ/์ด๋ฒคํŠธ ๋งคํ•‘ user_locations(visit_order, event_id, event_published_at), user_tasks(task_id, status) + tasks.location_id ์„ธ์…˜ ์ƒ์„ฑ ์‹œ ์ด๋ฏธ ํ™•์ •๋˜๋Š” ๋งคํ•‘. ๋„์ฐฉ ์‹œ "์ด ์„ธ์…˜์ด ์ด ์žฅ์†Œ์—์„œ ํ•  ํƒœ์Šคํฌ/์ด๋ฒคํŠธ"๋ฅผ ๋ฐ”๋กœ ์กฐํšŒ ๊ฐ€๋Šฅ
SPOT ๋ช…๋ น ์ •์˜ UI task_options / event_options์˜ spot_action, spot_destination_mode, spot_params + backoffice/.../components/SpotOptionFields.tsx, spotOptions.ts "์„ ํƒ์ง€๋ฅผ ๊ณ ๋ฅด๋ฉด SPOT ๋ช…๋ น"์ด ์ด๋ฏธ ๊ตฌํ˜„๋จ. ํŠธ๋ฆฌ๊ฑฐ๋งŒ "์„ ํƒ โ†’ ๋„์ฐฉ"์œผ๋กœ ๋ฐ”๊พธ๋ฉด ๊ฐ™์€ ๋ชจ๋ธ. ํƒœ์Šคํฌยท์ด๋ฒคํŠธ ์ƒ์„ธ ํ™”๋ฉด์— ์ด๋ฏธ SPOT ์—ฐ๋™ UI๊ฐ€ ์žˆ์œผ๋ฏ€๋กœ ๋„์ฐฉ ์ž๋™ํ™”๋„ ๊ฐ™์€ ํ™”๋ฉด์— ๋ถ™์ธ๋‹ค
SPOT ๋””์ŠคํŒจ์น˜ internal/mqttx/spot.go DispatchSpotCommand(), SpotQueue ์„ธ์…˜ โ†’ ๋ฐฐ์ • ์žฅ์†Œ โ†’ devices.type='spot' resolve + ํ์ž‰ + ack/plan ๋ฆด๋ ˆ์ด ์ „๋ถ€ ์žฌ์‚ฌ์šฉ
๋””๋ฐ”์ด์Šค โ†” ์žฅ์†Œ ๋ฐฐ์ • devices + ์žฅ์†Œ ๋ฐฐ์ •, SpotManagement(waypoint label ๋งคํ•‘) ๋กœ๋ด‡ํŒ”ยท๋ฐ˜๋ ค๋กœ๋ด‡๋„ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ devices.type๋งŒ ์ถ”๊ฐ€ํ•˜๋ฉด resolve ๊ฐ€๋Šฅ
Hub ๋ช…๋ น ํฌ๋งท MQTT_SCHEMA-HUB diorama/{device}/request {session_id, scenario_id, command, target, payload, timestamp} ๋ฒ”์šฉ ํฌ๋งท์ด ์ด๋ฏธ ํ™•์ •
๋ณด์œ  ์•„์ดํ…œ ํŒ์ • task_options.required_product_id + ์Šคํ† ์–ด ๊ตฌ๋งค(store_purchase.go, orchestration/{sessionId}/store/owned) "์ด ์ƒํ’ˆ์„ ๊ฐ€์ง„ ์„ธ์…˜๋งŒ" ์กฐ๊ฑด์ด ์„ ํƒ์ง€ ๋‹จ์œ„๋กœ ์ด๋ฏธ ์กด์žฌ โ†’ ๊ทœ์น™ ์กฐ๊ฑด์œผ๋กœ ๊ทธ๋Œ€๋กœ ํ™•์žฅ
ํ…Œ์ŠคํŠธ ๊ฒฝ๋กœ ์‚ฌ์šฉ์ž ์ƒ์„ธ "์œ„์น˜ ํ…Œ์ŠคํŠธ" ํƒญ (POST /admin/users/:id/position/test) recordLocationChange๋ฅผ ํƒ€๋ฏ€๋กœ ์ž๋™ํ™” ๊ทœ์น™๋„ ํ•จ๊ป˜ ๋ฐœํ™” โ†’ ๋ณ„๋„ ํ…Œ์ŠคํŠธ ํ™”๋ฉด ๋ถˆํ•„์š”

โš ๏ธ MQTT_SCHEMA-ROBOT_ARM, MQTT_SCHEMA-COMPANION_ROBOT์€ ์•„์ง ํ† ํ”ฝ๋ช…์กฐ์ฐจ ๋น„์–ด ์žˆ๋Š” ํ…œํ”Œ๋ฆฟ์ด๋‹ค. ์ด ๋‘ ๋””๋ฐ”์ด์Šค๋Š” ์Šคํ‚ค๋งˆ ํ™•์ • ์ „๊นŒ์ง€ "ํ† ํ”ฝ + payload JSON ์ž์œ  ์ž…๋ ฅ"์œผ๋กœ ๋ฐ›์•„๋‘”๋‹ค (ยง5.3 ์ฐธ๊ณ ).

3. ๋ฐ์ดํ„ฐ ๋ชจ๋ธ

3.1 arrival_automations โ€” ๊ทœ์น™ (์ฃผ์ธ์€ ํƒœ์Šคํฌ / ์ด๋ฒคํŠธ / ์žฅ์†Œ)

์ปฌ๋Ÿผ ํƒ€์ž… ์„ค๋ช…
id TEXT PK UUID v7
owner_type TEXT task ยท event ยท location โ€” ๊ทœ์น™์ด ๋ถ™๋Š” ๋Œ€์ƒ ์ข…๋ฅ˜
owner_id TEXT tasks.id / events.id / locations.id
task_status_filter TEXT owner_type='task'์ผ ๋•Œ: pending(๊ธฐ๋ณธ, ์•„์ง ์•ˆ ํ•œ ํƒœ์Šคํฌ๋งŒ) ยท any
name TEXT ์–ด๋“œ๋ฏผ ํ‘œ์‹œ์šฉ ์ด๋ฆ„ (์˜ˆ: "์ง‘ ๋„์ฐฉ โ€” ๋ฐ˜๋ ค๋กœ๋ด‡ ์ธ์‚ฌ + SPOT ์ถ”์ข…")
trigger TEXT arrive(๋„์ฐฉ, ๊ธฐ๋ณธ) ยท leave(์ดํƒˆ, 114 โ€” ๋‹ค๋ฅธ ์žฅ์†Œ๋กœ ์ด๋™ ๊ธฐ๋ก ์‹œ ์ง์ „ ์žฅ์†Œ ๊ธฐ์ค€) ยท wrong_item/right_item(๋ฌผ๊ฑด ์ง‘์Œ, 103, ํƒœ์Šคํฌ ์ „์šฉ) ยท option_selected(ํŒŒ์ดํ”„๋ผ์ธ ์„ ํƒ์ง€, 118, ์ด๋ฒคํŠธ ์ „์šฉ) ยท pending_entered(ํƒœ์Šคํฌ๊ฐ€ pending ์ด ๋œ ์ˆœ๊ฐ„, 152, ํƒœ์Šคํฌ ์ „์šฉ) ยท dwell (ํ–ฅํ›„)
dwell_seconds INTEGER trigger='dwell'์ผ ๋•Œ ์ฒด๋ฅ˜ ๊ธฐ์ค€ ์ดˆ
condition_json TEXT(JSON) {"has_children": true|false|null, "first_visit_only": bool, "persona_ids": [], "required_product_ids": [], "required_products_mode": "all"|"any"} โ€” null/๋นˆ ๊ฐ’์€ "๋ฌด๊ด€"
fire_policy TEXT once_per_session (๊ธฐ๋ณธ) ยท every_arrival
cooldown_seconds INTEGER every_arrival์ผ ๋•Œ ์žฌ๋ฐœํ™” ์ตœ์†Œ ๊ฐ„๊ฒฉ (๊ธฐ๋ณธ 30)
is_active INTEGER 0/1
display_order INTEGER ๊ฐ™์€ owner ๋‚ด ํ‘œ์‹œ ์ˆœ์„œ
created_at, updated_at TIMESTAMP

์žฅ์†Œ๋Š” owner_type์œผ๋กœ ์œ ๋„๋œ๋‹ค โ€” task โ†’ tasks.location_id, event โ†’ ๊ทธ ์„ธ์…˜์˜ user_locations.event_id๊ฐ€ ๊ฐ€๋ฆฌํ‚ค๋Š” ์žฅ์†Œ, location โ†’ ์ž๊ธฐ ์ž์‹ . ๊ทœ์น™ ํ…Œ์ด๋ธ”์— location_id๋ฅผ ์ค‘๋ณต ์ €์žฅํ•˜์ง€ ์•Š๋Š”๋‹ค (ํƒœ์Šคํฌ๊ฐ€ ๋‹ค๋ฅธ ์žฅ์†Œ๋กœ ์˜ฎ๊ฒจ์ ธ๋„ ๊ทœ์น™์ด ๋”ฐ๋ผ๊ฐ€๋„๋ก).

3.2 arrival_automation_actions โ€” ๊ทœ์น™ 1๊ฐœ โ†’ ์ˆœ์„œ ์žˆ๋Š” ์•ก์…˜ N๊ฐœ

์ปฌ๋Ÿผ ํƒ€์ž… ์„ค๋ช…
id TEXT PK
automation_id TEXT FK โ†’ arrival_automations (ON DELETE CASCADE)
device_type TEXT spot ยท robot_arm ยท companion ยท hub
action TEXT SPOT: navigate_to/run_scenario/follow/return_home/stop (๊ธฐ์กด SPOT_ACTIONS). Hub: command ๊ฐ’. ๋กœ๋ด‡ํŒ”/๋ฐ˜๋ ค๋กœ๋ด‡: ์ž์œ  ๋ฌธ์ž์—ด
params_json TEXT(JSON) SPOT: ๊ธฐ์กด spot_params์™€ ๋™์ผ ๊ตฌ์กฐ (destination์€ ์‹คํ–‰ ์‹œ ๋™์  ๊ณ„์‚ฐ). Hub: {target, scenario_id, payload}. ๋กœ๋ด‡ํŒ”/๋ฐ˜๋ ค๋กœ๋ด‡: {topic, payload}
delay_ms INTEGER ์ด์ „ ์•ก์…˜ ๋””์ŠคํŒจ์น˜ ํ›„ ๋Œ€๊ธฐ ์‹œ๊ฐ„ (๊ธฐ๋ณธ 0)
display_order INTEGER ์‹คํ–‰ ์ˆœ์„œ

3.3 automation_runs โ€” ์‹คํ–‰ ๋กœ๊ทธ

์ปฌ๋Ÿผ ํƒ€์ž… ์„ค๋ช…
id TEXT PK
automation_id TEXT FK
user_id TEXT ์„ธ์…˜ ID (users.id)
location_id TEXT ๋ฐœํ™” ์žฅ์†Œ
matched_owner_type, matched_owner_id TEXT ์–ด๋–ค ํƒœ์Šคํฌ/์ด๋ฒคํŠธ ๋งคํ•‘ ๋•Œ๋ฌธ์— ๋ฐœํ™”(๋˜๋Š” ์Šคํ‚ต)๋๋Š”์ง€
fired_at TIMESTAMP Vision ๋ฉ”์‹œ์ง€ timestamp ๊ธฐ์ค€
status TEXT fired ยท skipped ยท partial ยท failed
skip_reason TEXT condition_unmet ยท missing_item ยท task_completed ยท already_fired ยท cooldown ยท inactive ยท no_device
action_results_json TEXT(JSON) [{device_type, action, status, command_id, error, dispatched_at}] โ€” ack ๋ฆด๋ ˆ์ด ์ˆ˜์‹  ์‹œ ๊ฐฑ์‹ 

3.4 ER ์š”์•ฝ

erDiagram
  tasks ||--o{ arrival_automations : "owner_type=task"
  events ||--o{ arrival_automations : "owner_type=event"
  locations ||--o{ arrival_automations : "owner_type=location"
  users ||--o{ user_tasks : assigned
  users ||--o{ user_locations : assigned
  user_locations }o--o| events : event_id
  tasks }o--|| locations : location_id
  arrival_automations ||--o{ arrival_automation_actions : contains
  arrival_automations ||--o{ automation_runs : fired
  users ||--o{ automation_runs : session
  devices }o--o{ locations : assigned

4. ์„œ๋ฒ„ ์‹คํ–‰ ํ๋ฆ„

vision/position/person ์ˆ˜์‹ 
  โ””โ”€ PublishSessionPosition()
       โ””โ”€ resolveSessionLocationForZone()           (๊ธฐ์กด)
            โ””โ”€ recordLocationChange() == (prevLoc, true)   (๊ธฐ์กด: ์žฅ์†Œ๊ฐ€ ๋ฐ”๋€ ์ˆœ๊ฐ„๋งŒ true; ์ง์ „ ์žฅ์†Œ๋„ ๋ฐ˜ํ™˜)
                 โ”œโ”€ maybePublishArrivalEvent()      (๊ธฐ์กด: ๋Œ๋ฐœ ์ด๋ฒคํŠธ)
                 โ”œโ”€ onLeave(session, prevLoc, ts)   โ† 114: ์ง์ „ ์žฅ์†Œ์˜ trigger='leave' ๊ทœ์น™์„ ์•„๋ž˜์™€ ๊ฐ™์€
                 โ”‚                                     ํ๋ฆ„์œผ๋กœ ํ‰๊ฐ€ (LocationID=prevLoc, ๋„์ฐฉ๋ณด๋‹ค ๋จผ์ € ์ ์žฌ,
                 โ”‚                                     ํƒœ์Šคํฌ ์ž๋™ ์‹œ์ž‘ยท์ด๋ฒคํŠธ ํƒ€์ด๋ฐ ๋“ฑ ๋ถ€์ˆ˜ ํšจ๊ณผ ์—†์Œ)
                 โ””โ”€ runArrivalAutomations(session, loc, ts)   โ† ์‹ ๊ทœ
                      1. ์ด ์„ธ์…˜ยท์žฅ์†Œ์˜ ๋งคํ•‘์„ ์ˆ˜์ง‘
                           tasks  : user_tasks โ‹ˆ tasks WHERE tasks.location_id = loc (status ํ•„ํ„ฐ ์ ์šฉ)
                           event  : user_locations(user, loc).event_id
                           location: loc ์ž์ฒด
                         โ†’ (owner_type, owner_id) ๋ชฉ๋ก์œผ๋กœ arrival_automations ํ™œ์„ฑ ๊ทœ์น™ ๋กœ๋“œ
                           (์ •๋ ฌ: location ๊ทœ์น™ โ†’ event ๊ทœ์น™ โ†’ task ๊ทœ์น™(visit/display ์ˆœ))
                         โš ๏ธ ์žฅ์†Œ ๊ทœ์น™ ์šฐ์„ (2026-10-06, Run() ๋‚ด ๊ฒŒ์ดํŠธ) โ€” arrive ํŠธ๋ฆฌ๊ฑฐ์— location/
                           event ๊ทœ์น™๊ณผ task ๊ทœ์น™์ด ์„ž์—ฌ ์žˆ์œผ๋ฉด, location/event ๊ทœ์น™์„ ๋จผ์ € ํ‰๊ฐ€ยท
                           ๋””์ŠคํŒจ์น˜ํ•˜๊ณ  ๊ทธ ์•ก์…˜์ด ์ „๋ถ€ ์ข…๊ฒฐ(completed/failed/timeout/skipped)๋ 
                           ๋•Œ๊นŒ์ง€ task ๊ทœ์น™ ํ‰๊ฐ€ ์ž์ฒด๋ฅผ ๋ฏธ๋ฃฌ๋‹ค(ํด๋ง, ์ตœ๋Œ€ 45์ดˆ โ€” ๋„˜์œผ๋ฉด ํฌ๊ธฐํ•˜๊ณ 
                           ์ง„ํ–‰). ๋‹จ์ˆœ "์ •๋ ฌ ์ˆœ์„œ"๊ฐ€ ์•„๋‹ˆ๋ผ "์žฅ์†Œ๊ฐ€ ๋จผ์ € ๋๋‚˜์•ผ ํƒœ์Šคํฌ๊ฐ€ ์‹œ์ž‘"์œผ๋กœ
                           ๊ฐ•ํ™”๋œ ๊ฒƒ. leaveยท๋ฌผ๊ฑด ์ง‘์Œยทoption_selected ํŠธ๋ฆฌ๊ฑฐ๋Š” ์• ์ดˆ์— location๊ณผ
                           task๊ฐ€ ์„ž์ด์ง€ ์•Š์œผ๋ฏ€๋กœ ์ด ๊ฒŒ์ดํŠธ๊ฐ€ ์•ˆ ๊ฑธ๋ฆฐ๋‹ค
                         โš ๏ธ ์„ธ์…˜ ๋‹จ์œ„ ๊ฒŒ์ดํŠธ(2026-10-07, location_gate.go) โ€” ์ฒซ ๋„์ฐฉ์€ ๋ณดํ†ต ํƒœ์Šคํฌ
                           ์‹œ๋”ฉ๋ณด๋‹ค ๋จผ์ € ์™€์„œ ๊ทธ ํ‰๊ฐ€์—” task ๊ทœ์น™์ด ์—†๋‹ค. ๊ทธ๋ž˜์„œ ๋„์ฐฉ์—์„œ ๋ˆ
                           location/event run ์„ ์„ธ์…˜๋ณ„๋กœ ๊ธฐ์–ตํ•˜๊ณ , ๋’ค๋Šฆ๊ฒŒ task ๊ทœ์น™๋งŒ ๋„๋Š” ํ‰๊ฐ€
                           (์‹œ๋”ฉ ํ›„ ์žฌํ‰๊ฐ€ยทpending_enteredยท์ˆœ์ฐจ ํ›„์†)๋„ ๊ทธ run ์ด ์ „๋ถ€ ์ข…๊ฒฐ๋  ๋•Œ๊นŒ์ง€
                           (์ตœ๋Œ€ 45์ดˆ) ๊ธฐ๋‹ค๋ฆฐ๋‹ค. ์ˆ˜๋™ ์‹คํ–‰ยทleaveยท๋ฌผ๊ฑด ์ง‘์Œยทoption_selected ๋Š” ์•ˆ ๊ธฐ๋‹ค๋ฆผ
                      2. ๊ทœ์น™๋ณ„ ์กฐ๊ฑด ํ‰๊ฐ€ (has_children / first_visit_only / persona / ๋ณด์œ  ์•„์ดํ…œ)
                           ๋ณด์œ  ์•„์ดํ…œ: ์„ธ์…˜์˜ ๊ตฌ๋งค ๋‚ด์—ญ(store_purchases)์— required_product_ids๊ฐ€
                           ์ „๋ถ€(all) ๋˜๋Š” ํ•˜๋‚˜๋ผ๋„(any) ์žˆ์–ด์•ผ ๋ฐœํ™”. ์—†์œผ๋ฉด skipped: missing_item
                      3. fire_policy ยท cooldown ๊ฒ€์‚ฌ (automation_runs ์ตœ๊ทผ ํ–‰ ์กฐํšŒ)
                      4. ์•ก์…˜์„ display_order ์ˆœ์œผ๋กœ goroutine์—์„œ delay_ms ๋‘๊ณ  ๋””์ŠคํŒจ์น˜
                           spot      โ†’ DispatchSpotCommand() ์žฌ์‚ฌ์šฉ (์„ธ์…˜ ๋ฐฐ์ • ์žฅ์†Œ์˜ spot ๋””๋ฐ”์ด์Šค resolve)
                           hub       โ†’ diorama/{device}/request publish
                           lg        โ†’ HA ์ „์›/๋ชจ๋“œ. params._lg_condition(2026-10-07)์ด ์žˆ์œผ๋ฉด ๊ทœ์น™
                                       ์‹œ์ž‘ ๋•Œ ๊ฐ€์ „ ์ƒํƒœ๋ฅผ ํ•œ ๋ฒˆ ์ฝ์–ด(์Šค๋ƒ…์ƒท) ๋งž์„ ๋•Œ๋งŒ ๋ณด๋‚ด๊ณ , ์•ˆ ๋งž์œผ๋ฉด
                                       skipped: lg_condition_not_met. ๋ชจ์–‘์€ ๋ฐœํ™” _lg_branches ํ•œ ์ค„๊ณผ ๊ฐ™๋‹ค
                                       ({appliance_id, feature(๊ธฐ๋ณธ power), op: is|is_not|unknown|any, value})
                           companion say + _lg_branches[i].lg_command(2026-10-07) โ†’ ๋ฐœํ™” ๋ถ„๊ธฐ๋งˆ๋‹ค ๊ฐ€์ „ ๋ช…๋ น.
                                       ๋ฐœํ™”๊ฐ€ ์†ํ•œ ๋™์‹œ ๋ฌถ์Œ ๋’ค์— "๋ถ„๊ธฐ ๊ฐ€์ „ ๋ช…๋ น" ๋‹จ๊ณ„(action_id={say}#lg)๋ฅผ
                                       ๋ผ์šฐ๊ณ , ๋ฐœํ™” ์ง์ „ ๊ณ ๋ฅธ ๋ถ„๊ธฐ์˜ ๋ช…๋ น๋งŒ ๋ฐœํ™”๊ฐ€ ๋๋‚œ ๋’ค ๋ณด๋‚ธ๋‹ค(์—†์œผ๋ฉด skipped).
                                       ์‹คํ–‰ ๋กœ๊ทธ์— branchยทlg_command ๊ธฐ๋ก, ์žฌ์‹œ๋„๋Š” ๊ทธ ๋ช…๋ น๋งŒ ์žฌ๋ฐœ์†ก
                           robot_arm / companion โ†’ params_json.topic ์œผ๋กœ payload publish (์Šคํ‚ค๋งˆ ํ™•์ • ์ „ ์ž„์‹œ)
                      5. automation_runs ๊ธฐ๋ก (skipped ํฌํ•จ), ack/response ๋ฆด๋ ˆ์ด ์ˆ˜์‹  ์‹œ ๊ฒฐ๊ณผ ๊ฐฑ์‹ 

4.0 ๋งคํ•‘ ๊ธฐ๋ฐ˜ ์„ ํƒ ์˜ˆ์‹œ

์„ธ์…˜ ์žฅ์†Œ "์ง‘"์— ๋ฐฐ์ •๋œ ๊ฒƒ ๋ณด์œ  ์•„์ดํ…œ ๋ฐœํ™”๋˜๋Š” ๊ทœ์น™
์ง€ํ˜ธ ํƒœ์Šคํฌ "์กฐ๋ช… ์ผœ๊ธฐ" + ์ด๋ฒคํŠธ "ํƒ๋ฐฐ ๋„์ฐฉ" ์—†์Œ ์ด๋ฒคํŠธ "ํƒ๋ฐฐ ๋„์ฐฉ"์˜ ๊ทœ์น™(๋กœ๋ด‡ํŒ” ์ „๋‹ฌ)์€ ์•„์ดํ…œ ํ•„์š” โ†’ missing_item ์Šคํ‚ต(= ์ฆ‰์‹œ ์ข…๊ฒฐ๋กœ ๊ฐ„์ฃผ). ์ด๋ฒคํŠธ ๊ทœ์น™์ด ์ข…๊ฒฐ๋œ ๋’ค์—์•ผ ํƒœ์Šคํฌ ๊ทœ์น™(๋ฐ˜๋ ค๋กœ๋ด‡ ์ธ์‚ฌ) ํ‰๊ฐ€
์ˆ˜์•„ ํƒœ์Šคํฌ "์กฐ๋ช… ์ผœ๊ธฐ" ๊ฐ„์‹ ์„ธํŠธ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐฐ์ • ์•ˆ ๋์œผ๋ฏ€๋กœ ์ด๋ฒคํŠธ ๊ทœ์น™์€ ํ›„๋ณด์—๋„ ์•ˆ ๋“ค์–ด๊ฐ โ†’ ์žฅ์†Œ ๊ทœ์น™์ด ์—†์œผ๋ฉด ํƒœ์Šคํฌ ๊ทœ์น™์ด ๋ฐ”๋กœ ํ‰๊ฐ€(๊ฒŒ์ดํŠธ ๋Œ€์ƒ์ด ์—†์Œ)
๋ฏผ์ค€ ํƒœ์Šคํฌ "์กฐ๋ช… ์ผœ๊ธฐ"(์ด๋ฏธ ์™„๋ฃŒ) ๊ฐ„์‹ ์„ธํŠธ task_status_filter=pending์ด๋ฉด ํ›„๋ณด ์ œ์™ธ โ†’ ์•„๋ฌด๊ฒƒ๋„ ๋ฐœํ™” ์•ˆ ํ•จ (์žฅ์†Œ ๊ทœ์น™์ด ์žˆ์œผ๋ฉด ๊ทธ๊ฒƒ๋ถ€ํ„ฐ ์ „๋ถ€ ๋๋‚œ ๋’ค์— ํ‰๊ฐ€ํ•˜์ง€๋งŒ, ์ด ๊ฒฝ์šฐ ์• ์ดˆ์— ํƒœ์Šคํฌ ํ›„๋ณด๊ฐ€ ์—†์Œ)

4.1 ๋ณด์œ  ์•„์ดํ…œ ์กฐ๊ฑด ํ‰๊ฐ€

  • ํŒ์ • ์†Œ์Šค๋Š” ์„ธ์…˜์˜ ์Šคํ† ์–ด ๊ตฌ๋งค ๋‚ด์—ญ = orchestration/{sessionId}/store/owned์™€ ๋™์ผํ•œ ์ง‘ํ•ฉ.
  • ๊ทœ์น™ ๋‹จ์œ„(condition_json.required_product_ids)์™€ ์•ก์…˜ ๋‹จ์œ„(params_json.required_product_id, ์„ ํƒ) ๋‘ ์ธต์œผ๋กœ ๋‘”๋‹ค. ๊ทœ์น™ ๋‹จ์œ„๋Š” "์•„์ดํ…œ ์—†์œผ๋ฉด ๊ทœ์น™ ์ „์ฒด ์Šคํ‚ต", ์•ก์…˜ ๋‹จ์œ„๋Š” "์ด ์•ก์…˜๋งŒ ๊ฑด๋„ˆ๋›ฐ๊ณ  ๋‚˜๋จธ์ง€๋Š” ์ง„ํ–‰" (์˜ˆ: ๊ฐ„์‹ ์•„์ดํ…œ ์žˆ์„ ๋•Œ๋งŒ ๋กœ๋ด‡ํŒ” ์ „๋‹ฌ, ๋ฐ˜๋ ค๋กœ๋ด‡ ์ธ์‚ฌ๋Š” ํ•ญ์ƒ).
  • ๊ตฌ๋งค๋Š” ์„ธ์…˜ ์ค‘ ์–ธ์ œ๋“  ์ผ์–ด๋‚˜๋ฏ€๋กœ ๋ฐœํ™” ์‹œ์ ์— ์กฐํšŒํ•œ๋‹ค (์บ์‹œ X). once_per_session ๊ทœ์น™์ด ์•„์ดํ…œ ๋ถ€์žฌ๋กœ missing_item ์Šคํ‚ต๋œ ๊ฒฝ์šฐ๋Š” "๋ฐœํ™”ํ•œ ๊ฒƒ"์œผ๋กœ ์น˜์ง€ ์•Š์•„, ์ดํ›„ ์•„์ดํ…œ์„ ์‚ฌ๊ณ  ๋‹ค์‹œ ๋„์ฐฉํ•˜๋ฉด ๋ฐœํ™”๋œ๋‹ค.

4.2 ๋””๋ฐ”์ด์Šค resolve ๊ทœ์น™

  • ์„ธ์…˜์— ๋ฐฐ์ •๋œ ์žฅ์†Œ(user_locations)์— ๋งคํ•‘๋œ devices(ํ•ด๋‹น type, is_active=1) ์ค‘ ์ฒซ ๋ฒˆ์งธ.
  • ์—†์œผ๋ฉด ์•ก์…˜์„ skipped: no device๋กœ ๋กœ๊ทธ์— ๋‚จ๊ธฐ๊ณ  ๋‹ค์Œ ์•ก์…˜์€ ๊ณ„์† ์ง„ํ–‰ํ•œ๋‹ค (ํ•œ ๋””๋ฐ”์ด์Šค ๋ถ€์žฌ๊ฐ€ ์ „์ฒด ๊ทœ์น™์„ ๋ง‰์ง€ ์•Š๋„๋ก).

4.3 ๋””๋ฐ”์ด์Šค ๋ช…๋ น ํ (SQS FIFO) โ€” 2026-08-22 ๊ตฌํ˜„

๋ชจ๋“  ๋””๋ฐ”์ด์Šค ๋ช…๋ น(SPOTยท๋กœ๋ด‡ํŒ”ยท๋ฐ˜๋ ค๋กœ๋ด‡ยทHub, ์ž๋™ํ™”/ํƒœ๋ธ”๋ฆฟ/์„ ํƒ์ง€ ์ถœ์ฒ˜ ๋ถˆ๋ฌธ)์€ internal/mqttx/device_queue.go์˜ ๋””๋ฐ”์ด์Šค ๋ช…๋ น ํ๋ฅผ ๊ฑฐ์นœ๋‹ค. ๋ฐฑ์—”๋“œ๋Š” internal/devicequeue/ โ€” SQS_ENDPOINT๊ฐ€ ์žˆ์œผ๋ฉด ministack SQS FIFO(device-commands.fifo

  • -dlq.fifo), ์—†์œผ๋ฉด ํ”„๋กœ์„ธ์Šค ๋‚ด ๋ฉ”๋ชจ๋ฆฌ ํ(๊ฐ™์€ ๊ณ„์•ฝ).
Enqueue(job)  โ†’ SQS FIFO (MessageGroupId = ๋””๋ฐ”์ด์Šค ํ‚ค: spot:{robotId} | hub | robot_arm:{topic} | companion:{topic})
consumer      โ†’ Receive โ†’ delay_ms ๋Œ€๊ธฐ โ†’ MQTT ๋ฐœํ–‰ โ†’ ์™„๋ฃŒ ์‹ ํ˜ธ ๋Œ€๊ธฐ(timeout_ms) โ†’ DeleteMessage โ†’ job.next ์ ์žฌ
  • ๋””๋ฐ”์ด์Šค ๋‹จ์œ„ ์ง๋ ฌํ™”: ๊ฐ™์€ ๊ทธ๋ฃน์€ ์•ž ์žก์„ Deleteํ•˜๊ธฐ ์ „๊นŒ์ง€ ๋‹ค์Œ ์žก์ด ๋‚˜์˜ค์ง€ ์•Š๋Š”๋‹ค. ์„ธ์…˜์ด ๋‹ฌ๋ผ๋„ ๊ฐ™์€ SPOT์ด๋ฉด ์ˆœ์„œ๋Œ€๋กœ. (SQS๋Š” ํ•œ Receive ๋ฐฐ์น˜ ์•ˆ์—์„œ ๊ฐ™์€ ๊ทธ๋ฃน์„ ์—ฌ๋Ÿฌ ๊ฐœ ๋‚ด์ค„ ์ˆ˜ ์žˆ์–ด ์†Œ๋น„์ž๋„ ๊ทธ๋ฃน๋ณ„ ๋ฝ์œผ๋กœ ํ•œ ๋ฒˆ ๋” ์ง๋ ฌํ™”ํ•œ๋‹ค.)
  • ๊ทœ์น™ ์•ˆ ์•ก์…˜ ์ฒด์ธ: ์ฒซ ์•ก์…˜๋งŒ ํ์— ๋„ฃ๊ณ  job.next๋กœ ๋‹ค์Œ ์•ก์…˜์„ ์‹ค์–ด, ์•ž ์•ก์…˜ ์™„๋ฃŒ ํ›„ ์†Œ๋น„์ž๊ฐ€ ๋‹ค์Œ์„ ์ ์žฌํ•œ๋‹ค โ†’ ๋””๋ฐ”์ด์Šค๊ฐ€ ๋‹ฌ๋ผ๋„ ์•ก์…˜์€ ์ˆœ์ฐจ. delay_ms = "์•ž ์•ก์…˜ ์™„๋ฃŒ ํ›„ ๋Œ€๊ธฐ".
  • ์™„๋ฃŒ ์‹ ํ˜ธ: SPOT robot/{id}/ack ์ข…๊ฒฐ status ยท Hub diorama/{device}/response.result ยท ๋กœ๋ด‡ํŒ”/๋ฐ˜๋ ค๋กœ๋ด‡์€ ์Šคํ‚ค๋งˆ ํ™•์ • ์ „๊นŒ์ง€ params.duration_ms(๊ธฐ๋ณธ 3์ดˆ) ๊ฒฝ๊ณผ๋ฅผ ์™„๋ฃŒ๋กœ ๊ฐ„์ฃผ.
  • ํƒ€์ž„์•„์›ƒ: arrival_automation_actions.timeout_ms(๊ธฐ๋ณธ 30์ดˆ). ๋„˜๊ธฐ๋ฉด timeout์œผ๋กœ ๊ธฐ๋กํ•˜๊ณ  ๋‹ค์Œ์œผ๋กœ ์ง„ํ–‰. ๊ฐ€์‹œ์„ฑ ํƒ€์ž„์•„์›ƒ์„ delay+timeout+์—ฌ์œ ๋กœ ๋Š˜๋ ค ์žฌ์ „๋‹ฌ์„ ๋ง‰๊ณ , ์†Œ๋น„์ž๊ฐ€ ๋‘ ๋ฒˆ ์ฃฝ์œผ๋ฉด DLQ.
  • ์•ก์…˜ ์ƒํƒœ: queued โ†’ dispatched โ†’ completed | timeout | failed (+ device_status์— ack ์›๋ฌธ)์ด automation_runs.action_results_json์— ๊ฐฑ์‹ ๋˜๊ณ  ๋ฐฑ์˜คํ”ผ์Šค ์‹คํ–‰ ๋กœ๊ทธยทministack ํ™”๋ฉด์—์„œ ๋ณด์ธ๋‹ค.
  • ์šฐ์„ ์ˆœ์œ„(์ˆ˜๋™ > ์ž๋™)๋Š” ๋ฏธ๊ตฌํ˜„ โ€” ๋ชจ๋‘ ๊ฐ™์€ FIFO.

4.4 MQTT ํ† ํ”ฝ (์‹ ๊ทœ)

Topic ๋ฐฉํ–ฅ QoS Retained ์„ค๋ช…
orchestration/{sessionId}/automation/fired Sโ†’C 1 X ๊ทœ์น™ ๋ฐœํ™” ์•Œ๋ฆผ {automation_id, name, location_id, actions:[{device_type, action, command_id, status}]} โ€” ํƒœ๋ธ”๋ฆฟ์ด "๋กœ๋ด‡์ด ์›€์ง์ž…๋‹ˆ๋‹ค" ๊ฐ™์€ ์•ˆ๋‚ด๋ฅผ ๋„์šฐ๋Š” ์šฉ๋„(์„ ํƒ)

5. ๋ฐฑ์˜คํ”ผ์Šค ํ™”๋ฉด ๊ตฌ์กฐ

์‚ฌ์ด๋“œ๋ฐ”
โ”œโ”€ ํƒœ์Šคํฌ ๊ด€๋ฆฌ โ†’ ํƒœ์Šคํฌ ์ƒ์„ธ (/tasks/:id)
โ”‚       โ”œโ”€ ๊ธฐ๋ณธ ์ •๋ณด / ์„ ํƒ์ง€(์˜ต์…˜๋ณ„ SPOT ์—ฐ๋™)    (๊ธฐ์กด)
โ”‚       โ””โ”€ [NEW] ๋„์ฐฉ ์ž๋™ํ™” ์„น์…˜                  โ† ๊ทœ์น™์˜ "์ฃผ ํŽธ์ง‘ ํ™”๋ฉด" โ‘ 
โ”œโ”€ ์ด๋ฒคํŠธ ๊ด€๋ฆฌ โ†’ ์ด๋ฒคํŠธ ์ƒ์„ธ (/events/:id)
โ”‚       โ”œโ”€ ๊ธฐ๋ณธ ์ •๋ณด / ์„ ํƒ์ง€(์˜ต์…˜๋ณ„ SPOT ์—ฐ๋™)    (๊ธฐ์กด)
โ”‚       โ””โ”€ [NEW] ๋„์ฐฉ ์ž๋™ํ™” ์„น์…˜                  โ† ๊ทœ์น™์˜ "์ฃผ ํŽธ์ง‘ ํ™”๋ฉด" โ‘ก
โ”œโ”€ ์žฅ์†Œ ๊ด€๋ฆฌ
โ”‚   โ””โ”€ ์žฅ์†Œ ์ƒ์„ธ (/locations/:id)
โ”‚       โ”œโ”€ ๊ธฐ๋ณธ ์ •๋ณด / ํƒœ์Šคํฌ / ์ฝ˜ํ…์ธ            (๊ธฐ์กด)
โ”‚       โ””โ”€ [NEW] ๋„์ฐฉ ์ž๋™ํ™” ํƒญ
โ”‚             โ”œโ”€ ์žฅ์†Œ ์ž์ฒด ๊ทœ์น™ (owner_type=location) ํŽธ์ง‘
โ”‚             โ””โ”€ ์ด ์žฅ์†Œ์˜ ํƒœ์Šคํฌยท์ด๋ฒคํŠธ์— ๋ถ™์€ ๊ทœ์น™ **์ฝ๊ธฐ ์ „์šฉ ์ง‘๊ณ„** (ํด๋ฆญ โ†’ ํ•ด๋‹น ์ƒ์„ธ๋กœ)
โ”‚
โ””โ”€ ๋กœ๋ด‡ ์—ฐ๋™   โ† ๊ธฐ์กด "SPOT ๊ด€๋ฆฌ" ๋„ค๋น„ ํŠธ๋ฆฌ๋ฅผ ์ด๋ฆ„ ๋ฐ”๊ฟ” ํ™•์žฅ
    โ”œโ”€ SPOT ์žฅ์†Œ ๊ด€๋ฆฌ (/spot-management)          (๊ธฐ์กด, waypoint ๋งคํ•‘)
    โ”œโ”€ [NEW] ์ž๋™ํ™” ๊ทœ์น™ ์ „์ฒด (/automations)       โ† ์žฅ์†Œยท๋””๋ฐ”์ด์Šค ์ถ• read-mostly ๋ชฉ๋ก
    โ”œโ”€ [NEW] ์ž๋™ํ™” ์‹คํ–‰ ๋กœ๊ทธ (/automations/runs)  โ† automation_runs ํƒ€์ž„๋ผ์ธ
    โ””โ”€ ํ…Œ์ŠคํŠธ (/spot-test)                         (๊ธฐ์กด + SPOT ์œ„์น˜ ํ…Œ์ŠคํŠธ)

์™œ ํƒœ์Šคํฌ/์ด๋ฒคํŠธ ์ƒ์„ธ๋ฅผ ์ฃผ ํŽธ์ง‘ ํ™”๋ฉด์œผ๋กœ ๋‘๋‚˜ โ€” ์ˆ˜ํ–‰ ์—ฌ๋ถ€๋ฅผ ๊ฐ€๋ฅด๋Š” ๊ฑด "์ด ์„ธ์…˜์ด ์ด ์žฅ์†Œ์—์„œ ๋ฌด์Šจ ํƒœ์Šคํฌ/์ด๋ฒคํŠธ๋ฅผ ํ•˜๋А๋ƒ"์ด๋ฏ€๋กœ, ๊ทœ์น™์€ ๊ทธ ํƒœ์Šคํฌ/์ด๋ฒคํŠธ์˜ ์†์„ฑ์ด๋‹ค. ์ด๋ฏธ ํƒœ์Šคํฌยท์ด๋ฒคํŠธ ์ƒ์„ธ์— "์„ ํƒ์ง€๋ฅผ ๊ณ ๋ฅด๋ฉด SPOT ๋ช…๋ น"(SpotOptionFields)์ด ์žˆ์–ด์„œ, ๋ฐ”๋กœ ์˜†์— "๋„์ฐฉํ•˜๋ฉด ์ž๋™์œผ๋กœ" ์„น์…˜์ด ๋ถ™์œผ๋ฉด ์–ด๋“œ๋ฏผ์ด ํ•œ ํ™”๋ฉด์—์„œ ์„ ํƒ ์‹œ ๋™์ž‘ vs ๋„์ฐฉ ์‹œ ๋™์ž‘์„ ๊ฐ™์ด ์„ค๊ณ„ํ•  ์ˆ˜ ์žˆ๋‹ค. ์žฅ์†Œ ์ƒ์„ธ ํƒญ์€ "์ด ์žฅ์†Œ์—์„œ ์ผ์–ด๋‚  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ์ž๋™ํ™”"๋ฅผ ํ•œ๋ˆˆ์— ๋ณด๋Š” ์ง‘๊ณ„ + ์žฅ์†Œ ๊ณตํ†ต ๊ทœ์น™ ํŽธ์ง‘์šฉ. ์ „์ฒด ๋ชฉ๋ก์€ ๋””๋ฐ”์ด์Šค ์ถ• ์—ญ์กฐํšŒ์šฉ ๋ณด์กฐ ๋ทฐ.

5.1 ํƒœ์Šคํฌ/์ด๋ฒคํŠธ ์ƒ์„ธ โ†’ ๋„์ฐฉ ์ž๋™ํ™” ์„น์…˜ (์žฅ์†Œ ์ƒ์„ธ์˜ ์žฅ์†Œ ๊ทœ์น™๋„ ๋™์ผ ์ปดํฌ๋„ŒํŠธ)

โ”Œโ”€ ๋„์ฐฉ ์ž๋™ํ™”  (์ด ํƒœ์Šคํฌ๊ฐ€ ๋ฐฐ์ •๋œ ์„ธ์…˜์ด "์ง‘"์— ๋„์ฐฉํ•˜๋ฉด) โ”€โ”€โ”€โ”€ [+ ๊ทœ์น™ ์ถ”๊ฐ€] โ”€โ”
โ”‚                                                                           โ”‚
โ”‚ โ–พ ๋ฐ˜๋ ค๋กœ๋ด‡ ์ธ์‚ฌ + SPOT ์ถ”์ข…                        [ํ™œ์„ฑ โ—]  [์‹œํ—˜ ๋ฐœํ™” โ–พ] โ”‚
โ”‚   ํŠธ๋ฆฌ๊ฑฐ: ๋„์ฐฉ ยท ๋Œ€์ƒ: ๋ฏธ์™„๋ฃŒ ํƒœ์Šคํฌ๋งŒ ยท ์กฐ๊ฑด: ์•„์ด ๋™๋ฐ˜ ๋ฌด๊ด€ ยท ์„ธ์…˜๋‹น 1ํšŒ   โ”‚
โ”‚   ํ•„์š” ์•„์ดํ…œ: ์—†์Œ                                                        โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚   โ”‚ ์ˆœ์„œโ”‚ ๋””๋ฐ”์ด์Šค  โ”‚ ๋ช…๋ น         โ”‚ ํŒŒ๋ผ๋ฏธํ„ฐ               โ”‚ ์ง€์—ฐ   โ”‚      โ”‚ โ”‚
โ”‚   โ”œโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚
โ”‚   โ”‚ 1  โ”‚ ๋ฐ˜๋ ค๋กœ๋ด‡  โ”‚ greet        โ”‚ {"voice":"hello"}      โ”‚ 0 ms   โ”‚ โ†‘โ†“ ๐Ÿ—‘ โ”‚ โ”‚
โ”‚   โ”‚ 2  โ”‚ SPOT     โ”‚ follow       โ”‚ mode=start             โ”‚ 1500ms โ”‚ โ†‘โ†“ ๐Ÿ—‘ โ”‚ โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚   [+ ์•ก์…˜ ์ถ”๊ฐ€]                                                            โ”‚
โ”‚                                                                           โ”‚
โ”‚ โ–ธ (์•„์ด ๋™๋ฐ˜) ๋กœ๋ด‡ํŒ” ๊ฐ„์‹ ์ „๋‹ฌ                     [ํ™œ์„ฑ โ—‹]  [์‹œํ—˜ ๋ฐœํ™” โ–พ] โ”‚
โ”‚   ํ•„์š” ์•„์ดํ…œ: ๐Ÿ›’ ๊ฐ„์‹ ์„ธํŠธ (all)                                          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
  • ์„น์…˜ ํ—ค๋”์— ๊ทœ์น™์ด ๋ฐœํ™”๋˜๋Š” ์žฅ์†Œ๋ฅผ ๋ช…์‹œ (tasks.location_id / ์ด๋ฒคํŠธ๋Š” "๋ฐฐ์ •๋œ ์žฅ์†Œ")
  • ๊ทœ์น™ ์นด๋“œ: ์ด๋ฆ„ ยท ํŠธ๋ฆฌ๊ฑฐ ยท (ํƒœ์Šคํฌ) ์ƒํƒœ ํ•„ํ„ฐ ยท ์กฐ๊ฑด ์š”์•ฝ ยท ํ•„์š” ์•„์ดํ…œ ์นฉ(์Šคํ† ์–ด ์ƒํ’ˆ ๋‹ค์ค‘ ์„ ํƒ, all/any) ยท ๋ฐœํ™” ์ •์ฑ… ยท ํ™œ์„ฑ ํ† ๊ธ€ ยท ์‹œํ—˜ ๋ฐœํ™”(์ด ํƒœ์Šคํฌ/์ด๋ฒคํŠธ๊ฐ€ ๋ฐฐ์ •๋œ ํ™œ์„ฑ ์„ธ์…˜๋งŒ ๋ชฉ๋ก์— ๋œธ โ†’ ์„ ํƒ ์‹œ ์ฆ‰์‹œ ์‹คํ–‰, automation_runs์— manual=true๋กœ ๊ธฐ๋ก)
  • ๊ฐ™์€ ์ปดํฌ๋„ŒํŠธ(ArrivalAutomationEditor)๋ฅผ ์žฅ์†Œ ์ƒ์„ธ ํƒญ์˜ "์žฅ์†Œ ๊ทœ์น™"์—๋„ ์žฌ์‚ฌ์šฉ
  • ์•ก์…˜ ํ…Œ์ด๋ธ”: ์ˆœ์„œ ๋ณ€๊ฒฝ(โ†‘โ†“), ์‚ญ์ œ, ํ–‰ ํด๋ฆญ ์‹œ ์ธ๋ผ์ธ/๋ชจ๋‹ฌ ํŽธ์ง‘
  • ๋งˆ์ง€๋ง‰ ์‹คํ–‰ ๊ฒฐ๊ณผ ๋ฐฐ์ง€ (์ตœ๊ทผ run์˜ status) โ€” ๋กœ๊ทธ ํ™”๋ฉด์œผ๋กœ ๋งํฌ

5.2 ์ž๋™ํ™” ๊ทœ์น™ ์ „์ฒด (/automations)

์ปฌ๋Ÿผ ์„ค๋ช…
์ฃผ์ธ ํƒœ์Šคํฌ ยท ์กฐ๋ช… ์ผœ๊ธฐ / ์ด๋ฒคํŠธ ยท ํƒ๋ฐฐ ๋„์ฐฉ / ์žฅ์†Œ ยท ์ง‘ ์นฉ โ€” ํด๋ฆญ โ†’ ํ•ด๋‹น ์ƒ์„ธ
์žฅ์†Œ ์œ ๋„๋œ ์žฅ์†Œ
๊ทœ์น™๋ช…
ํŠธ๋ฆฌ๊ฑฐ / ์กฐ๊ฑด ์š”์•ฝ (ํ•„์š” ์•„์ดํ…œ ํฌํ•จ)
์•ก์…˜ ๋””๋ฐ”์ด์Šค ์นฉ ๋‚˜์—ด (SPOT ๋ฐ˜๋ ค๋กœ๋ด‡ โ€ฆ)
ํ™œ์„ฑ ํ† ๊ธ€ (์—ฌ๊ธฐ์„œ๋„ ๋ฐ”๋กœ on/off)
์ตœ๊ทผ ๋ฐœํ™” ์‹œ๊ฐ + ์ƒํƒœ ๋ฐฐ์ง€

ํ•„ํ„ฐ: ์ฃผ์ธ ์ข…๋ฅ˜(ํƒœ์Šคํฌ/์ด๋ฒคํŠธ/์žฅ์†Œ) ยท ์žฅ์†Œ ยท ๋””๋ฐ”์ด์Šค ํƒ€์ž… ยท ํ™œ์„ฑ ์—ฌ๋ถ€.

5.3 ์•ก์…˜ ํŽธ์ง‘ ํผ (๋””๋ฐ”์ด์Šค ํƒ€์ž…๋ณ„ ๋ถ„๊ธฐ)

๋””๋ฐ”์ด์Šค ์ž…๋ ฅ ๋น„๊ณ 
SPOT SpotOptionFields ๊ทธ๋Œ€๋กœ ์žฌ์‚ฌ์šฉ (spot_action / spot_destination_mode / scenarioยทvariablesยทfollow mode) navigate_to์˜ destination์€ ์„ธ์…˜ ์žฅ์†Œ โ†’ waypoint label๋กœ ๋™์  ๊ณ„์‚ฐ(own/next). 2026-09-16๋ถ€ํ„ฐ destination_mode=waypoint์ด๋ฉด ํŽธ์ง‘๊ธฐ์—์„œ SPOT์˜ retained waypoints ๋ชฉ๋ก(+์žฅ์†Œ ๋งคํ•‘)์—์„œ ๊ณ ๋ฅธ params.destination์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ โ€” SPOT ํ…Œ์ŠคํŠธ ํ™”๋ฉด๊ณผ ๋™์ผ
Hub command, target, scenario_id, payload(JSON) diorama/{device}/request ํฌ๋งท ํ™•์ •๋จ
๋กœ๋ด‡ํŒ” ยท ๋ฐ˜๋ ค๋กœ๋ด‡ ํ† ํ”ฝ ๋ฌธ์ž์—ด + payload JSON ์ž์œ  ์ž…๋ ฅ + โš ๏ธ "์Šคํ‚ค๋งˆ ๋ฏธํ™•์ •" ๋ฐฐ์ง€ ์Šคํ‚ค๋งˆ ํ™•์ •๋˜๋ฉด ๋“œ๋กญ๋‹ค์šด/ํƒ€์ž…๋“œ ํผ์œผ๋กœ ๊ต์ฒด. session_id๋Š” ์„œ๋ฒ„๊ฐ€ ์ž๋™ ์ฃผ์ž…

๊ณตํ†ต: delay_ms ์ˆซ์ž ์ž…๋ ฅ, ์ˆœ์„œ, ํ•„์š” ์•„์ดํ…œ(์„ ํƒ) โ€” ์ด ์•ก์…˜๋งŒ ํ•ด๋‹น ์ƒํ’ˆ ๋ณด์œ  ์‹œ ์‹คํ–‰.

5.4 ์ž๋™ํ™” ์‹คํ–‰ ๋กœ๊ทธ (/automations/runs)

์‹œ๊ฐ        ์„ธ์…˜(ํŽ˜๋ฅด์†Œ๋‚˜)   ์žฅ์†Œ    ์ฃผ์ธ                 ๊ทœ์น™                  ์ƒํƒœ      ์•ก์…˜ ๊ฒฐ๊ณผ
13:36:23    ์ง€ํ˜ธ (abc123)    ์ง‘      ํƒœ์Šคํฌยท์กฐ๋ช… ์ผœ๊ธฐ      ๋ฐ˜๋ ค๋กœ๋ด‡+SPOT         fired     ๋ฐ˜๋ ค๋กœ๋ด‡ โœ“ ยท SPOT ACCEPTEDโ†’SUCCESS
13:36:23    ์ง€ํ˜ธ (abc123)    ์ง‘      ์ด๋ฒคํŠธยทํƒ๋ฐฐ ๋„์ฐฉ      ๋กœ๋ด‡ํŒ” ๊ฐ„์‹ ์ „๋‹ฌ      skipped   missing_item (๊ฐ„์‹ ์„ธํŠธ)
13:35:50    ์ง€ํ˜ธ (abc123)    ๋งˆํŠธ    ํƒœ์Šคํฌยท์žฅ๋ณด๊ธฐ         ๋กœ๋ด‡ํŒ” ์ƒํ’ˆ ์ „๋‹ฌ      skipped   no device (robot_arm)
13:20:01    ๋ฏผ์ค€ (ghi789)    ์ง‘      ํƒœ์Šคํฌยท์กฐ๋ช… ์ผœ๊ธฐ      ๋ฐ˜๋ ค๋กœ๋ด‡+SPOT        skipped   task_completed (status filter)
13:20:01    ์ˆ˜์•„ (def456)    ์ง‘      ์ง‘ ๋„์ฐฉ โ€” ๋ฐ˜๋ ค๋กœ๋ด‡+SPOT   skipped   already_fired (once_per_session)
  • ํ•„ํ„ฐ: ์„ธ์…˜ ยท ์žฅ์†Œ ยท ๊ทœ์น™ ยท ์ƒํƒœ ยท ๊ธฐ๊ฐ„
  • ํ–‰ ํŽผ์น˜๋ฉด action_results_json ์›๋ฌธ + ๋ฐœํ–‰ํ•œ MQTT payload
  • ์‚ฌ์šฉ์ž ์ƒ์„ธ(/task-completions/:sessionUuid)์—๋„ "์ž๋™ํ™”" ํƒญ์œผ๋กœ ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฅผ ์„ธ์…˜ ํ•„ํ„ฐ ๊ณ ์ •์œผ๋กœ ๋…ธ์ถœ

6. ์ •์ฑ… ๊ฒฐ์ • ์‚ฌํ•ญ

# ์ด์Šˆ ์ œ์•ˆ
1 ์ˆ˜๋™ vs ์ž๋™ SPOT ๋ช…๋ น ์ถฉ๋Œ โ€” ๋„์ฐฉ ๊ทœ์น™์ด follow๋ฅผ ๋ณด๋‚ธ ์งํ›„ ์‚ฌ์šฉ์ž๊ฐ€ ํƒœ๋ธ”๋ฆฟ์—์„œ navigate_to๋ฅผ ๊ณ ๋ฅด๋ฉด? ์ˆ˜๋™(์‚ฌ์šฉ์ž ์„ ํƒ) > ์ž๋™. ์ž๋™ ๋ช…๋ น์€ stop ์™ธ์—” ์ง„ํ–‰ ์ค‘ ๋ช…๋ น์„ ๋Š์ง€ ์•Š๊ณ  ํ ๋’ค์— ๋ถ™๋Š”๋‹ค. ์—ญ์œผ๋กœ ์ˆ˜๋™ ๋ช…๋ น์€ ๋Œ€๊ธฐ ์ค‘์ธ ์ž๋™ ๋ช…๋ น์„ ๊ฑด๋„ˆ๋›ธ ์ˆ˜ ์žˆ๊ฒŒ SpotQueue์— source ํ•„๋“œ ์ถ”๊ฐ€
2 every_arrival ํญ์ฃผ โ€” Vision์ด ์ง‘โ†”๋งˆํŠธ ๊ฒฝ๊ณ„์—์„œ ๊นœ๋นก์ด๋ฉด ๋„์ฐฉ์ด ์—ฐ๋‹ฌ์•„ ์ฐํž˜ ๊ทœ์น™๋ณ„ cooldown_seconds ๊ธฐ๋ณธ 30์ดˆ. once_per_session์„ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ
3 ๋””๋ฐ”์ด์Šค ๋ถ€์žฌ โ€” ์„ธ์…˜ ์žฅ์†Œ์— ํ•ด๋‹น ํƒ€์ž… ๋””๋ฐ”์ด์Šค๊ฐ€ ์—†์„ ๋•Œ ์กฐ์šฉํžˆ ์Šคํ‚ตํ•˜์ง€ ์•Š๊ณ  automation_runs์— skipped: no device๋กœ ๋‚จ๊น€. ๊ทœ์น™ ์นด๋“œ์— ๊ฒฝ๊ณ  ๋ฐฐ์ง€
3b ํƒœ์Šคํฌ ์™„๋ฃŒ ํ›„ ์žฌ๋„์ฐฉ โ€” ํƒœ์Šคํฌ๋ฅผ ๋งˆ์นœ ๋’ค ๊ฐ™์€ ์žฅ์†Œ์— ๋‹ค์‹œ ์˜ค๋ฉด? task_status_filter=pending(๊ธฐ๋ณธ)์ด๋ฉด ํ›„๋ณด์—์„œ ๋น ์ง โ†’ ์™„๋ฃŒ ํ›„์—” ๋ฐœํ™” ์•ˆ ํ•จ. ์™„๋ฃŒ ํ›„์—๋„ ๋Œ์•„๊ฐ€๊ธธ ์›ํ•˜๋ฉด any. ์ด๋ฒคํŠธ๋Š” event_published_at๊ณผ ๋ฌด๊ด€ํ•˜๊ฒŒ ๋„์ฐฉ๋งˆ๋‹ค ํ›„๋ณด(fire_policy๋กœ ์ œ์–ด)
3c ํ•œ ๋„์ฐฉ์— ๊ทœ์น™ ์—ฌ๋Ÿฌ ๊ฐœ โ€” ํƒœ์Šคํฌ 2๊ฐœ + ์ด๋ฒคํŠธ 1๊ฐœ๊ฐ€ ๊ฐ๊ฐ SPOT ๋ช…๋ น์„ ๊ฐ–๊ณ  ์žˆ์œผ๋ฉด? ์ „๋ถ€ ๋ฐœํ™”ํ•˜๋˜ ์ˆœ์„œ๋Š” location โ†’ event โ†’ task(visit/display ์ˆœ). SPOT์€ SpotQueue๊ฐ€ ์ง๋ ฌํ™”. ์ƒ์ถฉ(follow vs navigate_to)์€ ์–ด๋“œ๋ฏผ ์ฑ…์ž„์œผ๋กœ ๋‘๊ณ , ๊ทœ์น™ ์ „์ฒด ๋ชฉ๋ก์—์„œ ๊ฐ™์€ ์žฅ์†Œยท๊ฐ™์€ ๋””๋ฐ”์ด์Šค์— ๊ทœ์น™์ด ์—ฌ๋Ÿฌ ๊ฐœ๋ฉด โš ๏ธ ๋ฐฐ์ง€
4 zone_id='home' ์ธ์Šคํ„ด์Šค ์„ ํƒ โ€” ์ฒซ ๋ฐฉ๋ฌธ์€ STEP์ด ๊ฐ€์žฅ ์•ž์„  ์ง‘, ๋‘ ๋ฒˆ์งธ๋ถ€ํ„ฐ๋Š” ์ฒซ ์ธ์Šคํ„ด์Šค๋กœ ๋Œ์•„๊ฐ€์ง€ ์•Š๊ณ  ๋‚˜๋จธ์ง€ ์•ˆ์—์„œ ์ˆœํ™˜ (2026-09-27) ์ž๋™ํ™”๋„ ๋™์ผ ์ธ์Šคํ„ด์Šค ๊ธฐ์ค€์œผ๋กœ first_visit_only ํŒ๋‹จ. ์ง‘-์•„์นจ/์ง‘-์ €๋…์ด ๊ฐ™์€ ๊ทœ์น™์„ ๊ณต์œ ํ•˜๋ ค๋ฉด ์žฅ์†Œ ์ธ์Šคํ„ด์Šค๋งˆ๋‹ค ๊ทœ์น™์„ ๋”ฐ๋กœ ๋‘๊ฑฐ๋‚˜, ํ–ฅํ›„ "zone ๋‹จ์œ„ ๊ทœ์น™" ์˜ต์…˜ ๊ฒ€ํ† 
5 ์„ธ์…˜ ์ข…๋ฃŒ ์‹œ ์„ธ์…˜ ์ข…๋ฃŒ(end-session) ์‹œ ํ•ด๋‹น ์„ธ์…˜์˜ ๋Œ€๊ธฐ ์ค‘ ์ž๋™ ์•ก์…˜(delay ์ค‘์ธ goroutine) ์ทจ์†Œ
6 ์•„์ดํ…œ ์กฐ๊ฑด๊ณผ once_per_session missing_item ์Šคํ‚ต์€ ๋ฐœํ™”๋กœ ์น˜์ง€ ์•Š์Œ โ†’ ๊ตฌ๋งค ํ›„ ์žฌ๋„์ฐฉ ์‹œ ๋ฐœํ™”. ์‹œํ—˜ ๋ฐœํ™”๋Š” ์•„์ดํ…œ ์กฐ๊ฑด์„ ๋ฌด์‹œํ• ์ง€ ํ† ๊ธ€ ์ œ๊ณต
7 ์„œ๋ฒ„ ์žฌ๊ธฐ๋™ delay ์ค‘์ด๋˜ ์•ก์…˜์€ ์œ ์‹ค ํ—ˆ์šฉ (์ „์‹œ ํ˜„์žฅ ํŠน์„ฑ์ƒ ์ˆ˜ ์ดˆ ๋‹จ์œ„๋ผ ์˜์†ํ™” ๋ถˆํ•„์š”)
9 ์•ก์…˜ ๋ณ‘๋ ฌ ์‹คํ–‰ (2026-08-30, ๋ฐ˜๋ ค๋กœ๋ด‡ ์ฑ„๋„ ๋ชจ๋ธ) arrival_automation_actions.parallel=1 ์ด๋ฉด ์•ž ์•ก์…˜์˜ ์™„๋ฃŒ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ์•ž ์•ก์…˜๊ณผ ๋™์‹œ์— ์ ์žฌ. ์—ฐ์†๋œ ๋ณ‘๋ ฌ ์•ก์…˜์€ ํ•œ ๋‹จ๊ณ„๋กœ ๋ฌถ์ด๊ณ  ๋‹จ๊ณ„ ์ „์ฒด๊ฐ€ ์ข…๊ฒฐ๋ผ์•ผ ๋‹ค์Œ ๋‹จ๊ณ„๋กœ. ๋ฐ˜๋ ค๋กœ๋ด‡ ๋ช…๋ น์€ ํ ๊ทธ๋ฃน์ด companion:{robot}:{์ฑ„๋„}์ด๋ผ locomotion(ํ•˜์ฒด)๋งŒ ์ง๋ ฌ, audio/led/gaze/projection์€ ์‹ค์ œ๋กœ ๋™์‹œ์— ๋‚˜๊ฐ„๋‹ค(์˜ˆ: ์ œ์Šค์ฒ˜ โˆฅ ๋ฐœํ™”). ์ฒซ ์•ก์…˜์˜ parallel์€ ๋ฌด์‹œ. ๋ณ‘๋ ฌ ๊ทธ๋ฃน ์นด์šดํŠธ๋Š” ํ”„๋กœ์„ธ์Šค ๋ฉ”๋ชจ๋ฆฌ(์žฌ๊ธฐ๋™ ์‹œ ๊ทธ ๋‹จ๊ณ„์˜ ๋‹ค์Œ ์ ์žฌ ์œ ์‹ค โ€” 7๋ฒˆ๊ณผ ๋™์ผ ์ˆ˜์ค€)
9-1 ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๋Š” ๋‹จ๊ณ„ (2026-10-04) params_json._no_wait=true ๋ฉด ๊ฐ™์ด ์ถœ๋ฐœํ•˜๋˜ ์•„๋ฌด๋„ ๊ทธ ๋‹จ๊ณ„์˜ ์™„๋ฃŒ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๋Š”๋‹ค. ๋“œ๋ก  ๋ฐฐ์†ก์ฒ˜๋Ÿผ ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ์—ฐ์ถœ์ด ๋’ค ๋‹จ๊ณ„(๋กœ๋ด‡ํŒ”ยทSPOT)๋ฅผ ๋ถ™์žก์ง€ ์•Š๊ฒŒ ํ•˜๋ ค๊ณ  ๋„ฃ์—ˆ๋‹ค โ€” ์ „์—๋Š” Hub(๋“œ๋ก ) โˆฅ SPOT ๋ฌถ์Œ์ด ์ „๋ถ€ ๋๋‚˜์•ผ ๋กœ๋ด‡ํŒ”์ด ๋‚˜๊ฐ”๊ณ , ๋“œ๋ก  ack ๊ฐ€ ํƒ€์ž„์•„์›ƒ์ด๋ฉด ์ฒด์ธ์ด ๋ฉˆ์ท„๋‹ค. ์„œ๋ฒ„๋Š” ์•ก์…˜์„ buildChainStages ๋กœ "ํ•œ๊บผ๋ฒˆ์— ์ ์žฌํ•  ๋ฌถ์Œ"์œผ๋กœ ๋‚˜๋ˆ„๊ณ , ๊ทธ ๋ฌถ์Œ์˜ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์žก(= _no_wait ์•„๋‹Œ ๊ฒƒ)์ด ์ „๋ถ€ ์ข…๊ฒฐ๋  ๋•Œ๋งŒ ๋‹ค์Œ ๋ฌถ์Œ์„ ์ ์žฌํ•œ๋‹ค. ๋ฌถ์Œ์ด ํ†ต์งธ๋กœ _no_wait ๋ฉด ๋‹ค์Œ ๋ฌถ์Œ์„ ๊ฐ™์ด ์ ์žฌํ•œ๋‹ค. ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๋Š” ๋‹จ๊ณ„๋Š” ์‹คํŒจํ•ด๋„ ์ฒด์ธ์„ ๋ฉˆ์ถ”์ง€ ์•Š๊ณ (๋‚ด๋ถ€์ ์œผ๋กœ _continue_on_failure ์ทจ๊ธ‰), ์ˆ˜๋™ ์žฌ์‹œ๋„๋Š” ๊ทธ ๋‹จ๊ณ„๋งŒ ๋‹ค์‹œ ๋ณด๋‚ธ๋‹ค(RetryRunFrom). ๊ฐ™์€ ๋กœ๋ด‡์— ๋’ค ๋‹จ๊ณ„๊ฐ€ ๋˜ ๋ช…๋ น์„ ๋„ฃ์œผ๋ฉด ๊ทธ ๋กœ๋ด‡ ํ์—์„œ ์ฐจ๋ก€๋ฅผ ๊ธฐ๋‹ค๋ฆฐ๋‹ค โ€” ๋ณ‘๋ ฌ ๊ธˆ์ง€ ๊ทœ์น™(ยง๊ฐ™์€ ๋กœ๋ด‡ ๋™์‹œ ๊ธˆ์ง€)์€ ๋ฌถ์Œ ์•ˆ์—์„œ๋งŒ ๊ฒ€์‚ฌํ•œ๋‹ค. ์–ด๋“œ๋ฏผ: ๋‹จ๊ณ„ ์นด๋“œ "๋’ค ๋‹จ๊ณ„๊ฐ€ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š์Œ" ์ฒดํฌ, ๋‹ค์ด์–ด๊ทธ๋žจ์€ ์ ์„  ๊ฐ€์ง€๋กœ ๋น ์ ธ๋‚˜๊ฐ€๊ฒŒ ๊ทธ๋ฆฐ๋‹ค. ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์—†์Œ โ€” parallel(100) ๊ณผ ๋‹ฌ๋ฆฌ ์ปฌ๋Ÿผ์ด ์•„๋‹ˆ๋ผ params_json ์˜ˆ์•ฝ ํ‚ค๋‹ค(_continue_on_failure ์™€ ๊ฐ™์€ ์ž๋ฆฌ)
8 ํƒœ์Šคํฌ ์ž๋™ ์™„๋ฃŒ (2026-08-30) โ€” ํƒœ์Šคํฌ๋Š” ํƒœ๋ธ”๋ฆฟ ์„ ํƒ ํŒ์—… ์—†์ด ๋„์ฐฉ ์ž๋™ํ™”๋กœ๋งŒ ์‹คํ–‰๋œ๋‹ค owner_type=task ๊ทœ์น™์ด ์‹ค์ œ ๋ฐœํ™”(์‹œํ—˜ ๋ฐœํ™” ์ œ์™ธ)ํ•˜๋ฉด ๊ฐ ์•ก์…˜์ด user_task_commands์— queued๋กœ ๊ธฐ๋ก๋˜๊ณ , ํ๊ฐ€ ์•ก์…˜์„ ์ข…๊ฒฐ(ack/timeout/failed)ํ•  ๋•Œ๋งˆ๋‹ค ๊ฐฑ์‹  โ†’ ์ „๋ถ€ ์ข…๊ฒฐ๋˜๋ฉด user_tasks๋ฅผ ์ž๋™ completed ํ•˜๊ณ  task/complete์™€ ๊ฐ™์€ ํ›„์† ๋ฐœํ–‰. ์‹คํŒจยทํƒ€์ž„์•„์›ƒ๋„ ์ข…๊ฒฐ๋กœ ์„ผ๋‹ค. ๋ฐฑ์˜คํ”ผ์Šค ์„ธ์…˜ ์ƒ์„ธ(์œ„์น˜๋ณ„ ํƒœ์Šคํฌ)์— ๋ช…๋ น๋ณ„ ์ƒํƒœ ํ‘œ์‹œ. ์ž๋™ ์™„๋ฃŒ ํ›„ ํƒœ๋ธ”๋ฆฟ task/complete๊ฐ€ ๋˜ ์™€๋„ ํฌ์ธํŠธ ์žฌ์ง€๊ธ‰ ์—†์Œ
10 pending_entered ํŠธ๋ฆฌ๊ฑฐ (2026-10-06, 152) โ€” ๊ธฐ์กด ํŠธ๋ฆฌ๊ฑฐ(arrive/leave/๋ฌผ๊ฑด ์ง‘์Œ/์„ ํƒ์ง€)์™€ ๋™์ผํ•œ ๊ทœ์น™ ๋ชจ๋ธ(UIยท์กฐ๊ฑดยท๋ฐœํ™” ์ •์ฑ…ยท์•ก์…˜ยท์‹คํ–‰ ๋กœ๊ทธยท์‹œํ—˜ ๋ฐœํ™”)๋กœ, ํŠธ๋ฆฌ๊ฑฐ ๊ฐ’๋งŒ ๋‹ค๋ฅด๋‹ค. ํƒœ์Šคํฌ ์ „์šฉ(owner_type='task') โ€” ์žฅ์†Œยท์ด๋ฒคํŠธ์—๋Š” pending ์ƒํƒœ ๊ฐœ๋…์ด ์—†๋‹ค "๋‚ด ์ฐจ๋ก€" = ๊ด€๋žŒ๊ฐ์ด ๊ทธ ์žฅ์†Œ์— ์žˆ๊ณ  ์•ž ํƒœ์Šคํฌ์— ๋ง‰ํžˆ์ง€ ์•Š์Œ (2026-10-10). ๋ฐœํ™” ์ง€์  ์„ธ ๊ณณ: โ‘  ์„ธ์…˜ ์‹œ๋”ฉ ์งํ›„ โ€” ๊ด€๋žŒ๊ฐ์ด ์ง€๊ธˆ ์žˆ๋Š” ์žฅ์†Œ์˜ pending ์ด๋ฉด์„œ ์•ž ํƒœ์Šคํฌ์— ๋ง‰ํžˆ์ง€ ์•Š์€ ํƒœ์Šคํฌ๋งŒ(seedPendingEnteredTargets). ์˜ˆ์ „์—” ์ฝ”์Šค ์ „์ฒด ์žฅ์†Œ๋ฅผ ํ›‘์–ด, ๊ณต์› ์ฒซ ํƒœ์Šคํฌ์˜ pending_entered ๊ฐ€ ๊ด€๋žŒ๊ฐ์ด ์ง‘์— ์žˆ์„ ๋•Œ ๋Œ์•„ ํƒœ์Šคํฌ๊ฐ€ ์ž๋™ ์™„๋ฃŒ๋˜๊ณ  ๊ณต์› ์ด๋ฒคํŠธ๊นŒ์ง€ ๋ฐœํ–‰๋๋‹ค(์„ธ์…˜ 01a125b1). โ‘ก ์žฅ์†Œ ๋„์ฐฉ(onArrival โ†’ fireReadyPendingEntered, ๋„์ฐฉ ํ‰๊ฐ€ ๋’ค๋ผ ์žฅ์†Œ ์—ฐ์ถœ ๊ฒŒ์ดํŠธ๋ฅผ ํƒ„๋‹ค). โ‘ข ์ˆœ์ฐจ ์‹คํ–‰(117) ์ฒด์ธ์—์„œ ์•ž ํƒœ์Šคํฌ๊ฐ€ ์ข…๊ฒฐ๋ผ ๋Œ€๊ธฐ๊ฐ€ ํ’€๋ฆฌ๋Š” ์ˆœ๊ฐ„(OnUserTaskFinished, ๊ด€๋žŒ๊ฐ์ด ๊ทธ ์žฅ์†Œ์— ์žˆ์„ ๋•Œ). ์ค‘๋ณต ๋ฐœํ™”๋Š” ๊ทœ์น™์˜ fire_policy ๊ฐ€ ๋ง‰๋Š”๋‹ค. ์šฉ๋„: detailed ์ฒ˜๋Ÿผ ํƒœ๋ธ”๋ฆฟ์˜ ๋ช…์‹œ์  task/start ์—†์ด ์ž๋™ํ™”๋กœ๋งŒ ์‹œ์ž‘๋˜๋Š” ํƒœ์Šคํฌ ํƒ€์ž…์ด, ์žฅ์†Œ ๋„์ฐฉ(arrive)์„ ๋‹ค์‹œ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ ๋„ "๋‚ด ์ฐจ๋ก€๊ฐ€ ๋œ ์ˆœ๊ฐ„" ๋ฐ”๋กœ ๋กœ๋ด‡ ๋ช…๋ น์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค โ€” ๊ด€๋žŒ๊ฐ์ด ์ด๋ฏธ ๊ทธ ์žฅ์†Œ์— ์„œ ์žˆ๋Š” ์ฑ„๋กœ ์ˆœ์ฐจ ์ฒด์ธ์˜ ๋‘ ๋ฒˆ์งธ ์ดํ›„ ํƒœ์Šคํฌ๊ฐ€ ์‹œ์ž‘๋˜๋Š” ๊ฒฝ์šฐ, ๊ทธ ์‚ฌ๋žŒ์€ ๋‹ค์‹œ "๋„์ฐฉ"ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ arrive ๋งŒ์œผ๋กœ๋Š” ์˜์˜ ๋ฐœํ™”ํ•˜์ง€ ์•Š์•˜๋‹ค. ๊ตฌ์ฒด์ ์œผ๋กœ ์–ธ์ œ ์“ธ์ง€๋Š” ์šด์˜์ž๊ฐ€ ์ž๋™ํ™” ๊ทœ์น™์„ ๋งŒ๋“ค๋ฉฐ ์ •ํ•œ๋‹ค. ์ถ”์ : arrive ์™€ ๋™์ผํ•˜๊ฒŒ user_task_commands ๋กœ ์ถ”์ ํ•ด ์•ก์…˜์ด ์ „๋ถ€ ์ข…๊ฒฐ๋˜๋ฉด ํƒœ์Šคํฌ๋ฅผ ์ž๋™ completed ํ•œ๋‹ค(isTrackableTaskTrigger) โ€” leaveยท๋ฌผ๊ฑด ์ง‘์Œ์ฒ˜๋Ÿผ "์—ฐ์ถœยทํ›„์ฒ˜๋ฆฌ"๊ฐ€ ์•„๋‹ˆ๋ผ "์ด ํƒœ์Šคํฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ผ"๋Š” ๋ช…๋ น์ด๊ธฐ ๋•Œ๋ฌธ

7. ๋‹จ๊ณ„๋ณ„ ๊ตฌํ˜„ ์ˆœ์„œ

  1. ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ + sqlc โ€” ยง3 ํ…Œ์ด๋ธ” 3๊ฐœ (-- +goose Up ์ง€์‹œ์ž ํ•„์ˆ˜). ๋งคํ•‘ ์ˆ˜์ง‘์šฉ ์ฟผ๋ฆฌ: ListUserTasksAtLocation(user, location), ๊ธฐ์กด GetUserLocationByUserAndLocation ์žฌ์‚ฌ์šฉ
  2. ์„œ๋ฒ„ ์‹คํ–‰ ์—”์ง„ โ€” internal/mqttx/automation.go: runArrivalAutomations() + recordLocationChange ํ›…. ๋งคํ•‘ ์ˆ˜์ง‘ โ†’ ๊ทœ์น™ ๋กœ๋“œ โ†’ ์•„์ดํ…œ/์ƒํƒœ ์กฐ๊ฑด โ†’ ๋””์ŠคํŒจ์น˜. SPOT๋งŒ ๋จผ์ €, ๋กœ๋ด‡ํŒ”/๋ฐ˜๋ ค๋กœ๋ด‡/Hub๋Š” generic publish
  3. Admin REST โ€” /admin/{tasks|events|locations}/:id/automations CRUD(๊ณตํ†ต ํ•ธ๋“ค๋Ÿฌ, owner_type๋งŒ ๋‹ค๋ฆ„), /admin/automations, /admin/automations/runs, POST /admin/automations/:id/fire?user_id= (์‹œํ—˜ ๋ฐœํ™”)
  4. ๋ฐฑ์˜คํ”ผ์Šค โ€” ArrivalAutomationEditor ๊ณต์šฉ ์ปดํฌ๋„ŒํŠธ โ†’ ํƒœ์Šคํฌ ์ƒ์„ธ โ†’ ์ด๋ฒคํŠธ ์ƒ์„ธ โ†’ ์žฅ์†Œ ์ƒ์„ธ ํƒญ(์žฅ์†Œ ๊ทœ์น™ + ์ง‘๊ณ„) โ†’ ์ „์ฒด ๋ชฉ๋ก โ†’ ์‹คํ–‰ ๋กœ๊ทธ
  5. ๋ฌธ์„œ โ€” MQTT_SCHEMA-ORCHESTRATION_SERVER์— automation/fired ํ† ํ”ฝ, ์ด ๋ฌธ์„œ ์ƒํƒœ๋ฅผ "ํ™•์ •"์œผ๋กœ
  6. (์Šคํ‚ค๋งˆ ํ™•์ • ํ›„) ๋กœ๋ด‡ํŒ”ยท๋ฐ˜๋ ค๋กœ๋ด‡ ํƒ€์ž…๋“œ ํผ ๊ต์ฒด

8. ๋ฏธ๊ฒฐ ์‚ฌํ•ญ

  • ๋กœ๋ด‡ํŒ” ยท ๋ฐ˜๋ ค๋กœ๋ด‡ MQTT ์Šคํ‚ค๋งˆ (๊ฐ ํŒ€ ์ž‘์„ฑ ๋Œ€๊ธฐ) โ€” ํ™•์ • ์ „๊นŒ์ง€ JSON ์ž์œ  ์ž…๋ ฅ
  • leave ํŠธ๋ฆฌ๊ฑฐ โ€” 2026-09-12 ๊ตฌํ˜„(114). ํƒ€์ž„์•„์›ƒ ๊ธฐ๋ฐ˜์ด ์•„๋‹ˆ๋ผ ๋‹ค๋ฅธ ์žฅ์†Œ ๋„์ฐฉ = ์ง์ „ ์žฅ์†Œ ์ดํƒˆ๋กœ ํŒ์ •ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ๋งˆ์ง€๋ง‰ ์žฅ์†Œ(๋ณดํ†ต home)๋Š” ์„ธ์…˜ ์ข…๋ฃŒ๋กœ๋Š” ์ดํƒˆ์ด ๋ฐœํ™”ํ•˜์ง€ ์•Š์Œ โ€” ์„ธ์…˜ ์ข…๋ฃŒ ์‹œ ๋ฐœํ™”๊ฐ€ ํ•„์š”ํ•˜๋ฉด ยง6-5(์ข…๋ฃŒ ์‹œ ๋Œ€๊ธฐ ์•ก์…˜ ์ทจ์†Œ)์™€ ํ•จ๊ป˜ ๊ฒฐ์ •
  • dwell ํŠธ๋ฆฌ๊ฑฐ ์‹ค์ œ ์ˆ˜์š” ํ™•์ธ
  • orchestration/{sessionId}/automation/fired๋ฅผ ํƒœ๋ธ”๋ฆฟ์ด ์‹ค์ œ๋กœ ์“ธ์ง€ (FE ํŒ€ ํ™•์ธ)
  • ์žฅ์†Œ ์ธ์Šคํ„ด์Šค ๋‹จ์œ„ ๊ทœ์น™ vs zone ๋‹จ์œ„ ๊ทœ์น™ (ยง6-4)
  • ํƒœ์Šคํฌ ์„ ํƒ์ง€(option)๋ณ„ ๋„์ฐฉ ์ž๋™ํ™”๊ฐ€ ํ•„์š”ํ•œ์ง€ โ€” ํ˜„์žฌ๋Š” ํƒœ์Šคํฌ ๋‹จ์œ„. "์ด ์„ ํƒ์ง€๋ฅผ ์ด๋ฏธ ๊ณ ๋ฅธ ์„ธ์…˜์ด ๋„์ฐฉํ•˜๋ฉด"์€ task_option_selections ์กฐ๊ฑด์œผ๋กœ ํ™•์žฅ ๊ฐ€๋Šฅ
  • ์ด๋ฒคํŠธ ๊ทœ์น™์˜ ๋ฐœํ™” ์‹œ์  โ€” ๋„์ฐฉ ์ฆ‰์‹œ vs ๋Œ๋ฐœ ์ด๋ฒคํŠธ ํŒ์—…(maybePublishArrivalEvent) ์งํ›„. ๋กœ๋ด‡ํŒ” ์ „๋‹ฌ์ฒ˜๋Ÿผ "์ด๋ฒคํŠธ ์—ฐ์ถœ"์ธ ๊ฒฝ์šฐ ํŒ์—…๊ณผ ํƒ€์ด๋ฐ์„ ๋งž์ถฐ์•ผ ํ•  ์ˆ˜ ์žˆ์Œ
  • ์•„์ดํ…œ "์†Œ๋ชจ" ์—ฌ๋ถ€ โ€” ๋กœ๋ด‡ํŒ”์ด ๊ฐ„์‹์„ ์ „๋‹ฌํ•˜๋ฉด ๋ณด์œ  ๋ชฉ๋ก์—์„œ ์ฐจ๊ฐํ•ด์•ผ ํ•˜๋Š”์ง€ (ํ˜„์žฌ store/owned๋Š” ๋ˆ„์  ๋ณด์œ ๋งŒ ํ‘œํ˜„)