Commit c775bcf2 authored by o's avatar o
Browse files

Add etsi_dq library and FastAPI backend

parent 56cf682c
Loading
Loading
Loading
Loading

api/__init__.py

0 → 100644
+0 −0

Empty file added.

api/main.py

0 → 100644
+86 −0
Changes for api/main.py: 86 added lines, 0 removed lines.
Original line number Diff line number Diff line
"""
ETSI DQ - FastAPI Backend
=========================

프로젝트 구조:

etsi_dq/                  ← 기존 코드 (그대로 유지)
├── metrics/
│   ├── completeness.py
│   ├── accuracy.py
│   ├── consistency.py
│   └── ...
├── pipeline.py
├── schemas.py
├── profiler.py
├── io.py
└── ...

api/                      ← 새로 추가하는 백엔드 계층
├── __init__.py
├── main.py               ← FastAPI 앱 진입점 (이 파일)
├── routers/
│   ├── __init__.py
│   ├── datasets.py       ← 데이터셋 업로드/조회 API
│   ├── metrics.py        ← 지표 설정/pre-check API
│   ├── analysis.py       ← 분석 실행 API
│   └── results.py        ← 결과 조회 API
├── schemas/
│   ├── __init__.py
│   ├── datasets.py       ← 데이터셋 관련 요청/응답 모델
│   ├── metrics.py        ← 지표 설정 관련 모델
│   ├── analysis.py       ← 분석 요청/응답 모델
│   └── results.py        ← 결과 응답 모델
└── services/
    ├── __init__.py
    ├── dataset_service.py  ← 기존 io.py 함수를 감싸는 서비스
    ├── metric_service.py   ← 기존 precheck/설정 로직 감싸는 서비스
    └── analysis_service.py ← 기존 pipeline.py의 run_analysis 감싸는 서비스

실행 방법:
    uvicorn api.main:app --reload --port 8000

API 문서 자동 생성:
    http://localhost:8000/docs  (Swagger UI)
    http://localhost:8000/redoc (ReDoc)
"""

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from api.routers import datasets, metrics, analysis, results


app = FastAPI(
    title="ETSI Data Quality API",
    description="ETSI 데이터 품질 평가 시스템 백엔드 API",
    version="0.1.0",
)

# CORS 설정 - Streamlit(8501) 또는 React(3000)에서 접근 허용
app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "http://localhost:8501",   # Streamlit 기본 포트
        "http://localhost:3000",   # React 개발 서버 포트 (나중에)
    ],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 라우터 등록
app.include_router(datasets.router, prefix="/api/datasets", tags=["datasets"])
app.include_router(metrics.router,  prefix="/api/metrics",  tags=["metrics"])
app.include_router(analysis.router, prefix="/api/analysis", tags=["analysis"])
app.include_router(results.router,  prefix="/api/results",  tags=["results"])


@app.get("/")
def root():
    return {"message": "ETSI Data Quality API", "version": "0.1.0"}


@app.get("/health")
def health_check():
    return {"status": "ok"}
+0 −0

Empty file added.

+45 −0
Changes for api/routers/analysis.py: 45 added lines, 0 removed lines.
Original line number Diff line number Diff line
"""분석 실행 API 엔드포인트"""

from fastapi import APIRouter, HTTPException
from api.schemas.analysis import AnalysisRunRequest, AnalysisRunResponse
from api.services import analysis_service
router = APIRouter()
service = analysis_service


@router.post("/run", response_model=AnalysisRunResponse)
async def run_analysis(request: AnalysisRunRequest):
    """
    품질 분석 실행

    선택된 지표와 설정값을 바탕으로 분석 수행.
    기존 코드의 run_analysis() 함수를 내부적으로 호출.

    요청 예시:
    {
        "dataset_id": "abc123",
        "metrics": ["completeness", "accuracy"],
        "config": {
            "completeness": {
                "required_columns": ["Na", "Al", "K", "Si"],
                "missing_values": ["?", "N/A", "null"],
                "granularity": "dataset"
            },
            "accuracy": {
                "rules": [
                    {
                        "measured_column": "RI",
                        "method": "statistical_bounds",
                        "k_sigma": 3.0,
                        "center": "mean"
                    }
                ]
            }
        }
    }
    """
    try:
        result = await service.execute(request)
        return result
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))
+59 −0
Changes for api/routers/datasets.py: 59 added lines, 0 removed lines.
Original line number Diff line number Diff line
"""데이터셋 관리 API 엔드포인트"""

from fastapi import APIRouter, UploadFile, File, HTTPException
from api.schemas.datasets import DatasetUploadResponse, DatasetInfo, DatasetPreview
from api.services import dataset_service

router = APIRouter()
service = dataset_service


@router.post("/upload", response_model=DatasetUploadResponse)
async def upload_dataset(file: UploadFile = File(...)):
    """
    데이터셋 업로드

    지원 형식: CSV, XLSX, XLS, ZIP
    기존 코드의 load_dataset() 함수를 내부적으로 호출
    """
    try:
        result = await service.upload_and_parse(file)
        return result
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))


@router.post("/reference/upload", response_model=DatasetUploadResponse)
async def upload_reference_dataset(file: UploadFile = File(...)):
    """
    참조 데이터셋 업로드 (Accuracy, Consistency에서 사용)
    """
    try:
        result = await service.upload_and_parse(file, is_reference=True)
        return result
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))


@router.get("/", response_model=list[DatasetInfo])
async def list_datasets():
    """업로드된 데이터셋 목록 조회"""
    return service.list_datasets()


@router.get("/{dataset_id}", response_model=DatasetInfo)
async def get_dataset(dataset_id: str):
    """특정 데이터셋 정보 조회"""
    result = service.get_dataset(dataset_id)
    if not result:
        raise HTTPException(status_code=404, detail="Dataset not found")
    return result


@router.get("/{dataset_id}/preview", response_model=DatasetPreview)
async def preview_dataset(dataset_id: str, rows: int = 10):
    """데이터셋 미리보기 (상위 N행)"""
    result = service.get_preview(dataset_id, rows)
    if not result:
        raise HTTPException(status_code=404, detail="Dataset not found")
    return result
Loading