돌발변수 대응 Re-planning Engine

화재 확산·탈출·도로 차단 등 새 이벤트 발생 시
DB 상태를 재구성하고 추론을 즉시 재실행하는 핵심 아키텍처

핵심 개념 — DB를 "전환"하지 않는다
잘못된 접근

이벤트 발생 시 DB의 기존 레코드를 직접 수정한다.

→ 이전 상태가 사라져 롤백 불가
→ 이벤트 순서 추적 불가
→ 디버깅 시 "어느 시점에 어떤 판단을 했는지" 알 수 없음

올바른 접근 — Event Sourcing

기반 DB는 절대 수정하지 않는다.
이벤트를 불변 로그에 쌓고, 조회 시마다 base + 누적 이벤트를 합산해 현재 상태를 도출한다.

→ 모든 시점의 상태 재현 가능
→ 추론 근거 추적 가능

데이터 흐름 — 이벤트 1개 처리 과정
1
Event 수신
# "불이 옆집으로 번짐"
Event(
  type=FIRE_SPREAD,
  target_id="house_B"
)
2
Delta 누적
# 불변 이벤트 로그에 추가
delta_store.append(event)

# 기반 DB는 절대 수정 안 함
# base_db 변경 없음
3
상태 병합
# base + 모든 delta 오버레이
state = delta_store.apply_to(
  base_db
)
# → WorldState 반환
4
추론 재실행
# 전체 추론 재실행
result = engine.run(state)

# 새 우선순위 목록 반환
# plan_version += 1
모듈 구조 — event_engine.py
Event

돌발변수의 타입과 대상을 담는 불변 데이터 객체. 타임스탬프 자동 기록.

EventType.FIRE_SPREAD
EventType.RESIDENT_ESCAPED
EventType.ROAD_BLOCKED
EventDeltaStore

이벤트를 순서대로 쌓는 불변 로그. base_db에 오버레이해 WorldState를 생성.

.append(event)
.apply_to(base_db)
.history
InferenceEngine

WorldState를 입력받아 인지·물리 취약성 점수를 계산하고 우선순위 목록을 반환.

P = cog × 0.6
    + phys × 0.4
ReplanningLoop

오케스트레이터. 이벤트 1개를 받아 Delta → Merge → Inference 전 과정을 실행.

.handle_event(ev)
.current_state()
실행 결과 — 4단계 돌발변수 시나리오

python event_engine.py

플랜 v1 최초 화재: house_A 발화
활성 화재: ["house_A"]
1. 김순자 (70점) — 직접 화재 / 비닐 자재
2. 박정수 (36점) — 인접 화재
3. 이복순 (54점)
플랜 v2 화재 확산: house_B로 번짐 ← 돌발변수
활성 화재: ["house_A", "house_B"]
1. 김순자 (70점) — 직접 화재
2. 박정수 (66점) ↑ 순위 상승 — 직접 화재 추가
3. 이복순 (54점) — 인접 화재
플랜 v3 박정수 자력 탈출 확인 ← 돌발변수
탈출 확인: ["r002(박정수)"]
1. 김순자 (70점)
2. 이복순 (54점)
박정수 → 목록에서 제외됨
플랜 v4 house_C 진입로 차단 ← 돌발변수
도로 차단: ["house_C"]
1. 김순자 (70점)
2. 이복순 (66점) ↑ 순위 상승 — 도로 차단 반영
각 플랜 재계산 소요 시간 < 1 ms — 실제 TypeDB 연동 시에도 목표 < 3초
app.py 연동 — HTTP 엔드포인트 설계
엔드포인트 추가 (app.py)
# 전역으로 루프 초기화
loop = ReplanningLoop(base_db)

@app.route('/api/event', methods=['POST'])
def receive_event():
  data = request.get_json()
  event = Event(
    type=EventType(data['type']),
    target_id=data['target_id']
  )
  result = loop.handle_event(event)
  return jsonify(result)
프론트엔드 호출 (index.html)
// "불 번짐" 버튼 클릭 시
async function triggerFireSpread(id) {
  const res = await fetch('/api/event', {
    method: 'POST',
    body: JSON.stringify({
      type: 'fire_spread',
      target_id: id
    })
  });
  const plan = await res.json();
  updateDashboard(plan); // UI 갱신
}

DB 처리 방식 비교

항목 v4 — DB 직접 수정 v5 — Event Sourcing
이벤트 처리 레코드 직접 UPDATE 이벤트 로그에 APPEND
기반 DB 이벤트마다 변형됨 항상 불변 유지
과거 상태 재현 불가 이벤트 n번까지 replay
추론 근거 추적 어느 이벤트가 판단을 바꿨는지 불명 plan_version 별 전체 로그 보존
TypeDB 연동 트랜잭션 충돌 위험 읽기 전용 쿼리 + delta 오버레이