MHD011 commited on
Commit
47b67ad
·
verified ·
1 Parent(s): 8507df0

Upload 4 files

Browse files
Files changed (4) hide show
  1. Dockerfile +32 -0
  2. app.py +279 -0
  3. dockerignore +9 -0
  4. requirements.txt +8 -0
Dockerfile ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # استخدام صورة أساسية خفيفة الوزن مع بايثون
2
+ FROM python:3.9-slim
3
+
4
+ # تعيين مجلد العمل
5
+ WORKDIR /app
6
+
7
+ # تثبيت الاعتمادات النظامية المطلوبة
8
+ RUN apt-get update && apt-get install -y \
9
+ build-essential \
10
+ libpq-dev \
11
+ && rm -rf /var/lib/apt/lists/*
12
+
13
+ # نسخ ملفات المشروع
14
+ COPY . .
15
+
16
+ # إنشاء مجلد للذاكرة المؤقتة
17
+ RUN mkdir -p /app/model_cache && chmod -R 777 /app
18
+
19
+ # تثبيت متطلبات بايثون
20
+ RUN pip install --no-cache-dir -r requirements.txt
21
+
22
+ # تعيين متغيرات البيئة
23
+ ENV FLASK_APP=app.py
24
+ ENV FLASK_ENV=production
25
+ ENV TRANSFORMERS_CACHE=/app/model_cache
26
+ ENV TORCH_CACHE=/app/model_cache
27
+
28
+ # فتح المنفذ
29
+ EXPOSE 7860
30
+
31
+ # تشغيل التطبيق مع Gunicorn
32
+ CMD ["gunicorn", "--workers", "2", "--threads", "4", "--bind", "0.0.0.0:7860", "--timeout", "120", "app:app"]
app.py ADDED
@@ -0,0 +1,279 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import logging
4
+ from flask import Flask, request, jsonify, Response
5
+ from flask_cors import CORS
6
+ from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
7
+ import torch
8
+ import psycopg2
9
+ import json
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
+ local_files_only=False
38
+ )
39
+ model = AutoModelForSeq2SeqLM.from_pretrained(
40
+ MODEL_NAME,
41
+ cache_dir="model_cache",
42
+ device_map="auto"
43
+ )
44
+ model.eval()
45
+ logger.info("تم تحميل النموذج بنجاح")
46
+ except Exception as e:
47
+ logger.error(f"فشل في تحميل النموذج: {str(e)}")
48
+ raise
49
+
50
+ initialize()
51
+
52
+ # --- سكيمة قاعدة البيانات ---
53
+ DB_SCHEMA = """
54
+ CREATE TABLE public.profiles (
55
+ id uuid NOT NULL,
56
+ updated_at timestamp with time zone,
57
+ username text UNIQUE CHECK (char_length(username) >= 3),
58
+ full_name text,
59
+ avatar_url text,
60
+ website text,
61
+ cam_mac text UNIQUE,
62
+ fcm_token text,
63
+ notification_enabled boolean DEFAULT true,
64
+ CONSTRAINT profiles_pkey PRIMARY KEY (id),
65
+ CONSTRAINT profiles_id_fkey FOREIGN KEY (id) REFERENCES auth.users(id)
66
+ );
67
+
68
+ CREATE TABLE public.place (
69
+ id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
70
+ created_at timestamp with time zone DEFAULT (now() AT TIME ZONE 'utc'::text),
71
+ name text,
72
+ CONSTRAINT place_pkey PRIMARY KEY (id)
73
+ );
74
+
75
+ CREATE TABLE public.user_place (
76
+ id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
77
+ created_at timestamp with time zone NOT NULL DEFAULT now(),
78
+ place_id bigint,
79
+ user_cam_mac text,
80
+ CONSTRAINT user_place_pkey PRIMARY KEY (id),
81
+ CONSTRAINT user_place_place_id_fkey FOREIGN KEY (place_id) REFERENCES public.place(id),
82
+ CONSTRAINT user_place_user_cam_mac_fkey FOREIGN KEY (user_cam_mac) REFERENCES public.profiles(cam_mac)
83
+ );
84
+
85
+ CREATE TABLE public.data (
86
+ id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
87
+ created_at timestamp without time zone,
88
+ caption text,
89
+ image_url text,
90
+ latitude double precision DEFAULT '36.1833854'::double precision,
91
+ longitude double precision DEFAULT '37.1309255'::double precision,
92
+ user_place_id bigint,
93
+ cam_mac text,
94
+ CONSTRAINT data_pkey PRIMARY KEY (id),
95
+ CONSTRAINT data_user_place_id_fkey FOREIGN KEY (user_place_id) REFERENCES public.user_place(id)
96
+ );
97
+
98
+ CREATE TABLE public.biodata (
99
+ id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
100
+ created_at timestamp with time zone NOT NULL DEFAULT now(),
101
+ mac_address text,
102
+ acceleration_x double precision,
103
+ acceleration_y double precision,
104
+ acceleration_z double precision,
105
+ gyro_x double precision,
106
+ gyro_y double precision,
107
+ gyro_z double precision,
108
+ temperature double precision,
109
+ CONSTRAINT biodata_pkey PRIMARY KEY (id),
110
+ CONSTRAINT biodata_mac_address_fkey FOREIGN KEY (mac_address) REFERENCES public.profiles(cam_mac)
111
+ );
112
+
113
+ CREATE TABLE public.notification (
114
+ id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
115
+ created_at timestamp without time zone NOT NULL DEFAULT now(),
116
+ user_cam_mac text,
117
+ title text,
118
+ message text,
119
+ is_read boolean,
120
+ acceleration_x double precision,
121
+ acceleration_y double precision,
122
+ acceleration_z double precision,
123
+ gyro_x double precision,
124
+ gyro_y double precision,
125
+ gyro_z double precision,
126
+ CONSTRAINT notification_pkey PRIMARY KEY (id),
127
+ CONSTRAINT notification_user_cam_mac_fkey FOREIGN KEY (user_cam_mac) REFERENCES public.profiles(cam_mac)
128
+ );
129
+
130
+ CREATE TABLE public.flag (
131
+ id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
132
+ flag smallint,
133
+ user_mac_address text,
134
+ CONSTRAINT flag_pkey PRIMARY KEY (id),
135
+ CONSTRAINT flag_user_mac_address_fkey FOREIGN KEY (user_mac_address) REFERENCES public.profiles(cam_mac)
136
+ );
137
+ """.strip()
138
+
139
+ # --- الاتصال بقاعدة البيانات ---
140
+ def get_db_connection():
141
+ try:
142
+ return psycopg2.connect(SUPABASE_DB_URL)
143
+ except Exception as err:
144
+ logger.error(f"Database connection error: {err}")
145
+ return None
146
+
147
+ # --- التحقق من صحة cam_mac ---
148
+ def validate_cam_mac(cam_mac):
149
+ conn = get_db_connection()
150
+ if not conn:
151
+ return False
152
+
153
+ try:
154
+ cursor = conn.cursor()
155
+ cursor.execute("SELECT 1 FROM profiles WHERE cam_mac = %s;", (cam_mac,))
156
+ return cursor.fetchone() is not None
157
+ except Exception as e:
158
+ logger.error(f"Validation error: {e}")
159
+ return False
160
+ finally:
161
+ if conn:
162
+ conn.close()
163
+
164
+ @app.route('/api/query', methods=['POST'])
165
+ def handle_query():
166
+ if tokenizer is None or model is None:
167
+ return jsonify({"error": "النموذج غير محمل، يرجى المحاولة لاحقاً"}), 503
168
+
169
+ try:
170
+ data = request.get_json()
171
+ if not data or 'text' not in data or 'cam_mac' not in data:
172
+ return jsonify({"error": "يرجى إرسال 'text' و 'cam_mac'"}), 400
173
+
174
+ natural_query = data['text']
175
+ cam_mac = data['cam_mac']
176
+ logger.info(f"استعلام من {cam_mac}: {natural_query}")
177
+
178
+ if not validate_cam_mac(cam_mac):
179
+ return jsonify({"error": "عنوان MAC غير صالح"}), 403
180
+
181
+ prompt = f"""
182
+ ### Postgres SQL table definitions
183
+ {DB_SCHEMA}
184
+
185
+ ### Rules:
186
+ - Always filter by cam_mac = '{cam_mac}'
187
+ - Use only SELECT statements
188
+ - Use proper JOINs
189
+ - Use table aliases when helpful
190
+ - The output must contain only the SQL query
191
+
192
+ ### User question: {natural_query}
193
+
194
+ ### SQL query:
195
+ SELECT
196
+ """.strip()
197
+
198
+ inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
199
+
200
+ with torch.no_grad():
201
+ outputs = model.generate(**inputs, max_length=256)
202
+
203
+ sql = tokenizer.decode(outputs[0], skip_special_tokens=True)
204
+
205
+ # تنظيف الناتج
206
+ sql = re.sub(r"^```sql\s*", "", sql, flags=re.IGNORECASE)
207
+ sql = re.sub(r"\s*```$", "", sql)
208
+ sql = re.sub(r"^SQL:\s*", "", sql, flags=re.IGNORECASE)
209
+
210
+ if not sql.upper().startswith("SELECT"):
211
+ sql = "SELECT " + sql.split("SELECT")[-1] if "SELECT" in sql else f"SELECT * FROM ({sql}) AS subquery"
212
+
213
+ if not sql.endswith(";"):
214
+ sql += ";"
215
+
216
+ logger.info(f"استعلام SQL المولد: {sql}")
217
+
218
+ if not sql.upper().strip().startswith("SELECT"):
219
+ return jsonify({"error": "يُسمح فقط باستعلامات SELECT"}), 403
220
+
221
+ conn = get_db_connection()
222
+ if not conn:
223
+ return jsonify({"error": "فشل الاتصال بقاعدة البيانات"}), 500
224
+
225
+ cursor = None
226
+ try:
227
+ cursor = conn.cursor()
228
+ cursor.execute(sql)
229
+ columns = [desc[0] for desc in cursor.description]
230
+ rows = cursor.fetchall()
231
+ data = [dict(zip(columns, row)) for row in rows]
232
+
233
+ response_data = {
234
+ "data": data,
235
+ "generated_sql": sql
236
+ }
237
+
238
+ response_json = json.dumps(response_data, ensure_ascii=False)
239
+
240
+ return Response(
241
+ response_json,
242
+ status=200,
243
+ mimetype='application/json; charset=utf-8'
244
+ )
245
+
246
+ except Exception as e:
247
+ logger.error(f"خطأ في تنفيذ SQL: {e}")
248
+ return jsonify({
249
+ "error": str(e),
250
+ "generated_sql": sql
251
+ }), 500
252
+ finally:
253
+ if cursor:
254
+ cursor.close()
255
+ if conn:
256
+ conn.close()
257
+
258
+ except Exception as e:
259
+ logger.error(f"خطأ في التوليد: {str(e)}")
260
+ return jsonify({"error": "فشل في توليد الاستعلام"}), 500
261
+
262
+ @app.route('/health')
263
+ def health_check():
264
+ return jsonify({
265
+ "status": "healthy",
266
+ "model_loaded": model is not None,
267
+ "db_connection": get_db_connection() is not None
268
+ })
269
+
270
+ @app.route('/')
271
+ def home():
272
+ return """
273
+ <h1>Text2SQL API</h1>
274
+ <p>استخدم <code>/api/query</code> مع POST {"text": "سؤالك", "cam_mac": "عنوان MAC"}</p>
275
+ <p>تحقق من حالة الخدمة: <a href="/health">/health</a></p>
276
+ """
277
+
278
+ if __name__ == '__main__':
279
+ app.run(host='0.0.0.0', port=7860)
dockerignore ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__
2
+ *.pyc
3
+ *.pyo
4
+ *.pyd
5
+ .DS_Store
6
+ .env
7
+ venv/
8
+ model_cache/
9
+ *.log
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ flask==2.3.2
2
+ flask-cors==3.0.10
3
+ torch==2.0.1
4
+ transformers==4.30.2
5
+ psycopg2-binary==2.9.7
6
+ gunicorn==20.1.0
7
+ accelerate==0.21.0
8
+ python-dotenv==1.0.0