MHD011 commited on
Commit
6d856e6
·
verified ·
1 Parent(s): 7d15cae

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +117 -210
app.py CHANGED
@@ -1,239 +1,146 @@
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
  from psycopg2 import pool
 
11
 
12
  app = Flask(__name__)
13
  CORS(app)
14
 
15
- # إعدادات التسجيل
16
- logging.basicConfig(level=logging.INFO)
17
- logger = logging.getLogger(__name__)
18
-
19
- # إعدادات النموذج
20
- MODEL_NAME = "tscholak/3vnuv1vf" # نموذج متخصص لـ PostgreSQL
21
- SUPABASE_DB_URL = os.getenv('SUPABASE_DB_URL')
22
-
23
- # تهيئة connection pool
24
- connection_pool = None
25
- try:
26
- connection_pool = psycopg2.pool.SimpleConnectionPool(
27
- minconn=1,
28
- maxconn=5,
29
- dsn=SUPABASE_DB_URL
30
- )
31
- logger.info("تم إنشاء connection pool بنجاح")
32
- except Exception as e:
33
- logger.error(f"خطأ في إنشاء connection pool: {str(e)}")
34
 
35
- # تحميل النموذج مرة واحدة عند بدء التشغيل
36
- try:
37
- tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
38
- model = AutoModelForSeq2SeqLM.from_pretrained(MODEL_NAME)
39
 
40
- # استخدام GPU إذا كان متاحًا، وإلا استخدام CPU
41
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
42
- model.to(device)
43
- model.eval()
44
- logger.info("تم تحميل النموذج بنجاح على الجهاز: %s", device)
45
- except Exception as e:
46
- logger.error(f"خطأ في تحميل النموذج: {str(e)}")
47
- raise
48
-
49
- # سكيما قاعدة البيانات (مختصرة لتحسين الأداء)
50
- DB_SCHEMA = """
51
- CREATE TABLE public.biodata (
52
- id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
53
- created_at timestamp with time zone NOT NULL DEFAULT now(),
54
- mac_address text,
55
- acceleration_x double precision,
56
- acceleration_y double precision,
57
- acceleration_z double precision,
58
- gyro_x double precision,
59
- gyro_y double precision,
60
- gyro_z double precision,
61
- temperature double precision,
62
- CONSTRAINT biodata_pkey PRIMARY KEY (id),
63
- CONSTRAINT biodata_mac_address_fkey FOREIGN KEY (mac_address) REFERENCES public.profiles(cam_mac)
64
- );
65
- CREATE TABLE public.data (
66
- user_place_id bigint,
67
- cam_mac text,
68
- does_loaction_update boolean DEFAULT false,
69
- caption text,
70
- image_url text,
71
- id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
72
- longitude double precision DEFAULT '37.1309255'::double precision,
73
- created_at timestamp without time zone,
74
- latitude double precision DEFAULT '36.1833854'::double precision,
75
- CONSTRAINT data_pkey PRIMARY KEY (id),
76
- CONSTRAINT data_user_place_id_fkey FOREIGN KEY (user_place_id) REFERENCES public.user_place(id)
77
- );
78
- CREATE TABLE public.flag (
79
- id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
80
- flag smallint,
81
- user_mac_address text,
82
- CONSTRAINT flag_pkey PRIMARY KEY (id),
83
- CONSTRAINT flag_user_mac_address_fkey FOREIGN KEY (user_mac_address) REFERENCES public.profiles(cam_mac)
84
- );
85
- CREATE TABLE public.notification (
86
- id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
87
- created_at timestamp without time zone NOT NULL DEFAULT now(),
88
- user_cam_mac text,
89
- title text,
90
- message text,
91
- is_read boolean,
92
- acceleration_x double precision,
93
- acceleration_y double precision,
94
- acceleration_z double precision,
95
- gyro_x double precision,
96
- gyro_y double precision,
97
- gyro_z double precision,
98
- CONSTRAINT notification_pkey PRIMARY KEY (id),
99
- CONSTRAINT notification_user_cam_mac_fkey FOREIGN KEY (user_cam_mac) REFERENCES public.profiles(cam_mac)
100
- );
101
- CREATE TABLE public.place (
102
- name text,
103
- created_at timestamp with time zone DEFAULT (now() AT TIME ZONE 'utc'::text),
104
- id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
105
- CONSTRAINT place_pkey PRIMARY KEY (id)
106
- );
107
- CREATE TABLE public.profiles (
108
- fcm_token text,
109
- notification_enabled boolean DEFAULT true,
110
- id uuid NOT NULL,
111
- updated_at timestamp with time zone,
112
- username text UNIQUE CHECK (char_length(username) >= 3),
113
- full_name text,
114
- website text,
115
- cam_mac text UNIQUE,
116
- avatar_url text DEFAULT 'https://mougnkvoyyhcuxeeqvmh.supabase.co/storage/v1/object/public/profiles//download.jpeg'::text,
117
- CONSTRAINT profiles_pkey PRIMARY KEY (id),
118
- CONSTRAINT profiles_id_fkey FOREIGN KEY (id) REFERENCES auth.users(id)
119
- );
120
- CREATE TABLE public.user_place (
121
- place_id bigint,
122
- user_cam_mac text,
123
- id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
124
- created_at timestamp with time zone NOT NULL DEFAULT now(),
125
- CONSTRAINT user_place_pkey PRIMARY KEY (id),
126
- CONSTRAINT user_place_user_cam_mac_fkey FOREIGN KEY (user_cam_mac) REFERENCES public.profiles(cam_mac),
127
- CONSTRAINT user_place_place_id_fkey FOREIGN KEY (place_id) REFERENCES public.place(id)
128
- );
129
- """.strip()
130
-
131
- def clean_sql(sql: str, cam_mac: str) -> str:
132
- """
133
- تنظيف استعلام SQL وإضافة شروط الأمان
134
- """
135
- # إزالة أي أوامر غير مسموح بها
136
- forbidden_keywords = ['insert', 'update', 'delete', 'drop', 'alter', 'create', 'truncate']
137
- for keyword in forbidden_keywords:
138
- if keyword in sql.lower():
139
- raise ValueError(f"استعلام غير مسموح به يحتوي على {keyword}")
140
 
141
- # التأكد من وجود شرط cam_mac
142
- if 'where' in sql.lower():
143
- sql = re.sub(r'where\s+', f"WHERE cam_mac = '{cam_mac}' AND ", sql, flags=re.IGNORECASE)
144
- else:
145
- if ';' in sql:
146
- sql = sql.replace(';', f" WHERE cam_mac = '{cam_mac}';")
147
- else:
148
- sql += f" WHERE cam_mac = '{cam_mac}'"
 
 
 
149
 
150
- # التأكد من أن الاستعلام يبدأ بـ SELECT فقط
151
- if not sql.strip().lower().startswith('select'):
152
- raise ValueError("يسمح فقط باستعلامات SELECT")
 
 
 
 
 
 
 
 
153
 
154
- return sql
155
-
156
- def generate_sql(prompt: str, cam_mac: str) -> str:
157
- """
158
- توليد استعلام SQL من النص باستخدام النموذج
159
- """
160
- try:
161
- inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=512).to(device)
162
-
163
- with torch.no_grad():
164
- outputs = model.generate(
165
- **inputs,
166
- max_length=256,
167
- num_beams=4,
168
- early_stopping=True,
169
- temperature=0.7
170
- )
171
-
172
- sql = tokenizer.decode(outputs[0], skip_special_tokens=True)
173
- return clean_sql(sql, cam_mac)
174
- except Exception as e:
175
- logger.error(f"خطأ في توليد SQL: {str(e)}")
176
- return f"SELECT * FROM data WHERE cam_mac = '{cam_mac}' LIMIT 10;"
177
 
178
- @app.route('/api/query', methods=['POST'])
179
- def handle_query():
180
- if not connection_pool:
181
- return jsonify({"error": "لا يوجد اتصال بقاعدة البيانات"}), 500
182
 
183
- try:
184
- data = request.get_json()
185
-
186
- if not all(k in data for k in ['text', 'cam_mac']):
187
- return jsonify({"error": "المعطيات ناقصة"}), 400
188
-
189
- # إنشاء الـ prompt المحسن
190
- prompt = f"""
191
- قم بتحويل السؤال التالي إلى استعلام PostgreSQL SELECT صالح:
192
- السؤال: {data['text']}
193
-
194
- الشروط:
195
- 1. يجب تضمين شرط WHERE: cam_mac = '{data['cam_mac']}'
196
- 2. مسموح فقط باستخدام SELECT
197
- 3. الجداول المتاحة: profiles, data, place
198
- """
199
- # توليد الاستعلام
200
- sql = generate_sql(prompt, data['cam_mac'])
201
-
202
- # تنفيذ الاستعلام باستخدام connection pool
203
  conn = None
204
  try:
205
  conn = connection_pool.getconn()
206
- with conn.cursor() as cursor:
207
- cursor.execute(sql)
208
-
209
- # إذا كان الاستعلام لا يعيد بيانات (مثل COUNT)
210
- if cursor.description:
211
- columns = [desc[0] for desc in cursor.description]
212
- rows = cursor.fetchall()
213
- result = [dict(zip(columns, row)) for row in rows]
214
- else:
215
- result = {"message": "تم تنفيذ الاستعلام بنجاح"}
216
-
217
- return jsonify({
218
- "data": result,
219
- "sql": sql,
220
- "timestamp": datetime.now().isoformat()
221
- })
222
- except Exception as e:
223
- logger.error(f"خطأ في قاعدة البيانات: {str(e)}")
224
- return jsonify({"error": "حدث خطأ في معالجة الاستعلام"}), 500
225
  finally:
226
  if conn:
227
  connection_pool.putconn(conn)
228
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
  except Exception as e:
230
- logger.error(f"خطأ عام: {str(e)}")
231
- return jsonify({"error": "حدث خطأ في المعالجة"}), 500
232
 
233
  @app.route('/health', methods=['GET'])
234
  def health_check():
235
- return jsonify({"status": "healthy", "timestamp": datetime.now().isoformat()})
 
 
 
 
 
236
 
237
  if __name__ == '__main__':
238
- port = int(os.environ.get('PORT', 8080))
239
- app.run(host='0.0.0.0', port=port)
 
1
  import os
2
  import re
3
+ from datetime import datetime
4
  from flask import Flask, request, jsonify
5
  from flask_cors import CORS
 
 
6
  import psycopg2
 
7
  from psycopg2 import pool
8
+ from typing import Optional, Dict, List
9
 
10
  app = Flask(__name__)
11
  CORS(app)
12
 
13
+ # إعدادات اتصال قاعدة البيانات
14
+ DB_URL = os.getenv('SUPABASE_DB_URL')
15
+ connection_pool = psycopg2.pool.SimpleConnectionPool(
16
+ minconn=1,
17
+ maxconn=5,
18
+ dsn=DB_URL
19
+ )
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
+ class QueryBuilder:
22
+ """فئة مسؤولة عن بناء استعلامات SQL الآمنة"""
 
 
23
 
24
+ @staticmethod
25
+ def get_last_office_visit(cam_mac: str) -> str:
26
+ """استعلام عن آخر زيارة للمكتب"""
27
+ return f"""
28
+ SELECT caption, created_at, image_url
29
+ FROM data
30
+ WHERE cam_mac = '{cam_mac}'
31
+ AND (caption LIKE '%مكتب%' OR caption LIKE '%عمل%')
32
+ ORDER BY created_at DESC
33
+ LIMIT 1
34
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
+ @staticmethod
37
+ def get_last_five_places(cam_mac: str) -> str:
38
+ """استعلام عن آخر 5 أماكن مميزة"""
39
+ return f"""
40
+ SELECT DISTINCT ON (caption) caption, created_at, latitude, longitude, image_url
41
+ FROM data
42
+ WHERE cam_mac = '{cam_mac}'
43
+ AND caption IS NOT NULL
44
+ ORDER BY caption, created_at DESC
45
+ LIMIT 5
46
+ """
47
 
48
+ @staticmethod
49
+ def check_garden_visit(cam_mac: str) -> str:
50
+ """استعلام عن زيارة الحديقة"""
51
+ return f"""
52
+ SELECT caption, created_at, image_url
53
+ FROM data
54
+ WHERE cam_mac = '{cam_mac}'
55
+ AND (caption LIKE '%حديقة%' OR caption LIKE '%حدائق%' OR caption LIKE '%نباتات%')
56
+ ORDER BY created_at DESC
57
+ LIMIT 1
58
+ """
59
 
60
+ @staticmethod
61
+ def check_classroom_visit(cam_mac: str) -> str:
62
+ """استعلام عن زيارة صف دراسي"""
63
+ return f"""
64
+ SELECT caption, created_at, image_url
65
+ FROM data
66
+ WHERE cam_mac = '{cam_mac}'
67
+ AND (caption LIKE '%صف%' OR caption LIKE '%قاعة%' OR caption LIKE '%محاضرة%'
68
+ OR caption LIKE '%جامعة%' OR caption LIKE '%مدرسة%')
69
+ ORDER BY created_at DESC
70
+ LIMIT 1
71
+ """
 
 
 
 
 
 
 
 
 
 
 
72
 
73
+ class DatabaseExecutor:
74
+ """فئة مسؤولة عن تنفيذ الاستعلامات وإدارة الاتصالات"""
 
 
75
 
76
+ @staticmethod
77
+ def execute_query(sql: str) -> List[Dict]:
78
+ """تنفيذ استعلام SQL وإرجاع النتائج"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  conn = None
80
  try:
81
  conn = connection_pool.getconn()
82
+ cursor = conn.cursor()
83
+ cursor.execute(sql)
84
+
85
+ if cursor.description:
86
+ columns = [desc[0] for desc in cursor.description]
87
+ return [dict(zip(columns, row)) for row in cursor.fetchall()]
88
+ return []
 
 
 
 
 
 
 
 
 
 
 
 
89
  finally:
90
  if conn:
91
  connection_pool.putconn(conn)
92
+
93
+ @app.route('/query', methods=['POST'])
94
+ def handle_query():
95
+ try:
96
+ data = request.get_json()
97
+ cam_mac = data.get('cam_mac')
98
+ question = data.get('question')
99
+
100
+ if not cam_mac or not question:
101
+ return jsonify({"error": "يجب تقديم cam_mac و question"}), 400
102
+
103
+ # تنظيف المدخلات
104
+ cam_mac = re.sub(r'[^a-fA-F0-9:]', '', cam_mac)
105
+ question = question.strip().lower()
106
+
107
+ # تحديد الاستعلام المناسب
108
+ sql = None
109
+
110
+ if 'مكتب' in question or 'عمل' in question:
111
+ sql = QueryBuilder.get_last_office_visit(cam_mac)
112
+ elif 'خمس أماكن' in question or 'آخر أماكن' in question:
113
+ sql = QueryBuilder.get_last_five_places(cam_mac)
114
+ elif 'حديقة' in question or 'حدائق' in question:
115
+ sql = QueryBuilder.check_garden_visit(cam_mac)
116
+ elif 'صف دراسي' in question or 'محاضرة' in question or 'قاعة' in question:
117
+ sql = QueryBuilder.check_classroom_visit(cam_mac)
118
+
119
+ if not sql:
120
+ return jsonify({"error": "نوع السؤال غير معروف"}), 400
121
+
122
+ # تنفيذ الاستعلام
123
+ results = DatabaseExecutor.execute_query(sql)
124
+
125
+ return jsonify({
126
+ "question": question,
127
+ "results": results,
128
+ "timestamp": datetime.now().isoformat()
129
+ })
130
+
131
+ except psycopg2.Error as e:
132
+ return jsonify({"error": "خطأ في قاعدة البيانات", "details": str(e)}), 500
133
  except Exception as e:
134
+ return jsonify({"error": "خطأ غير متوقع", "details": str(e)}), 500
 
135
 
136
  @app.route('/health', methods=['GET'])
137
  def health_check():
138
+ try:
139
+ conn = connection_pool.getconn()
140
+ conn.close()
141
+ return jsonify({"status": "يعمل بنجاح"})
142
+ except Exception as e:
143
+ return jsonify({"status": "غير متصل", "error": str(e)}), 500
144
 
145
  if __name__ == '__main__':
146
+ app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 5000)))