MHD011 commited on
Commit
412ef8b
·
verified ·
1 Parent(s): 0cfe8c0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +163 -280
app.py CHANGED
@@ -1,280 +1,163 @@
1
- import os
2
- import re
3
- import psycopg2
4
- from flask import Flask, request, jsonify
5
- import google.generativeai as genai
6
- from flask import Response
7
- import json
8
-
9
- # --- إعدادات Flask ---
10
- app = Flask(__name__)
11
-
12
- # --- إعدادات Gemini ---
13
- GEMINI_API_KEY = "AIzaSyCWukRy76nPgkrMflCTWh_s4gEU--wSVr8" # يفضل استخدام متغيرات البيئة
14
- genai.configure(api_key=GEMINI_API_KEY)
15
- model = genai.GenerativeModel('gemini-2.0-flash')
16
-
17
- # --- إعدادات Supabase ---
18
- SUPABASE_DB_URL = "postgresql://postgres.mougnkvoyyhcuxeeqvmh:Xf5E0DhUvKEHEAqq@aws-0-eu-central-1.pooler.supabase.com:6543/postgres"
19
-
20
- # --- سكيمة قاعدة البيانات ---
21
- DB_SCHEMA = """
22
- CREATE TABLE public.profiles (
23
- id uuid NOT NULL,
24
- updated_at timestamp with time zone,
25
- username text UNIQUE CHECK (char_length(username) >= 3),
26
- full_name text,
27
- avatar_url text,
28
- website text,
29
- cam_mac text UNIQUE,
30
- fcm_token text,
31
- notification_enabled boolean DEFAULT true,
32
- CONSTRAINT profiles_pkey PRIMARY KEY (id),
33
- CONSTRAINT profiles_id_fkey FOREIGN KEY (id) REFERENCES auth.users(id)
34
- );
35
-
36
- CREATE TABLE public.place (
37
- id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
38
- created_at timestamp with time zone DEFAULT (now() AT TIME ZONE 'utc'::text),
39
- name text,
40
- CONSTRAINT place_pkey PRIMARY KEY (id)
41
- );
42
-
43
- CREATE TABLE public.user_place (
44
- id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
45
- created_at timestamp with time zone NOT NULL DEFAULT now(),
46
- place_id bigint,
47
- user_cam_mac text,
48
- CONSTRAINT user_place_pkey PRIMARY KEY (id),
49
- CONSTRAINT user_place_place_id_fkey FOREIGN KEY (place_id) REFERENCES public.place(id),
50
- CONSTRAINT user_place_user_cam_mac_fkey FOREIGN KEY (user_cam_mac) REFERENCES public.profiles(cam_mac)
51
- );
52
-
53
- CREATE TABLE public.data (
54
- id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
55
- created_at timestamp without time zone,
56
- caption text,
57
- image_url text,
58
- latitude double precision DEFAULT '36.1833854'::double precision,
59
- longitude double precision DEFAULT '37.1309255'::double precision,
60
- user_place_id bigint,
61
- cam_mac text,
62
- CONSTRAINT data_pkey PRIMARY KEY (id),
63
- CONSTRAINT data_user_place_id_fkey FOREIGN KEY (user_place_id) REFERENCES public.user_place(id)
64
- );
65
-
66
- CREATE TABLE public.biodata (
67
- id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
68
- created_at timestamp with time zone NOT NULL DEFAULT now(),
69
- mac_address text,
70
- acceleration_x double precision,
71
- acceleration_y double precision,
72
- acceleration_z double precision,
73
- gyro_x double precision,
74
- gyro_y double precision,
75
- gyro_z double precision,
76
- temperature double precision,
77
- CONSTRAINT biodata_pkey PRIMARY KEY (id),
78
- CONSTRAINT biodata_mac_address_fkey FOREIGN KEY (mac_address) REFERENCES public.profiles(cam_mac)
79
- );
80
-
81
- CREATE TABLE public.notification (
82
- id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
83
- created_at timestamp without time zone NOT NULL DEFAULT now(),
84
- user_cam_mac text,
85
- title text,
86
- message text,
87
- is_read boolean,
88
- acceleration_x double precision,
89
- acceleration_y double precision,
90
- acceleration_z double precision,
91
- gyro_x double precision,
92
- gyro_y double precision,
93
- gyro_z double precision,
94
- CONSTRAINT notification_pkey PRIMARY KEY (id),
95
- CONSTRAINT notification_user_cam_mac_fkey FOREIGN KEY (user_cam_mac) REFERENCES public.profiles(cam_mac)
96
- );
97
-
98
- CREATE TABLE public.flag (
99
- id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
100
- flag smallint,
101
- user_mac_address text,
102
- CONSTRAINT flag_pkey PRIMARY KEY (id),
103
- CONSTRAINT flag_user_mac_address_fkey FOREIGN KEY (user_mac_address) REFERENCES public.profiles(cam_mac)
104
- );
105
-
106
- """
107
-
108
- # --- الاتصال بقاعدة البيانات ---
109
- def get_db_connection():
110
- try:
111
- return psycopg2.connect(SUPABASE_DB_URL)
112
- except Exception as err:
113
- print(f"Database connection error: {err}")
114
- return None
115
-
116
- # --- التحقق من صحة cam_mac ---
117
- def validate_cam_mac(cam_mac):
118
- conn = get_db_connection()
119
- if not conn:
120
- return False
121
-
122
- try:
123
- cursor = conn.cursor()
124
- cursor.execute("SELECT 1 FROM profiles WHERE cam_mac = %s;", (cam_mac,))
125
- return cursor.fetchone() is not None
126
- except Exception as e:
127
- print(f"Validation error: {e}")
128
- return False
129
- finally:
130
- if conn:
131
- conn.close()
132
-
133
- # --- توليد SQL باستخدام Gemini مع تخصيص حسب cam_mac ---
134
- def generate_sql_gemini(natural_language_query, cam_mac):
135
- prompt = f"""YYou are a PostgreSQL expert.
136
- Your job is to convert a natural language query into a SQL SELECT statement, based on the following database schema.
137
-
138
- The query **must always be filtered by the camera MAC address: '{cam_mac}'**, using the appropriate field.
139
-
140
- Schema:
141
- {DB_SCHEMA}
142
-
143
- Schema Description:
144
-
145
- 1. **profiles**
146
- - Represents users/devices.
147
- - cam_mac (TEXT, UNIQUE) is the MAC address of the camera device.
148
- - Linked to most tables using cam_mac.
149
-
150
- 2. **data**
151
- - Stores captured image info (image_url, caption, created_at, etc.).
152
- - Linked via cam_mac and user_place_id.
153
- - To find places, JOIN with `user_place` → `place`.
154
-
155
- 3. **biodata**
156
- - Contains sensor readings (acceleration, gyro, temp).
157
- - Linked via mac_address to profiles.cam_mac.
158
-
159
- 4. **notification**
160
- - Stores alerts/messages for the user.
161
- - Linked via user_cam_mac to profiles.cam_mac.
162
-
163
- 5. **flag**
164
- - Represents boolean flags (e.g. status).
165
- - Linked via user_mac_address to profiles.cam_mac.
166
-
167
- 6. **user_place**
168
- - Connects a user_cam_mac to a place_id.
169
- - JOIN with `place` to get the name.
170
-
171
- 7. **place**
172
- - List of place names.
173
-
174
- Rules:
175
- - If the question is about number of visits, frequency, or attendance to a specific place, use the `data` table.
176
- - Use **only SELECT** statements.
177
- - Use only the provided schema.
178
- - Use **camel_mac** filter in WHERE clause.
179
- - Use proper JOINs (no subqueries unless necessary).
180
- - Always match table relationships correctly:
181
- data.user_place_id = user_place.id
182
- user_place.place_id = place.id
183
- user_place.user_cam_mac = profiles.cam_mac
184
- - Use table aliases (like d, p, up, pl) when helpful.
185
- - The output must contain only the SQL query, no comments or explanations.
186
- - Add a semicolon at the end.
187
-
188
-
189
- Question: "{natural_language_query}"
190
-
191
-
192
- SQL:"""
193
-
194
- try:
195
- response = model.generate_content(prompt)
196
- sql = response.text.strip()
197
-
198
- # تنظيف الناتج
199
- sql = re.sub(r"^```sql\s*", "", sql, flags=re.IGNORECASE)
200
- sql = re.sub(r"\s*```$", "", sql)
201
- sql = re.sub(r"^SQL:\s*", "", sql, flags=re.IGNORECASE)
202
-
203
- if not sql.upper().startswith("SELECT"):
204
- sql = "SELECT " + sql.split("SELECT")[-1] if "SELECT" in sql else f"SELECT * FROM ({sql}) AS subquery"
205
-
206
- if not sql.endswith(";"):
207
- sql += ";"
208
-
209
- return sql
210
- except Exception as e:
211
- print(f"Gemini error: {e}")
212
- return None
213
-
214
- # --- نقطة النهاية الرئيسية ---
215
- @app.route('/api/query', methods=['POST'])
216
- def handle_query():
217
- data = request.get_json()
218
- if not data or 'text' not in data or 'cam_mac' not in data:
219
- return jsonify({"error": "Please send 'text' and 'cam_mac' in the request body"}), 400
220
-
221
- natural_query = data['text']
222
- cam_mac = data['cam_mac']
223
- print(f"Natural query from {cam_mac}: {natural_query}")
224
-
225
- # التحقق من صحة cam_mac
226
- if not validate_cam_mac(cam_mac):
227
- return jsonify({"error": "Invalid cam_mac address"}), 403
228
-
229
- sql_query = generate_sql_gemini(natural_query, cam_mac)
230
-
231
- if not sql_query:
232
- return jsonify({"error": "Failed to generate SQL query"}), 500
233
-
234
- print(f"Generated SQL: {sql_query}")
235
-
236
- if not sql_query.upper().strip().startswith("SELECT"):
237
- return jsonify({"error": "Only SELECT queries are allowed"}), 403
238
-
239
- conn = get_db_connection()
240
- if not conn:
241
- return jsonify({"error": "Database connection failed"}), 500
242
-
243
- cursor = None
244
- try:
245
- cursor = conn.cursor()
246
- cursor.execute(sql_query)
247
- columns = [desc[0] for desc in cursor.description]
248
- rows = cursor.fetchall()
249
- data = [dict(zip(columns, row)) for row in rows]
250
-
251
- response_data = {
252
- "data": data,
253
- }
254
-
255
- response_json = json.dumps(response_data, ensure_ascii=False)
256
-
257
- return Response(
258
- response_json,
259
- status=200,
260
- mimetype='application/json; charset=utf-8'
261
- )
262
-
263
- except Exception as e:
264
- print(f"SQL execution error: {e}")
265
- return jsonify({"error": str(e), "generated_sql": sql_query}), 500
266
- finally:
267
- if cursor:
268
- cursor.close()
269
- if conn:
270
- conn.close()
271
-
272
- @app.route('/')
273
- def home():
274
- return """
275
- <h1>Natural Language to SQL API (Gemini)</h1>
276
- <p>Use <code>/api/query</code> with POST {"text": "your question", "cam_mac": "device_mac_address"}.</p>
277
- """
278
-
279
- if __name__ == '__main__':
280
- app.run(host='0.0.0.0', port=7860)
 
1
+ import os
2
+ import logging
3
+ from flask import Flask, request, jsonify
4
+ from flask_cors import CORS
5
+ from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
6
+ import torch
7
+
8
+ app = Flask(__name__)
9
+ CORS(app)
10
+
11
+ # --- إعداد السجل ---
12
+ logging.basicConfig(level=logging.INFO)
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # --- إعداد النموذج ---
16
+ MODEL_NAME = "tscholak/sqlcoder" # يمكنك تغييره لنموذج آخر إذا رغبت
17
+
18
+ tokenizer = None
19
+ model = None
20
+
21
+ def initialize():
22
+ global tokenizer, model
23
+ device = "cuda" if torch.cuda.is_available() else "cpu"
24
+ logger.info(f"تحميل النموذج على الجهاز: {device}")
25
+
26
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
27
+ model = AutoModelForSeq2SeqLM.from_pretrained(MODEL_NAME).to(device)
28
+ logger.info("تم تحميل النموذج بنجاح")
29
+
30
+ initialize()
31
+
32
+ # --- سكيمة قاعدة البيانات (كمثال) ---
33
+ DB_SCHEMA = """
34
+ CREATE TABLE public.profiles (
35
+ id uuid NOT NULL,
36
+ updated_at timestamp with time zone,
37
+ username text UNIQUE CHECK (char_length(username) >= 3),
38
+ full_name text,
39
+ avatar_url text,
40
+ website text,
41
+ cam_mac text UNIQUE,
42
+ fcm_token text,
43
+ notification_enabled boolean DEFAULT true,
44
+ CONSTRAINT profiles_pkey PRIMARY KEY (id),
45
+ CONSTRAINT profiles_id_fkey FOREIGN KEY (id) REFERENCES auth.users(id)
46
+ );
47
+
48
+ CREATE TABLE public.place (
49
+ id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
50
+ created_at timestamp with time zone DEFAULT (now() AT TIME ZONE 'utc'::text),
51
+ name text,
52
+ CONSTRAINT place_pkey PRIMARY KEY (id)
53
+ );
54
+
55
+ CREATE TABLE public.user_place (
56
+ id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
57
+ created_at timestamp with time zone NOT NULL DEFAULT now(),
58
+ place_id bigint,
59
+ user_cam_mac text,
60
+ CONSTRAINT user_place_pkey PRIMARY KEY (id),
61
+ CONSTRAINT user_place_place_id_fkey FOREIGN KEY (place_id) REFERENCES public.place(id),
62
+ CONSTRAINT user_place_user_cam_mac_fkey FOREIGN KEY (user_cam_mac) REFERENCES public.profiles(cam_mac)
63
+ );
64
+
65
+ CREATE TABLE public.data (
66
+ id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
67
+ created_at timestamp without time zone,
68
+ caption text,
69
+ image_url text,
70
+ latitude double precision DEFAULT '36.1833854'::double precision,
71
+ longitude double precision DEFAULT '37.1309255'::double precision,
72
+ user_place_id bigint,
73
+ cam_mac text,
74
+ CONSTRAINT data_pkey PRIMARY KEY (id),
75
+ CONSTRAINT data_user_place_id_fkey FOREIGN KEY (user_place_id) REFERENCES public.user_place(id)
76
+ );
77
+
78
+ CREATE TABLE public.biodata (
79
+ id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
80
+ created_at timestamp with time zone NOT NULL DEFAULT now(),
81
+ mac_address text,
82
+ acceleration_x double precision,
83
+ acceleration_y double precision,
84
+ acceleration_z double precision,
85
+ gyro_x double precision,
86
+ gyro_y double precision,
87
+ gyro_z double precision,
88
+ temperature double precision,
89
+ CONSTRAINT biodata_pkey PRIMARY KEY (id),
90
+ CONSTRAINT biodata_mac_address_fkey FOREIGN KEY (mac_address) REFERENCES public.profiles(cam_mac)
91
+ );
92
+
93
+ CREATE TABLE public.notification (
94
+ id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
95
+ created_at timestamp without time zone NOT NULL DEFAULT now(),
96
+ user_cam_mac text,
97
+ title text,
98
+ message text,
99
+ is_read boolean,
100
+ acceleration_x double precision,
101
+ acceleration_y double precision,
102
+ acceleration_z double precision,
103
+ gyro_x double precision,
104
+ gyro_y double precision,
105
+ gyro_z double precision,
106
+ CONSTRAINT notification_pkey PRIMARY KEY (id),
107
+ CONSTRAINT notification_user_cam_mac_fkey FOREIGN KEY (user_cam_mac) REFERENCES public.profiles(cam_mac)
108
+ );
109
+
110
+ CREATE TABLE public.flag (
111
+ id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
112
+ flag smallint,
113
+ user_mac_address text,
114
+ CONSTRAINT flag_pkey PRIMARY KEY (id),
115
+ CONSTRAINT flag_user_mac_address_fkey FOREIGN KEY (user_mac_address) REFERENCES public.profiles(cam_mac)
116
+ );
117
+ """.strip()
118
+
119
+ @app.route('/generate-sql', methods=['POST'])
120
+ def generate_sql():
121
+ try:
122
+ body = request.get_json()
123
+ user_text = body.get("text", "").strip()
124
+ cam_mac = body.get("cam_mac", "").strip()
125
+
126
+ if not user_text or not cam_mac:
127
+ return jsonify({"error": "يرجى إرسال 'text' و 'cam_mac'"}), 400
128
+
129
+ prompt = f"""
130
+ ### Postgres SQL table definitions
131
+ {DB_SCHEMA}
132
+
133
+ ### User question: {user_text}
134
+
135
+ ### SQL query to answer the question filtered by cam_mac = '{cam_mac}':
136
+ SELECT
137
+ """.strip()
138
+
139
+ inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
140
+ outputs = model.generate(**inputs, max_length=256)
141
+ sql = tokenizer.decode(outputs[0], skip_special_tokens=True)
142
+
143
+ # تنظيف الناتج
144
+ if not sql.lower().startswith("select"):
145
+ sql = "SELECT " + sql
146
+ if not sql.endswith(";"):
147
+ sql += ";"
148
+
149
+ return jsonify({"sql": sql})
150
+
151
+ except Exception as e:
152
+ logger.error(f"خطأ في التوليد: {str(e)}")
153
+ return jsonify({"error": "فشل في توليد الاستعلام"}), 500
154
+
155
+ @app.route('/')
156
+ def home():
157
+ return """
158
+ <h1>Text2SQL API</h1>
159
+ <p>Send a POST request to <code>/generate-sql</code> with JSON: {"text": "سؤالك", "cam_mac": "عنوان MAC"}</p>
160
+ """
161
+
162
+ if __name__ == '__main__':
163
+ app.run(host='0.0.0.0', port=7860)