TIF_E41253385/api_slearn/main.py

488 lines
12 KiB
Python

from uuid import uuid4
from fastapi import FastAPI, HTTPException, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
from typing import Literal
from training_input import validate_column
from model_registry import bundle, history, activate, queue, train_job
from model_service import (
analisis_teks,
preprocessing_detail,
get_model_info
)
from database import (
test_db_connection,
get_database_summary,
save_analysis_batch,
get_analysis_batch,
get_analysis_history,
save_single_prediction,
get_single_prediction_history,
get_single_prediction
)
# ============================================================
# FASTAPI
# ============================================================
app = FastAPI(
title="S-Learn Sentiment Analysis API",
description=(
"Backend API analisis sentimen S-Learn "
"menggunakan TF-IDF dan Logistic Regression."
),
version="1.0.0"
)
# ============================================================
# CORS
# ============================================================
app.add_middleware(
CORSMiddleware,
allow_origin_regex=r"http://(localhost|127\.0\.0\.1):\d+",
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"]
)
# ============================================================
# REQUEST MODEL
# ============================================================
class TextRequest(BaseModel):
text: str
class CSVReviewItem(BaseModel):
source_row_number: int
text: str
class CSVAnalysisRequest(BaseModel):
file_name: str
selected_column: str
reviews: list[CSVReviewItem]
class TrainingRequest(BaseModel):
selected_column: str = Field(min_length=1, max_length=200)
confirmed_review_column: Literal[True]
file_name: str = Field(min_length=1, max_length=200)
reviews: list[str] = Field(min_length=1, max_length=5000)
@app.get('/models')
def model_history():
return {'status': 'success', 'data': history()}
@app.get('/model-info/original')
def original_model_info():
return {'status': 'success', 'data': bundle('v1')[2]}
@app.post('/models/{version}/activate')
def activate_model(version: str):
try:
return {'status': 'success', 'data': activate(version)}
except ValueError as error:
raise HTTPException(status_code=400, detail=str(error)) from error
@app.post('/training', status_code=202)
def training(request: TrainingRequest, background: BackgroundTasks):
try:
validate_column(request.selected_column)
except ValueError as error:
raise HTTPException(status_code=400, detail=str(error)) from error
if any(len(text) > 10000 for text in request.reviews):
raise HTTPException(status_code=400, detail='Panjang ulasan maksimal 10.000 karakter.')
if not any(text.strip() for text in request.reviews):
raise HTTPException(status_code=400, detail='CSV tidak berisi ulasan.')
try:
job_id = queue(request.file_name)
except ValueError as error:
raise HTTPException(status_code=409, detail=str(error)) from error
background.add_task(train_job, job_id, request.reviews, request.selected_column)
return {'status': 'success', 'job_id': job_id}
# ============================================================
# ROOT
# ============================================================
@app.get("/")
def root():
return {
"message": "S-Learn Sentiment Analysis API",
"status": "running"
}
# ============================================================
# HEALTH CHECK BACKEND
# ============================================================
@app.get("/health")
def health_check():
return {
"status": "ok",
"service": "api_slearn",
"model": "loaded"
}
# ============================================================
# HEALTH CHECK DATABASE
# ============================================================
@app.get("/database/health")
def database_health():
try:
koneksi = test_db_connection()
summary = get_database_summary()
return {
"status": "success",
"message": "Koneksi MySQL berhasil.",
"database": koneksi["database"],
"tables": koneksi["tables"],
"summary": summary
}
except ConnectionError as error:
raise HTTPException(
status_code=500,
detail=str(error)
)
# ============================================================
# INFORMASI MODEL
# ============================================================
@app.get("/model-info")
def model_info():
return {
"status": "success",
"data": get_model_info()
}
# ============================================================
# PREPROCESSING SATU ULASAN
# ============================================================
@app.post("/preprocess")
def preprocess(request: TextRequest):
try:
hasil = preprocessing_detail(
request.text
)
return {
"status": "success",
"data": hasil
}
except ValueError as error:
raise HTTPException(
status_code=400,
detail=str(error)
)
# ============================================================
# PREDIKSI SATU ULASAN
# ============================================================
@app.post("/predict")
def predict(request: TextRequest):
try:
# ----------------------------------------------------
# 1. ANALISIS TEKS
# ----------------------------------------------------
hasil = analisis_teks(
request.text
)
# ----------------------------------------------------
# 2. SIMPAN HASIL KE MYSQL
# ----------------------------------------------------
try:
prediction_id = save_single_prediction(
hasil
)
except Exception as error:
raise HTTPException(
status_code=500,
detail=(
"Prediksi berhasil dilakukan, "
"tetapi hasil gagal disimpan "
"ke database. "
f"{str(error)}"
)
)
# ----------------------------------------------------
# 3. KEMBALIKAN HASIL
# ----------------------------------------------------
return {
"status": "success",
"message": (
"Prediksi berhasil dilakukan "
"dan disimpan ke riwayat."
),
"data": {
**hasil,
"prediction_id": prediction_id
}
}
except HTTPException:
raise
except ValueError as error:
raise HTTPException(
status_code=400,
detail=str(error)
)
# ============================================================
# RIWAYAT PREDIKSI SATU ULASAN
# ============================================================
@app.get("/predictions")
def prediction_history():
try:
history = get_single_prediction_history()
return {
"status": "success",
"data": history,
"total": len(history)
}
except Exception as error:
raise HTTPException(
status_code=500,
detail=(
"Gagal mengambil riwayat "
"Prediksi Satu Ulasan. "
f"{str(error)}"
)
)
# ============================================================
# DETAIL SATU PREDIKSI
# ============================================================
@app.get("/predictions/{prediction_id}")
def prediction_detail(
prediction_id: int
):
if prediction_id < 1:
raise HTTPException(
status_code=400,
detail="ID prediksi tidak valid."
)
try:
hasil = get_single_prediction(
prediction_id
)
if hasil is None:
raise HTTPException(
status_code=404,
detail="Riwayat prediksi tidak ditemukan."
)
return {
"status": "success",
"data": hasil
}
except HTTPException:
raise
except Exception as error:
raise HTTPException(
status_code=500,
detail=(
"Gagal mengambil detail prediksi. "
f"{str(error)}"
)
)
# ============================================================
# ANALISIS CSV
# ============================================================
@app.post("/analysis/csv")
def analysis_csv(
request: CSVAnalysisRequest
):
if not request.file_name.strip():
raise HTTPException(
status_code=400,
detail="Nama file CSV tidak boleh kosong."
)
if not request.selected_column.strip():
raise HTTPException(
status_code=400,
detail="Kolom ulasan belum dipilih."
)
if not request.reviews:
raise HTTPException(
status_code=400,
detail="Tidak ada ulasan yang dapat dianalisis."
)
batch_id = (
"BATCH-"
+ uuid4().hex[:12].upper()
)
hasil_analisis = []
snapshot = bundle()
for review in request.reviews:
if review.source_row_number < 1:
raise HTTPException(
status_code=400,
detail="Nomor baris CSV tidak valid."
)
try:
hasil = analisis_teks(
review.text, snapshot=snapshot
)
except ValueError as error:
raise HTTPException(
status_code=400,
detail=(
f"Baris {review.source_row_number}: "
f"{str(error)}"
)
)
hasil[
"source_row_number"
] = review.source_row_number
hasil_analisis.append(
hasil
)
try:
batch = save_analysis_batch(
batch_id=batch_id,
file_name=request.file_name.strip(),
selected_column=request.selected_column.strip(),
results=hasil_analisis
)
except Exception as error:
raise HTTPException(
status_code=500,
detail=(
"Analisis berhasil dilakukan, "
"tetapi hasil gagal disimpan "
"ke database. "
f"{str(error)}"
)
)
return {
"status": "success",
"message": (
"CSV berhasil dianalisis dan "
"disimpan ke database."
),
"data": {
**batch,
"results": hasil_analisis
}
}
# ============================================================
# RIWAYAT SEMUA BATCH CSV
# ============================================================
@app.get("/analysis")
def analysis_history():
try:
history = get_analysis_history()
return {
"status": "success",
"data": history,
"total": len(history)
}
except Exception as error:
raise HTTPException(
status_code=500,
detail=(
"Gagal mengambil riwayat analisis. "
f"{str(error)}"
)
)
# ============================================================
# DETAIL SATU BATCH CSV
# ============================================================
@app.get("/analysis/{batch_id}")
def analysis_detail(
batch_id: str
):
try:
hasil = get_analysis_batch(
batch_id.strip()
)
if hasil is None:
raise HTTPException(
status_code=404,
detail="Batch analisis tidak ditemukan."
)
return {
"status": "success",
"data": hasil
}
except HTTPException:
raise
except Exception as error:
raise HTTPException(
status_code=500,
detail=(
"Gagal mengambil hasil analisis. "
f"{str(error)}"
)
)