Spaces:
Runtime error
Runtime error
Ludy Hasby Aulia commited on
Commit ·
96c14c1
1
Parent(s): b64426a
first commit
Browse files- Dockerfile +9 -0
- app.py +248 -0
- model_kelas.h5 +3 -0
- requirements.txt +3 -0
- tokenizer.pickle +3 -0
Dockerfile
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.12-slim
|
| 2 |
+
WORKDIR /backEndAI
|
| 3 |
+
ENV FLASK_APP=app.py
|
| 4 |
+
ENV FLASK_RUN_HOST=0.0.0.0
|
| 5 |
+
COPY requirements.txt requirements.txt
|
| 6 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 7 |
+
EXPOSE 8000
|
| 8 |
+
COPY . .
|
| 9 |
+
CMD ["flask", "run"]
|
app.py
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from flask import Flask, request, jsonify
|
| 2 |
+
import re
|
| 3 |
+
import pickle
|
| 4 |
+
import numpy as np
|
| 5 |
+
from tensorflow.keras.models import load_model
|
| 6 |
+
from tensorflow.keras.preprocessing.sequence import pad_sequences
|
| 7 |
+
|
| 8 |
+
# STATICS
|
| 9 |
+
stop_words = {'baik', 'saatnya', 'semasa', 'sesudah', 'menginginkan', 'tampak', 'sekitar', 'berikut', 'sebagainya', 'semua', 'sudahkah', 'sambil', 'karenanya', 'diperlukan', 'termasuk', 'setiba', 'malah', 'jawaban', 'kata', 'demi', 'tutur', 'meyakinkan', 'siap', 'umumnya', 'terjadi', 'tandasnya', 'mampukah', 'sampai', 'bersama-sama', 'kamulah', 'apa', 'memihak', 'sekitarnya', 'waktu', 'berapalah', 'bakalan', 'tadi', 'jadi', 'kedua', 'dalam', 'sepanjang', 'ditunjuki', 'mengibaratkan', 'mempergunakan', 'semisalnya', 'sudahlah', 'kepada', 'teringat', 'manakala', 'menunjuki', 'sekurangnya', 'jelasnya', 'selama-lamanya', 'dia', 'sesaat', 'sesekali', 'tidak', 'betul', 'memungkinkan', 'pernah', 'masalahnya', 'sebagian', 'disini', 'atau', 'berupa', 'bila', 'saya', 'sekalipun', 'tersebut', 'dikatakan', 'amat', 'apaan', 'menjawab', 'selalu', 'balik', 'usah', 'berakhir', 'menjelaskan', 'tentulah', 'macam', 'memberi', 'sesuatunya', 'baru', 'dijelaskan', 'semakin', 'seolah', 'belakangan', 'serupa', 'terbanyak', 'mengerjakan', 'sebut', 'semata-mata', 'sama-sama', 'diminta', 'bapak', 'memulai', 'satu', 'biasa', 'semaunya', 'bahwa', 'bermula', 'sudah', 'hanya', 'setengah', 'dijelaskannya', 'dapat', 'nyatanya', 'karena', 'mendatangi', 'disebut', 'masih', 'berdatangan', 'selaku', 'tidaklah', 'diinginkan', 'kepadanya', 'namun', 'cukupkah', 'mungkin', 'kemungkinannya', 'terhadap', 'empat', 'sesama', 'kemudian', 'terutama', 'yakin', 'kitalah', 'seberapa', 'ditunjuk', 'diantara', 'mengingat', 'sampai-sampai', 'tanpa', 'ke', 'akankah', 'asal', 'meyakini', 'mulailah', 'sebaik-baiknya', 'tambahnya', 'saling', 'usai', 'paling', 'diri', 'menuturkan', 'makanya', 'bolehkah', 'menyatakan', 'kamilah', 'cukup', 'berujar', 'mengetahui', 'tahun', 'guna', 'maupun', 'dialah', 'persoalan', 'sana', 'berakhirnya', 'terjadilah', 'akhir', 'harus', 'semacam', 'misal', 'sekali-kali', 'pertanyaan', 'sebelumnya', 'pula', 'sini', 'jika', 'sangatlah', 'kapankah', 'jauh', 'kemungkinan', 'sekaligus', 'soal', 'bakal', 'kalaulah', 'lama', 'semata', 'sendiri', 'semasih', 'yaitu', 'mendapatkan', 'telah', 'ingat-ingat', 'atas', 'toh', 'tanyanya', 'dimaksud', 'dimintai', 'menyangkut', 'benar', 'terasa', 'melakukan', 'mengenai', 'keinginan', 'wah', 'dirinya', 'makin', 'hal', 'sedikitnya', 'se', 'diibaratkannya', 'sela', 'menunjuknya', 'oleh', 'mengibaratkannya', 'masing', 'hanyalah', 'beginilah', 'beri', 'wong', 'menggunakan', 'bulan', 'menurut', 'dong', 'itulah', 'berapa', 'tiba', 'semula', 'sajalah', 'hampir', 'agak', 'terlebih', 'membuat', 'caranya', 'perlu', 'segalanya', 'merekalah', 'sewaktu', 'lainnya', 'siapakah', 'tetapi', 'memerlukan', 'pertama-tama', 'tegas', 'sempat', 'entahlah', 'bukan', 'bermaksud', 'sebegini', 'pada', 'diperbuat', 'sendirinya', 'diakhirinya', 'menandaskan', 'mempersoalkan', 'setelah', 'diucapkan', 'antara', 'berikutnya', 'diketahui', 'sejak', 'jelaskan', 'dimaksudnya', 'itukah', 'enggaknya', 'kenapa', 'sekecil', 'sebetulnya', 'seolah-olah', 'mempunyai', 'sepihak', 'selama', 'semampunya', 'ucap', 'ataupun', 'kecil', 'bisakah', 'dipergunakan', 'nantinya', 'tampaknya', 'lah', 'mau', 'bung', 'dimulai', 'mendapat', 'ibarat', 'bawah', 'sendirian', 'kami', 'akan', 'kinilah', 'bersiap', 'buat', 'pukul', 'berbagai', 'dipertanyakan', 'melainkan', 'cuma', 'sepantasnya', 'berlangsung', 'kini', 'sampaikan', 'bertanya', 'belumlah', 'menambahkan', 'lanjut', 'betulkah', 'bagaimanapun', 'jikalau', 'sebesar', 'merasa', 'terdapat', 'sebaik', 'menjadi', 'diperbuatnya', 'ucapnya', 'jawabnya', 'kalaupun', 'antaranya', 'tanya', 'bekerja', 'kesampaian', 'dan', 'dibuatnya', 'dijawab', 'kapan', 'kira', 'untuk', 'pihaknya', 'bagaikan', 'sebagai', 'tiga', 'apakah', 'seterusnya', 'ditunjukkan', 'dimulainya', 'mana', 'ditandaskan', 'asalkan', 'tanyakan', 'didatangkan', 'sedang', 'tengah', 'selain', 'justru', 'tertuju', 'terlihat', 'lalu', 'merupakan', 'perlunya', 'mengatakannya', 'jumlahnya', 'besar', 'pihak', 'semampu', 'inginkan', 'masihkah', 'tandas', 'diibaratkan', 'sebaliknya', 'dahulu', 'berkeinginan', 'daripada', 'akulah', 'bisa', 'depan', 'menuju', 'sejumlah', 'katanya', 'bukankah', 'mengingatkan', 'hingga', 'padahal', 'seperti', 'awalnya', 'diperlihatkan', 'demikian', 'seusai', 'ibaratkan', 'andalah', 'kala', 'malahan', 'kok', 'sangat', 'sekurang-kurangnya', 'sementara', 'kelihatannya', 'kembali', 'beginikah', 'semuanya', 'melihat', 'tersebutlah', 'beberapa', 'dekat', 'ini', 'memintakan', 'walaupun', 'apabila', 'belum', 'berada', 'sebagaimana', 'sebanyak', 'bukannya', 'cukuplah', 'kamu', 'secukupnya', 'sekiranya', 'sepantasnyalah', 'lagi', 'inilah', 'berturut-turut', 'sekarang', 'dilihat', 'agar', 'lebih', 'ujar', 'mampu', 'terdiri', 'sebutlah', 'berakhirlah', 'ditunjuknya', 'berlalu', 'awal', 'dimisalkan', 'bermacam', 'meskipun', 'panjang', 'dikarenakan', 'umum', 'mulai', 'tapi', 'hendaknya', 'diantaranya', 'memisalkan', 'mungkinkah', 'begitupun', 'menegaskan', 'segala', 'sekali', 'kapanpun', 'keseluruhan', 'artinya', 'menantikan', 'sekadarnya', 'berkali-kali', 'ditanya', 'sebaiknya', 'sedemikian', 'seketika', 'diperlukannya', 'mempertanyakan', 'naik', 'setidak-tidaknya', 'demikianlah', 'mulanya', 'lima', 'mula', 'ditunjukkannya', 'pantas', 'menaiki', 'anda', 'diakhiri', 'lanjutnya', 'kelima', 'dikira', 'ada', 'keterlaluan', 'tertentu', 'yang', 'per', 'ternyata', 'berapapun', 'bukanlah', 'seingat', 'mengungkapkan', 'begini', 'janganlah', 'diucapkannya', 'dikerjakan', 'menanya', 'dimaksudkannya', 'berlebihan', 'tidakkah', 'menyampaikan', 'boleh', 'terjadinya', 'bagaimana', 'dulu', 'jangan', 'adalah', 'menanyakan', 'secara', 'bersama', 'begitukah', 'rata', 'siapapun', 'terkira', 'berturut', 'itu', 'selamanya', 'menyeluruh', 'bagaimanakah', 'sebutnya', 'dimungkinkan', 'kita', 'seorang', 'sekadar', 'dipersoalkan', 'inginkah', 'setibanya', 'belakang', 'katakan', 'melalui', 'mendatang', 'jumlah', 'rupanya', 'berkenaan', 'ungkapnya', 'soalnya', 'ingin', 'ditambahkan', 'banyak', 'apalagi', 'amatlah', 'ditujukan', 'datang', 'menanti-nanti', 'dini', 'mengucapkannya', 'setidaknya', 'rasa', 'tiap', 'setiap', 'begitulah', 'melihatnya', 'mengucapkan', 'kan', 'sedikit', 'sesampai', 'sesegera', 'mendatangkan', 'tentunya', 'masalah', 'mengatakan', 'percuma', 'menunjukkan', 'dikatakannya', 'misalkan', 'sesudahnya', 'penting', 'bahkan', 'apatah', 'di', 'tegasnya', 'seharusnya', 'ungkap', 'yakni', 'mengakhiri', 'teringat-ingat', 'dibuat', 'meski', 'bermacam-macam', 'dilalui', 'sebenarnya', 'bahwasanya', 'ditanyakan', 'jangankan', 'sebegitu', 'sering', 'masing-masing', 'selanjutnya', 'sebabnya', 'ujarnya', 'diingatkan', 'padanya', 'terhadapnya', 'misalnya', 'seluruh', 'kelihatan', 'memastikan', 'tuturnya', 'mempersiapkan', 'bertutur', 'lagian', 'siapa', 'seseorang', 'aku', 'sejauh', 'walau', 'disebutkannya', 'tempat', 'diberi', 'biasanya', 'menunjuk', 'suatu', 'jelas', 'disinilah', 'ditanyai', 'sebisanya', 'berkehendak', 'sinilah', 'diungkapkan', 'memperlihatkan', 'agaknya', 'dua', 'dari', 'hendaklah', 'jelaslah', 'katakanlah', 'hari', 'adapun', 'memberikan', 'benarkah', 'sama', 'terlalu', 'lewat', 'setempat', 'ingat', 'manalagi', 'segera', 'tak', 'inikah', 'nah', 'berkata', 'punya', 'minta', 'menyebutkan', 'berawal', 'dituturkannya', 'kasus', 'kira-kira', 'dengan', 'diketahuinya', 'ibu', 'enggak', 'sekalian', 'antar', 'masa', 'maka', 'diberikannya', 'pertama', 'memperbuat', 'berarti', 'gunakan', 'memperkirakan', 'tepat', 'wahai', 'perlukah', 'saat', 'hendak', 'terakhir', 'ataukah', 'sayalah', 'tinggi', 'ialah', 'nanti', 'dipastikan', 'pak', 'mengira', 'keduanya', 'mengapa', 'terus', 'setinggi', 'kiranya', 'adanya', 'entah', 'bagi', 'seringnya', 'sehingga', 'berikan', 'ditegaskan', 'semisal', 'dituturkan', 'berapakah', 'supaya', 'dilakukan', 'kebetulan', 'keluar', 'ketika', 'sesuatu', 'menanyai', 'olehnya', 'lamanya', 'luar', 'meminta', 'juga', 'terdahulu', 'didapat', 'berjumlah', 'akhiri', 'serta', 'disebutkan', 'tahu', 'rasanya', 'pun', 'begitu', 'kurang', 'sebab', 'waduh', 'tiba-tiba', 'ia', 'digunakan', 'waktunya', 'harusnya', 'pentingnya', 'seenaknya', 'tadinya', 'kalian', 'bolehlah', 'dimaksudkan', 'dimulailah', 'menghendaki', 'lain', 'jadilah', 'sedangkan', 'bersiap-siap', 'pastilah', 'memang', 'bilakah', 'pertanyaan', 'saja', 'jadinya', 'keadaan', 'pasti', 'tunjuk', 'benarlah', 'diberikan', 'jawab', 'ibaratnya', 'mereka', 'seluruhnya', 'dipunyai', 'keseluruhannya', 'menanti', 'disampaikan', 'sejenak', 'kalau', 'sebelum', 'tersampaikan', 'haruslah', 'diingat', 'cara', 'ikut', 'khususnya', 'tambah', 'akhirnya', 'para', 'mirip', 'kelamaan', 'turut', 'diperkirakan', 'tentu', 'menyiapkan', 'sebuah', 'seperlunya', 'tetap', 'tentang', 'bagai', 'beginian', 'berlainan', 'bertanya-tanya', 'sepertinya', 'nyaris', 'bagian'}
|
| 10 |
+
|
| 11 |
+
# Load model and tokenizer
|
| 12 |
+
print("Loading model...")
|
| 13 |
+
model = load_model('model_kelas.h5')
|
| 14 |
+
print("Model loaded successfully!")
|
| 15 |
+
|
| 16 |
+
print("Loading tokenizer...")
|
| 17 |
+
with open('tokenizer.pickle', 'rb') as handle:
|
| 18 |
+
tokenizer = pickle.load(handle)
|
| 19 |
+
print("Tokenizer loaded successfully!")
|
| 20 |
+
|
| 21 |
+
# Constants
|
| 22 |
+
max_length = 75
|
| 23 |
+
trunc_type = 'post'
|
| 24 |
+
padding_type = 'post'
|
| 25 |
+
|
| 26 |
+
# FUNCTIONS
|
| 27 |
+
def preprocessing_transaksi(detail):
|
| 28 |
+
"""Preprocess transaction text"""
|
| 29 |
+
dt = detail.lower()
|
| 30 |
+
dt = re.sub(r'[^\w\s]', '', dt)
|
| 31 |
+
dt = re.sub(r'\b\d+\b', ' ', dt)
|
| 32 |
+
dt = re.sub(r'\b(rp|rupiah|sebesar|ribu)\b', ' ', dt)
|
| 33 |
+
dt = ' '.join([word for word in dt.split() if word not in stop_words])
|
| 34 |
+
dt = re.sub(r'\s+', ' ', dt).strip()
|
| 35 |
+
return dt
|
| 36 |
+
|
| 37 |
+
def decode_label(coding):
|
| 38 |
+
"""Convert numeric prediction to category label"""
|
| 39 |
+
categories = {
|
| 40 |
+
0: "Makanan & Kebutuhan Rumah Tangga",
|
| 41 |
+
1: "Tagihan dan Lainnya",
|
| 42 |
+
2: "Transportasi",
|
| 43 |
+
3: "Konektivitas",
|
| 44 |
+
4: "Hiburan"
|
| 45 |
+
}
|
| 46 |
+
return categories.get(coding, "Unknown")
|
| 47 |
+
|
| 48 |
+
def decode_label_cat_2(coding):
|
| 49 |
+
categories = {
|
| 50 |
+
0: "Primer",
|
| 51 |
+
1: "Sekunder",
|
| 52 |
+
2: "Sekunder",
|
| 53 |
+
3: "Sekunder",
|
| 54 |
+
4: "Tersier"
|
| 55 |
+
}
|
| 56 |
+
return categories.get(coding, "Unknown")
|
| 57 |
+
|
| 58 |
+
def predict_category(text):
|
| 59 |
+
"""Predict category for given text"""
|
| 60 |
+
# Preprocess
|
| 61 |
+
processed_text = preprocessing_transaksi(text)
|
| 62 |
+
|
| 63 |
+
# Tokenize and pad
|
| 64 |
+
sequences = tokenizer.texts_to_sequences([processed_text])
|
| 65 |
+
padded = pad_sequences(sequences,
|
| 66 |
+
maxlen=max_length,
|
| 67 |
+
truncating=trunc_type,
|
| 68 |
+
padding=padding_type)
|
| 69 |
+
|
| 70 |
+
# Predict
|
| 71 |
+
prediction = model.predict(padded, verbose=0)
|
| 72 |
+
category_idx = np.argmax(prediction)
|
| 73 |
+
confidence = float(prediction[0][category_idx])
|
| 74 |
+
|
| 75 |
+
return {
|
| 76 |
+
"category_1": decode_label(category_idx),
|
| 77 |
+
"category_2": decode_label_cat_2(category_idx),
|
| 78 |
+
"confidence": round(confidence * 100, 2),
|
| 79 |
+
"all_probabilities": {
|
| 80 |
+
decode_label(i): round(float(prediction[0][i]) * 100, 2)
|
| 81 |
+
for i in range(5)
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
# Initialize Flask app
|
| 86 |
+
app = Flask(__name__)
|
| 87 |
+
|
| 88 |
+
# ROUTES
|
| 89 |
+
|
| 90 |
+
@app.route('/', methods=['GET'])
|
| 91 |
+
def home():
|
| 92 |
+
"""Home endpoint with API documentation"""
|
| 93 |
+
return jsonify({
|
| 94 |
+
"message": "Expense Category Classifier API",
|
| 95 |
+
"version": "1.0",
|
| 96 |
+
"endpoints": {
|
| 97 |
+
"/": "API documentation (this page)",
|
| 98 |
+
"/predict": "POST - Predict single transaction category",
|
| 99 |
+
"/predict/batch": "POST - Predict multiple transactions",
|
| 100 |
+
"/health": "GET - Check API health status",
|
| 101 |
+
"/categories": "GET - List all available categories"
|
| 102 |
+
},
|
| 103 |
+
"example_usage": {
|
| 104 |
+
"url": "/predict",
|
| 105 |
+
"method": "POST",
|
| 106 |
+
"body": {
|
| 107 |
+
"text": "beli nasi goreng di warteg"
|
| 108 |
+
}
|
| 109 |
+
}
|
| 110 |
+
})
|
| 111 |
+
|
| 112 |
+
@app.route('/health', methods=['GET'])
|
| 113 |
+
def health():
|
| 114 |
+
"""Health check endpoint"""
|
| 115 |
+
return jsonify({
|
| 116 |
+
"status": "healthy",
|
| 117 |
+
"model_loaded": model is not None,
|
| 118 |
+
"tokenizer_loaded": tokenizer is not None
|
| 119 |
+
})
|
| 120 |
+
|
| 121 |
+
@app.route('/categories', methods=['GET'])
|
| 122 |
+
def get_categories():
|
| 123 |
+
"""Get list of all categories"""
|
| 124 |
+
categories = [
|
| 125 |
+
"Makanan & Kebutuhan Rumah Tangga",
|
| 126 |
+
"Tagihan dan Lainnya",
|
| 127 |
+
"Transportasi",
|
| 128 |
+
"Konektivitas",
|
| 129 |
+
"Hiburan"
|
| 130 |
+
]
|
| 131 |
+
return jsonify({
|
| 132 |
+
"categories": categories,
|
| 133 |
+
"total": len(categories)
|
| 134 |
+
})
|
| 135 |
+
|
| 136 |
+
@app.route('/predict', methods=['POST'])
|
| 137 |
+
def predict():
|
| 138 |
+
"""Predict category for single transaction"""
|
| 139 |
+
try:
|
| 140 |
+
# Get JSON data
|
| 141 |
+
data = request.get_json()
|
| 142 |
+
|
| 143 |
+
# Validate input
|
| 144 |
+
if not data or 'text' not in data:
|
| 145 |
+
return jsonify({
|
| 146 |
+
"error": "Missing 'text' field in request body",
|
| 147 |
+
"example": {"text": "beli nasi goreng"}
|
| 148 |
+
}), 400
|
| 149 |
+
|
| 150 |
+
text = data['text']
|
| 151 |
+
|
| 152 |
+
# Validate text is not empty
|
| 153 |
+
if not text or text.strip() == "":
|
| 154 |
+
return jsonify({
|
| 155 |
+
"error": "Text field cannot be empty"
|
| 156 |
+
}), 400
|
| 157 |
+
|
| 158 |
+
# Get prediction
|
| 159 |
+
result = predict_category(text)
|
| 160 |
+
|
| 161 |
+
# Add original and processed text
|
| 162 |
+
result['original_text'] = text
|
| 163 |
+
result['processed_text'] = preprocessing_transaksi(text)
|
| 164 |
+
|
| 165 |
+
return jsonify({
|
| 166 |
+
"success": True,
|
| 167 |
+
"data": result
|
| 168 |
+
})
|
| 169 |
+
|
| 170 |
+
except Exception as e:
|
| 171 |
+
return jsonify({
|
| 172 |
+
"error": str(e),
|
| 173 |
+
"success": False
|
| 174 |
+
}), 500
|
| 175 |
+
|
| 176 |
+
@app.route('/predict/batch', methods=['POST'])
|
| 177 |
+
def predict_batch():
|
| 178 |
+
"""Predict categories for multiple transactions"""
|
| 179 |
+
try:
|
| 180 |
+
# Get JSON data
|
| 181 |
+
data = request.get_json()
|
| 182 |
+
|
| 183 |
+
# Validate input
|
| 184 |
+
if not data or 'texts' not in data:
|
| 185 |
+
return jsonify({
|
| 186 |
+
"error": "Missing 'texts' field in request body",
|
| 187 |
+
"example": {"texts": ["beli nasi", "bayar listrik"]}
|
| 188 |
+
}), 400
|
| 189 |
+
|
| 190 |
+
texts = data['texts']
|
| 191 |
+
|
| 192 |
+
# Validate texts is a list
|
| 193 |
+
if not isinstance(texts, list):
|
| 194 |
+
return jsonify({
|
| 195 |
+
"error": "'texts' must be a list of strings"
|
| 196 |
+
}), 400
|
| 197 |
+
|
| 198 |
+
# Validate list is not empty
|
| 199 |
+
if len(texts) == 0:
|
| 200 |
+
return jsonify({
|
| 201 |
+
"error": "List of texts cannot be empty"
|
| 202 |
+
}), 400
|
| 203 |
+
|
| 204 |
+
# Get predictions for all texts
|
| 205 |
+
results = []
|
| 206 |
+
for text in texts:
|
| 207 |
+
if text and text.strip():
|
| 208 |
+
result = predict_category(text)
|
| 209 |
+
result['original_text'] = text
|
| 210 |
+
result['processed_text'] = preprocessing_transaksi(text)
|
| 211 |
+
results.append(result)
|
| 212 |
+
|
| 213 |
+
return jsonify({
|
| 214 |
+
"success": True,
|
| 215 |
+
"total": len(results),
|
| 216 |
+
"data": results
|
| 217 |
+
})
|
| 218 |
+
|
| 219 |
+
except Exception as e:
|
| 220 |
+
return jsonify({
|
| 221 |
+
"error": str(e),
|
| 222 |
+
"success": False
|
| 223 |
+
}), 500
|
| 224 |
+
|
| 225 |
+
# Error handlers
|
| 226 |
+
@app.errorhandler(404)
|
| 227 |
+
def not_found(error):
|
| 228 |
+
return jsonify({
|
| 229 |
+
"error": "Endpoint not found",
|
| 230 |
+
"success": False
|
| 231 |
+
}), 404
|
| 232 |
+
|
| 233 |
+
@app.errorhandler(405)
|
| 234 |
+
def method_not_allowed(error):
|
| 235 |
+
return jsonify({
|
| 236 |
+
"error": "Method not allowed",
|
| 237 |
+
"success": False
|
| 238 |
+
}), 405
|
| 239 |
+
|
| 240 |
+
@app.errorhandler(500)
|
| 241 |
+
def internal_error(error):
|
| 242 |
+
return jsonify({
|
| 243 |
+
"error": "Internal server error",
|
| 244 |
+
"success": False
|
| 245 |
+
}), 500
|
| 246 |
+
|
| 247 |
+
if __name__ == '__main__':
|
| 248 |
+
app.run(debug=False, host='0.0.0.0', port=8000)
|
model_kelas.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7ed635b4852e0717c1efbe9a7470a9c1e013e9ef42172d24a073afd1e74b76f5
|
| 3 |
+
size 2658744
|
requirements.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Flask==3.1.2
|
| 2 |
+
numpy==2.0.2
|
| 3 |
+
tensorflow==2.19.0
|
tokenizer.pickle
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5cd7d1da7fe13605a5787c57365bef639896d0d4407255586223f464560b4d13
|
| 3 |
+
size 19638
|