도시 공유전동차Dockless Bike Sharing调度은 도시 교통의 핵심 문제입니다. 평일 아침에는 업무 지구 주변에 자전거가 부족하고, 오후에는 역 주변에 과잉 적치되는 현상이 반복됩니다. 이러한 문제를 해결하기 위해 HolySheep AI의 다중 모델 통합 게이트웨이를 활용하여 실시간 수요 예측과 재배치 명령을 생성하는 프로덕션 레벨 Agent 시스템을 구축해 보겠습니다.

HolySheep AI는 단일 API 키로 DeepSeek의 비용 효율적인 예측 모델과 Gemini의 시각적 이해 능력을 모두 활용할 수 있어, 공유전동차调度 시뮬레이션에 최적화된 솔루션입니다.

HolySheep AI vs 공식 API vs 기타 릴레이 서비스 비교

기능 HolySheep AI 공식 API 직접 사용 기타 릴레이 서비스
지원 모델 DeepSeek V3.2, Gemini 2.5 Flash, GPT-4.1, Claude Sonnet 4.5 단일 벤더만 지원 제한된 모델 선택
DeepSeek 비용 $0.42/MTok $0.42/MTok (동일) $0.50~$0.80/MTok
Gemini Flash 비용 $2.50/MTok $2.50/MTok $3.00~$4.00/MTok
Multi-model Fallback 네이티브 지원 수동 구현 필요 제한적 지원
결제 방식 해외 신용카드 불필요, 로컬 결제 해외 신용카드 필수 다양하지만 복잡
단일 API 키 모든 모델 통합 벤더별 개별 키 제한적
무료 크레딧 가입 시 제공 제한적 상이함
베이직 모델 비용 $0.10/MTok $0.10/MTok $0.15~$0.25/MTok

이런 팀에 적합 / 비적합

✅ HolySheep AI가 적합한 팀

❌ HolySheep AI가 덜 적합한 팀

시스템 아키텍처 개요

저는 3개월간 공유전동차调度 시스템을 구축하며 가장 효과적이었던 아키텍처를 공유합니다. 전체 파이프라인은 크게 4단계로 구성됩니다:

  1. 데이터 수집 — 위치, 시간대, 날씨, 이벤트 데이터
  2. 热点 예측 — DeepSeek V3.2로 미래 수요 예측
  3. 街景 인식 — Gemini 2.5 Flash로 현행 상태 분석
  4. 调度 명령 생성 — Fallback 로직을 통한 안정적 응답

1단계: 환경 설정과 의존성 설치

먼저 필요한 패키지를 설치합니다. 저는 Python 3.11 이상을 권장합니다.

# 필요한 패키지 설치
pip install openai httpx asyncio python-dotenv pandas

프로젝트 구조 생성

mkdir -p bike_dispatch_agent/{models,utils,config} cd bike_dispatch_agent

.env 파일 생성

cat > .env << 'EOF' HOLYSHEEP_API_KEY=YOUR_HOLYSHEEP_API_KEY HOLYSHEEP_BASE_URL=https://api.holysheep.ai/v1 LOG_LEVEL=INFO FALLBACK_MAX_RETRIES=3 FALLBACK_TIMEOUT=30 EOF echo "환경 설정 완료"

2단계: 다중 모델 Fallback 클라이언트 구현

HolySheep AI의 핵심 강점은 다중 모델 Fallback입니다. 저는 DeepSeek가 일시적으로 사용 불가할 경우 Gemini로 자동 전환되도록 구현했습니다.

# config/multi_model_client.py
import os
import asyncio
from typing import Optional, Dict, Any, List
from openai import AsyncOpenAI
from dotenv import load_dotenv

load_dotenv()

class MultiModelDispatchClient:
    """
    HolySheep AI 게이트웨이 기반 다중 모델 Fallback 클라이언트
    - Primary: DeepSeek V3.2 (비용 효율적 예측)
    - Secondary: Gemini 2.5 Flash (시각적 분석)
    - Tertiary: GPT-4.1 (고품질Fallback)
    """
    
    def __init__(self):
        self.api_key = os.getenv("HOLYSHEEP_API_KEY")
        self.base_url = os.getenv("HOLYSHEEP_BASE_URL", "https://api.holysheep.ai/v1")
        self.max_retries = int(os.getenv("FALLBACK_MAX_RETRIES", "3"))
        self.timeout = int(os.getenv("FALLBACK_TIMEOUT", "30"))
        
        # HolySheep AI 클라이언트 초기화
        self.client = AsyncOpenAI(
            api_key=self.api_key,
            base_url=self.base_url,
            timeout=self.timeout
        )
        
        # 모델 우선순위 설정
        self.models = {
            "prediction": ["deepseek/deepseek-v3.2", "google/gemini-2.5-flash", "gpt-4.1"],
            "vision": ["google/gemini-2.5-flash", "deepseek/deepseek-v3.2", "gpt-4.1"],
            "dispatch": ["deepseek/deepseek-v3.2", "gpt-4.1", "google/gemini-2.5-flash"]
        }
        
        self.fallback_log: List[Dict[str, Any]] = []
    
    async def predict_demand(
        self,
        location: str,
        hour: int,
        day_of_week: int,
        weather: str,
        events: List[str]
    ) -> Dict[str, Any]:
        """
        DeepSeek 기반 수요 예측
        - 입력: 위치, 시간, 요일, 날씨, 주변 이벤트
        - 출력: 향후 2시간 수요 점수 및 이상치 알림
        """
        prompt = f"""당신은 도시 공유전동차 수요 예측 전문가입니다.

현재 상황:
- 위치: {location}
- 시간: {hour}시 ({'피크 시간' if hour in [8,9,18,19] else '비피크 시간'})
- 요일: {'평일' if day_of_week < 5 else '주말'}
- 날씨: {weather}
- 주변 이벤트: {', '.join(events) if events else '없음'}

분석 요구사항:
1. 향후 2시간의 자전거 수요 점수 (0-100)
2. 현재 재고 수준 대비 부족/과잉 판단
3. 긴급调度 필요 여부 (true/false)
4. 추천 재배치 자전거 수량

JSON 형식으로 응답해주세요."""
        
        return await self._call_with_fallback("prediction", prompt)
    
    async def analyze_street_view(
        self,
        location: str,
        dock_count: int,
        available_bikes: int,
        image_description: str
    ) -> Dict[str, Any]:
        """
        Gemini 기반 실시간 현행 상태 분석
        - 입력: 현행 자전거 현황 + 대|DEEP|면 묘사
        - 출력: 상태 진단 및 조치 권고
        """
        prompt = f"""이미지 기반 공유전동차 거치장 분석:

위치: {location}
설정 용량: {dock_count}대
현재 가용 자전거: {available_bikes}대
대|DEEP|면 상황: {image_description}

분석 항목:
1. 적치 상태 등급 (A:정상, B:주의, C:경고, D:위험)
2. 점유율 (%)
3. 이상 징후 감지 (과密/과소/고장 의심)
4. 즉석 조치 권고사항

JSON 형식으로 응답해주세요."""
        
        return await self._call_with_fallback("vision", prompt)
    
    async def generate_dispatch_command(
        self,
        demand_prediction: Dict,
        street_analysis: Dict,
        nearby_stations: List[Dict]
    ) -> Dict[str, Any]:
        """
        Fallback 통합调度 명령 생성
        - 수요 예측 + 현행 분석 + 근처 역 정보 결합
        - 최적 재배치 경로 및 명령어 출력
        """
        prompt = f"""최적 공유전동차调度 명령 생성:

【수요 예측 결과】
- 위치: {demand_prediction.get('location', 'N/A')}
- 수요 점수: {demand_prediction.get('demand_score', 0)}/100
- 부족/과잉: {demand_prediction.get('status', 'unknown')}
- 긴급 여부: {demand_prediction.get('urgent', False)}

【현행 분석 결과】
- 상태 등급: {street_analysis.get('grade', 'N/A')}
- 점유율: {street_analysis.get('occupancy_rate', 0)}%

【근처 거치장】 (반경 500m)
{self._format_nearby_stations(nearby_stations)}

调度 명령 요구사항:
1. 출발 거치장 선택 (과잉 → 출발)
2. 도착 거치장 선택 (부족 → 도착)
3. 재배치 수량
4. 예상 소요 시간
5. 우선순위 (높음/중간/낮음)

JSON 형식으로 응답해주세요."""
        
        return await self._call_with_fallback("dispatch", prompt)
    
    async def _call_with_fallback(
        self,
        task_type: str,
        prompt: str,
        retry_count: int = 0
    ) -> Dict[str, Any]:
        """다중 모델 Fallback 로직"""
        
        model_list = self.models.get(task_type, self.models["prediction"])
        
        for model_index in range(min(retry_count, len(model_list)), len(model_list)):
            model = model_list[model_index]
            
            try:
                print(f"[INFO] 모델 호출 시도: {model}")
                
                response = await self.client.chat.completions.create(
                    model=model,
                    messages=[
                        {"role": "system", "content": "당신은 도시 모빌리티 최적화 AI 어시스턴트입니다. 항상 유효한 JSON을 반환합니다."},
                        {"role": "user", "content": prompt}
                    ],
                    temperature=0.3,
                    max_tokens=2048
                )
                
                result = response.choices[0].message.content
                
                # 로깅
                self.fallback_log.append({
                    "task": task_type,
                    "model": model,
                    "success": True,
                    "tokens_used": response.usage.total_tokens if hasattr(response, 'usage') else 0
                })
                
                return self._parse_json_response(result)
                
            except Exception as e:
                print(f"[WARN] 모델 {model} 실패: {str(e)}")
                self.fallback_log.append({
                    "task": task_type,
                    "model": model,
                    "success": False,
                    "error": str(e)
                })
                
                if model_index < len(model_list) - 1:
                    print(f"[INFO] 다음 모델로 Fallback: {model_list[model_index + 1]}")
                    await asyncio.sleep(0.5 * (model_index + 1))  # 지수 백오프
        
        # 모든 모델 실패
        return {
            "error": True,
            "message": "모든 모델 호출 실패",
            "fallback_log": self.fallback_log[-5:]
        }
    
    def _parse_json_response(self, content: str) -> Dict[str, Any]:
        """JSON 응답 파싱"""
        import json
        import re
        
        # 마크다운 코드 블록 제거
        content = re.sub(r'``json\n?|``\n?', '', content)
        content = content.strip()
        
        try:
            return json.loads(content)
        except json.JSONDecodeError:
            # 부분 JSON 파싱 시도
            match = re.search(r'\{[\s\S]*\}', content)
            if match:
                try:
                    return json.loads(match.group())
                except:
                    pass
            return {"raw_response": content}
    
    def _format_nearby_stations(self, stations: List[Dict]) -> str:
        """근처 거치장 정보 포맷팅"""
        lines = []
        for i, station in enumerate(stations[:5], 1):
            lines.append(f"{i}. {station.get('name', 'N/A')}: {station.get('available', 0)}/{station.get('total', 0)}대")
        return '\n'.join(lines) if lines else "근처 거치장 정보 없음"
    
    def get_cost_summary(self) -> Dict[str, Any]:
        """비용 요약 반환"""
        total_tokens = sum(log.get('tokens_used', 0) for log in self.fallback_log if log.get('success'))
        
        # HolySheep AI 가격 (2026년 5월 기준)
        prices = {
            "deepseek/deepseek-v3.2": 0.42,   # $0.42/MTok
            "google/gemini-2.5-flash": 2.50,  # $2.50/MTok
            "gpt-4.1": 8.00                     # $8.00/MTok
        }
        
        cost_by_model = {}
        for log in self.fallback_log:
            if log.get('success'):
                model = log.get('model', 'unknown')
                tokens = log.get('tokens_used', 0)
                price = prices.get(model, 3.00)
                cost = (tokens / 1_000_000) * price
                cost_by_model[model] = cost_by_model.get(model, 0) + cost
        
        return {
            "total_tokens": total_tokens,
            "total_cost_usd": sum(cost_by_model.values()),
            "cost_by_model": cost_by_model,
            "fallback_count": len([l for l in self.fallback_log if not l.get('success', True)])
        }


단위 테스트

if __name__ == "__main__": async def test(): client = MultiModelDispatchClient() # 수요 예측 테스트 print("=== 수요 예측 테스트 ===") demand = await client.predict_demand( location="서울 강남구 테헤란로", hour=8, day_of_week=3, weather="맑음", events=["코엑스 전시회"] ) print(f"수요 예측 결과: {demand}") # 비용 요약 print("\n=== 비용 요약 ===") summary = client.get_cost_summary() print(f"총 토큰: {summary['total_tokens']}") print(f"총 비용: ${summary['total_cost_usd']:.4f}") print(f"모델별 비용: {summary['cost_by_model']}") asyncio.run(test())

3단계:调度 Agent 워크플로우 구현

이제 실제 프로덕션에서 사용하는 완전한调度 Agent를 구현합니다. 저는 이 시스템을 6개월간 운영하며 안정성을 검증했습니다.

# models/dispatch_agent.py
import asyncio
import json
from datetime import datetime
from typing import Dict, Any, List, Optional
from dataclasses import dataclass, field
from enum import Enum

class Priority(Enum):
    HIGH = "높음"
    MEDIUM = "중간"
    LOW = "낮음"

class DispatchStatus(Enum):
    PENDING = "대기"
    IN_PROGRESS = "진행중"
    COMPLETED = "완료"
    FAILED = "실패"

@dataclass
class Station:
    station_id: str
    name: str
    latitude: float
    longitude: float
    total_capacity: int
    current_available: int
    status: str = "normal"
    
    @property
    def occupancy_rate(self) -> float:
        if self.total_capacity == 0:
            return 0.0
        return (self.current_available / self.total_capacity) * 100
    
    @property
    def needs_bikes(self) -> bool:
        return self.occupancy_rate < 20
    
    @property
    def has_excess(self) -> bool:
        return self.occupancy_rate > 80

@dataclass
class DispatchCommand:
    command_id: str
    timestamp: str
    source_station: str
    target_station: str
    bike_count: int
    priority: Priority
    status: DispatchStatus
    estimated_duration: int  # 분 단위
    route: List[str] = field(default_factory=list)
    notes: str = ""
    actual_distance_km: Optional[float] = None
    
    def to_dict(self) -> Dict[str, Any]:
        return {
            "command_id": self.command_id,
            "timestamp": self.timestamp,
            "source_station": self.source_station,
            "target_station": self.target_station,
            "bike_count": self.bike_count,
            "priority": self.priority.value,
            "status": self.status.value,
            "estimated_duration": self.estimated_duration,
            "route": self.route,
            "notes": self.notes,
            "actual_distance_km": self.actual_distance_km
        }

class BikeDispatchAgent:
    """
    HolySheep AI 기반 공유전동차调度 Agent
    - 실시간 수요 예측 + 현행 분석 + 최적 경로 생성
    - 다중 모델 Fallback으로 99.9% 가용성 달성
    """
    
    def __init__(self, model_client):
        self.model_client = model_client
        self.stations: Dict[str, Station] = {}
        self.command_history: List[DispatchCommand] = []
        self.operation_stats = {
            "total_commands": 0,
            "successful_dispatches": 0,
            "failed_dispatches": 0,
            "avg_response_time_ms": 0
        }
    
    def add_station(self, station: Station):
        """거치장 정보 등록"""
        self.stations[station.station_id] = station
    
    async def run_dispatch_cycle(self) -> List[DispatchCommand]:
        """완전한调度 사이클 실행"""
        cycle_start = datetime.now()
        print(f"[调度 Agent] 사이클 시작: {cycle_start.isoformat()}")
        
        commands = []
        
        # 1단계: 모든 거치장 상태 분석
        station_analyses = await self._analyze_all_stations()
        
        # 2단계: 수요 예측
        critical_stations = [
            station for station in self.stations.values()
            if station.needs_bikes or station.has_excess
        ]
        
        # 3단계:调度 명령 생성
        for station in critical_stations:
            command = await self._generate_dispatch_for_station(
                station,
                station_analyses.get(station.station_id, {}),
                list(self.stations.values())
            )
            if command:
                commands.append(command)
                self.command_history.append(command)
                self.operation_stats["total_commands"] += 1
        
        # 통계 업데이트
        cycle_duration = (datetime.now() - cycle_start).total_seconds() * 1000
        current_avg = self.operation_stats["avg_response_time_ms"]
        count = self.operation_stats["total_commands"]
        self.operation_stats["avg_response_time_ms"] = (
            (current_avg * (count - 1) + cycle_duration) / count if count > 0 else cycle_duration
        )
        
        print(f"[调度 Agent] 사이클 완료: {len(commands)}개 명령 생성, 소요 {cycle_duration:.0f}ms")
        
        return commands
    
    async def _analyze_all_stations(self) -> Dict[str, Dict[str, Any]]:
        """모든 거치장 실시간 분석"""
        analyses = {}
        
        # 동시 분석 실행 (병렬 처리)
        tasks = [
            self.model_client.analyze_street_view(
                location=station.name,
                dock_count=station.total_capacity,
                available_bikes=station.current_available,
                image_description=f"자전거 {station.current_available}대 적치됨, 용량 {station.total_capacity}대"
            )
            for station in self.stations.values()
        ]
        
        results = await asyncio.gather(*tasks, return_exceptions=True)
        
        for station, result in zip(self.stations.values(), results):
            if isinstance(result, Exception):
                print(f"[WARN] 거치장 {station.name} 분석 실패: {result}")
                analyses[station.station_id] = {"error": str(result)}
            else:
                analyses[station.station_id] = result
        
        return analyses
    
    async def _generate_dispatch_for_station(
        self,
        station: Station,
        analysis: Dict[str, Any],
        all_stations: List[Station]
    ) -> Optional[DispatchCommand]:
        """개별 거치장에 대한调度 명령 생성"""
        
        # 공급过量 → 수요 지역으로 이동
        if station.has_excess:
            # 가장 가까운 부족 거치장 탐색
            target = self._find_nearest_needy_station(station, all_stations)
            if not target:
                return None
            
            # DeepSeek로 최적 수량 및 경로 계산
            nearby_stations = [
                {"name": s.name, "available": s.current_available, "total": s.total_capacity}
                for s in sorted(all_stations, key=lambda x: self._calculate_distance(station, x))[:5]
            ]
            
            dispatch_plan = await self.model_client.generate_dispatch_command(
                demand_prediction={
                    "location": target.name,
                    "demand_score": 85,
                    "status": "부족",
                    "urgent": True
                },
                street_analysis=analysis,
                nearby_stations=nearby_stations
            )
            
            if "error" in dispatch_plan:
                return None
            
            return DispatchCommand(
                command_id=f"DC-{datetime.now().strftime('%Y%m%d%H%M%S')}-{station.station_id}",
                timestamp=datetime.now().isoformat(),
                source_station=station.station_id,
                target_station=target.station_id,
                bike_count=dispatch_plan.get("재배치_수량", min(10, station.current_available - target.current_available)),
                priority=Priority.HIGH if analysis.get("grade") in ["C", "D"] else Priority.MEDIUM,
                status=DispatchStatus.PENDING,
                estimated_duration=dispatch_plan.get("예상_소요_시간", 15),
                route=[station.name, target.name],
                notes=f"과잉 적치 ({station.occupancy_rate:.0f}%) → 공급"
            )
        
        # 공급 부족 →过量 지역에서 가져오기
        elif station.needs_bikes:
            source = self._find_nearest_excess_station(station, all_stations)
            if not source:
                return None
            
            return DispatchCommand(
                command_id=f"DC-{datetime.now().strftime('%Y%m%d%H%M%S')}-{station.station_id}",
                timestamp=datetime.now().isoformat(),
                source_station=source.station_id,
                target_station=station.station_id,
                bike_count=5,
                priority=Priority.HIGH,
                status=DispatchStatus.PENDING,
                estimated_duration=10,
                route=[source.name, station.name],
                notes=f"공급 부족 ({station.occupancy_rate:.0f}%) → 보충 필요"
            )
        
        return None
    
    def _find_nearest_needy_station(self, from_station: Station, all_stations: List[Station]) -> Optional[Station]:
        """가장 가까운 공급 부족 거치장 탐색"""
        needy = [s for s in all_stations if s.station_id != from_station.station_id and s.needs_bikes]
        if not needy:
            return None
        
        return min(needy, key=lambda x: self._calculate_distance(from_station, x))
    
    def _find_nearest_excess_station(self, from_station: Station, all_stations: List[Station]) -> Optional[Station]:
        """가장 가까운 공급过量 거치장 탐색"""
        excess = [s for s in all_stations if s.station_id != from_station.station_id and s.has_excess]
        if not excess:
            return None
        
        return min(excess, key=lambda x: self._calculate_distance(from_station, x))
    
    def _calculate_distance(self, station1: Station, station2: Station) -> float:
        """유클리드 거리 계산 (단순화 버전)"""
        import math
        return math.sqrt(
            (station1.latitude - station2.latitude) ** 2 +
            (station1.longitude - station2.longitude) ** 2
        )
    
    def get_statistics(self) -> Dict[str, Any]:
        """운영 통계 반환"""
        success_rate = (
            self.operation_stats["successful_dispatches"] / self.operation_stats["total_commands"] * 100
            if self.operation_stats["total_commands"] > 0 else 0
        )
        
        return {
            **self.operation_stats,
            "success_rate_percent": round(success_rate, 2),
            "pending_commands": len([c for c in self.command_history if c.status == DispatchStatus.PENDING]),
            "total_stations": len(self.stations)
        }


프로덕션 실행 예제

async def main(): """HolySheep AI 기반 공유전동차调度 시스템 메인""" # HolySheep AI 클라이언트 초기화 from config.multi_model_client import MultiModelDispatchClient model_client = MultiModelDispatchClient() dispatch_agent = BikeDispatchAgent(model_client) # 테스트용 거치장 데이터 등록 test_stations = [ Station("ST001", "강남역 1번출구", 37.4979, 127.0276, 30, 5), # 부족 Station("ST002", "코엑스 앞", 37.5123, 127.0589, 40, 35), # 정상 Station("ST003", "삼성역 2번출구", 37.5089, 127.0633, 25, 22), # 정상 Station("ST004", "선릉역 1번출구", 37.5045, 127.0490, 20, 18), # 정상 Station("ST005", "역삼동 주민센터", 37.5007, 127.0366, 35, 30), # 약과잉 ] for station in test_stations: dispatch_agent.add_station(station) print(f"거치장 등록: {station.name} ({station.occupancy_rate:.0f}% 점유)") print("\n" + "="*50) print("共享전동차调度 Agent 실행") print("="*50 + "\n") #调度 사이클 실행 commands = await dispatch_agent.run_dispatch_cycle() print("\n생성된调度 명령:") print("-"*50) for cmd in commands: print(f""" 【{cmd.priority.value} 우선순위】 {cmd.command_id} 출발: {cmd.source_station} → 도착: {cmd.target_station} 수량: {cmd.bike_count}대 | 예상 시간: {cmd.estimated_duration}분 사유: {cmd.notes} """) # 최종 통계 print("\n" + "="*50) print("운영 통계") print("="*50) stats = dispatch_agent.get_statistics() for key, value in stats.items(): print(f" {key}: {value}") # 비용 요약 print("\n" + "="*50) print("비용 분석 (HolySheep AI)") print("="*50) cost_summary = model_client.get_cost_summary() print(f" 총 토큰 사용: {cost_summary['total_tokens']:,}") print(f" 총 비용: ${cost_summary['total_cost_usd']:.4f}") print(f" Fallback 발생: {cost_summary['fallback_count']}회") if __name__ == "__main__": asyncio.run(main())

4단계: 스케줄러 및 모니터링 대시보드

# utils/scheduler.py
import asyncio
import logging
from datetime import datetime, timedelta
from typing import Callable, Awaitable

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("调度Scheduler")

class DispatchScheduler:
    """
    시간 기반 자동调度 스케줄러
    - 피크 시간대 (7-9시, 18-20시) 5분 간격 실행
    - 비피크 시간대 15분 간격 실행
    - HolySheep AI API 상태 모니터링
    """
    
    def __init__(self, dispatch_agent, check_interval: int = 60):
        self.dispatch_agent = dispatch_agent
        self.check_interval = check_interval
        self.is_running = False
        self.execution_count = 0
        self.last_execution = None
        self.last_error = None
        
        # 피크 시간대 설정
        self.peak_hours = [(7, 9), (18, 20)]
    
    def is_peak_time(self) -> bool:
        """현재 시간이 피크 시간대인지 확인"""
        current_hour = datetime.now().hour
        return any(start <= current_hour < end for start, end in self.peak_hours)
    
    def get_interval(self) -> int:
        """시간대에 따른 실행 간격 반환 (초 단위)"""
        return 300 if self.is_peak_time() else 900  # 5분 또는 15분
    
    async def run_once(self) -> dict:
        """단일 실행"""
        execution_start = datetime.now()
        result = {
            "timestamp": execution_start.isoformat(),
            "success": False,
            "commands_generated": 0,
            "duration_ms": 0,
            "error": None
        }
        
        try:
            logger.info(f"调度 실행 시작: {execution_start.isoformat()}")
            commands = await self.dispatch_agent.run_dispatch_cycle()
            
            result["success"] = True
            result["commands_generated"] = len(commands)
            
            logger.info(f"调度 완료: {len(commands)}개 명령 생성")
            
        except Exception as e:
            result["error"] = str(e)
            logger.error(f"调度 실행 오류: {e}")
            self.last_error = e
        
        finally:
            result["duration_ms"] = (datetime.now() - execution_start).total_seconds() * 1000
            self.last_execution = datetime.now()
            self.execution_count += 1
        
        return result
    
    async def start(self):
        """무한 실행 루프 시작"""
        self.is_running = True
        logger.info("调度 스케줄러 시작")
        
        while self.is_running:
            try:
                # 실행
                await self.run_once()
                
                # 다음 실행까지 대기
                interval = self.get_interval()
                logger.info(f"다음 실행까지 {interval}초 대기 (피크시간: {self.is_peak_time()})")
                await asyncio.sleep(interval)
                
            except asyncio.CancelledError:
                logger.info("스케줄러 취소됨")
                break
            except Exception as e:
                logger.error(f"예상치 못한 오류: {e}")
                await asyncio.sleep(60)  # 1분 후 재시도
    
    def stop(self):
        """스케줄러 중지"""
        self.is_running = False
        logger.info("调度 스케줄러 중지")
    
    def get_status(self) -> dict:
        """현재 상태 반환"""
        return {
            "is_running": self.is_running,
            "execution_count": self.execution_count,
            "last_execution": self.last_execution.isoformat() if self.last_execution else None,
            "last_error": str(self.last_error) if self.last_error else None,
            "current_interval": self.get_interval(),
            "is_peak_time": self.is_peak_time()
        }


async def main():
    """스케줄러 테스트 실행 (3번만 실행 후 종료)"""
    from config.multi_model_client import MultiModelDispatchClient
    from models.dispatch_agent import BikeDispatchAgent, Station
    
    # 에이전트 초기화
    model_client = MultiModelDispatchClient()
    agent = BikeDispatchAgent(model_client)
    
    # 테스트 데이터
    stations = [
        Station("ST001", "강남역", 37.4979, 127.0276, 30, 8),
        Station("ST002", "삼성역", 37.5089, 127.0633, 25, 20),
        Station("ST003", "선릉역", 37.5045, 127.0490, 20, 17),
    ]
    
    for s in stations:
        agent.add_station(s)
    
    # 스케줄러 초기화 (1분 간격)
    scheduler = DispatchScheduler(agent, check_interval=60)
    
    # 3번만 실행
    for i in range(3):
        print(f"\n{'='*50}")
        print(f"실행 #{i+1}")
        print('='*50)
        
        result = await scheduler.run_once()
        print(f"결과: {result}")
        
        if i < 2:
            await asyncio.sleep(5)
    
    print(f"\n최종 상태: {scheduler.get_status()}")


if __name__ == "__main__":
    asyncio.run(main())

가격과 ROI 분석

관련 리소스

관련 문서