refactor: Streamlit 레거시 페이지 API 마이그레이션 + DB 용량 표시 (#54, #55) #64

Merged
xhh merged 2 commits from issue-54-streamlit-api-migration into main 2026-04-26 13:33:14 +09:00
Owner

Closes #54, Closes #55.

요약

Streamlit 레거시 프로토타입 페이지를 API 기반으로 전환하면서 운영 대시보드에 DB 용량 메트릭도 추가. 두 이슈가 자연스럽게 묶임 — /api/meta/coverage 가 양쪽의 데이터 소스.

커밋

feat: /api/meta/coverage 응답에 db_size_bytes + 운영 페이지 표시 (#55)

  • CoverageResponse.db_size_bytes (nullable int) 추가
  • meta_coverageDepends(get_database) 로 주입받아 SQLite 파일 용량 계산. in-memory 또는 파일 부재 시 None
  • 5_Data_Coverage 페이지 상단 메트릭 카드에 'DB 파일 용량' 표시 (KB/MB/GB 자동 단위)
  • 회귀 테스트 2건 (in-memory None / 파일 엔진 양수)

refactor: Streamlit 레거시 페이지 API 기반 마이그레이션 (#54)

  • frontend/streamlit/utils/data_loader.py: 함수 시그니처 유지, 내부만 API 호출로 교체. 페이지 본문 코드는 손대지 않음 (위험 최소화)
  • 1/2/3 레거시 페이지 + app.py 의 sys.path 해킹 제거, ensure_sidebar_controls() 호출 추가
  • docker-compose.yml: streamlit 의 ./data:/app/data:ro 볼륨 마운트 제거

API 매핑

기존 (SQLite 직접) 새 경로
get_database_stats / get_latest_update_time /api/meta/coverage 의 테이블 + 심볼 max_date 집계
get_fred_indicators /api/meta/coverage 심볼 상세 + /api/indicators/latest 최신값
get_equity_symbols /api/meta/coverage 심볼 상세 + /api/prices/{symbol} 1건
get_time_series /api/indicators/{symbol} (FRED 우선) → /api/prices/{symbol}
get_sector_etfs / get_market_indices /api/meta/coverage 의 max_date + /api/prices/{symbol} 정밀 1건

/api/prices 는 asc 정렬만 지원해 "최신 1건"을 직접 못 받기 때문에, coverage 의 max_date 로 좌표를 잡고 start_date=end_date=max_date 로 정밀 조회하는 패턴 사용.

효과

  • Streamlit 컨테이너에 SQLite 파일이 마운트되지 않아도 정상 동작. 공격면 축소 — 외부 노출 시 DB 파일 직접 접근 경로 차단
  • 스키마 변경 시 API/프론트 두 쪽을 동시에 고칠 필요 없음 (단일 책임)
  • 새 운영 페이지(#22, #46) 와 같은 패턴으로 수렴 — 유지보수 단일 경로
  • 운영 관점에서 ssh + ls -lh data/ 안 해도 DB 용량을 대시보드에서 즉시 확인

테스트

  • uv run pytest: 113 passed (기존 111 + 신규 2)
  • uv run ruff check: clean

Test plan

  • 단위/회귀 테스트 통과
  • 배포 후 운영 대시보드 진입 — 5_Data_Coverage 상단 'DB 파일 용량' 표시 확인
  • 1/2/3 레거시 페이지가 ./data: 볼륨 없이 정상 렌더링 확인
  • app.py 사이드바의 통계 (전체 심볼/레코드/기간) 가 prod DB 와 일치하는지 확인

알려진 한계

  • get_market_indices / get_sector_etfs 가 심볼당 2회 호출 (coverage 1회는 모듈 내 캐싱 없음, 가격 1회). Streamlit 캐싱 도입은 후속 이슈로 분리.
  • /api/prices 의 desc 정렬 지원이 있으면 일부 패턴이 더 깔끔해질 것 — 별도 이슈 검토.
Closes #54, Closes #55. ## 요약 Streamlit 레거시 프로토타입 페이지를 API 기반으로 전환하면서 운영 대시보드에 DB 용량 메트릭도 추가. 두 이슈가 자연스럽게 묶임 — `/api/meta/coverage` 가 양쪽의 데이터 소스. ## 커밋 ### `feat: /api/meta/coverage 응답에 db_size_bytes + 운영 페이지 표시 (#55)` - `CoverageResponse.db_size_bytes` (nullable int) 추가 - `meta_coverage` 가 `Depends(get_database)` 로 주입받아 SQLite 파일 용량 계산. in-memory 또는 파일 부재 시 None - `5_Data_Coverage` 페이지 상단 메트릭 카드에 'DB 파일 용량' 표시 (KB/MB/GB 자동 단위) - 회귀 테스트 2건 (in-memory None / 파일 엔진 양수) ### `refactor: Streamlit 레거시 페이지 API 기반 마이그레이션 (#54)` - `frontend/streamlit/utils/data_loader.py`: 함수 시그니처 유지, 내부만 API 호출로 교체. 페이지 본문 코드는 손대지 않음 (위험 최소화) - 1/2/3 레거시 페이지 + app.py 의 `sys.path` 해킹 제거, `ensure_sidebar_controls()` 호출 추가 - `docker-compose.yml`: streamlit 의 `./data:/app/data:ro` 볼륨 마운트 제거 ## API 매핑 | 기존 (SQLite 직접) | 새 경로 | |---|---| | `get_database_stats` / `get_latest_update_time` | `/api/meta/coverage` 의 테이블 + 심볼 max_date 집계 | | `get_fred_indicators` | `/api/meta/coverage` 심볼 상세 + `/api/indicators/latest` 최신값 | | `get_equity_symbols` | `/api/meta/coverage` 심볼 상세 + `/api/prices/{symbol}` 1건 | | `get_time_series` | `/api/indicators/{symbol}` (FRED 우선) → `/api/prices/{symbol}` | | `get_sector_etfs` / `get_market_indices` | `/api/meta/coverage` 의 max_date + `/api/prices/{symbol}` 정밀 1건 | `/api/prices` 는 asc 정렬만 지원해 "최신 1건"을 직접 못 받기 때문에, coverage 의 `max_date` 로 좌표를 잡고 `start_date=end_date=max_date` 로 정밀 조회하는 패턴 사용. ## 효과 - Streamlit 컨테이너에 SQLite 파일이 마운트되지 않아도 정상 동작. **공격면 축소** — 외부 노출 시 DB 파일 직접 접근 경로 차단 - 스키마 변경 시 API/프론트 두 쪽을 동시에 고칠 필요 없음 (단일 책임) - 새 운영 페이지(#22, #46) 와 같은 패턴으로 수렴 — 유지보수 단일 경로 - 운영 관점에서 `ssh + ls -lh data/` 안 해도 DB 용량을 대시보드에서 즉시 확인 ## 테스트 - `uv run pytest`: **113 passed** (기존 111 + 신규 2) - `uv run ruff check`: clean ## Test plan - [x] 단위/회귀 테스트 통과 - [ ] 배포 후 운영 대시보드 진입 — `5_Data_Coverage` 상단 'DB 파일 용량' 표시 확인 - [ ] 1/2/3 레거시 페이지가 `./data:` 볼륨 없이 정상 렌더링 확인 - [ ] `app.py` 사이드바의 통계 (전체 심볼/레코드/기간) 가 prod DB 와 일치하는지 확인 ## 알려진 한계 - `get_market_indices` / `get_sector_etfs` 가 심볼당 2회 호출 (coverage 1회는 모듈 내 캐싱 없음, 가격 1회). Streamlit 캐싱 도입은 후속 이슈로 분리. - `/api/prices` 의 desc 정렬 지원이 있으면 일부 패턴이 더 깔끔해질 것 — 별도 이슈 검토.
- CoverageResponse 에 db_size_bytes (nullable int) 필드 추가
- meta_coverage 라우터가 Database dependency 를 주입받아 SQLite 파일
  용량을 계산. in-memory(":memory:") 또는 파일 부재 시 None
- 5_Data_Coverage 페이지 상단 메트릭 카드에 'DB 파일 용량' 표시
  (humanize 보조 함수: KB/MB/GB 자동 단위)

회귀 테스트 2건:
- in-memory 엔진 → db_size_bytes None
- 파일 기반 엔진 → 양의 정수

이전엔 컨테이너 ssh 들어가 'ls -lh data/' 해야 알 수 있던 정보를
운영 대시보드에서 한눈에 확인 가능.

Closes #55

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
refactor: Streamlit 레거시 페이지 API 기반 마이그레이션 (#54)
All checks were successful
Tests (PR) / pytest (pull_request) Successful in 27s
b4a827d867
레거시 프로토타입 페이지 (Market_Coverage / Sector_Flows /
Indicator_Detail) 와 app.py 사이드바가 SQLite 파일을 직접 읽어
컨테이너에 read-only 볼륨을 공유하던 구조를 제거.

변경:
- frontend/streamlit/utils/data_loader.py: 모든 함수의 시그니처는
  유지하되 내부를 utils.api.get 호출로 교체. 페이지 본문 코드는
  손대지 않음 (위험 최소화).
  * get_database_stats / get_latest_update_time → /api/meta/coverage
  * get_fred_indicators → /api/meta/coverage + /api/indicators/latest
  * get_equity_symbols → /api/meta/coverage + /api/prices/{symbol}
  * get_time_series → /api/indicators/{symbol} 또는 /api/prices/{symbol}
  * get_sector_etfs / get_market_indices → /api/meta/coverage 의 max_date
    + /api/prices/{symbol} 정밀 조회
- frontend/streamlit/app.py + 1/2/3 페이지: sys.path 해킹 제거,
  ensure_sidebar_controls 호출 추가 (API URL/Key 입력 일관성)
- docker-compose.yml: streamlit 서비스의 './data:/app/data:ro' 마운트
  제거. 데이터 접근은 전부 API_URL 의 FastAPI 경유.

이점:
- Streamlit 컨테이너에 SQLite 파일이 없어도 정상 동작 — 공격면
  하나 줄어듦 (외부 노출 시 DB 파일 직접 접근 불가)
- 스키마 변경 시 API/프론트 두 쪽을 동시에 고칠 필요 없음
- 새 운영 페이지(#22, #46) 와 같은 패턴으로 수렴

Closes #54

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
xhh merged commit 23b3bed8bd into main 2026-04-26 13:33:14 +09:00
xhh deleted branch issue-54-streamlit-api-migration 2026-04-26 13:33:14 +09:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
xhh/financial-data-platform!64
No description provided.