Spaces:
Sleeping
Sleeping
| from fastapi import FastAPI, HTTPException | |
| from pydantic import BaseModel | |
| import pandas as pd | |
| from prophet import Prophet | |
| import psycopg2 | |
| import os | |
| app = FastAPI() | |
| db = os.getenv('db') | |
| # Database URL | |
| DATABASE_URL = db | |
| # Request body schema | |
| class PredictionRequest(BaseModel): | |
| user_id: str | |
| tanggal_prediksi: str # Format YYYY-MM-DD | |
| # Fungsi ambil data transaksi | |
| def get_transactions(user_id): | |
| try: | |
| conn = psycopg2.connect(DATABASE_URL) | |
| cur = conn.cursor() | |
| cur.execute(""" | |
| SELECT id, user_id, amount, type, description, transaction_date | |
| FROM transactions | |
| WHERE user_id = %s | |
| ORDER BY transaction_date | |
| """, (user_id,)) | |
| rows = cur.fetchall() | |
| columns = ['id', 'user_id', 'amount', 'type', 'description', 'transaction_date'] | |
| df = pd.DataFrame(rows, columns=columns) | |
| cur.close() | |
| conn.close() | |
| return df | |
| except Exception as e: | |
| return None | |
| # Hitung grand total harian | |
| def prepare_data(df): | |
| df['transaction_date'] = pd.to_datetime(df['transaction_date']) | |
| df = df.sort_values(by=['transaction_date', 'id']) | |
| grand_total = 0 | |
| grand_totals = [] | |
| for _, row in df.iterrows(): | |
| if row['type'] == 'pemasukan': | |
| grand_total += row['amount'] | |
| elif row['type'] == 'pengeluaran': | |
| grand_total -= row['amount'] | |
| grand_totals.append(grand_total) | |
| df['grand_total'] = grand_totals | |
| return df | |
| def predict_saldo(request: PredictionRequest): | |
| try: | |
| future_date = pd.to_datetime(request.tanggal_prediksi) | |
| except: | |
| raise HTTPException(status_code=400, detail="Format tanggal tidak valid. Gunakan YYYY-MM-DD.") | |
| df = get_transactions(request.user_id) | |
| if df is None or df.empty: | |
| raise HTTPException(status_code=404, detail="Data transaksi tidak ditemukan untuk user_id tersebut.") | |
| df = prepare_data(df) | |
| df_prophet = df[['transaction_date', 'grand_total']].rename( | |
| columns={'transaction_date': 'ds', 'grand_total': 'y'} | |
| ) | |
| model = Prophet() | |
| model.fit(df_prophet) | |
| last_date = df_prophet['ds'].max() | |
| days_ahead = (future_date - last_date).days | |
| if days_ahead <= 0: | |
| raise HTTPException(status_code=400, detail=f"Tanggal prediksi ({future_date.date()}) harus setelah data terakhir ({last_date.date()}).") | |
| future = model.make_future_dataframe(periods=days_ahead, freq='D') | |
| forecast = model.predict(future) | |
| prediction = forecast[forecast['ds'] == future_date] | |
| if prediction.empty: | |
| raise HTTPException(status_code=404, detail="Tanggal tidak ditemukan dalam hasil prediksi.") | |
| result = prediction[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].iloc[0] | |
| return { | |
| "tanggal": result['ds'].date().isoformat(), | |
| "prediksi_saldo": round(result['yhat'], 2), | |
| "batas_bawah": round(result['yhat_lower'], 2), | |
| "batas_atas": round(result['yhat_upper'], 2) | |
| } | |