EGYADMIN commited on
Commit
53bbfe2
·
verified ·
1 Parent(s): 90c4f34

Update utils/helpers.py

Browse files
Files changed (1) hide show
  1. utils/helpers.py +234 -284
utils/helpers.py CHANGED
@@ -2,331 +2,281 @@
2
  # -*- coding: utf-8 -*-
3
 
4
  """
5
- وحدة المساعدة المركزية للنظام
6
- تحتوي على دوال مساعدة مشتركة تستخدم في جميع أنحاء التطبيق
7
  """
8
 
9
  import os
10
- import sys
11
- import streamlit as st
12
- import pandas as pd
13
- import numpy as np
14
  import json
15
  import re
16
- import time
17
- import tempfile
18
- from datetime import datetime, timedelta
19
- import random
20
- import secrets
21
- import shutil
22
- import base64
23
- import logging
24
- from pathlib import Path
25
-
26
- # تكوين التسجيلات
27
- logging.basicConfig(
28
- level=logging.INFO,
29
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
30
- )
31
- logger = logging.getLogger("wahbi-ai")
32
-
33
 
34
  def create_directory_if_not_exists(directory_path):
35
- """إنشاء مسار إذا لم يكن موجوداً"""
36
- try:
37
- if not os.path.exists(directory_path):
38
- os.makedirs(directory_path)
39
- logger.info(f"تم إنشاء المجلد: {directory_path}")
40
  return True
41
- except Exception as e:
42
- logger.error(f"خطأ في إنشاء المجلد {directory_path}: {e}")
43
- return False
44
-
45
 
46
  def get_data_folder():
47
- """الحصول على مسار مجلد البيانات الرئيسي"""
48
- # مسار بيانات النظام الرئيسي
49
- data_folder = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "data")
50
  create_directory_if_not_exists(data_folder)
51
  return data_folder
52
 
53
-
54
- def load_config():
55
- """تحميل إعدادات التكوين من ملف config.json"""
56
- config_path = os.path.join(get_data_folder(), "config.json")
57
-
58
- # إذا لم يكن ملف التكوين موجوداً، قم بإنشاء ملف افتراضي
59
- if not os.path.exists(config_path):
60
- default_config = {
61
- "system": {
62
- "version": "1.0.0",
63
- "release_date": "2025-03-30",
64
- "company_name": "شركة شبه الجزيرة للمقاولات",
65
- "company_logo": "",
66
- "language": "ar",
67
- "theme": "light",
68
- "debug_mode": False
69
- },
70
- "ai_models": {
71
- "default_model": "claude-3-7-sonnet-20250219",
72
- "openai_model": "gpt-4o",
73
- "huggingface_model": "mistralai/Mistral-7B-Instruct-v0.2"
74
- },
75
- "notifications": {
76
- "enable_email": False,
77
- "enable_browser": True,
78
- "check_interval": 60
79
- }
80
- }
81
-
82
- with open(config_path, 'w', encoding='utf-8') as f:
83
- json.dump(default_config, f, ensure_ascii=False, indent=2)
84
-
85
- return default_config
86
-
87
- # تحميل ملف التكوين الموجود
88
- try:
89
- with open(config_path, 'r', encoding='utf-8') as f:
90
- return json.load(f)
91
- except Exception as e:
92
- logger.error(f"خطأ في تحميل ملف التكوين: {e}")
93
- return {}
94
-
95
-
96
- def save_config(config):
97
- """حفظ إعدادات التكوين إلى ملف config.json"""
98
- config_path = os.path.join(get_data_folder(), "config.json")
99
-
100
- try:
101
- with open(config_path, 'w', encoding='utf-8') as f:
102
- json.dump(config, f, ensure_ascii=False, indent=2)
103
- return True
104
- except Exception as e:
105
- logger.error(f"خطأ في حفظ ملف التكوين: {e}")
106
- return False
107
-
108
-
109
- def format_time(timestamp=None, format_str="%Y-%m-%d %H:%M:%S"):
110
- """تنسيق الوقت إلى صيغة معينة"""
111
  if timestamp is None:
112
- timestamp = datetime.now()
113
- elif isinstance(timestamp, (int, float)):
114
- timestamp = datetime.fromtimestamp(timestamp)
115
-
116
- return timestamp.strftime(format_str)
117
-
118
 
119
  def get_user_info():
120
  """الحصول على معلومات المستخدم الحالي"""
121
- # في التطبيق الفعلي، يمكن استرداد معلومات المستخدم من قاعدة البيانات أو من حالة الجلسة
122
- if "user_info" in st.session_state:
123
- return st.session_state.user_info
124
-
125
- # معلومات افتراضية للتطوير
126
  return {
127
  "id": 1,
128
  "username": "admin",
129
- "full_name": "مدير النظام",
130
- "email": "admin@example.com",
131
- "role": "مدير",
132
- "department": "الإدارة",
133
- "last_login": format_time()
134
  }
135
 
136
-
137
- def get_current_project():
138
- """الحصول على معلومات المشروع الحالي"""
139
- if "current_project" in st.session_state:
140
- return st.session_state.current_project
 
 
 
 
 
141
 
142
- # في حالة عدم وجود مشروع محدد
143
- return None
144
-
145
-
146
- def load_icons():
147
- """تحميل الأيقونات المستخدمة في النظام"""
148
- icons_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "assets", "icons")
149
- icons = {}
150
 
151
- # التأكد من وجود مجلد الأيقونات
152
- if not os.path.exists(icons_path):
153
- create_directory_if_not_exists(icons_path)
154
- return icons
 
 
155
 
156
- # تحميل جميع الأيقونات
157
- for icon_file in os.listdir(icons_path):
158
- if icon_file.endswith(('.png', '.svg', '.jpg')):
159
- icon_name = os.path.splitext(icon_file)[0]
160
- icon_path = os.path.join(icons_path, icon_file)
161
-
162
- try:
163
- with open(icon_path, "rb") as f:
164
- icons[icon_name] = base64.b64encode(f.read()).decode()
165
- except Exception as e:
166
- logger.error(f"خطأ في تحميل الأيقونة {icon_name}: {e}")
167
 
168
- return icons
169
-
170
-
171
- def get_random_id(length=8):
172
- """إنشاء معرف عشوائي بطول محدد"""
173
- return secrets.token_hex(length // 2)
174
-
175
-
176
- def compress_text(text, max_length=10000):
177
- """اختصار النص إلى حد أقصى محدد مع الحفاظ على المعنى"""
178
- if not text or len(text) <= max_length:
179
- return text
 
 
 
 
 
 
 
 
 
 
 
 
180
 
181
- # تقسيم النص إلى جمل
182
- sentences = re.split(r'(?<=[.!?])\s+', text)
 
 
183
 
184
- # حساب متوسط طول الجملة
185
- avg_sentence_length = len(text) / len(sentences)
 
 
 
 
 
 
186
 
187
- # حساب عدد الجمل التي يمكن تضمينها
188
- num_sentences_to_keep = int(max_length / avg_sentence_length)
 
189
 
190
- # الاحتفاظ بالجمل الأولى والأخيرة للحفاظ على السياق
191
- keep_first = num_sentences_to_keep // 2
192
- keep_last = num_sentences_to_keep - keep_first
 
 
 
193
 
194
- # دمج الجمل المختارة
195
- compressed_text = ' '.join(sentences[:keep_first] + sentences[-keep_last:])
 
196
 
197
- # إضافة إشارة إلى أن النص تم اختصاره
198
- if len(compressed_text) < len(text):
199
- compressed_text += " [...المزيد من النص تم اختصاره...]"
 
 
200
 
201
- return compressed_text
 
202
 
 
 
 
 
 
 
 
 
203
 
204
- def str_to_bool(text):
205
- """تحويل النص إلى قيمة منطقية"""
206
- return text.lower() in ('yes', 'true', 'y', 't', '1', 'نعم', 'صحيح')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
 
 
 
 
 
 
 
 
 
208
 
209
- def handle_arabic_text(text):
210
- """معالجة النص العربي للعرض بشكل صحيح"""
211
- if not text:
212
- return ""
213
-
214
- # إضافة علامة RTL لضمان عرض النص العربي بشكل صحيح
215
- return f"<div dir='rtl'>{text}</div>"
216
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
 
218
- def render_credits():
219
- """عرض معلومات النظام وحقوق الملكية"""
220
- st.markdown("---")
221
 
222
- config = load_config()
223
- system_info = config.get("system", {})
 
 
 
 
 
 
224
 
225
- col1, col2, col3 = st.columns([1, 2, 1])
 
226
 
227
- with col2:
228
- st.markdown(
229
- f"""
230
- <div style="text-align: center; color: #666;">
231
- <p>{system_info.get('company_name', 'شركة شبه الجزيرة للمقاولات')}</p>
232
- <p>الإصدار: {system_info.get('version', '1.0.0')}</p>
233
- <p>© جميع الحقوق محفوظة 2025</p>
234
- </div>
235
- """,
236
- unsafe_allow_html=True
237
- )
238
-
239
-
240
- # دالة للحصول على اتصال قاعدة البيانات
241
- def get_connection():
242
- """
243
- دالة للحصول على اتصال بقاعدة البيانات
244
 
245
- ملاحظة: ينبغي أن تكون مستبدلة بالدالة من db_connector.py في البيئة الإنتاجية
246
- """
247
- try:
248
- # استيراد المستوى الفعلي للاتصال بقاعدة البيانات
249
- from database.db_connector import get_connection as get_db_connection
250
- return get_db_connection()
251
- except ImportError:
252
- # إذا كان الاتصال بقاعدة البيانات غير متاح
253
- logger.warning("لم يتم العثور على وحدة اتصال قاعدة البيانات. استخدام مخزن بيانات مؤقت.")
254
- # إرجاع None للإشارة إلى عدم وجود اتصال
255
- return None
256
-
257
-
258
- def load_css(file_name=None):
259
- """تحميل ملف CSS مخصص"""
260
- try:
261
- if file_name:
262
- css_file = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "assets", "css", file_name)
263
-
264
- if os.path.exists(css_file):
265
- with open(css_file, "r", encoding="utf-8") as f:
266
- css_content = f.read()
267
- else:
268
- logger.warning(f"ملف CSS غير موجود: {css_file}")
269
- return
270
  else:
271
- # CSS افتراضي
272
- css_content = """
273
- .sidebar .sidebar-content {
274
- direction: rtl;
275
- text-align: right;
276
- }
277
- div[data-testid="stForm"] {
278
- border: 1px solid #ddd;
279
- padding: 10px;
280
- border-radius: 10px;
281
- }
282
- .module-title {
283
- color: #1E88E5;
284
- text-align: center;
285
- font-size: 1.8rem;
286
- margin-bottom: 1rem;
287
- }
288
- .instructions {
289
- background-color: #f8f9fa;
290
- border-right: 3px solid #4CAF50;
291
- padding: 10px;
292
- margin-bottom: 15px;
293
- }
294
- .results-container {
295
- background-color: #f5f5f5;
296
- padding: 15px;
297
- border-radius: 5px;
298
- margin-top: 20px;
299
- }
300
- .risk-high {
301
- color: #d32f2f;
302
- font-weight: bold;
303
- }
304
- .risk-medium {
305
- color: #f57c00;
306
- font-weight: bold;
307
- }
308
- .risk-low {
309
- color: #388e3c;
310
- font-weight: bold;
311
- }
312
- .form-container {
313
- background-color: #f9f9f9;
314
- padding: 20px;
315
- border-radius: 10px;
316
- margin-bottom: 20px;
317
- }
318
- .section-header {
319
- color: #2196F3;
320
- font-size: 1.2rem;
321
- font-weight: bold;
322
- margin-top: 20px;
323
- margin-bottom: 10px;
324
- border-bottom: 1px solid #eee;
325
- padding-bottom: 5px;
326
- }
327
- """
328
-
329
- st.markdown(f"<style>{css_content}</style>", unsafe_allow_html=True)
330
-
331
- except Exception as e:
332
- logger.error(f"خطأ في تحميل ملف CSS: {e}")
 
2
  # -*- coding: utf-8 -*-
3
 
4
  """
5
+ وحدة المساعدات العامة
6
+ توفر هذه الوحدة مجموعة من الدوال المساعدة المستخدمة في مختلف أجزاء النظام
7
  """
8
 
9
  import os
10
+ import datetime
 
 
 
11
  import json
12
  import re
13
+ import streamlit as st
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
  def create_directory_if_not_exists(directory_path):
16
+ """إنشاء مجلد إذا لم يكن موجوداً بالفعل"""
17
+ if not os.path.exists(directory_path):
18
+ os.makedirs(directory_path)
 
 
19
  return True
20
+ return False
 
 
 
21
 
22
  def get_data_folder():
23
+ """الحصول على مسار مجلد البيانات"""
24
+ data_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'data'))
 
25
  create_directory_if_not_exists(data_folder)
26
  return data_folder
27
 
28
+ def format_time(timestamp=None):
29
+ """تنسيق الوقت بصيغة قابلة للقراءة"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  if timestamp is None:
31
+ timestamp = datetime.datetime.now()
32
+ return timestamp.strftime("%Y-%m-%d %H:%M:%S")
 
 
 
 
33
 
34
  def get_user_info():
35
  """الحصول على معلومات المستخدم الحالي"""
36
+ # في الوقت الحالي، نستخدم معلومات مستخدم افتراضية
37
+ # يمكن تعديل هذه الدالة لاحقاً للتكامل مع نظام المصادقة
 
 
 
38
  return {
39
  "id": 1,
40
  "username": "admin",
41
+ "name": "مدير النظام",
42
+ "role": "admin"
 
 
 
43
  }
44
 
45
+ def load_css():
46
+ """تحميل أنماط CSS المخصصة"""
47
+ st.markdown("""
48
+ <style>
49
+ .module-title {
50
+ color: #1E88E5;
51
+ text-align: center;
52
+ margin-bottom: 20px;
53
+ font-weight: bold;
54
+ }
55
 
56
+ .section-title {
57
+ color: #1565C0;
58
+ margin-top: 20px;
59
+ margin-bottom: 10px;
60
+ font-weight: bold;
61
+ }
 
 
62
 
63
+ .info-box {
64
+ background-color: #E3F2FD;
65
+ padding: 15px;
66
+ border-radius: 5px;
67
+ margin-bottom: 15px;
68
+ }
69
 
70
+ .warning-box {
71
+ background-color: #FFF8E1;
72
+ padding: 15px;
73
+ border-radius: 5px;
74
+ margin-bottom: 15px;
75
+ }
 
 
 
 
 
76
 
77
+ .error-box {
78
+ background-color: #FFEBEE;
79
+ padding: 15px;
80
+ border-radius: 5px;
81
+ margin-bottom: 15px;
82
+ }
83
+
84
+ .success-box {
85
+ background-color: #E8F5E9;
86
+ padding: 15px;
87
+ border-radius: 5px;
88
+ margin-bottom: 15px;
89
+ }
90
+
91
+ .centered {
92
+ text-align: center;
93
+ }
94
+
95
+ .footer {
96
+ text-align: center;
97
+ margin-top: 30px;
98
+ color: #78909C;
99
+ font-size: 0.8em;
100
+ }
101
 
102
+ /* تحسين تصميم الجداول */
103
+ table {
104
+ width: 100%;
105
+ }
106
 
107
+ thead th {
108
+ background-color: #1976D2 !important;
109
+ color: white !important;
110
+ }
111
+
112
+ tbody tr:nth-child(even) {
113
+ background-color: #f2f2f2;
114
+ }
115
 
116
+ tbody tr:hover {
117
+ background-color: #E3F2FD;
118
+ }
119
 
120
+ /* تحسين ظهور الفورم */
121
+ input, select, textarea {
122
+ border-radius: 5px !important;
123
+ border: 1px solid #ddd !important;
124
+ padding: 10px !important;
125
+ }
126
 
127
+ .stButton>button {
128
+ border-radius: 5px !important;
129
+ }
130
 
131
+ /* زيادة حجم الخط للتوافق مع اللغة العربية */
132
+ html, body, [class*="css"] {
133
+ font-family: 'Tajawal', sans-serif;
134
+ }
135
+ </style>
136
 
137
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Tajawal:wght@400;700&display=swap">
138
+ """, unsafe_allow_html=True)
139
 
140
+ def render_credits():
141
+ """عرض المعلومات عن حقوق الملكية وإصدار النظام"""
142
+ st.markdown("""
143
+ <div class="footer">
144
+ <p>هذا النظام يعمل لصالح شركة شبه الجزيرة للمقاولات، جميع الحقوق محفوظة 2025</p>
145
+ <p>نظام WAHBi AI - الإصدار 2.0</p>
146
+ </div>
147
+ """, unsafe_allow_html=True)
148
 
149
+ def load_icons():
150
+ """تحميل الأيقونات المستخدمة في النظام"""
151
+ icons = {
152
+ "project": "🏗️",
153
+ "document": "📄",
154
+ "analysis": "🔍",
155
+ "warning": "⚠️",
156
+ "success": "✅",
157
+ "error": "❌",
158
+ "info": "ℹ️",
159
+ "settings": "⚙️",
160
+ "user": "👤",
161
+ "money": "💰",
162
+ "time": "⏱️",
163
+ "location": "📍",
164
+ "notification": "🔔",
165
+ "edit": "✏️",
166
+ "delete": "🗑️",
167
+ "upload": "📤",
168
+ "download": "📥",
169
+ "save": "💾",
170
+ "cancel": "❌",
171
+ "add": "➕",
172
+ "calendar": "📅",
173
+ "chat": "💬",
174
+ "search": "🔎",
175
+ "star": "⭐",
176
+ "trophy": "🏆",
177
+ "medal": "🥇",
178
+ "chart": "📊",
179
+ "map": "🗺️",
180
+ "building": "🏢",
181
+ "road": "🛣️",
182
+ "bridge": "🌉",
183
+ }
184
+ return icons
185
 
186
+ def format_number(number, decimal_places=2):
187
+ """تنسيق الأرقام بطريقة أنيقة"""
188
+ if isinstance(number, (int, float)):
189
+ if decimal_places == 0:
190
+ return "{:,.0f}".format(number)
191
+ else:
192
+ return "{:,.{dp}f}".format(number, dp=decimal_places)
193
+ return str(number)
194
 
195
+ def format_currency(amount, currency="ريال", decimal_places=2):
196
+ """تنسيق المبالغ المالية"""
197
+ if amount is None:
198
+ return "غير محدد"
199
+ formatted = format_number(amount, decimal_places)
200
+ return f"{formatted} {currency}"
 
201
 
202
+ def styled_button(label, key=None, type="primary", on_click=None, args=None, full_width=False, icon=None, is_link=False, help=None):
203
+ """
204
+ إنشاء زر بتنسيق معين
205
+ :param label: نص الزر
206
+ :param key: مفتاح الزر الفريد
207
+ :param type: نوع التنسيق ('primary', 'secondary', 'success', 'warning', 'danger', 'info', 'glass', 'flat')
208
+ :param on_click: الدالة التي سيتم تنفيذها عند النقر
209
+ :param args: معاملات الدالة
210
+ :param full_width: هل يأخذ الزر العرض كاملاً
211
+ :param icon: أيقونة لعرضها قبل النص (emoji أو HTML)
212
+ :param is_link: إذا كان الزر رابطاً بدلاً من زر عادي
213
+ :param help: نص المساعدة للزر
214
+ :return: زر مُنسّق
215
+ """
216
+ if is_link:
217
+ btn_class = f"{type}-btn"
218
+ if icon:
219
+ btn_class += " action-btn"
220
+ label_with_icon = f"{icon} {label}"
221
+ else:
222
+ label_with_icon = label
223
+
224
+ button_html = f"""
225
+ <div class="{btn_class}">
226
+ {label_with_icon}
227
+ </div>
228
+ """
229
+ return st.markdown(button_html, unsafe_allow_html=True)
230
+ else:
231
+ with st.container():
232
+ btn_class = f"{type}-btn"
233
+ if icon:
234
+ btn_class += " action-btn"
235
+ label_with_icon = f"{icon} {label}"
236
+ else:
237
+ label_with_icon = label
238
+
239
+ st.markdown(f'<div class="{btn_class}">', unsafe_allow_html=True)
240
+ clicked = st.button(label_with_icon, key=key, on_click=on_click, args=args, use_container_width=full_width, help=help)
241
+ st.markdown('</div>', unsafe_allow_html=True)
242
+ return clicked
243
+
244
+ def filter_dataframe(df, column, value):
245
+ """ترشيح إطار البيانات"""
246
+ if value == "الكل":
247
+ return df
248
+ return df[df[column] == value]
249
+
250
+ def get_file_extension(filename):
251
+ """استخراج امتداد الملف"""
252
+ if not filename:
253
+ return ""
254
+ return os.path.splitext(filename)[-1].lower()
255
 
256
+ def extract_numbers_from_text(text):
257
+ """استخراج الأرقام من النص
 
258
 
259
+ Args:
260
+ text (str): النص المراد استخراج الأرقام منه
261
+
262
+ Returns:
263
+ list: قائمة بالأرقام المستخرجة
264
+ """
265
+ if not text:
266
+ return []
267
 
268
+ # نمط للعثور على الأرقام (صحيحة أو عشرية) في النص
269
+ pattern = r'[-+]?\d*\.\d+|\d+'
270
 
271
+ # استخراج جميع الأرقام من النص
272
+ numbers = re.findall(pattern, text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
 
274
+ # تحويل النصوص المستخرجة إلى أرقام (صحيحة أو عشرية)
275
+ converted_numbers = []
276
+ for num in numbers:
277
+ if '.' in num:
278
+ converted_numbers.append(float(num))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  else:
280
+ converted_numbers.append(int(num))
281
+
282
+ return converted_numbers