Ludy Hasby Aulia commited on
Commit
96c14c1
·
1 Parent(s): b64426a

first commit

Browse files
Files changed (5) hide show
  1. Dockerfile +9 -0
  2. app.py +248 -0
  3. model_kelas.h5 +3 -0
  4. requirements.txt +3 -0
  5. 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