MHD011 commited on
Commit
53f2234
·
verified ·
1 Parent(s): 7a3df70

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +329 -279
app.py CHANGED
@@ -1,280 +1,330 @@
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:[email protected]: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 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:[email protected]: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
+ try:
218
+ data = request.get_json()
219
+ if not data or 'text' not in data or 'cam_mac' not in data:
220
+ return jsonify({
221
+ "status": "error",
222
+ "message": "Please provide both 'text' and 'cam_mac' in the request body",
223
+ "data": None
224
+ }), 400
225
+
226
+ natural_query = data['text']
227
+ cam_mac = data['cam_mac']
228
+ print(f"Natural query from {cam_mac}: {natural_query}")
229
+
230
+ # التحقق من صحة cam_mac
231
+ if not validate_cam_mac(cam_mac):
232
+ return jsonify({
233
+ "status": "error",
234
+ "message": "Invalid camera MAC address",
235
+ "data": None
236
+ }), 403
237
+
238
+ # توليد استعلام SQL
239
+ sql_query = generate_sql_gemini(natural_query, cam_mac)
240
+ if not sql_query:
241
+ return jsonify({
242
+ "status": "error",
243
+ "message": "Failed to generate SQL query",
244
+ "data": None
245
+ }), 500
246
+
247
+ print(f"Generated SQL: {sql_query}")
248
+
249
+ # التأكد من أن الاستعلام SELECT فقط
250
+ if not re.match(r'^\s*SELECT', sql_query, re.IGNORECASE):
251
+ return jsonify({
252
+ "status": "error",
253
+ "message": "Only SELECT queries are allowed",
254
+ "data": None
255
+ }), 403
256
+
257
+ # تنفيذ الاستعلام
258
+ conn = get_db_connection()
259
+ if not conn:
260
+ return jsonify({
261
+ "status": "error",
262
+ "message": "Database connection failed",
263
+ "data": None
264
+ }), 500
265
+
266
+ try:
267
+ with conn.cursor() as cursor:
268
+ cursor.execute(sql_query)
269
+
270
+ # إذا كان الاستعلام لا يعيد بيانات (مثل SELECT COUNT)
271
+ if cursor.description is None:
272
+ return jsonify({
273
+ "status": "success",
274
+ "message": "Query executed successfully",
275
+ "data": []
276
+ }), 200
277
+
278
+ columns = [desc[0] for desc in cursor.description]
279
+ rows = cursor.fetchall()
280
+
281
+ # تحويل النتائج إلى قاموس
282
+ result_data = []
283
+ for row in rows:
284
+ row_dict = {}
285
+ for i, col in enumerate(columns):
286
+ # تحويل أنواع البيانات غير القابلة للتسلسل
287
+ if isinstance(row[i], (datetime.date, datetime.time, datetime.datetime)):
288
+ row_dict[col] = row[i].isoformat()
289
+ elif isinstance(row[i], decimal.Decimal):
290
+ row_dict[col] = float(row[i])
291
+ else:
292
+ row_dict[col] = row[i]
293
+ result_data.append(row_dict)
294
+
295
+ return jsonify({
296
+ "status": "success",
297
+ "message": "Query executed successfully",
298
+ "data": result_data
299
+ }), 200
300
+
301
+ except Exception as e:
302
+ print(f"SQL execution error: {e}")
303
+ return jsonify({
304
+ "status": "error",
305
+ "message": f"Database error: {str(e)}",
306
+ "data": None,
307
+ "generated_sql": sql_query
308
+ }), 500
309
+
310
+ finally:
311
+ if conn:
312
+ conn.close()
313
+
314
+ except Exception as e:
315
+ print(f"Unexpected error: {e}")
316
+ return jsonify({
317
+ "status": "error",
318
+ "message": f"Internal server error: {str(e)}",
319
+ "data": None
320
+ }), 500
321
+
322
+ @app.route('/')
323
+ def home():
324
+ return """
325
+ <h1>Natural Language to SQL API (Gemini)</h1>
326
+ <p>Use <code>/api/query</code> with POST {"text": "your question", "cam_mac": "device_mac_address"}.</p>
327
+ """
328
+
329
+ if __name__ == '__main__':
330
  app.run(host='0.0.0.0', port=7860)