moodread-api / app.py
abzhorrr's picture
feat: change functional function
f7ad185
Raw
History Blame Contribute Delete
8.02 kB
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from transformers import pipeline
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
import pandas as pd
import os
app = FastAPI(title="MoodRead API", version="2.0.0")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# ── 1. Load model emosi RoBERTa ──────────────────────────────────────────────
MODEL_NAME = "StevenLimcorn/indonesian-roberta-base-emotion-classifier"
print("Memuat model emosi RoBERTa...")
emotion_pipeline = pipeline(
task="text-classification",
model=MODEL_NAME,
device=-1 # CPU
)
print("βœ“ Model emosi siap!")
# ── 2. Load Sentence Transformer (Dania) ─────────────────────────────────────
ST_MODEL = "paraphrase-multilingual-MiniLM-L12-v2"
print("Memuat Sentence Transformer...")
sentence_model = SentenceTransformer(ST_MODEL)
print("βœ“ Sentence Transformer siap!")
# ── 3. Load dataset buku ─────────────────────────────────────────────────────
DATASET_PATH = "books_clean.csv"
if os.path.exists(DATASET_PATH):
books_df = pd.read_csv(DATASET_PATH, encoding="utf-8")
books_df["genre"] = books_df["genre"].str.lower().str.strip()
print(f"βœ“ Dataset buku dimuat: {len(books_df)} buku")
else:
books_df = pd.DataFrame()
print("⚠️ books_clean.csv tidak ditemukan!")
# ── Schema ───────────────────────────────────────────────────────────────────
class TextInput(BaseModel):
text: str
top_k: int = 5
# ── Fungsi Yuri: Hybrid Contextual Genre Mapping ─────────────────────────────
def get_recommended_genres(emotion: str, text_user: str) -> list[str]:
"""
Logika hybrid: kombinasi hasil model AI + aturan kata kunci kontekstual.
"""
teks_bersih = text_user.lower().strip()
emosi_bersih = emotion.lower().strip()
kata_kunci_karir = [
'presentasi', 'ujian', 'sidang', 'kerja', 'tugas', 'dosen',
'kuliah', 'gagal', 'bos', 'kantor', 'capek', 'skripsi', 'kelulusan'
]
kata_kunci_sosial = [
'sahabat', 'cinta', 'putus', 'dikhianati', 'pacar', 'mantan',
'teman', 'kucing', 'kehilangan', 'ditipu', 'rekan bisnis'
]
if emosi_bersih in ['happy', 'joy']:
return ['pengembangan_diri', 'novel']
elif emosi_bersih == 'love':
return ['romance']
elif emosi_bersih == 'fear':
if any(k in teks_bersih for k in kata_kunci_karir):
return ['pengembangan_diri']
else:
return ['novel', 'horor']
elif emosi_bersih == 'sadness':
if any(k in teks_bersih for k in ['marah', 'kesal', 'ditipu', 'ketidakadilan', 'geram']):
return ['novel', 'thriller']
elif any(k in teks_bersih for k in kata_kunci_sosial):
return ['romance']
elif any(k in teks_bersih for k in kata_kunci_karir):
return ['pengembangan_diri']
else:
return ['fiksi_sastra']
elif emosi_bersih == 'anger':
if any(k in teks_bersih for k in kata_kunci_karir):
return ['cerpen']
else:
return ['novel', 'thriller']
return ['novel', 'fiksi_sastra']
# ── Fungsi Yuri: Deteksi emosi mentah ────────────────────────────────────────
def detect_emotion(text: str, top_k: int = 3) -> dict:
if not text.strip():
return {
'primary_emotion': 'neutral',
'primary_score': 0.0,
'top_emotions': [],
}
raw = emotion_pipeline(text, top_k=top_k)
return {
'primary_emotion': raw[0]['label'].lower(),
'primary_score': round(raw[0]['score'], 4),
'top_emotions': [
{'emotion': r['label'].lower(), 'score': round(r['score'], 4)}
for r in raw
],
}
# ── Fungsi Yuri: Full pipeline emosi + hybrid genre ──────────────────────────
def full_emotion_pipeline(text: str) -> dict:
analisis = detect_emotion(text)
genres = get_recommended_genres(analisis['primary_emotion'], text)
return {
'emotion': analisis['primary_emotion'],
'confidence': analisis['primary_score'],
'recommended_genres': genres,
'all_emotions': analisis['top_emotions'],
}
# ── Fungsi Dania: Rekomendasi buku via Sentence Transformer ──────────────────
def get_book_recommendations(user_text: str, target_genre: str, top_k: int = 5) -> list[dict]:
if books_df.empty:
return []
df_filtered = books_df[
books_df['genre'].str.contains(target_genre, case=False, na=False)
].copy()
if df_filtered.empty:
return []
user_vector = sentence_model.encode([user_text])
title_vectors = sentence_model.encode(df_filtered['judul'].tolist())
similarities = cosine_similarity(user_vector, title_vectors)[0]
df_filtered['similarity_score'] = similarities
top_books = (
df_filtered
.sort_values('similarity_score', ascending=False)
.head(top_k)
)
return top_books.drop(columns=['similarity_score']).to_dict('records')
# ── Fungsi utama: moodread_pipeline ──────────────────────────────────────────
def moodread_pipeline(teks_curhat: str, top_k: int = 5) -> dict:
"""
Pipeline lengkap end-to-end:
Tahap 1 β†’ Deteksi emosi + hybrid genre mapping (Yuri)
Tahap 2 β†’ Rekomendasi buku via Sentence Transformer (Dania)
"""
# Tahap 1: emosi & genre
hasil_emosi = full_emotion_pipeline(teks_curhat)
genre_utama = hasil_emosi['recommended_genres'][0]
# Tahap 2: rekomendasi buku semantik
buku = get_book_recommendations(teks_curhat, genre_utama, top_k=top_k)
return {
'emotion': hasil_emosi['emotion'],
'confidence': hasil_emosi['confidence'],
'all_emotions': hasil_emosi['all_emotions'],
'recommended_genres': hasil_emosi['recommended_genres'],
'book_recommendations': buku,
}
# ── Endpoints ────────────────────────────────────────────────────────────────
@app.get("/")
def root():
return {"message": "MoodRead API v2 aktif πŸŽ‰", "docs": "/docs"}
@app.get("/health")
def health():
return {"status": "ok"}
@app.post("/detect-emotion")
def endpoint_detect(payload: TextInput):
if not payload.text.strip():
raise HTTPException(status_code=400, detail="Teks tidak boleh kosong.")
# Panggil moodread_pipeline sebagai entry point utama
hasil = moodread_pipeline(payload.text, top_k=payload.top_k)
return {
"primary_emotion": hasil["emotion"],
"primary_score": hasil["confidence"],
"top_emotions": hasil["all_emotions"],
"recommended_genres": hasil["recommended_genres"],
"book_recommendations": hasil["book_recommendations"],
}
@app.get("/emotions")
def list_emotions():
return {"emotions": ["happy", "joy", "love", "sadness", "anger", "fear", "surprise", "neutral"]}
@app.get("/genres")
def list_genres():
if books_df.empty:
return {"genres": []}
return {"genres": books_df["genre"].unique().tolist()}