Spaces:
Sleeping
Sleeping
Create utils/helpers.py
Browse files- utils/helpers.py +326 -0
utils/helpers.py
ADDED
@@ -0,0 +1,326 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import re
|
3 |
+
import json
|
4 |
+
import pandas as pd
|
5 |
+
from typing import Dict, List, Any, Union, Tuple, Optional
|
6 |
+
from datetime import datetime
|
7 |
+
|
8 |
+
def format_currency(amount: float, currency: str = "ريال") -> str:
|
9 |
+
"""
|
10 |
+
تنسيق المبالغ المالية
|
11 |
+
|
12 |
+
المعاملات:
|
13 |
+
----------
|
14 |
+
amount : float
|
15 |
+
المبلغ
|
16 |
+
currency : str, optional
|
17 |
+
العملة (افتراضي: "ريال")
|
18 |
+
|
19 |
+
المخرجات:
|
20 |
+
--------
|
21 |
+
str
|
22 |
+
المبلغ بالتنسيق المناسب
|
23 |
+
"""
|
24 |
+
if not amount and amount != 0:
|
25 |
+
return "غير محدد"
|
26 |
+
|
27 |
+
# تنسيق الأرقام بالفواصل الآلاف
|
28 |
+
formatted = f"{amount:,.2f}".replace(".00", "")
|
29 |
+
|
30 |
+
# ترتيب العملة حسب اللغة العربية (يمين)
|
31 |
+
return f"{formatted} {currency}"
|
32 |
+
|
33 |
+
def format_percentage(value: float) -> str:
|
34 |
+
"""
|
35 |
+
تنسيق النسب المئوية
|
36 |
+
|
37 |
+
المعاملات:
|
38 |
+
----------
|
39 |
+
value : float
|
40 |
+
القيمة
|
41 |
+
|
42 |
+
المخرجات:
|
43 |
+
--------
|
44 |
+
str
|
45 |
+
النسبة بالتنسيق المناسب
|
46 |
+
"""
|
47 |
+
if not value and value != 0:
|
48 |
+
return "غير محدد"
|
49 |
+
|
50 |
+
return f"{value:.2f}%".replace(".00", "")
|
51 |
+
|
52 |
+
def format_date(date_obj: Any) -> str:
|
53 |
+
"""
|
54 |
+
تنسيق التواريخ
|
55 |
+
|
56 |
+
المعاملات:
|
57 |
+
----------
|
58 |
+
date_obj : Any
|
59 |
+
كائن التاريخ
|
60 |
+
|
61 |
+
المخرجات:
|
62 |
+
--------
|
63 |
+
str
|
64 |
+
التاريخ بالتنسيق المناسب
|
65 |
+
"""
|
66 |
+
if not date_obj:
|
67 |
+
return "غير محدد"
|
68 |
+
|
69 |
+
# إذا كان التاريخ سلسلة نصية
|
70 |
+
if isinstance(date_obj, str):
|
71 |
+
try:
|
72 |
+
date_obj = datetime.strptime(date_obj, "%Y-%m-%d")
|
73 |
+
except ValueError:
|
74 |
+
try:
|
75 |
+
date_obj = datetime.strptime(date_obj, "%Y-%m-%d %H:%M:%S")
|
76 |
+
except ValueError:
|
77 |
+
return date_obj
|
78 |
+
|
79 |
+
# تنسيق التاريخ بالطريقة العربية
|
80 |
+
month_names = [
|
81 |
+
"يناير", "فبراير", "مارس", "إبريل", "مايو", "يونيو",
|
82 |
+
"يوليو", "أغسطس", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر"
|
83 |
+
]
|
84 |
+
|
85 |
+
return f"{date_obj.day} {month_names[date_obj.month - 1]} {date_obj.year}"
|
86 |
+
|
87 |
+
def extract_keywords(text: str, keywords: List[str], context_size: int = 50) -> List[Dict[str, str]]:
|
88 |
+
"""
|
89 |
+
استخراج الكلمات المفتاحية من النص مع سياقها
|
90 |
+
|
91 |
+
المعاملات:
|
92 |
+
----------
|
93 |
+
text : str
|
94 |
+
النص المراد البحث فيه
|
95 |
+
keywords : List[str]
|
96 |
+
قائمة الكلمات المفتاحية
|
97 |
+
context_size : int, optional
|
98 |
+
حجم السياق بالأحرف قبل وبعد الكلمة (افتراضي: 50)
|
99 |
+
|
100 |
+
المخرجات:
|
101 |
+
--------
|
102 |
+
List[Dict[str, str]]
|
103 |
+
قائمة بالكلمات المفتاحية وسياقها
|
104 |
+
"""
|
105 |
+
results = []
|
106 |
+
|
107 |
+
for keyword in keywords:
|
108 |
+
# البحث عن الكلمة المفتاحية في النص
|
109 |
+
pattern = re.compile(r'\\b' + re.escape(keyword) + r'\\b', re.IGNORECASE | re.MULTILINE)
|
110 |
+
matches = pattern.finditer(text)
|
111 |
+
|
112 |
+
for match in matches:
|
113 |
+
start_idx = max(0, match.start() - context_size)
|
114 |
+
end_idx = min(len(text), match.end() + context_size)
|
115 |
+
|
116 |
+
# استخراج السياق
|
117 |
+
context = text[start_idx:end_idx]
|
118 |
+
|
119 |
+
# إضافة النتيجة
|
120 |
+
results.append({
|
121 |
+
"keyword": keyword,
|
122 |
+
"context": context,
|
123 |
+
"position": match.start()
|
124 |
+
})
|
125 |
+
|
126 |
+
# ترتيب النتائج حسب الموقع في النص
|
127 |
+
results = sorted(results, key=lambda x: x["position"])
|
128 |
+
|
129 |
+
return results
|
130 |
+
|
131 |
+
def calculate_progress(current: float, total: float) -> Dict[str, Any]:
|
132 |
+
"""
|
133 |
+
حساب نسبة الإنجاز
|
134 |
+
|
135 |
+
المعاملات:
|
136 |
+
----------
|
137 |
+
current : float
|
138 |
+
القيمة الحالية
|
139 |
+
total : float
|
140 |
+
القيمة الإجمالية
|
141 |
+
|
142 |
+
المخرجات:
|
143 |
+
--------
|
144 |
+
Dict[str, Any]
|
145 |
+
معلومات التقدم
|
146 |
+
"""
|
147 |
+
if total == 0:
|
148 |
+
return {
|
149 |
+
"percentage": 0,
|
150 |
+
"status": "لم يبدأ",
|
151 |
+
"color": "gray"
|
152 |
+
}
|
153 |
+
|
154 |
+
percentage = min(100, (current / total) * 100)
|
155 |
+
|
156 |
+
if percentage == 0:
|
157 |
+
status = "لم يبدأ"
|
158 |
+
color = "gray"
|
159 |
+
elif percentage < 25:
|
160 |
+
status = "بداية"
|
161 |
+
color = "red"
|
162 |
+
elif percentage < 50:
|
163 |
+
status = "قيد التنفيذ"
|
164 |
+
color = "orange"
|
165 |
+
elif percentage < 75:
|
166 |
+
status = "متقدم"
|
167 |
+
color = "blue"
|
168 |
+
elif percentage < 100:
|
169 |
+
status = "شبه مكتمل"
|
170 |
+
color = "teal"
|
171 |
+
else:
|
172 |
+
status = "مكتمل"
|
173 |
+
color = "green"
|
174 |
+
|
175 |
+
return {
|
176 |
+
"percentage": round(percentage, 1),
|
177 |
+
"status": status,
|
178 |
+
"color": color
|
179 |
+
}
|
180 |
+
|
181 |
+
def create_directory_structure(base_dir: str, structure: Dict[str, Any]) -> None:
|
182 |
+
"""
|
183 |
+
إنشاء هيكل المجلدات
|
184 |
+
|
185 |
+
المعاملات:
|
186 |
+
----------
|
187 |
+
base_dir : str
|
188 |
+
المجلد الأساسي
|
189 |
+
structure : Dict[str, Any]
|
190 |
+
هيكل المجلدات
|
191 |
+
"""
|
192 |
+
os.makedirs(base_dir, exist_ok=True)
|
193 |
+
|
194 |
+
for key, value in structure.items():
|
195 |
+
path = os.path.join(base_dir, key)
|
196 |
+
|
197 |
+
if isinstance(value, dict):
|
198 |
+
# إذا كانت القيمة قاموساً، استمر في إنشاء الهيكل الفرعي
|
199 |
+
create_directory_structure(path, value)
|
200 |
+
else:
|
201 |
+
# إنشاء المجلد
|
202 |
+
os.makedirs(path, exist_ok=True)
|
203 |
+
|
204 |
+
# إذا كانت القيمة قائمة، إنشاء ملفات فارغة
|
205 |
+
if isinstance(value, list):
|
206 |
+
for file_name in value:
|
207 |
+
file_path = os.path.join(path, file_name)
|
208 |
+
if not os.path.exists(file_path):
|
209 |
+
with open(file_path, 'w', encoding='utf-8') as f:
|
210 |
+
# يمكن كتابة محتوى افتراضي هنا
|
211 |
+
pass
|
212 |
+
|
213 |
+
def export_to_excel(data: List[Dict[str, Any]], file_path: str, sheet_name: str = "Sheet1") -> bool:
|
214 |
+
"""
|
215 |
+
تصدير البيانات إلى ملف Excel
|
216 |
+
|
217 |
+
المعاملات:
|
218 |
+
----------
|
219 |
+
data : List[Dict[str, Any]]
|
220 |
+
البيانات المراد تصديرها
|
221 |
+
file_path : str
|
222 |
+
مسار الملف
|
223 |
+
sheet_name : str, optional
|
224 |
+
اسم ورقة العمل (افتراضي: "Sheet1")
|
225 |
+
|
226 |
+
المخرجات:
|
227 |
+
--------
|
228 |
+
bool
|
229 |
+
نجاح أو فشل العملية
|
230 |
+
"""
|
231 |
+
try:
|
232 |
+
# تحويل البيانات إلى DataFrame
|
233 |
+
df = pd.DataFrame(data)
|
234 |
+
|
235 |
+
# تصدير إلى Excel
|
236 |
+
df.to_excel(file_path, sheet_name=sheet_name, index=False)
|
237 |
+
|
238 |
+
return True
|
239 |
+
except Exception as e:
|
240 |
+
print(f"Error exporting to Excel: {str(e)}")
|
241 |
+
return False
|
242 |
+
|
243 |
+
def export_to_json(data: Any, file_path: str) -> bool:
|
244 |
+
"""
|
245 |
+
تصدير البيانات إلى ملف JSON
|
246 |
+
|
247 |
+
المعاملات:
|
248 |
+
----------
|
249 |
+
data : Any
|
250 |
+
البيانات المراد تصديرها
|
251 |
+
file_path : str
|
252 |
+
مسار الملف
|
253 |
+
|
254 |
+
المخرجات:
|
255 |
+
--------
|
256 |
+
bool
|
257 |
+
نجاح أو فشل العملية
|
258 |
+
"""
|
259 |
+
try:
|
260 |
+
# إنشاء المجلد إذا لم يكن موجوداً
|
261 |
+
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
262 |
+
|
263 |
+
# تصدير إلى JSON
|
264 |
+
with open(file_path, 'w', encoding='utf-8') as f:
|
265 |
+
json.dump(data, f, ensure_ascii=False, indent=4)
|
266 |
+
|
267 |
+
return True
|
268 |
+
except Exception as e:
|
269 |
+
print(f"Error exporting to JSON: {str(e)}")
|
270 |
+
return False
|
271 |
+
|
272 |
+
def validate_input(input_data: Dict[str, Any], validation_rules: Dict[str, Dict[str, Any]]) -> Dict[str, List[str]]:
|
273 |
+
"""
|
274 |
+
التحقق من صحة البيانات المدخلة
|
275 |
+
|
276 |
+
المعاملات:
|
277 |
+
----------
|
278 |
+
input_data : Dict[str, Any]
|
279 |
+
البيانات المدخلة
|
280 |
+
validation_rules : Dict[str, Dict[str, Any]]
|
281 |
+
قواعد التحقق
|
282 |
+
|
283 |
+
المخرجات:
|
284 |
+
--------
|
285 |
+
Dict[str, List[str]]
|
286 |
+
قائمة بالأخطاء لكل حقل
|
287 |
+
"""
|
288 |
+
errors = {}
|
289 |
+
|
290 |
+
for field, rules in validation_rules.items():
|
291 |
+
field_errors = []
|
292 |
+
value = input_data.get(field)
|
293 |
+
|
294 |
+
# التحقق من الحقول المطلوبة
|
295 |
+
if rules.get("required", False) and (value is None or (isinstance(value, str) and value.strip() == "")):
|
296 |
+
field_errors.append("هذا الحقل مطلوب")
|
297 |
+
|
298 |
+
# التحقق من النوع
|
299 |
+
if value is not None and "type" in rules:
|
300 |
+
expected_type = rules["type"]
|
301 |
+
if expected_type == "number" and not (isinstance(value, (int, float)) or (isinstance(value, str) and value.strip().replace(".", "", 1).isdigit())):
|
302 |
+
field_errors.append("يجب أن يكون هذا الحقل رقماً")
|
303 |
+
elif expected_type == "email" and not re.match(r'^[\w\.-]+@[\w\.-]+\.\w+$', str(value)):
|
304 |
+
field_errors.append("يرجى إدخال بريد إلكتروني صحيح")
|
305 |
+
elif expected_type == "date" and not re.match(r'^\d{4}-\d{2}-\d{2}$', str(value)):
|
306 |
+
field_errors.append("يرجى إدخال تاريخ صحيح (YYYY-MM-DD)")
|
307 |
+
|
308 |
+
# التحقق من الحد الأدنى والأقصى
|
309 |
+
if value is not None and isinstance(value, (int, float)):
|
310 |
+
if "min" in rules and value < rules["min"]:
|
311 |
+
field_errors.append(f"يجب أن يكون هذا الحقل أكبر من أو يساوي {rules['min']}")
|
312 |
+
if "max" in rules and value > rules["max"]:
|
313 |
+
field_errors.append(f"يجب أن يكون هذا الحقل أصغر من أو يساوي {rules['max']}")
|
314 |
+
|
315 |
+
# التحقق من طول النص
|
316 |
+
if value is not None and isinstance(value, str):
|
317 |
+
if "min_length" in rules and len(value) < rules["min_length"]:
|
318 |
+
field_errors.append(f"يجب أن يحتوي هذا الحقل على {rules['min_length']} أحرف على الأقل")
|
319 |
+
if "max_length" in rules and len(value) > rules["max_length"]:
|
320 |
+
field_errors.append(f"يجب أن يحتوي هذا الحقل على {rules['max_length']} أحرف كحد أقصى")
|
321 |
+
|
322 |
+
# إضافة الأخطاء إذا وجدت
|
323 |
+
if field_errors:
|
324 |
+
errors[field] = field_errors
|
325 |
+
|
326 |
+
return errors
|