23 KiB
23 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 위임) |
AppController |
update_manager.check_for_updates() |
없음 | Optional[VersionInfo] |
업데이트 확인 |
AppController |
update_manager.prepare_update(version_info) |
VersionInfo |
tuple[bool, str] |
업데이트 실행 준비 |
AppController |
update_manager.launch_updater() |
없음 | bool |
updater.exe 실행 |
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 속성을 가지며, 전체 작업을 중단하지 않습니다:
TrainInfoNotFoundErrorTimetableServiceError
처리 방법:
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
},
"update": {
"connection_config_path": "app/updater/config.json",
"environment": "main",
"check_interval_hours": 1,
"program_id": "voc_monitor",
"version_table": "program_versions"
},
"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 | 관심 조건(키워드/부서) 기반 알림만 사용할지 여부 |
update.connection_config_path |
str | "app/updater/config.json" | 업데이터 연결 설정 파일 경로 |
update.environment |
str | "main" | 원격 연결 설정 환경 이름 |
update.check_interval_hours |
int | 1 | 업데이트 체크 주기(시간) |
update.program_id |
str | "voc_monitor" | 버전 조회 program_id |
update.version_table |
str | "program_versions" | 버전 정보 테이블명 |
알림 설정 분류 원칙:
crawling.keywords,crawling.target_depts: 관심글 판정 기준 (중복 없이 단일 기준)noti.*: 알림 주기/조건 토글/사운드 등 알림 동작 제어
업데이터 연결 설정 원칙:
- Supabase URL/KEY는 코드에 하드코딩하지 않고
app/updater/config.json+settings.json(update.*)로 관리 - 원격 config 로드 실패 시 로컬 fallback을 사용
빌드 계약:
app/update_build_setup.py는 단일updater.exe를 생성해야 함- 메인 패키징(
app/setup.py)은updater_build/dist/updater.exe를updater.exe이름으로 포함해야 함
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() |
트레이 메뉴 클릭 | 없음 |
check_updates_manual() |
트레이 메뉴 클릭(업데이트 확인) | 없음 |
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 |
백그라운드 업데이트 체크 시작 |
보강된 계약 사항:
check_for_updates()는supabase_url,supabase_key누락 시ConfigError를 발생시킴prepare_update()는updater.exe미존재 시(False, "...")를 반환prepare_update()는 config 파일을 임시 파일 생성 후 원자적으로 교체 저장함
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