MHD011 commited on
Commit
0cfe8c0
·
verified ·
1 Parent(s): 12e34ef

Upload 3 files

Browse files
Files changed (3) hide show
  1. Dockerfile +17 -0
  2. app.py +280 -0
  3. requirements.txt +4 -0
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as a parent image
2
+ FROM python:3.9-slim
3
+
4
+ # Set the working directory in the container
5
+ WORKDIR /app
6
+
7
+ # Copy the current directory contents into the container
8
+ COPY . .
9
+
10
+ # Install any needed packages specified in requirements.txt
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ # Make port 7860 available to the world outside this container
14
+ EXPOSE 7860
15
+
16
+ # Run app.py when the container launches
17
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,280 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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)
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ flask==2.3.2
2
+ psycopg2-binary==2.9.7
3
+ google-generativeai==0.3.2
4
+ python-dotenv==1.0.0