공장 설비의 가용률(availability), 불량률(defect_rate), 다운타임(downtime_min)을 실시간으로 모니터링하는 대시보드 시스템입니다. CSV 기반 프로토타입에서 시작하여 PostgreSQL + FastAPI 기반의 API 분리 아키텍처로 전환했습니다.
| 구분 | 기술 | 용도 |
|---|---|---|
| Frontend/Dashboard | Flask, Jinja2 | 대시보드 렌더링 (run.py) |
| Backend API | FastAPI, Uvicorn | DB 접근을 전담하는 API 서버 (api_server.py) |
| Database | PostgreSQL | 설비 가동 데이터 저장 (정형/시계열) |
| DB 관리 | pgAdmin | GUI 기반 데이터 조회/편집 |
| ORM/DB 연동 | SQLAlchemy, psycopg2 | DB 커넥션 및 쿼리 |
| 데이터 처리 | pandas | CSV 파싱, 집계, DB 마이그레이션 |
| 통신 | requests | Flask ↔ FastAPI 간 내부 HTTP 호출 |
| 환경 관리 | python-dotenv | DB 자격증명 등 민감정보 분리 |
| 실행 환경 | Python venv (Windows) | 의존성 격리 |
초기에는 pandas.read_csv로 정적 파일을 읽는 구조였으나, 다음 이유로 관계형 DB로 전환했습니다.
- 데이터가
machine_id / date / plan_min / run_min / downtime_min등 명확한 스키마를 가진 정형 데이터라 관계형 모델에 적합 - 다중 클라이언트(대시보드, 향후 모바일/리포트 도구)가 동시에 같은 데이터를 조회해야 하는 요구를 CSV 파일 하나로는 감당할 수 없음
- 인덱스, 트랜잭션, 동시성 제어 등 실무 표준 기능이 필요
- 시계열·정형 데이터에 강하고, row-level security·SSL 등 보안 기능이 실무 환경(클라우드 RDS 등)과 동일하게 실습 가능
- SQLite는 파일 기반이라 동시 접속·확장성에 한계, MS SQL Server는 라이선스/설치 부담이 큼
- 무료·오픈소스이면서 프로덕션급 신뢰성 확보
- 자격증명 최소 노출: DB 비밀번호를 대시보드 서버(
run.py)가 아닌 API 서버 한 곳에만 보관 - 접근 통제 지점 확보: API Key/JWT 인증, Rate Limiting을 API 레벨에서 적용 가능
- WAF 적용 가능성: 내부 DB에는 WAF를 붙일 수 없지만, API 앞단에는 리버스 프록시 + WAF를 배치할 수 있음
- 확장성: 향후 모바일 앱, 다른 대시보드가 늘어나도 동일 API를 재사용
- FastAPI는 비동기 처리와 Swagger 자동 문서화(
/docs)를 기본 제공해 API 서버로 적합
- 이미 존재하는 대시보드 렌더링 로직(
render_template)을 유지하면서 데이터 소스만 API로 교체하는 최소 변경 전략 - 대시보드 자체는 무거운 프레임워크가 필요 없는 단순 렌더링 계층이므로 경량 Flask가 적합
[PostgreSQL]
↑ (SQLAlchemy, DB 자격증명 보유)
[api_server.py : FastAPI, :8000]
↑ (HTTP GET, 무자격증명)
[run.py : Flask, :5000]
↑ (HTML/JSON)
[Browser]
계층별 책임 분리
- PostgreSQL:
machine_uptime테이블에 원본 데이터 저장 - api_server.py: DB 접근을 전담하는 유일한 컴포넌트.
/api/machine-uptime,/api/summary,/health제공, Swagger(/docs) 자동 문서화 - run.py: DB에 직접 접속하지 않고 API만 호출, HTML 렌더링과 프론트용 JSON 응답만 담당
- migrate_csv_to_db.py: 기존 CSV 데이터를 PostgreSQL로 1회 이전하는 마이그레이션 스크립트
| 기능 | 엔드포인트 | 설명 |
|---|---|---|
| 대시보드 렌더링 | GET / |
machine_uptime_dashboard.html 서빙 |
| 전체 데이터 조회 | GET /api/data (Flask) → GET /api/machine-uptime (FastAPI) |
설비별 가동 데이터 전체 반환 |
| 설비별 필터 조회 | GET /api/machine-uptime?machine_id=... |
특정 설비 데이터만 필터링 |
| 요약 통계 | GET /api/summary |
평균 가용률/불량률, 총 다운타임, 데이터 기간 등 집계 |
| 헬스체크 | GET /health (FastAPI) |
DB 연결 상태 확인 |
| 캐시 무효화 | GET /api/refresh |
DB 값을 수정한 뒤 캐시된 데이터를 강제로 재조회 |
| API 문서 자동화 | GET /docs (FastAPI Swagger) |
엔드포인트별 요청/응답 스펙을 브라우저에서 바로 테스트 |
- 원인: Flask는 앱 생성 위치(
run.py) 기준templates/폴더만 인식. 폴더 부재, 경로 불일치, 파일명 오타/확장자 문제로 발생 - 해결:
templates/폴더를run.py와 동일 위치에 배치하고,Flask(__name__, template_folder='templates')로 명시적 지정. 실행 시 작업 디렉터리(cwd)가 프로젝트 루트인지 확인
- 원인: 가상환경(
.venv)이 활성화된 상태에서도python/pip이 서로 다른 인터프리터 경로를 참조하는 경우 발생 - 해결:
python -m pip install <package>형태로 항상 현재 활성 인터프리터에 종속된 pip을 사용.where python으로 실제 실행 경로 확인 후.venv\Scripts\python.exe인지 검증
- 원인: winget 패키지 ID가 버전별로 세분화(
PostgreSQL.PostgreSQL.17등)되어 있어 상위 ID만으로는 매칭 실패 - 해결:
winget search postgresql로 정확한 ID 확인 후-e(정확히 일치) 옵션과 함께 재시도
- 원인:
psql.exe가 설치된C:\Program Files\PostgreSQL\<버전>\bin경로가 시스템 PATH에 등록되지 않음 - 해결:
[Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\Program Files\PostgreSQL\17\bin", "User")실행 후 터미널(VSCode 포함) 재시작.where psql로 인식 여부 확인
- 원인: 설치 과정에서 지정한 superuser 비밀번호를 분실/미기억
- 해결:
pg_hba.conf의 인증 방식을 임시로scram-sha-256→trust로 변경 → PostgreSQL 서비스 재시작 → 비밀번호 없이 접속 →ALTER USER postgres WITH PASSWORD '새비밀번호';실행 →pg_hba.conf를 원래대로 되돌리고 서비스 재시작 (보안상 필수)
- 원인: Windows 서비스 제어는 관리자 권한이 필요한데 일반 권한 PowerShell에서 실행
- 해결: PowerShell/VSCode를 관리자 권한으로 재실행하거나,
services.mscGUI에서 서비스를 우클릭 → 다시 시작
- 원인:
run.py,api_server.py가 데이터를 최초 조회 시점에 메모리 캐시(_data_cache)로 저장해두기 때문 - 해결:
/api/refresh엔드포인트 호출로 캐시를 초기화하거나 서버 재시작
- 원인: 초기 구조에서 DB 연결 문자열을 코드에 하드코딩할 위험
- 해결:
.env+python-dotenv로 분리,.gitignore에.env등록. 이미 git에 커밋된 적이 있다면git rm --cached .env및 히스토리 제거(git filter-repo) 후 자격증명 교체
# 1. 가상환경 생성 및 활성화
python -m venv .venv
.venv\Scripts\activate
# 2. 의존성 설치
python -m pip install fastapi "uvicorn[standard]" sqlalchemy psycopg2-binary python-dotenv pandas requests flaskpsql -U postgresCREATE DATABASE factoryopt;
\c factoryopt
CREATE TABLE machine_uptime (
id SERIAL PRIMARY KEY,
machine_id VARCHAR(50) NOT NULL,
date_str DATE NOT NULL,
availability FLOAT,
defect_rate FLOAT,
downtime_min FLOAT
);DB_URL=postgresql+psycopg2://postgres:your_password@localhost:5432/factoryopt
UPTIME_API_URL=http://127.0.0.1:8000/api/machine-uptime
UPTIME_API_SUMMARY_URL=http://127.0.0.1:8000/api/summary
python migrate_csv_to_db.py# 터미널 1 — API 서버
uvicorn api_server:app --host 127.0.0.1 --port 8000 --reload
# 터미널 2 — 대시보드 서버
python run.py- 대시보드: http://127.0.0.1:5000
- API 문서(Swagger): http://127.0.0.1:8000/docs
- 헬스체크: http://127.0.0.1:8000/health