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
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
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
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