VOC_Monitor/docs/api_contract.md

22 KiB

🤝 API & Module Contract

이 문서는 모듈 간의 통신 규약과 데이터 규격을 정의합니다. 모든 AI 에이전트는 코드를 생성하거나 수정하기 전 본 문서를 참조하여 인터페이스의 정합성을 유지해야 합니다.


1. 데이터 모델 (Pydantic Schemas)

1.1 VOCPost

위치: app/models/model.py

class VOCPost(BaseModel):
    id: str                    # 게시글 ID (Primary Key)
    title: str                 # 제목
    writer: str                # 작성자
    department: str            # 담당 부서 (예: "차량", "시설")
    date: str                  # 작성일 (YYYY-MM-DD HH:MM:SS)
    status: str                # 처리 상태 (예: "접수", "처리중", "완료")
    channel: str               # 접수 경로 (예: "인터넷", "전화")
    is_public: int             # 공개 여부 (0: 비공개, 1: 공개)
    content: Optional[str]     # 상세 내용 (초기엔 None, 상세 조회 후 채워짐)
    attachment: Optional[str]  # 첨부파일명
    is_related: int = 0        # 관심글 여부 (0: 일반, 1: 관심)

사용처:

  • VOCScraper: 크롤링 결과 → VOCPost 변환
  • AppController: 비즈니스 로직 적용 (is_related 판별)
  • VOCDatabase: DB 저장/조회

1.2 StatisticsReport (신규)

위치: app/models/statistics.py

class StatisticsReport(BaseModel):
    """
    VOC 통계 분석 보고서 데이터 모델
    
    Attributes:
        period_start (str): 분석 기간 시작일 (YYYY-MM-DD)
        period_end (str): 분석 기간 종료일 (YYYY-MM-DD)
        total_count (int): 총 VOC 건수
        by_department (dict): 부서별 통계 {부서명: 건수}
        by_status (dict): 상태별 통계 {상태: 건수}
        by_date (dict): 날짜별 통계 {날짜: 건수}
        top_keywords (list): 빈출 키워드 TOP 10 [(키워드, 빈도), ...]
        charts (dict): 차트 이미지 경로 {차트명: 파일경로}
        generated_at (str): 보고서 생성 시각 (YYYY-MM-DD HH:MM:SS)
    """
    period_start: str = Field(..., description="분석 기간 시작일 (YYYY-MM-DD)")
    period_end: str = Field(..., description="분석 기간 종료일 (YYYY-MM-DD)")
    total_count: int = Field(0, description="총 VOC 건수", ge=0)
    by_department: dict = Field(default_factory=dict, description="부서별 통계")
    by_status: dict = Field(default_factory=dict, description="상태별 통계")
    by_date: dict = Field(default_factory=dict, description="날짜별 통계")
    top_keywords: list = Field(default_factory=list, description="빈출 키워드 TOP 10")
    charts: dict = Field(default_factory=dict, description="차트 이미지 경로")
    generated_at: Optional[str] = Field(None, description="보고서 생성 시각")
    
    @validator('period_start', 'period_end')
    def validate_date_format(cls, v):
        """날짜 형식 검증 (YYYY-MM-DD)"""
        import re
        from datetime import datetime
        
        pattern = r'^\d{4}-\d{2}-\d{2}$'
        if not re.match(pattern, v):
            raise ValueError(f"날짜 형식이 잘못되었습니다: {v} (YYYY-MM-DD 형식 필요)")
        
        try:
            datetime.strptime(v, "%Y-%m-%d")
        except ValueError as e:
            raise ValueError(f"유효하지 않은 날짜입니다: {v}")
        
        return v
    
    @root_validator
    def validate_period(cls, values):
        """기간 유효성 검증"""
        from datetime import datetime, timedelta
        
        start = values.get('period_start')
        end = values.get('period_end')
        
        if start and end:
            start_date = datetime.strptime(start, "%Y-%m-%d")
            end_date = datetime.strptime(end, "%Y-%m-%d")
            
            # 시작일이 종료일보다 늦으면 에러
            if start_date > end_date:
                raise ValueError("시작일이 종료일보다 늦을 수 없습니다.")
            
            # 기간이 1년을 초과하면 에러
            if (end_date - start_date).days > 365:
                raise ValueError("분석 기간은 최대 1년까지만 가능합니다.")
        
        return values

사용처:

  • StatisticsService: 통계 데이터 집계 및 보고서 생성
  • StatisticsDialog: UI에서 보고서 미리보기
  • AppController: 보고서 생성 요청 처리

1.3 StatisticsOptions (신규)

위치: app/models/statistics.py

class StatisticsOptions(BaseModel):
    """
    통계 분석 옵션 모델
    
    Attributes:
        period_start (str): 분석 기간 시작일
        period_end (str): 분석 기간 종료일
        include_department (bool): 부서별 통계 포함 여부
        include_status (bool): 상태별 통계 포함 여부
        include_keywords (bool): 키워드 분석 포함 여부
        include_charts (bool): 차트 생성 여부
        output_format (str): 출력 형식 ("excel" | "pdf" | "both")
        output_path (str): 저장 경로
    """
    period_start: str
    period_end: str
    include_department: bool = True
    include_status: bool = True
    include_keywords: bool = True
    include_charts: bool = True
    output_format: str = Field("excel", pattern="^(excel|pdf|both)$")
    output_path: Optional[str] = None

2. 모듈 간 인터페이스

2.1 Controller → Manager (위임 패턴)

Controller 리팩토링 (v3.0) - AppController는 이제 Manager들에게 작업을 위임합니다.

호출자 메서드 입력 출력 설명
AppController report_manager.request_create_report(data, parent) dict, Widget None 보고서 생성 요청 (ReportManager 위임)
AppController file_manager.open_attachment(data, parent) dict, Widget None 첨부파일 열기 (FileManager 위임)
AppController ui_manager.open_list_view(focus_id) str (선택) None 히스토리 창 열기 (UIManager 위임)
AppController ui_manager.open_settings(icon, item) any, any None 설정 창 열기 (UIManager 위임)
AppController ui_manager.request_detail_popup(voc_id) str None 상세 팝업 열기 (UIManager 위임)
AppController scheduler.run_crawling_cycle() 없음 없음 크롤링 사이클 실행 (SchedulerManager 위임)
AppController notifier.show_popup(title, msg, voc_id) str, str, str 없음 팝업 알림 표시 (NotificationManager 위임)

2.2 Controller ↔ Service

호출자 메서드 입력 출력 설명
AppController ReportService.create_hwp_report(data) dict (VOC 데이터) tuple[str, str] HWP 보고서 생성
AppController ReportService.create_pdf_report(data) dict tuple[bool, str] PDF 보고서 생성
AppController ReportService.print_voc_detail(data) dict tuple[bool, str] 프린터 출력
AppController VOCScraper.fetch_list_pages(max_pg, kw, dept) int, list, list dict 목록 페이지 크롤링
AppController VOCScraper.fetch_detail_content(voc_id) str dict 상세 내용 크롤링

2.2 Service ↔ Utils

호출자 메서드 입력 출력 설명
ReportService VOCParser.parse_basic_info(text) str dict 호선, 편성, 호차, 열차번호 추출
ReportService VOCParser.infer_line_and_direction(text) str tuple[str, str, str] 호선, 방향, 역명 유추
ReportService DateScheduleUtils.parse_schedule_type(text, date) str, str tuple[str, str] 날짜 및 스케줄 타입 판별
ReportService TrainAnalyzer.classify_train_details(num, dir) str, str dict 열차 종별/방향 분석
ReportService TrainAnalyzer.get_schedule_remarks(...) dict, str, date str 입고 정보 비고 생성
ReportService TimetableService.find_train_by_time(...) str, str, str, str dict 시각표 기반 열차 검색

2.3 Controller ↔ StatisticsService (신규)

호출자 메서드 입력 출력 설명
AppController StatisticsService.generate_report(options) StatisticsOptions tuple[bool, str] 통계 보고서 생성
AppController StatisticsService.get_stats_summary(start, end) str, str StatisticsReport 통계 요약 조회
AppController StatisticsService.export_to_excel(report, path) StatisticsReport, str tuple[bool, str] Excel 파일 생성
AppController StatisticsService.export_to_pdf(report, path) StatisticsReport, str tuple[bool, str] PDF 파일 생성

2.4 StatisticsService ↔ Database (신규)

호출자 메서드 입력 출력 설명
StatisticsService VOCDatabase.get_stats_by_period(start, end) str, str list[dict] 기간별 통계 조회
StatisticsService VOCDatabase.get_stats_by_department(start, end) str, str list[dict] 부서별 통계 조회
StatisticsService VOCDatabase.get_stats_by_status(start, end) str, str list[dict] 상태별 통계 조회
StatisticsService VOCDatabase.get_all_texts_in_period(start, end) str, str list[str] 키워드 분석용 텍스트 조회

2.5 StatisticsService ↔ ChartGenerator (신규)

호출자 메서드 입력 출력 설명
StatisticsService ChartGenerator.create_bar_chart(data, title) dict, str str 막대 그래프 생성 (파일 경로 반환)
StatisticsService ChartGenerator.create_line_chart(data, title) dict, str str 선 그래프 생성
StatisticsService ChartGenerator.create_pie_chart(data, title) dict, str str 파이 차트 생성
StatisticsService ChartGenerator.create_wordcloud(texts, title) list[str], str str 워드 클라우드 생성

3. 반환값 규약

3.1 성공/실패 패턴

기본 패턴: tuple[bool, str]

  • (True, "성공 메시지"): 성공
  • (False, "에러 메시지"): 실패

예외: ReportService.create_hwp_report()

  • ("FILE_EXISTS", file_path): 기존 파일 존재
  • (True, message): 정상 생성
  • (False, error_message): 생성 실패

3.2 크롤링 결과 패턴

{
    "status": "success" | "session_expired" | "error",
    "data": [VOCPost, ...],  # status == "success"
    "error": str             # status == "error"
}

4. 예외 처리 규약

4.1 예외 계층 구조

VOCMonitorError (기본)
├── DataError
│   ├── DataValidationError
│   └── DatabaseError
├── ScraperError
│   ├── LoginFailedError
│   ├── SessionExpiredError
│   └── PageLoadError
├── ReportError
│   ├── ReportGenerationError
│   ├── TemplateNotFoundError
│   ├── TrainInfoNotFoundError (부분 실패)
│   └── TimetableServiceError (부분 실패)
├── SystemError
│   ├── ConfigurationError
│   └── DependencyError
└── UIError
    └── DialogCancelledError (사용자 취소)

4.2 부분 실패 예외

다음 예외는 is_partial_failure = True 속성을 가지며, 전체 작업을 중단하지 않습니다:

  • TrainInfoNotFoundError
  • TimetableServiceError

처리 방법:

try:
    train_info = get_train_info(train_number)
except TrainInfoNotFoundError as e:
    if is_partial_failure(e):
        logger.warning(f"부분 실패: {e.message}")
        train_info = None  # 기본값 사용
    else:
        raise  # 전체 중단

4.3 에러 코드 체계

코드 설명 로그 레벨
DATA_VALIDATION_ERROR 데이터 검증 실패 ERROR
DATABASE_ERROR DB 작업 실패 ERROR
LOGIN_FAILED 로그인 실패 ERROR
SESSION_EXPIRED 세션 만료 WARNING
TRAIN_INFO_NOT_FOUND 열차 정보 미조회 WARNING
TIMETABLE_SERVICE_ERROR 시각표 조회 실패 WARNING
TEMPLATE_NOT_FOUND 템플릿 파일 없음 ERROR
DIALOG_CANCELLED 사용자 취소 INFO

5. 설정 파일 규약 (settings.json)

5.1 구조

{
    "login": {
        "id": "user_id",
        "pw": "password"
    },
    "crawling": {
        "interval_minutes": 10,
        "max_pages": 2,
        "target_depts": ["차량"],
        "keywords": ["지연", "불만"],
        "filter_mode": "OR",
        "recheck_hours": 3,
        "headless_mode": true,
        "max_retries": 3,
        "retry_delay": 2
    },
    "noti": {
        "sound": true,
        "db_check_interval_minutes": 3,
        "unchecked_check_interval_minutes": 10,
        "unchecked_delay_enabled": true,
        "use_related_filter": true
    },
    "report": {
        "output_path": "D:/Reports"
    },
    "report_options": {
        "office": "신평차량사업소",
        "team": "검수1",
        "reporter_name": "홍길동",
        "reporter_tel": "051-1234"
    },
    "master_data": {
        "stations_L1": ["서면", "부산역", "노포", ...],
        "office_tel_map": {
            "신평차량사업소": "051-1111",
            "노포차량사업소": "051-2222"
        }
    }
}

5.2 설정 항목 설명

설정 항목 타입 기본값 설명
crawling.interval_minutes int 10 크롤링 주기 (분)
crawling.max_pages int 2 최대 수집 페이지 수
crawling.target_depts list ["차량"] 관심 부서 목록
crawling.keywords list ["1호선", "1호선 역사명..."] 관심 키워드 목록 (관심글 판정 기준)
crawling.filter_mode str "OR" 필터링 모드 ("AND" 또는 "OR")
crawling.recheck_hours int 3 상세 재수집 주기 (시간)
crawling.headless_mode bool true 헤드리스 모드 여부
crawling.max_retries int 3 실패 시 최대 재시도 횟수
crawling.retry_delay int 2 재시도 간격 (초)
noti.db_check_interval_minutes int 3 신규 알림 DB 체크 주기 (분)
noti.unchecked_check_interval_minutes int 10 미확인 글 재알림 주기 (분)
noti.unchecked_delay_enabled bool true 미확인 글 30분 경과 조건 사용 여부
noti.use_related_filter bool true 관심 조건(키워드/부서) 기반 알림만 사용할지 여부

알림 설정 분류 원칙:

  • crawling.keywords, crawling.target_depts: 관심글 판정 기준 (중복 없이 단일 기준)
  • noti.*: 알림 주기/조건 토글/사운드 등 알림 동작 제어

filter_mode 설명:

  • "OR": 키워드 또는 부서가 매칭되면 관심글로 표시 (기본값)
  • "AND": 키워드 그리고 부서가 모두 매칭되어야 관심글로 표시

5.3 접근 방법

# Controller에서 로드
self.settings = json.load(open(settings_file))

# 값 접근
crawl_interval = self.settings['crawling']['interval_minutes']
filter_mode = self.settings['crawling'].get('filter_mode', 'OR')
stations = self.settings['master_data']['stations_L1']

6. 데이터베이스 규약

6.1 테이블 스키마

CREATE TABLE voc_posts (
    id TEXT PRIMARY KEY,
    title TEXT NOT NULL,
    writer TEXT,
    department TEXT,
    date TEXT,
    status TEXT,
    channel TEXT,
    is_public INTEGER DEFAULT 1,
    content TEXT,
    attachment TEXT,
    is_related INTEGER DEFAULT 0,
    checked_at TEXT,
    created_at TEXT DEFAULT CURRENT_TIMESTAMP
);

6.2 주요 쿼리 메서드

메서드 설명 반환
upsert_post(data) 게시글 저장/업데이트 None
get_all_posts() 전체 조회 list[sqlite3.Row]
get_post_by_id(voc_id) ID로 조회 sqlite3.Row
get_new_posts_since(timestamp) 특정 시간 이후 신규 list[sqlite3.Row]
get_unchecked_related_posts() 확인하지 않은 관심글 조회 list[sqlite3.Row]
mark_as_checked(voc_id) 읽음 처리 None

7. UI 이벤트 규약

7.1 Controller → View

메서드 트리거 파라미터
open_list_view(focus_id) 트레이 메뉴 클릭 focus_id: str (선택)
open_settings() 트레이 메뉴 클릭 없음
request_detail_popup(voc_id) 테이블 더블클릭 voc_id: str

7.2 View → Controller

메서드 트리거 파라미터
request_create_report(data, parent) 보고서 생성 버튼 data: dict, parent: Widget
request_print_voc(data) 인쇄 버튼 data: dict
open_attachment(data, parent) 첨부파일 버튼 data: dict, parent: Widget
mark_as_read(voc_id) 상세 조회 voc_id: str

8. 자동 업데이트 모듈 (Updater Module) 신규

8.1 모듈 구조

app/updater/
├── __init__.py           # 모듈 초기화
├── __version__.py        # 버전 정보
├── update_manager.py     # 메인 프로그램용 업데이트 관리자
├── updater_gui.py        # updater.exe용 GUI
└── updatelog.md          # 업데이트 로그

8.2 데이터 모델

UpdateConfig

위치: app/updater/update_manager.py

class UpdateConfig(BaseModel):
    """
    업데이트 구성 모델
    
    updater.exe로 전달되는 구성 정보
    
    Attributes:
        download_url (str): 다운로드 URL (zip 파일)
        target_path (str): 설치 대상 경로
        version (str): 업데이트할 버전
        restart_exe (str): 업데이트 후 실행할 실행파일명
    """
    download_url: str = Field(..., description="다운로드 URL")
    target_path: str = Field(..., description="설치 대상 경로")
    version: str = Field(..., description="업데이트 버전")
    restart_exe: str = Field(default="voc_noti.exe", description="재시작 실행파일")

VersionInfo

위치: app/updater/update_manager.py

class VersionInfo(BaseModel):
    """
    Supabase에서 조회한 버전 정보
    
    Attributes:
        version (str): 버전 번호
        is_stable (bool): 안정판 여부
        release_note (str): 배포 노트
        download_url (str): 다운로드 URL
        min_required_version (str): 최소 요구 버전
    """
    version: str
    is_stable: bool
    release_note: Optional[str] = None
    download_url: str
    min_required_version: Optional[str] = None

8.3 인터페이스 정의

UpdateManager (메인 프로그램용)

메서드 입력 출력 설명
check_for_updates() 없음 Optional[VersionInfo] Supabase에서 최신 버전 조회
is_update_available() 없음 bool 업데이트 필요 여부
prepare_update(version_info) VersionInfo tuple[bool, str] 업데이트 준비 (config.json 생성)
launch_updater() 없음 bool updater.exe 실행
start_background_check(callback) Callable None 백그라운드 업데이트 체크 시작

UpdaterGUI (updater.exe용)

메서드 입력 출력 설명
load_config() 없음 UpdateConfig config.json 로드
download_zip(url, progress_callback) str, Callable tuple[bool, str] zip 다운로드
extract_and_replace(zip_path, target_path) str, str tuple[bool, str] 압축 해제 및 파일 교체
restart_main_app(exe_name) str bool 메인 앱 재실행
show_error(message) str None 에러 다이얼로그 표시

8.4 통신 프로토콜

config.json 형식 (%TEMP%/voc_updater_config.json):

{
    "download_url": "https://supabase.../voc_noti_3.2.0.zip",
    "target_path": "C:/Program Files/voc_noti",
    "version": "3.2.0",
    "restart_exe": "voc_noti.exe"
}

8.5 Supabase API

테이블: program_version

조회 쿼리:

SELECT * FROM program_version 
WHERE program_id = 'voc_monitor' 
  AND is_stable = true 
ORDER BY created_at DESC 
LIMIT 1

8.6 예외 처리

예외 상황 처리
NetworkError Supabase 연결 실패 로그 기록, 다음 체크까지 대기
DownloadError 다운로드 실패 사용자 알림, 재시도 옵션
ExtractError 압축 해제 실패 롤백, 에러 로그
ConfigError config.json 읽기 실패 에러 다이얼로그, 종료

9. 변경 이력

날짜 버전 변경 내용
2026-02-18 3.3 알림 설정 확장 - noti.db_check_interval_minutes, noti.unchecked_check_interval_minutes, noti.unchecked_delay_enabled 규격 추가
2026-02-18 3.2 자동 업데이트 모듈 명세 추가 - UpdateManager, UpdaterGUI 인터페이스 정의, config.json 프로토콜
2026-02-18 3.1 크롤링 시스템 개선 - ScraperService 하드코딩 제거 (target_depts, keywords settings.json 연동), 필터링 로직 고도화 (AND/OR 모드, _check_filter_match 메서드), 예외처리 강화
2026-02-18 3.0 Controller 리팩토링 완료 - Manager 분리 (ReportManager, FileManager, UIManager), 알람 시스템 개선 (키워드 필터링 제거, 미확인 글 알림, 중복 방지, 영속화)
2026-02-17 2.0 전면 재작성 (에러 처리 체계 추가)
2026-02-14 1.5 시각표 서비스 추가
2026-02-11 1.0 초기 작성

작성자: KH.Choi 최종 수정: 2026-02-18 버전: 3.3