MHD011 commited on
Commit
c6497cf
·
verified ·
1 Parent(s): fdb7b37

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +336 -289
app.py CHANGED
@@ -1,290 +1,337 @@
1
- import os
2
- import re
3
- import logging
4
- from flask import Flask, request, jsonify
5
- from flask_cors import CORS
6
- from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
7
- import torch
8
- import psycopg2
9
- from datetime import datetime
10
-
11
- app = Flask(__name__)
12
- CORS(app)
13
-
14
- # إعدادات التسجيل
15
- logging.basicConfig(level=logging.INFO)
16
- logger = logging.getLogger(__name__)
17
-
18
- # إعدادات النموذج
19
- MODEL_NAME = "tscholak/cxmefzzi"
20
- SUPABASE_DB_URL = os.getenv('SUPABASE_DB_URL')
21
-
22
- # تهيئة مجلد الذاكرة المؤقتة
23
- os.makedirs("model_cache", exist_ok=True)
24
- os.environ["TRANSFORMERS_CACHE"] = "model_cache"
25
- os.environ["TORCH_CACHE"] = "model_cache"
26
-
27
- tokenizer = None
28
- model = None
29
-
30
- def initialize():
31
- global tokenizer, model
32
- try:
33
- logger.info("🚀 جاري تحميل النموذج...")
34
- tokenizer = AutoTokenizer.from_pretrained(
35
- MODEL_NAME,
36
- cache_dir="model_cache"
37
- )
38
-
39
- # تحميل النموذج مع إعدادات الذاكرة المنخفضة
40
- model = AutoModelForSeq2SeqLM.from_pretrained(
41
- MODEL_NAME,
42
- cache_dir="model_cache",
43
- device_map="auto",
44
- torch_dtype=torch.float32, # استخدام float32 بدلاً من float16
45
- low_cpu_mem_usage=True
46
- )
47
-
48
- # التحقق من توفر GPU
49
- if torch.cuda.is_available():
50
- model.to('cuda')
51
- logger.info("✅ تم تحميل النموذج على GPU")
52
- else:
53
- logger.info("✅ تم تحميل النموذج على CPU")
54
-
55
- model.eval()
56
- except Exception as e:
57
- logger.error(f"❌ فشل في تحميل النموذج: {str(e)}")
58
- raise
59
-
60
- initialize()
61
-
62
- # سكيما قاعدة البيانات
63
- DB_SCHEMA = """
64
- CREATE TABLE public.profiles (
65
- id uuid NOT NULL,
66
- updated_at timestamp with time zone,
67
- username text UNIQUE CHECK (char_length(username) >= 3),
68
- full_name text,
69
- avatar_url text,
70
- website text,
71
- cam_mac text UNIQUE,
72
- fcm_token text,
73
- notification_enabled boolean DEFAULT true,
74
- CONSTRAINT profiles_pkey PRIMARY KEY (id),
75
- CONSTRAINT profiles_id_fkey FOREIGN KEY (id) REFERENCES auth.users(id)
76
- );
77
-
78
- CREATE TABLE public.place (
79
- id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
80
- created_at timestamp with time zone DEFAULT (now() AT TIME ZONE 'utc'::text),
81
- name text,
82
- CONSTRAINT place_pkey PRIMARY KEY (id)
83
- );
84
-
85
- CREATE TABLE public.user_place (
86
- id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
87
- created_at timestamp with time zone NOT NULL DEFAULT now(),
88
- place_id bigint,
89
- user_cam_mac text,
90
- CONSTRAINT user_place_pkey PRIMARY KEY (id),
91
- CONSTRAINT user_place_place_id_fkey FOREIGN KEY (place_id) REFERENCES public.place(id),
92
- CONSTRAINT user_place_user_cam_mac_fkey FOREIGN KEY (user_cam_mac) REFERENCES public.profiles(cam_mac)
93
- );
94
-
95
- CREATE TABLE public.data (
96
- id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
97
- created_at timestamp without time zone,
98
- caption text,
99
- image_url text,
100
- latitude double precision DEFAULT '36.1833854'::double precision,
101
- longitude double precision DEFAULT '37.1309255'::double precision,
102
- user_place_id bigint,
103
- cam_mac text,
104
- CONSTRAINT data_pkey PRIMARY KEY (id),
105
- CONSTRAINT data_user_place_id_fkey FOREIGN KEY (user_place_id) REFERENCES public.user_place(id)
106
- );
107
-
108
- CREATE TABLE public.biodata (
109
- id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
110
- created_at timestamp with time zone NOT NULL DEFAULT now(),
111
- mac_address text,
112
- acceleration_x double precision,
113
- acceleration_y double precision,
114
- acceleration_z double precision,
115
- gyro_x double precision,
116
- gyro_y double precision,
117
- gyro_z double precision,
118
- temperature double precision,
119
- CONSTRAINT biodata_pkey PRIMARY KEY (id),
120
- CONSTRAINT biodata_mac_address_fkey FOREIGN KEY (mac_address) REFERENCES public.profiles(cam_mac)
121
- );
122
-
123
- CREATE TABLE public.notification (
124
- id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
125
- created_at timestamp without time zone NOT NULL DEFAULT now(),
126
- user_cam_mac text,
127
- title text,
128
- message text,
129
- is_read boolean,
130
- acceleration_x double precision,
131
- acceleration_y double precision,
132
- acceleration_z double precision,
133
- gyro_x double precision,
134
- gyro_y double precision,
135
- gyro_z double precision,
136
- CONSTRAINT notification_pkey PRIMARY KEY (id),
137
- CONSTRAINT notification_user_cam_mac_fkey FOREIGN KEY (user_cam_mac) REFERENCES public.profiles(cam_mac)
138
- );
139
-
140
- CREATE TABLE public.flag (
141
- id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
142
- flag smallint,
143
- user_mac_address text,
144
- CONSTRAINT flag_pkey PRIMARY KEY (id),
145
- CONSTRAINT flag_user_mac_address_fkey FOREIGN KEY (user_mac_address) REFERENCES public.profiles(cam_mac)
146
- );
147
- """.strip()
148
-
149
- def get_db_connection():
150
- try:
151
- conn = psycopg2.connect(SUPABASE_DB_URL)
152
- logger.info("✅ تم الاتصال بقاعدة البيانات")
153
- return conn
154
- except Exception as err:
155
- logger.error(f"❌ خطأ في الاتصال بقاعدة البيانات: {err}")
156
- return None
157
-
158
- def validate_cam_mac(cam_mac):
159
- conn = get_db_connection()
160
- if not conn:
161
- return False
162
-
163
- try:
164
- cursor = conn.cursor()
165
- cursor.execute("SELECT 1 FROM profiles WHERE cam_mac = %s;", (cam_mac,))
166
- return cursor.fetchone() is not None
167
- except Exception as e:
168
- logger.error(f"❌ خطأ في التحقق من cam_mac: {e}")
169
- return False
170
- finally:
171
- if conn:
172
- conn.close()
173
-
174
- @app.route('/api/query', methods=['POST'])
175
- def handle_query():
176
- try:
177
- data = request.get_json()
178
- logger.info(f"📩 بيانات الطلب: {data}")
179
-
180
- if not data or 'text' not in data or 'cam_mac' not in data:
181
- logger.error("❌ بيانات ناقصة في الطلب")
182
- return jsonify({"error": "يرجى إرسال 'text' و 'cam_mac'"}), 400
183
-
184
- if not validate_cam_mac(data['cam_mac']):
185
- logger.error(f"❌ cam_mac غير صالح: {data['cam_mac']}")
186
- return jsonify({"error": "عنوان MAC غير صالح"}), 403
187
-
188
- prompt = f"""
189
- ### هيكل قاعدة البيانات:
190
- {DB_SCHEMA}
191
-
192
- ### السؤال:
193
- {data['text']}
194
-
195
- ### قم بإنشاء استعلام SQL بحيث:
196
- - يستخدم SELECT فقط
197
- - يتضمن تصفية حسب cam_mac = '{data['cam_mac']}'
198
- - يكون صالحًا لـ PostgreSQL
199
-
200
- SQL:
201
- """
202
-
203
- # تحضير المدخلات مع التحقق من الجهاز
204
- inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=512)
205
- if torch.cuda.is_available():
206
- inputs = inputs.to('cuda')
207
-
208
- with torch.no_grad():
209
- outputs = model.generate(
210
- **inputs,
211
- max_length=256,
212
- num_beams=4,
213
- early_stopping=True
214
- )
215
-
216
- sql = tokenizer.decode(outputs[0], skip_special_tokens=True)
217
- logger.info(f"⚡ الاستعلام المولد: {sql}")
218
-
219
- # تنظيف الاستعلام
220
- sql = re.sub(r"^```sql\s*", "", sql, flags=re.IGNORECASE)
221
- sql = re.sub(r"\s*```$", "", sql).strip()
222
-
223
- if not sql.upper().startswith("SELECT"):
224
- sql = f"SELECT {sql}"
225
- if not sql.endswith(";"):
226
- sql += ";"
227
-
228
- conn = get_db_connection()
229
- if not conn:
230
- return jsonify({"error": "فشل الاتصال بقاعدة البيانات"}), 500
231
-
232
- try:
233
- cursor = conn.cursor()
234
- cursor.execute(sql)
235
-
236
- if cursor.description:
237
- columns = [desc[0] for desc in cursor.description]
238
- rows = cursor.fetchall()
239
- data = [dict(zip(columns, row)) for row in rows]
240
-
241
- response = {
242
- "data": data,
243
- "sql": sql,
244
- "timestamp": datetime.now().isoformat()
245
- }
246
- else:
247
- conn.commit()
248
- response = {
249
- "message": "تم تنفيذ الاستعلام بنجاح",
250
- "sql": sql
251
- }
252
-
253
- return jsonify(response)
254
-
255
- except Exception as e:
256
- logger.error(f"❌ خطأ في تنفيذ SQL: {e}\nالاستعلام: {sql}")
257
- return jsonify({
258
- "error": "خطأ في تنفيذ الاستعلام",
259
- "sql": sql,
260
- "details": str(e)
261
- }), 500
262
-
263
- finally:
264
- if conn:
265
- conn.close()
266
-
267
- except Exception as e:
268
- logger.error(f"❌ خطأ غير متوقع: {str(e)}", exc_info=True)
269
- return jsonify({"error": "فشل في معالجة الطلب"}), 500
270
-
271
- @app.route('/health')
272
- def health_check():
273
- return jsonify({
274
- "status": "healthy",
275
- "model_loaded": model is not None,
276
- "device": str(model.device) if model else "none",
277
- "db_connection": get_db_connection() is not None,
278
- "timestamp": datetime.now().isoformat()
279
- })
280
-
281
- @app.route('/')
282
- def home():
283
- return """
284
- <h1>Text2SQL API</h1>
285
- <p>استخدم <code>/api/query</code> مع POST</p>
286
- <p>تحقق من الحالة: <a href="/health">/health</a></p>
287
- """
288
-
289
- if __name__ == '__main__':
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
  app.run(host='0.0.0.0', port=7860)
 
1
+ import os
2
+ import re
3
+ import logging
4
+ from flask import Flask, request, jsonify
5
+ from flask_cors import CORS
6
+ from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
7
+ import torch
8
+ import psycopg2
9
+ from datetime import datetime
10
+
11
+ app = Flask(__name__)
12
+ CORS(app)
13
+
14
+ # إعدادات التسجيل
15
+ logging.basicConfig(level=logging.INFO)
16
+ logger = logging.getLogger(__name__)
17
+
18
+ # إعدادات النموذج
19
+ MODEL_NAME = "tscholak/3vnuv1vf" # نموذج متخصص لـ PostgreSQL
20
+ SUPABASE_DB_URL = os.getenv('SUPABASE_DB_URL')
21
+
22
+ # تهيئة مجلد الذاكرة المؤقتة
23
+ os.makedirs("model_cache", exist_ok=True)
24
+ os.environ["TRANSFORMERS_CACHE"] = "model_cache"
25
+ os.environ["TORCH_CACHE"] = "model_cache"
26
+
27
+ tokenizer = None
28
+ model = None
29
+
30
+ def initialize():
31
+ global tokenizer, model
32
+ try:
33
+ logger.info("🚀 جاري تحميل النموذج...")
34
+ tokenizer = AutoTokenizer.from_pretrained(
35
+ MODEL_NAME,
36
+ cache_dir="model_cache"
37
+ )
38
+
39
+ model = AutoModelForSeq2SeqLM.from_pretrained(
40
+ MODEL_NAME,
41
+ cache_dir="model_cache",
42
+ device_map="auto",
43
+ torch_dtype=torch.float16, # لتوفير الذاكرة
44
+ low_cpu_mem_usage=True
45
+ )
46
+
47
+ if torch.cuda.is_available():
48
+ model.to('cuda')
49
+ logger.info("✅ تم تحميل النموذج على GPU")
50
+ else:
51
+ logger.info("✅ تم تحميل النموذج على CPU")
52
+
53
+ model.eval()
54
+ except Exception as e:
55
+ logger.error(f"❌ فشل في تحميل النموذج: {str(e)}")
56
+ raise
57
+
58
+ initialize()
59
+
60
+ # سكيما قاعدة البيانات (نفسها كما في الكود الأصلي)
61
+ DB_SCHEMA = """
62
+ CREATE TABLE public.profiles (
63
+ id uuid NOT NULL,
64
+ updated_at timestamp with time zone,
65
+ username text UNIQUE CHECK (char_length(username) >= 3),
66
+ full_name text,
67
+ avatar_url text,
68
+ website text,
69
+ cam_mac text UNIQUE,
70
+ fcm_token text,
71
+ notification_enabled boolean DEFAULT true,
72
+ CONSTRAINT profiles_pkey PRIMARY KEY (id),
73
+ CONSTRAINT profiles_id_fkey FOREIGN KEY (id) REFERENCES auth.users(id)
74
+ );
75
+
76
+ CREATE TABLE public.place (
77
+ id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
78
+ created_at timestamp with time zone DEFAULT (now() AT TIME ZONE 'utc'::text),
79
+ name text,
80
+ CONSTRAINT place_pkey PRIMARY KEY (id)
81
+ );
82
+
83
+ CREATE TABLE public.user_place (
84
+ id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
85
+ created_at timestamp with time zone NOT NULL DEFAULT now(),
86
+ place_id bigint,
87
+ user_cam_mac text,
88
+ CONSTRAINT user_place_pkey PRIMARY KEY (id),
89
+ CONSTRAINT user_place_place_id_fkey FOREIGN KEY (place_id) REFERENCES public.place(id),
90
+ CONSTRAINT user_place_user_cam_mac_fkey FOREIGN KEY (user_cam_mac) REFERENCES public.profiles(cam_mac)
91
+ );
92
+
93
+ CREATE TABLE public.data (
94
+ id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
95
+ created_at timestamp without time zone,
96
+ caption text,
97
+ image_url text,
98
+ latitude double precision DEFAULT '36.1833854'::double precision,
99
+ longitude double precision DEFAULT '37.1309255'::double precision,
100
+ user_place_id bigint,
101
+ cam_mac text,
102
+ CONSTRAINT data_pkey PRIMARY KEY (id),
103
+ CONSTRAINT data_user_place_id_fkey FOREIGN KEY (user_place_id) REFERENCES public.user_place(id)
104
+ );
105
+
106
+ CREATE TABLE public.biodata (
107
+ id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
108
+ created_at timestamp with time zone NOT NULL DEFAULT now(),
109
+ mac_address text,
110
+ acceleration_x double precision,
111
+ acceleration_y double precision,
112
+ acceleration_z double precision,
113
+ gyro_x double precision,
114
+ gyro_y double precision,
115
+ gyro_z double precision,
116
+ temperature double precision,
117
+ CONSTRAINT biodata_pkey PRIMARY KEY (id),
118
+ CONSTRAINT biodata_mac_address_fkey FOREIGN KEY (mac_address) REFERENCES public.profiles(cam_mac)
119
+ );
120
+
121
+ CREATE TABLE public.notification (
122
+ id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
123
+ created_at timestamp without time zone NOT NULL DEFAULT now(),
124
+ user_cam_mac text,
125
+ title text,
126
+ message text,
127
+ is_read boolean,
128
+ acceleration_x double precision,
129
+ acceleration_y double precision,
130
+ acceleration_z double precision,
131
+ gyro_x double precision,
132
+ gyro_y double precision,
133
+ gyro_z double precision,
134
+ CONSTRAINT notification_pkey PRIMARY KEY (id),
135
+ CONSTRAINT notification_user_cam_mac_fkey FOREIGN KEY (user_cam_mac) REFERENCES public.profiles(cam_mac)
136
+ );
137
+
138
+ CREATE TABLE public.flag (
139
+ id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
140
+ flag smallint,
141
+ user_mac_address text,
142
+ CONSTRAINT flag_pkey PRIMARY KEY (id),
143
+ CONSTRAINT flag_user_mac_address_fkey FOREIGN KEY (user_mac_address) REFERENCES public.profiles(cam_mac)
144
+ );
145
+ """.strip()
146
+
147
+ def get_db_connection():
148
+ try:
149
+ conn = psycopg2.connect(SUPABASE_DB_URL)
150
+ logger.info("✅ تم الاتصال بقاعدة البيانات")
151
+ return conn
152
+ except Exception as err:
153
+ logger.error(f"❌ خطأ في الاتصال بقاعدة البيانات: {err}")
154
+ return None
155
+
156
+ def validate_cam_mac(cam_mac):
157
+ conn = get_db_connection()
158
+ if not conn:
159
+ return False
160
+
161
+ try:
162
+ cursor = conn.cursor()
163
+ cursor.execute("SELECT 1 FROM profiles WHERE cam_mac = %s;", (cam_mac,))
164
+ return cursor.fetchone() is not None
165
+ except Exception as e:
166
+ logger.error(f"❌ خطأ في التحقق من cam_mac: {e}")
167
+ return False
168
+ finally:
169
+ if conn:
170
+ conn.close()
171
+
172
+ def is_safe_sql(sql):
173
+ """تحقق مما إذا كان الاستعلام SELECT فقط وآمن للتنفيذ"""
174
+ sql_upper = sql.upper()
175
+
176
+ # قائمة بالأوامر المحظورة
177
+ forbidden_commands = [
178
+ "INSERT", "UPDATE", "DELETE", "CREATE",
179
+ "DROP", "ALTER", "TRUNCATE", "GRANT",
180
+ "REVOKE", "COMMIT", "ROLLBACK"
181
+ ]
182
+
183
+ # التأكد من أن الاستعلام يبدأ بـ SELECT
184
+ if not sql_upper.strip().startswith("SELECT"):
185
+ return False
186
+
187
+ # التأكد من عدم وجود أي أوامر محظورة
188
+ for cmd in forbidden_commands:
189
+ if cmd in sql_upper:
190
+ return False
191
+
192
+ return True
193
+
194
+ def clean_sql(sql):
195
+ """تنظيف استعلام SQL لضمان أنه SELECT فقط"""
196
+ # إزالة أي شيء قبل SELECT
197
+ sql = re.sub(r'^[^S]*(SELECT)', 'SELECT', sql, flags=re.IGNORECASE)
198
+
199
+ # أخذ أول عبارة SQL فقط (تجاهل أي شيء بعد ;)
200
+ sql = sql.split(';')[0] + ';'
201
+
202
+ # إزالة المسافات الزائدة
203
+ sql = ' '.join(sql.split()).strip()
204
+
205
+ return sql
206
+
207
+ @app.route('/api/query', methods=['POST'])
208
+ def handle_query():
209
+ try:
210
+ data = request.get_json()
211
+ logger.info(f"📩 بيانات الطلب: {data}")
212
+
213
+ # التحقق من وجود البيانات المطلوبة
214
+ if not data or 'text' not in data or 'cam_mac' not in data:
215
+ logger.error("❌ بيانات ناقصة في الطلب")
216
+ return jsonify({"error": "يرجى إرسال 'text' و 'cam_mac'"}), 400
217
+
218
+ # التحقق من صحة cam_mac
219
+ if not validate_cam_mac(data['cam_mac']):
220
+ logger.error(f" cam_mac غير صالح: {data['cam_mac']}")
221
+ return jsonify({"error": "عنوان MAC غير صالح"}), 403
222
+
223
+ # إنشاء الـ prompt مع تعليمات صارمة
224
+ prompt = f"""
225
+ ### التعليمات الصارمة:
226
+ 1. قم بتحويل السؤال إلى استعلام SELECT فقط لـ PostgreSQL.
227
+ 2. يجب أن يتضمن الشرط: WHERE cam_mac = '{data['cam_mac']}'.
228
+ 3. ممنوع تمامًا استخدام أي أوامر غير SELECT (مثل INSERT, UPDATE, DELETE, CREATE, DROP).
229
+
230
+ ### هيكل قاعدة البيانات:
231
+ {DB_SCHEMA}
232
+
233
+ ### أمثلة صحيحة:
234
+ - السؤال: "ما عدد زياراتي للمكتب؟"
235
+ SQL: SELECT COUNT(*) FROM data WHERE cam_mac = '{data['cam_mac']}';
236
+
237
+ - السؤال: "ما هي آخر 10 زيارات؟"
238
+ SQL: SELECT * FROM data WHERE cam_mac = '{data['cam_mac']}' ORDER BY created_at DESC LIMIT 10;
239
+
240
+ ### السؤال الحالي:
241
+ {data['text']}
242
+
243
+ ### استعلام SQL (SELECT فقط):
244
+ """
245
+ # توليد الاستعلام
246
+ inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=512)
247
+ if torch.cuda.is_available():
248
+ inputs = inputs.to('cuda')
249
+
250
+ with torch.no_grad():
251
+ outputs = model.generate(
252
+ **inputs,
253
+ max_length=256,
254
+ num_beams=4,
255
+ early_stopping=True
256
+ )
257
+
258
+ sql = tokenizer.decode(outputs[0], skip_special_tokens=True)
259
+ logger.info(f"⚡ الاستعلام المولد (قبل التنظيف): {sql}")
260
+
261
+ # تنظيف الاستعلام
262
+ sql = clean_sql(sql)
263
+ logger.info(f"🔧 الاستعلام بعد التنظيف: {sql}")
264
+
265
+ # التحقق من أن الاستعلام آمن
266
+ if not is_safe_sql(sql):
267
+ logger.error(f"❌ استعلام غير آمن: {sql}")
268
+ return jsonify({
269
+ "error": "تم توليد استعلام غير آمن",
270
+ "details": "الاستعلام يجب أن يكون SELECT فقط ويجب أن يتضمن شرط WHERE لـ cam_mac",
271
+ "sql": sql
272
+ }), 400
273
+
274
+ # تنفيذ الاستعلام
275
+ conn = get_db_connection()
276
+ if not conn:
277
+ return jsonify({"error": "فشل الاتصال بقاعدة البيانات"}), 500
278
+
279
+ try:
280
+ cursor = conn.cursor()
281
+ cursor.execute(sql)
282
+
283
+ if cursor.description:
284
+ columns = [desc[0] for desc in cursor.description]
285
+ rows = cursor.fetchall()
286
+ data = [dict(zip(columns, row)) for row in rows]
287
+
288
+ response = {
289
+ "data": data,
290
+ "sql": sql,
291
+ "timestamp": datetime.now().isoformat()
292
+ }
293
+ else:
294
+ conn.commit()
295
+ response = {
296
+ "message": "تم تنفيذ الاستعلام بنجاح (لا توجد بيانات للإرجاع)",
297
+ "sql": sql
298
+ }
299
+
300
+ return jsonify(response)
301
+
302
+ except Exception as e:
303
+ logger.error(f"❌ خطأ في تنفيذ SQL: {e}\nالاستعلام: {sql}")
304
+ return jsonify({
305
+ "error": "خطأ في تنفيذ الاستعلام",
306
+ "sql": sql,
307
+ "details": str(e)
308
+ }), 500
309
+
310
+ finally:
311
+ if conn:
312
+ conn.close()
313
+
314
+ except Exception as e:
315
+ logger.error(f"❌ خطأ غير متوقع: {str(e)}", exc_info=True)
316
+ return jsonify({"error": "فشل في معالجة الطلب"}), 500
317
+
318
+ @app.route('/health')
319
+ def health_check():
320
+ return jsonify({
321
+ "status": "healthy",
322
+ "model_loaded": model is not None,
323
+ "device": str(model.device) if model else "none",
324
+ "db_connection": get_db_connection() is not None,
325
+ "timestamp": datetime.now().isoformat()
326
+ })
327
+
328
+ @app.route('/')
329
+ def home():
330
+ return """
331
+ <h1>Text2SQL API</h1>
332
+ <p>استخدم <code>/api/query</code> مع POST</p>
333
+ <p>تحقق من الحالة: <a href="/health">/health</a></p>
334
+ """
335
+
336
+ if __name__ == '__main__':
337
  app.run(host='0.0.0.0', port=7860)