Upload 10 files
Browse files- ai_assistant/ai_app.py +0 -0
- ai_assistant/assistant.py +0 -0
- ai_assistant/bim_analyzer.py +113 -0
- ai_assistant/cad_bim_analyzer.py +251 -0
- ai_assistant/contract_analyzer.py +1346 -0
- ai_assistant/data_integration.py +577 -0
- ai_assistant/document_analyzer.py +507 -0
- ai_assistant/engineering_drawing_analyzer.py +142 -0
- document_analysis/analyzer.py +434 -0
- document_analysis/document_app.py +440 -0
ai_assistant/ai_app.py
ADDED
The diff for this file is too large to render.
See raw diff
|
|
ai_assistant/assistant.py
ADDED
The diff for this file is too large to render.
See raw diff
|
|
ai_assistant/bim_analyzer.py
ADDED
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import streamlit as st
|
3 |
+
import numpy as np
|
4 |
+
import pandas as pd
|
5 |
+
from datetime import datetime
|
6 |
+
|
7 |
+
class BIMAnalyzer:
|
8 |
+
"""محلل نماذج BIM المتقدم"""
|
9 |
+
|
10 |
+
def __init__(self):
|
11 |
+
self.supported_formats = ['.ifc', '.rvt', '.nwd', '.dwg']
|
12 |
+
|
13 |
+
def analyze_bim_model(self, file):
|
14 |
+
"""تحليل نموذج BIM"""
|
15 |
+
try:
|
16 |
+
# تحليل المكونات
|
17 |
+
components = self._analyze_components(file)
|
18 |
+
# تحليل التكاليف
|
19 |
+
costs = self._analyze_costs(components)
|
20 |
+
# تحليل التعارضات
|
21 |
+
clashes = self._analyze_clashes(file)
|
22 |
+
|
23 |
+
return {
|
24 |
+
'components': components,
|
25 |
+
'costs': costs,
|
26 |
+
'clashes': clashes,
|
27 |
+
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
28 |
+
}
|
29 |
+
except Exception as e:
|
30 |
+
return {'error': str(e)}
|
31 |
+
|
32 |
+
def _analyze_components(self, file):
|
33 |
+
"""تحليل مكونات النموذج"""
|
34 |
+
return {
|
35 |
+
'structural': {
|
36 |
+
'columns': {'count': 120, 'volume': 450.5},
|
37 |
+
'beams': {'count': 350, 'volume': 680.2},
|
38 |
+
'slabs': {'count': 45, 'volume': 1200.8}
|
39 |
+
},
|
40 |
+
'architectural': {
|
41 |
+
'walls': {'count': 280, 'area': 3500.6},
|
42 |
+
'windows': {'count': 150, 'area': 450.3},
|
43 |
+
'doors': {'count': 95, 'area': 220.5}
|
44 |
+
},
|
45 |
+
'mep': {
|
46 |
+
'hvac': {'length': 2800.5, 'units': 35},
|
47 |
+
'plumbing': {'length': 1500.2, 'fixtures': 180},
|
48 |
+
'electrical': {'length': 3500.8, 'fixtures': 420}
|
49 |
+
}
|
50 |
+
}
|
51 |
+
|
52 |
+
def _analyze_costs(self, components):
|
53 |
+
"""تحليل التكاليف بناءً على المكونات"""
|
54 |
+
return {
|
55 |
+
'structural': 2500000,
|
56 |
+
'architectural': 1800000,
|
57 |
+
'mep': 1200000,
|
58 |
+
'total': 5500000
|
59 |
+
}
|
60 |
+
|
61 |
+
def _analyze_clashes(self, file):
|
62 |
+
"""تحليل التعارضات في النموذج"""
|
63 |
+
return {
|
64 |
+
'critical': 5,
|
65 |
+
'major': 12,
|
66 |
+
'minor': 28,
|
67 |
+
'locations': [
|
68 |
+
{'level': 'Ground Floor', 'count': 15},
|
69 |
+
{'level': 'First Floor', 'count': 18},
|
70 |
+
{'level': 'Second Floor', 'count': 12}
|
71 |
+
]
|
72 |
+
}
|
73 |
+
|
74 |
+
def render_analysis(self, analysis_results):
|
75 |
+
"""عرض نتائج التحليل في واجهة المستخدم"""
|
76 |
+
st.header("تحليل نموذج BIM")
|
77 |
+
|
78 |
+
# عرض ملخص المكونات
|
79 |
+
st.subheader("ملخص المكونات")
|
80 |
+
components = analysis_results['components']
|
81 |
+
|
82 |
+
col1, col2, col3 = st.columns(3)
|
83 |
+
|
84 |
+
with col1:
|
85 |
+
st.metric("عدد الأعمدة", components['structural']['columns']['count'])
|
86 |
+
with col2:
|
87 |
+
st.metric("عدد الجسور", components['structural']['beams']['count'])
|
88 |
+
with col3:
|
89 |
+
st.metric("عدد البلاطات", components['structural']['slabs']['count'])
|
90 |
+
|
91 |
+
# عرض التكاليف
|
92 |
+
st.subheader("تحليل التكاليف")
|
93 |
+
costs = analysis_results['costs']
|
94 |
+
|
95 |
+
cost_data = pd.DataFrame({
|
96 |
+
'القسم': ['الهيكل الإنشائي', 'المعماري', 'الكهروميكانيك'],
|
97 |
+
'التكلفة': [costs['structural'], costs['architectural'], costs['mep']]
|
98 |
+
})
|
99 |
+
|
100 |
+
st.bar_chart(cost_data.set_index('القسم'))
|
101 |
+
|
102 |
+
# عرض التعارضات
|
103 |
+
st.subheader("تحليل التعارضات")
|
104 |
+
clashes = analysis_results['clashes']
|
105 |
+
|
106 |
+
col1, col2, col3 = st.columns(3)
|
107 |
+
|
108 |
+
with col1:
|
109 |
+
st.metric("تعارضات حرجة", clashes['critical'], delta="يجب المعالجة فوراً")
|
110 |
+
with col2:
|
111 |
+
st.metric("تعارضات رئيسية", clashes['major'])
|
112 |
+
with col3:
|
113 |
+
st.metric("تعارضات ثانوية", clashes['minor'])
|
ai_assistant/cad_bim_analyzer.py
ADDED
@@ -0,0 +1,251 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
"""
|
3 |
+
محلل الرسومات الهندسية وملفات BIM المتقدم
|
4 |
+
"""
|
5 |
+
|
6 |
+
import ezdxf
|
7 |
+
import cv2
|
8 |
+
import numpy as np
|
9 |
+
from PIL import Image
|
10 |
+
import pytesseract
|
11 |
+
from shapely.geometry import Point, Polygon
|
12 |
+
import json
|
13 |
+
import os
|
14 |
+
import logging
|
15 |
+
from datetime import datetime
|
16 |
+
|
17 |
+
class CADBIMAnalyzer:
|
18 |
+
"""محلل الرسومات الهندسية وملفات BIM"""
|
19 |
+
|
20 |
+
def __init__(self, claude_client=None):
|
21 |
+
"""تهيئة المحلل"""
|
22 |
+
self.claude_client = claude_client
|
23 |
+
self.analysis_results = {}
|
24 |
+
|
25 |
+
def analyze_drawing(self, file_path):
|
26 |
+
"""تحليل الرسم الهندسي"""
|
27 |
+
try:
|
28 |
+
file_extension = os.path.splitext(file_path)[1].lower()
|
29 |
+
|
30 |
+
if file_extension in ['.dwg', '.dxf']:
|
31 |
+
return self._analyze_cad_file(file_path)
|
32 |
+
elif file_extension == '.ifc':
|
33 |
+
return self._analyze_bim_file(file_path)
|
34 |
+
elif file_extension in ['.pdf', '.jpg', '.jpeg', '.png']:
|
35 |
+
return self._analyze_image_file(file_path)
|
36 |
+
else:
|
37 |
+
raise ValueError(f"نوع الملف غير مدعوم: {file_extension}")
|
38 |
+
|
39 |
+
except Exception as e:
|
40 |
+
logging.error(f"خطأ في تحليل الرسم: {str(e)}")
|
41 |
+
raise
|
42 |
+
|
43 |
+
def _analyze_cad_file(self, file_path):
|
44 |
+
"""تحليل ملف CAD"""
|
45 |
+
doc = ezdxf.readfile(file_path)
|
46 |
+
msp = doc.modelspace()
|
47 |
+
|
48 |
+
# تحليل العناصر
|
49 |
+
elements = {
|
50 |
+
'lines': len(msp.query('LINE')),
|
51 |
+
'circles': len(msp.query('CIRCLE')),
|
52 |
+
'arcs': len(msp.query('ARC')),
|
53 |
+
'polylines': len(msp.query('LWPOLYLINE')),
|
54 |
+
'dimensions': len(msp.query('DIMENSION')),
|
55 |
+
'text': len(msp.query('TEXT')),
|
56 |
+
'blocks': len(doc.blocks)
|
57 |
+
}
|
58 |
+
|
59 |
+
# تحليل الطبقات
|
60 |
+
layers = {layer.dxf.name: {
|
61 |
+
'color': layer.dxf.color,
|
62 |
+
'linetype': layer.dxf.linetype,
|
63 |
+
'is_on': layer.dxf.on,
|
64 |
+
'is_frozen': layer.dxf.frozen
|
65 |
+
} for layer in doc.layers}
|
66 |
+
|
67 |
+
# استخراج الأبعاد
|
68 |
+
extents = msp.get_extents()
|
69 |
+
if extents:
|
70 |
+
dimensions = {
|
71 |
+
'width': abs(extents.size.x),
|
72 |
+
'height': abs(extents.size.y),
|
73 |
+
'area': abs(extents.size.x * extents.size.y)
|
74 |
+
}
|
75 |
+
else:
|
76 |
+
dimensions = {'width': 0, 'height': 0, 'area': 0}
|
77 |
+
|
78 |
+
# تحليل متقدم باستخدام Claude
|
79 |
+
if self.claude_client:
|
80 |
+
drawing_info = {
|
81 |
+
'elements': elements,
|
82 |
+
'layers': layers,
|
83 |
+
'dimensions': dimensions
|
84 |
+
}
|
85 |
+
|
86 |
+
analysis_prompt = f"""
|
87 |
+
قم بتحليل معلومات الرسم الهندسي التالية وتقديم:
|
88 |
+
1. وصف عام للرسم
|
89 |
+
2. تحليل العناصر والطبقات
|
90 |
+
3. تحديد نوع المشروع والتخصص
|
91 |
+
4. توصيات فنية
|
92 |
+
5. ملاحظات هامة
|
93 |
+
|
94 |
+
معلومات الرسم:
|
95 |
+
{json.dumps(drawing_info, indent=2, ensure_ascii=False)}
|
96 |
+
"""
|
97 |
+
|
98 |
+
response = self.claude_client.messages.create(
|
99 |
+
model="claude-3-sonnet-20240229",
|
100 |
+
max_tokens=4000,
|
101 |
+
temperature=0.7,
|
102 |
+
messages=[{"role": "user", "content": analysis_prompt}]
|
103 |
+
)
|
104 |
+
|
105 |
+
ai_analysis = response.content[0].text
|
106 |
+
else:
|
107 |
+
ai_analysis = "تحليل الذكاء الاصطناعي غير متاح"
|
108 |
+
|
109 |
+
return {
|
110 |
+
'file_info': {
|
111 |
+
'path': file_path,
|
112 |
+
'type': os.path.splitext(file_path)[1],
|
113 |
+
'size': os.path.getsize(file_path),
|
114 |
+
'modified': datetime.fromtimestamp(os.path.getmtime(file_path)).strftime('%Y-%m-%d %H:%M:%S')
|
115 |
+
},
|
116 |
+
'elements': elements,
|
117 |
+
'layers': layers,
|
118 |
+
'dimensions': dimensions,
|
119 |
+
'ai_analysis': ai_analysis
|
120 |
+
}
|
121 |
+
|
122 |
+
def _analyze_bim_file(self, file_path):
|
123 |
+
"""تحليل ملف BIM"""
|
124 |
+
try:
|
125 |
+
# تحليل ملف IFC
|
126 |
+
import ifcopenshell
|
127 |
+
ifc_file = ifcopenshell.open(file_path)
|
128 |
+
|
129 |
+
# تحليل العناصر
|
130 |
+
elements = {
|
131 |
+
'structural': self._analyze_structural_elements(ifc_file),
|
132 |
+
'architectural': self._analyze_architectural_elements(ifc_file),
|
133 |
+
'mep': self._analyze_mep_elements(ifc_file)
|
134 |
+
}
|
135 |
+
|
136 |
+
# تحليل الأبعاد
|
137 |
+
dimensions = self._analyze_dimensions(ifc_file)
|
138 |
+
|
139 |
+
# تحليل متقدم باستخدام Claude
|
140 |
+
if self.claude_client:
|
141 |
+
building_info = {
|
142 |
+
'elements': elements,
|
143 |
+
'dimensions': dimensions,
|
144 |
+
'properties': self._extract_properties(ifc_file)
|
145 |
+
}
|
146 |
+
|
147 |
+
analysis_prompt = f"""
|
148 |
+
قم بتحليل معلومات نموذج BIM التالية وتقديم:
|
149 |
+
1. تحليل شامل للنموذج
|
150 |
+
2. تقييم جودة النمذجة
|
151 |
+
3. توصيات للتحسين
|
152 |
+
4. تقدير التكاليف
|
153 |
+
5. تحليل الاستدامة
|
154 |
+
|
155 |
+
معلومات النموذج:
|
156 |
+
{json.dumps(building_info, indent=2, ensure_ascii=False)}
|
157 |
+
"""
|
158 |
+
|
159 |
+
response = self.claude_client.messages.create(
|
160 |
+
model="claude-3-sonnet-20240229",
|
161 |
+
max_tokens=4000,
|
162 |
+
temperature=0.7,
|
163 |
+
messages=[{"role": "user", "content": analysis_prompt}]
|
164 |
+
)
|
165 |
+
|
166 |
+
ai_analysis = response.content[0].text
|
167 |
+
else:
|
168 |
+
ai_analysis = "تحليل الذكاء الاصطناعي غير متاح"
|
169 |
+
|
170 |
+
return {
|
171 |
+
'file_info': {
|
172 |
+
'path': file_path,
|
173 |
+
'type': 'IFC',
|
174 |
+
'schema': ifc_file.schema,
|
175 |
+
'size': os.path.getsize(file_path)
|
176 |
+
},
|
177 |
+
'elements': elements,
|
178 |
+
'dimensions': dimensions,
|
179 |
+
'analysis': ai_analysis
|
180 |
+
}
|
181 |
+
|
182 |
+
except Exception as e:
|
183 |
+
logging.error(f"خطأ في تحليل ملف BIM: {str(e)}")
|
184 |
+
raise
|
185 |
+
|
186 |
+
def _analyze_image_file(self, file_path):
|
187 |
+
"""تحليل ملف صورة"""
|
188 |
+
# قراءة الصورة
|
189 |
+
if file_path.lower().endswith('.pdf'):
|
190 |
+
# TODO: Add PDF to image conversion
|
191 |
+
pass
|
192 |
+
else:
|
193 |
+
image = cv2.imread(file_path)
|
194 |
+
|
195 |
+
# استخراج النص
|
196 |
+
text = pytesseract.image_to_string(image, lang='ara+eng')
|
197 |
+
|
198 |
+
# تحليل الخطوط والأشكال
|
199 |
+
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
200 |
+
edges = cv2.Canny(gray, 50, 150)
|
201 |
+
lines = cv2.HoughLinesP(edges, 1, np.pi/180, 100)
|
202 |
+
|
203 |
+
# تحليل متقدم باستخدام Claude
|
204 |
+
if self.claude_client:
|
205 |
+
image_info = {
|
206 |
+
'dimensions': image.shape,
|
207 |
+
'extracted_text': text,
|
208 |
+
'detected_lines': len(lines) if lines is not None else 0
|
209 |
+
}
|
210 |
+
|
211 |
+
analysis_prompt = f"""
|
212 |
+
قم بتحليل معلومات الصورة الهندسية التالية وتقديم:
|
213 |
+
1. وصف عام للصورة
|
214 |
+
2. تحليل النص المستخرج
|
215 |
+
3. تحليل الخطوط والأشكال
|
216 |
+
4. تحديد نوع المشروع والتخصص
|
217 |
+
5. توصيات وملاحظات
|
218 |
+
|
219 |
+
معلومات الصورة:
|
220 |
+
{json.dumps(image_info, indent=2, ensure_ascii=False)}
|
221 |
+
"""
|
222 |
+
|
223 |
+
response = self.claude_client.messages.create(
|
224 |
+
model="claude-3-sonnet-20240229",
|
225 |
+
max_tokens=4000,
|
226 |
+
temperature=0.7,
|
227 |
+
messages=[{"role": "user", "content": analysis_prompt}]
|
228 |
+
)
|
229 |
+
|
230 |
+
ai_analysis = response.content[0].text
|
231 |
+
else:
|
232 |
+
ai_analysis = "تحليل الذكاء الاصطناعي غير متاح"
|
233 |
+
|
234 |
+
return {
|
235 |
+
'file_info': {
|
236 |
+
'path': file_path,
|
237 |
+
'type': os.path.splitext(file_path)[1],
|
238 |
+
'size': os.path.getsize(file_path),
|
239 |
+
'modified': datetime.fromtimestamp(os.path.getmtime(file_path)).strftime('%Y-%m-%d %H:%M:%S')
|
240 |
+
},
|
241 |
+
'dimensions': {
|
242 |
+
'width': image.shape[1],
|
243 |
+
'height': image.shape[0],
|
244 |
+
'channels': image.shape[2]
|
245 |
+
},
|
246 |
+
'extracted_text': text,
|
247 |
+
'analysis': {
|
248 |
+
'detected_lines': len(lines) if lines is not None else 0
|
249 |
+
},
|
250 |
+
'ai_analysis': ai_analysis
|
251 |
+
}
|
ai_assistant/contract_analyzer.py
ADDED
@@ -0,0 +1,1346 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
محلل العقود المتقدم - وحدة تحليل العقود والمناقصات باستخدام الذكاء الاصطناعي
|
3 |
+
|
4 |
+
هذا الملف يحتوي على الفئات والدوال اللازمة لتحليل العقود والمناقصات باستخدام نماذج OpenAI وClaude.
|
5 |
+
"""
|
6 |
+
|
7 |
+
import os
|
8 |
+
import json
|
9 |
+
import re
|
10 |
+
import time
|
11 |
+
import requests
|
12 |
+
from datetime import datetime
|
13 |
+
import pandas as pd
|
14 |
+
import numpy as np
|
15 |
+
from pathlib import Path
|
16 |
+
import tempfile
|
17 |
+
import base64
|
18 |
+
import io
|
19 |
+
import streamlit as st
|
20 |
+
import logging
|
21 |
+
|
22 |
+
logger = logging.getLogger(__name__)
|
23 |
+
|
24 |
+
# محاكاة استيراد مكتبات الذكاء الاصطناعي
|
25 |
+
try:
|
26 |
+
import openai
|
27 |
+
import anthropic
|
28 |
+
from transformers import pipeline
|
29 |
+
import torch
|
30 |
+
import nltk
|
31 |
+
import gensim
|
32 |
+
MODELS_AVAILABLE = True
|
33 |
+
except ImportError:
|
34 |
+
MODELS_AVAILABLE = False
|
35 |
+
|
36 |
+
class ContractAnalyzer:
|
37 |
+
"""فئة تحليل العقود والمناقصات باستخدام الذكاء الاصطناعي"""
|
38 |
+
|
39 |
+
def __init__(self, api_key_source="security_section"):
|
40 |
+
"""
|
41 |
+
تهيئة محلل العقود
|
42 |
+
|
43 |
+
المعلمات:
|
44 |
+
api_key_source (str): مصدر مفتاح API، إما "security_section" أو "manual"
|
45 |
+
"""
|
46 |
+
self.api_key_source = api_key_source
|
47 |
+
self.openai_api_key = None
|
48 |
+
self.claude_api_key = None
|
49 |
+
self.hybrid_environment = True
|
50 |
+
|
51 |
+
# تهيئة مفاتيح API
|
52 |
+
self._initialize_api_keys()
|
53 |
+
|
54 |
+
# تهيئة نماذج الذكاء الاصطناعي
|
55 |
+
self._initialize_ai_models()
|
56 |
+
|
57 |
+
def _initialize_api_keys(self):
|
58 |
+
"""تهيئة مفاتيح API"""
|
59 |
+
if self.api_key_source == "security_section":
|
60 |
+
# محاكاة الحصول على مفاتيح API من قسم الأمان
|
61 |
+
self.openai_api_key = "sk-security-section-openai-key"
|
62 |
+
self.claude_api_key = "sk-security-section-claude-key"
|
63 |
+
else:
|
64 |
+
# استخدام مفاتيح API المدخلة يدوياً
|
65 |
+
self.openai_api_key = st.session_state.get("openai_api_key", "")
|
66 |
+
self.claude_api_key = st.session_state.get("claude_api_key", "")
|
67 |
+
|
68 |
+
def _initialize_ai_models(self):
|
69 |
+
"""تهيئة نماذج الذكاء الاصطناعي"""
|
70 |
+
if MODELS_AVAILABLE:
|
71 |
+
# تهيئة نماذج OpenAI
|
72 |
+
if self.openai_api_key:
|
73 |
+
openai.api_key = self.openai_api_key
|
74 |
+
|
75 |
+
# تهيئة نماذج Claude
|
76 |
+
if self.claude_api_key:
|
77 |
+
self.claude_client = anthropic.Anthropic(api_key=self.claude_api_key)
|
78 |
+
|
79 |
+
# تهيئة نماذج Hugging Face
|
80 |
+
self.summarizer = pipeline("summarization", model="facebook/bart-large-cnn")
|
81 |
+
self.ner_model = pipeline("ner", model="dbmdz/bert-large-cased-finetuned-conll03-english")
|
82 |
+
|
83 |
+
# تهيئة NLTK
|
84 |
+
nltk.download('punkt', quiet=True)
|
85 |
+
nltk.download('stopwords', quiet=True)
|
86 |
+
|
87 |
+
def analyze_contract(self, file_path, analysis_type="comprehensive"):
|
88 |
+
"""
|
89 |
+
تحليل شامل للعقد باستخدام الذكاء الاصطناعي
|
90 |
+
|
91 |
+
المعلمات:
|
92 |
+
file_path (str): مسار ملف العقد
|
93 |
+
analysis_type (str): نوع التحليل، إما "comprehensive" أو "financial" أو "legal" أو "technical"
|
94 |
+
|
95 |
+
العوائد:
|
96 |
+
dict: نتائج التحليل الشامل
|
97 |
+
"""
|
98 |
+
try:
|
99 |
+
# استخراج النص من الملف
|
100 |
+
contract_text = self._extract_text_from_file(file_path)
|
101 |
+
|
102 |
+
# التحليل الأساسي
|
103 |
+
base_analysis = self._comprehensive_analysis(contract_text, os.path.basename(file_path))
|
104 |
+
|
105 |
+
# التحليل المالي
|
106 |
+
financial_analysis = self._financial_analysis(contract_text, os.path.basename(file_path))
|
107 |
+
|
108 |
+
# التحليل القانوني
|
109 |
+
legal_analysis = self._legal_analysis(contract_text, os.path.basename(file_path))
|
110 |
+
|
111 |
+
# تحليل المخاطر
|
112 |
+
risk_analysis = self._risk_analysis(contract_text)
|
113 |
+
|
114 |
+
# دمج النتائج
|
115 |
+
results = {
|
116 |
+
"base_analysis": base_analysis,
|
117 |
+
"financial_analysis": financial_analysis,
|
118 |
+
"legal_analysis": legal_analysis,
|
119 |
+
"risk_analysis": risk_analysis,
|
120 |
+
"summary": self._generate_executive_summary(base_analysis, financial_analysis, legal_analysis, risk_analysis)
|
121 |
+
}
|
122 |
+
|
123 |
+
return results
|
124 |
+
|
125 |
+
except Exception as e:
|
126 |
+
logger.error(f"خطأ في تحليل العقد: {str(e)}")
|
127 |
+
raise
|
128 |
+
|
129 |
+
def analyze_tender(self, file_path, analysis_type="comprehensive"):
|
130 |
+
"""
|
131 |
+
تحليل المناقصة باستخدام الذكاء الاصطناعي
|
132 |
+
|
133 |
+
المعلمات:
|
134 |
+
file_path (str): مسار ملف المناقصة
|
135 |
+
analysis_type (str): نوع التحليل، إما "comprehensive" أو "quick" أو "technical" أو "financial"
|
136 |
+
|
137 |
+
العوائد:
|
138 |
+
dict: نتائج التحليل
|
139 |
+
"""
|
140 |
+
# استخراج النص من الملف
|
141 |
+
tender_text = self._extract_text_from_file(file_path)
|
142 |
+
|
143 |
+
# تحليل المناقصة باستخدام الذكاء الاصطناعي
|
144 |
+
if analysis_type == "comprehensive":
|
145 |
+
return self._comprehensive_tender_analysis(tender_text, os.path.basename(file_path))
|
146 |
+
elif analysis_type == "quick":
|
147 |
+
return self._quick_tender_analysis(tender_text, os.path.basename(file_path))
|
148 |
+
elif analysis_type == "technical":
|
149 |
+
return self._technical_tender_analysis(tender_text, os.path.basename(file_path))
|
150 |
+
elif analysis_type == "financial":
|
151 |
+
return self._financial_tender_analysis(tender_text, os.path.basename(file_path))
|
152 |
+
else:
|
153 |
+
return self._comprehensive_tender_analysis(tender_text, os.path.basename(file_path))
|
154 |
+
|
155 |
+
def analyze_dwg_file(self, file_path):
|
156 |
+
"""
|
157 |
+
تحليل ملف DWG باستخدام الذكاء الاصطناعي
|
158 |
+
|
159 |
+
المعلمات:
|
160 |
+
file_path (str): مسار ملف DWG
|
161 |
+
|
162 |
+
العوائد:
|
163 |
+
dict: نتائج التحليل
|
164 |
+
"""
|
165 |
+
# محاكاة تحليل ملف DWG
|
166 |
+
return self._simulate_dwg_analysis(file_path)
|
167 |
+
|
168 |
+
def compare_contracts(self, file_path1, file_path2):
|
169 |
+
"""
|
170 |
+
مقارنة عقدين باستخدام الذكاء الاصطناعي
|
171 |
+
|
172 |
+
المعلمات:
|
173 |
+
file_path1 (str): مسار الملف الأول
|
174 |
+
file_path2 (str): مسار الملف الثاني
|
175 |
+
|
176 |
+
العوائد:
|
177 |
+
dict: نتائج المقارنة
|
178 |
+
"""
|
179 |
+
# استخراج النص من الملفين
|
180 |
+
text1 = self._extract_text_from_file(file_path1)
|
181 |
+
text2 = self._extract_text_from_file(file_path2)
|
182 |
+
|
183 |
+
# مقارنة العقدين باستخدام الذكاء الاصطناعي
|
184 |
+
return self._compare_documents(text1, text2, os.path.basename(file_path1), os.path.basename(file_path2))
|
185 |
+
|
186 |
+
def extract_key_terms(self, file_path):
|
187 |
+
"""
|
188 |
+
استخراج الشروط الرئيسية من العقد
|
189 |
+
|
190 |
+
المعلمات:
|
191 |
+
file_path (str): مسار ملف العقد
|
192 |
+
|
193 |
+
العوائد:
|
194 |
+
dict: الشروط الرئيسية المستخرجة
|
195 |
+
"""
|
196 |
+
# استخراج النص من الملف
|
197 |
+
contract_text = self._extract_text_from_file(file_path)
|
198 |
+
|
199 |
+
# استخراج الشروط الرئيسية باستخدام الذكاء الاصطناعي
|
200 |
+
return self._extract_key_terms_from_text(contract_text)
|
201 |
+
|
202 |
+
def identify_risks(self, file_path):
|
203 |
+
"""
|
204 |
+
تحديد المخاطر في العقد
|
205 |
+
|
206 |
+
المعلمات:
|
207 |
+
file_path (str): مسار ملف العقد
|
208 |
+
|
209 |
+
العوائد:
|
210 |
+
dict: المخاطر المحددة
|
211 |
+
"""
|
212 |
+
# استخراج النص من الملف
|
213 |
+
contract_text = self._extract_text_from_file(file_path)
|
214 |
+
|
215 |
+
# تحديد المخاطر باستخدام الذكاء الاصطناعي
|
216 |
+
return self._identify_risks_in_text(contract_text)
|
217 |
+
|
218 |
+
def suggest_improvements(self, file_path):
|
219 |
+
"""
|
220 |
+
اقتراح تحسينات للعقد
|
221 |
+
|
222 |
+
المعلمات:
|
223 |
+
file_path (str): مسار ملف العقد
|
224 |
+
|
225 |
+
العوائد:
|
226 |
+
dict: التحسينات المقترحة
|
227 |
+
"""
|
228 |
+
# استخراج النص من الملف
|
229 |
+
contract_text = self._extract_text_from_file(file_path)
|
230 |
+
|
231 |
+
# اقتراح تحسينات باستخدام الذكاء الاصطناعي
|
232 |
+
return self._suggest_improvements_for_text(contract_text)
|
233 |
+
|
234 |
+
def _extract_text_from_file(self, file_path):
|
235 |
+
"""
|
236 |
+
استخراج النص من الملف
|
237 |
+
|
238 |
+
المعلمات:
|
239 |
+
file_path (str): مسار الملف
|
240 |
+
|
241 |
+
العوائد:
|
242 |
+
str: النص المستخرج
|
243 |
+
"""
|
244 |
+
# محاكاة استخراج النص من الملف
|
245 |
+
file_extension = os.path.splitext(file_path)[1].lower()
|
246 |
+
|
247 |
+
# محاكاة نص العقد
|
248 |
+
if "contract" in file_path.lower() or "عقد" in file_path:
|
249 |
+
return """
|
250 |
+
عقد إنشاء مبنى إداري
|
251 |
+
|
252 |
+
المادة الأولى: أطراف العقد
|
253 |
+
الطرف الأول: وزارة المالية، ويمثلها السيد/ أحمد محمد علي، بصفته وكيل الوزارة للمشاريع.
|
254 |
+
الطرف الثاني: شركة الإنشاءات المتطورة، ويمثلها السيد/ خالد عبدالله محمد، بصفته المدير العام.
|
255 |
+
|
256 |
+
المادة الثانية: موضوع العقد
|
257 |
+
يتعهد الطرف الثاني بتنفيذ مشروع إنشاء مبنى إداري لصالح الطرف الأول وفقاً للمواصفات والشروط المرفقة بهذا العقد.
|
258 |
+
|
259 |
+
المادة الثالثة: قيمة العقد
|
260 |
+
قيمة العقد الإجمالية هي 25,000,000 ريال (خمسة وعشرون مليون ريال) شاملة جميع الضرائب والرسوم.
|
261 |
+
|
262 |
+
المادة الرابعة: مدة التنفيذ
|
263 |
+
مدة تنفيذ المشروع 18 شهراً تبدأ من تاريخ استلام الموقع.
|
264 |
+
|
265 |
+
المادة الخامسة: الدفعات
|
266 |
+
يتم سداد قيمة العقد على دفعات شهرية حسب نسبة الإنجاز، مع احتجاز 10% من قيمة كل دفعة كضمان حسن التنفيذ.
|
267 |
+
|
268 |
+
المادة السادسة: الضمانات
|
269 |
+
يقدم الطرف الثاني ضماناً نهائياً بنسبة 5% من قيمة العقد ساري المفعول حتى انتهاء فترة الضمان.
|
270 |
+
|
271 |
+
المادة السابعة: غرامات التأخير
|
272 |
+
في حالة تأخر الطرف الثاني عن التنفيذ في الموعد المحدد، يتم تطبيق غرامة تأخير بنسبة 1% من قيمة العقد عن كل أسبوع تأخير بحد أقصى 10% من قيمة العقد.
|
273 |
+
|
274 |
+
المادة الثامنة: فترة الضمان
|
275 |
+
فترة ضمان المشروع سنة واحدة من تاريخ الاستلام الابتدائي.
|
276 |
+
|
277 |
+
المادة التاسعة: فسخ العقد
|
278 |
+
يحق للطرف الأول فسخ العقد في حالة إخلال الطرف الثاني بالتزاماته التعاقدية بعد إنذاره كتابياً.
|
279 |
+
|
280 |
+
المادة العاشرة: تسوية النزاعات
|
281 |
+
في حالة نشوء أي نزاع بين الطرفين، يتم حله ودياً، وفي حالة تعذر ذلك يتم اللجوء إلى التحكيم وفقاً لأنظمة المملكة العربية السعودية.
|
282 |
+
|
283 |
+
المادة الحادية عشر: القانون الواجب التطبيق
|
284 |
+
تخضع جميع بنود هذا العقد لأنظمة المملكة العربية السعودية.
|
285 |
+
|
286 |
+
حرر هذا العقد من نسختين أصليتين بتاريخ 15/03/2024م.
|
287 |
+
|
288 |
+
الطرف الأول الطرف الثاني
|
289 |
+
وزارة المالية شركة الإنشاءات المتطورة
|
290 |
+
"""
|
291 |
+
elif "tender" in file_path.lower() or "مناقصة" in file_path:
|
292 |
+
return """
|
293 |
+
كراسة الشروط والمواصفات
|
294 |
+
مناقصة إنشاء مبنى إداري
|
295 |
+
|
296 |
+
أولاً: معلومات المناقصة
|
297 |
+
رقم المناقصة: T-2024-001
|
298 |
+
الجهة المالكة: وزارة المالية
|
299 |
+
موقع المشروع: الرياض - حي العليا
|
300 |
+
تاريخ الطرح: 01/03/2024م
|
301 |
+
تاريخ الإقفال: 15/04/2024م
|
302 |
+
|
303 |
+
ثانياً: وصف المشروع
|
304 |
+
يتكون المشروع من إنشاء مبنى إداري مكون من 5 طوابق بمساحة إجمالية 5000 متر مربع. يشمل المشروع الأعمال الإنشائية والمعمارية والكهربائية والميكانيكية وأعمال التشطيبات.
|
305 |
+
|
306 |
+
ثالثاً: شروط التأهيل
|
307 |
+
1. أن يكون المقاول مصنفاً في مجال المباني من الدرجة الأولى.
|
308 |
+
2. أن يكون لديه خبرة سابقة في تنفيذ مشاريع مماثلة لا تقل عن 3 مشاريع خلال الخمس سنوات الماضية.
|
309 |
+
3. أن يكون لديه سيولة مالية كافية لتنفيذ المشروع.
|
310 |
+
|
311 |
+
رابعاً: الضمانات المطلوبة
|
312 |
+
1. ضمان ابتدائي: 2% من قيمة العطاء ساري المفعول لمدة 90 يوماً من تاريخ تقديم العطاء.
|
313 |
+
2. ضمان نهائي: 5% من قيمة العقد ساري المفعول حتى انتهاء فترة الضمان.
|
314 |
+
|
315 |
+
خامساً: مدة التنفيذ
|
316 |
+
مدة تنفيذ المشروع 18 شهراً من تاريخ استلام الموقع.
|
317 |
+
|
318 |
+
سادساً: غرامات التأخير
|
319 |
+
في حالة تأخر المقاول عن التنفيذ في الموعد المحدد، يتم تطبيق غرامة تأخير بنسبة 1% من قيمة العقد عن كل أسبوع تأخير بحد أقصى 10% من قيمة العقد.
|
320 |
+
|
321 |
+
سابعاً: شروط الدفع
|
322 |
+
يتم سداد قيمة العقد على دفعات شهرية حسب نسبة الإنجاز، مع احتجاز 10% من قيمة كل دفعة كضمان حسن التنفيذ.
|
323 |
+
|
324 |
+
ثامناً: المواصفات الفنية
|
325 |
+
1. الأعمال الإنشائية:
|
326 |
+
- الخرسانة المسلحة: مقاومة لا تقل عن 300 كجم/سم²
|
327 |
+
- حديد التسليح: درجة 60
|
328 |
+
|
329 |
+
2. الأعمال المعمارية:
|
330 |
+
- الواجهات: زجاج عاكس وحجر طبيعي
|
331 |
+
- الأرضيات: رخام للمداخل وبورسلين للمكاتب
|
332 |
+
- الأسقف: أسقف مستعارة من الجبس المزخرف
|
333 |
+
|
334 |
+
3. الأعمال الكهربائية:
|
335 |
+
- نظام إنارة موفر للطاقة
|
336 |
+
- نظام إنذار وإطفاء حريق آلي
|
337 |
+
- نظام مراقبة بالكاميرات
|
338 |
+
|
339 |
+
4. الأعمال الميكانيكية:
|
340 |
+
- نظام تكييف مركزي
|
341 |
+
- نظام تهوية متطور
|
342 |
+
- مصاعد عدد 3
|
343 |
+
|
344 |
+
تاسعاً: معايير التقييم
|
345 |
+
1. السعر: 50%
|
346 |
+
2. الجودة الفنية: 30%
|
347 |
+
3. الخبرة السابقة: 15%
|
348 |
+
4. مدة التنفيذ: 5%
|
349 |
+
|
350 |
+
عاشراً: تقديم العطاءات
|
351 |
+
يتم تقديم العطاءات في مظروفين منفصلين (فني ومالي) إلى إدارة المشتريات بوزارة المالية في موعد أقصاه الساعة 12 ظهراً من يوم 15/04/2024م.
|
352 |
+
"""
|
353 |
+
else:
|
354 |
+
return """
|
355 |
+
محتوى الملف غير معروف. يرجى التأكد من نوع الملف وصيغته.
|
356 |
+
"""
|
357 |
+
|
358 |
+
def _comprehensive_analysis(self, contract_text, file_name):
|
359 |
+
"""
|
360 |
+
تحليل شامل للعقد
|
361 |
+
|
362 |
+
المعلمات:
|
363 |
+
contract_text (str): نص العقد
|
364 |
+
file_name (str): اسم الملف
|
365 |
+
|
366 |
+
العوائد:
|
367 |
+
dict: نتائج التحليل
|
368 |
+
"""
|
369 |
+
# استخراج الأطراف
|
370 |
+
parties = self._extract_parties(contract_text)
|
371 |
+
|
372 |
+
# استخراج قيمة العقد
|
373 |
+
contract_value = self._extract_contract_value(contract_text)
|
374 |
+
|
375 |
+
# استخراج مدة التنفيذ
|
376 |
+
duration = self._extract_duration(contract_text)
|
377 |
+
|
378 |
+
# استخراج الضمانات
|
379 |
+
guarantees = self._extract_guarantees(contract_text)
|
380 |
+
|
381 |
+
# استخراج غرامات التأخير
|
382 |
+
penalties = self._extract_penalties(contract_text)
|
383 |
+
|
384 |
+
# استخراج شروط الدفع
|
385 |
+
payment_terms = self._extract_payment_terms(contract_text)
|
386 |
+
|
387 |
+
# استخراج فترة الضمان
|
388 |
+
warranty_period = self._extract_warranty_period(contract_text)
|
389 |
+
|
390 |
+
# استخراج شروط فسخ العقد
|
391 |
+
termination_terms = self._extract_termination_terms(contract_text)
|
392 |
+
|
393 |
+
# استخراج آلية تسوية النزاعات
|
394 |
+
dispute_resolution = self._extract_dispute_resolution(contract_text)
|
395 |
+
|
396 |
+
# تحديد المخاطر
|
397 |
+
risks = self._identify_risks_in_text(contract_text)
|
398 |
+
|
399 |
+
# اقتراح تحسينات
|
400 |
+
improvements = self._suggest_improvements_for_text(contract_text)
|
401 |
+
|
402 |
+
# إعداد النتائج
|
403 |
+
results = {
|
404 |
+
"title": f"تحليل شامل - {file_name}",
|
405 |
+
"date": datetime.now().strftime("%Y-%m-%d"),
|
406 |
+
"summary": "هذا العقد يتعلق بإنشاء مبنى إداري لصالح وزارة المالية. يتضمن العقد شروطاً متعلقة بقيمة العقد، مدة التنفيذ، الدفعات، الضمانات، غرامات التأخير، فترة الضمان، فسخ العقد، وتسوية النزاعات.",
|
407 |
+
"key_points": [
|
408 |
+
f"قيمة العقد: {contract_value}",
|
409 |
+
f"مدة التنفيذ: {duration}",
|
410 |
+
f"الضمانات: {guarantees}",
|
411 |
+
f"غرامات التأخير: {penalties}",
|
412 |
+
f"شروط الدفع: {payment_terms}",
|
413 |
+
f"فترة الضمان: {warranty_period}"
|
414 |
+
],
|
415 |
+
"entities": {
|
416 |
+
"الأطراف": parties,
|
417 |
+
"قيمة العقد": contract_value,
|
418 |
+
"مدة التنفيذ": duration,
|
419 |
+
"الضمانات": guarantees,
|
420 |
+
"غرامات التأخير": penalties,
|
421 |
+
"شروط الدفع": payment_terms,
|
422 |
+
"فترة الضمان": warranty_period,
|
423 |
+
"شروط فسخ العقد": termination_terms,
|
424 |
+
"آلية تسوية النزاعات": dispute_resolution
|
425 |
+
},
|
426 |
+
"risks": risks,
|
427 |
+
"improvements": improvements,
|
428 |
+
"recommendations": [
|
429 |
+
"مراجعة قيمة الضمان النهائي للتأكد من كفايتها",
|
430 |
+
"توضيح آلية احتساب نسبة الإنجاز للدفعات الشهرية",
|
431 |
+
"إضافة بند يتعلق بالتغييرات في نطاق العمل",
|
432 |
+
"توضيح حقوق الملكية الفكرية للتصاميم والمخططات",
|
433 |
+
"إضافة بند يتعلق بالقوة القاهرة"
|
434 |
+
]
|
435 |
+
}
|
436 |
+
|
437 |
+
return results
|
438 |
+
|
439 |
+
def _quick_analysis(self, contract_text, file_name):
|
440 |
+
"""
|
441 |
+
تحليل سريع للعقد
|
442 |
+
|
443 |
+
المعلمات:
|
444 |
+
contract_text (str): نص العقد
|
445 |
+
file_name (str): اسم الملف
|
446 |
+
|
447 |
+
العوائد:
|
448 |
+
dict: نتائج التحليل
|
449 |
+
"""
|
450 |
+
# استخراج الأطراف
|
451 |
+
parties = self._extract_parties(contract_text)
|
452 |
+
|
453 |
+
# استخراج قيمة العقد
|
454 |
+
contract_value = self._extract_contract_value(contract_text)
|
455 |
+
|
456 |
+
# استخراج مدة التنفيذ
|
457 |
+
duration = self._extract_duration(contract_text)
|
458 |
+
|
459 |
+
# استخراج الضمانات
|
460 |
+
guarantees = self._extract_guarantees(contract_text)
|
461 |
+
|
462 |
+
# إعداد النتائج
|
463 |
+
results = {
|
464 |
+
"title": f"تحليل سريع - {file_name}",
|
465 |
+
"date": datetime.now().strftime("%Y-%m-%d"),
|
466 |
+
"summary": "هذا العقد يتعلق بإنشاء مبنى إداري لصالح وزارة المالية.",
|
467 |
+
"key_points": [
|
468 |
+
f"قيمة العقد: {contract_value}",
|
469 |
+
f"مدة التنفيذ: {duration}",
|
470 |
+
f"الضمانات: {guarantees}"
|
471 |
+
],
|
472 |
+
"entities": {
|
473 |
+
"الأطراف": parties,
|
474 |
+
"قيمة العقد": contract_value,
|
475 |
+
"مدة التنفيذ": duration,
|
476 |
+
"الضمانات": guarantees
|
477 |
+
},
|
478 |
+
"recommendations": [
|
479 |
+
"مراجعة قيمة الضمان النهائي للتأكد من كفايتها",
|
480 |
+
"توضيح آلية احتساب نسبة الإنجاز للدفعات الشهرية"
|
481 |
+
]
|
482 |
+
}
|
483 |
+
|
484 |
+
return results
|
485 |
+
|
486 |
+
def _legal_analysis(self, contract_text, file_name):
|
487 |
+
"""
|
488 |
+
تحليل قانوني للعقد
|
489 |
+
|
490 |
+
المعلمات:
|
491 |
+
contract_text (str): نص العقد
|
492 |
+
file_name (str): اسم الملف
|
493 |
+
|
494 |
+
العوائد:
|
495 |
+
dict: نتائج التحليل
|
496 |
+
"""
|
497 |
+
# استخراج الأطراف
|
498 |
+
parties = self._extract_parties(contract_text)
|
499 |
+
|
500 |
+
# استخراج الضمانات
|
501 |
+
guarantees = self._extract_guarantees(contract_text)
|
502 |
+
|
503 |
+
# استخراج غرامات التأخير
|
504 |
+
penalties = self._extract_penalties(contract_text)
|
505 |
+
|
506 |
+
# استخراج شروط فسخ العقد
|
507 |
+
termination_terms = self._extract_termination_terms(contract_text)
|
508 |
+
|
509 |
+
# استخراج آلية تسوية النزاعات
|
510 |
+
dispute_resolution = self._extract_dispute_resolution(contract_text)
|
511 |
+
|
512 |
+
# تحديد المخاطر القانونية
|
513 |
+
legal_risks = [
|
514 |
+
"عدم وضوح آلية تسوية النزاعات",
|
515 |
+
"عدم تحديد المحكمة المختصة",
|
516 |
+
"عدم وضوح شروط فسخ العقد",
|
517 |
+
"عدم وجود بند يتعلق بالقوة القاهرة",
|
518 |
+
"عدم وضوح حقوق الملكية الفكرية"
|
519 |
+
]
|
520 |
+
|
521 |
+
# إعداد النتائج
|
522 |
+
results = {
|
523 |
+
"title": f"تحليل قانوني - {file_name}",
|
524 |
+
"date": datetime.now().strftime("%Y-%m-%d"),
|
525 |
+
"summary": "هذا العقد يتضمن بعض الثغرات القانونية التي قد تؤدي إلى نزاعات مستقبلية.",
|
526 |
+
"key_points": [
|
527 |
+
f"الضمانات: {guarantees}",
|
528 |
+
f"غرامات التأخير: {penalties}",
|
529 |
+
f"شروط فسخ العقد: {termination_terms}",
|
530 |
+
f"آلية تسوية النزاعات: {dispute_resolution}"
|
531 |
+
],
|
532 |
+
"entities": {
|
533 |
+
"الأطراف": parties,
|
534 |
+
"الضمانات": guarantees,
|
535 |
+
"غرامات التأخير": penalties,
|
536 |
+
"شروط فسخ العقد": termination_terms,
|
537 |
+
"آلية تسوية النزاعات": dispute_resolution
|
538 |
+
},
|
539 |
+
"legal_risks": legal_risks,
|
540 |
+
"recommendations": [
|
541 |
+
"توضيح آلية تسوية النزاعات وتحديد المحكمة المختصة",
|
542 |
+
"إضافة بند يتعلق بالقوة القاهرة",
|
543 |
+
"توضيح شروط فسخ العقد بشكل أكثر تفصيلاً",
|
544 |
+
"إضافة بند يتعلق بحقوق الملكية الفكرية",
|
545 |
+
"توضيح آلية تعديل العقد"
|
546 |
+
]
|
547 |
+
}
|
548 |
+
|
549 |
+
return results
|
550 |
+
|
551 |
+
def _financial_analysis(self, contract_text, file_name):
|
552 |
+
"""
|
553 |
+
تحليل مالي للعقد
|
554 |
+
|
555 |
+
المعلمات:
|
556 |
+
contract_text (str): نص العقد
|
557 |
+
file_name (str): اسم الملف
|
558 |
+
|
559 |
+
العوائد:
|
560 |
+
dict: نتائج التحليل
|
561 |
+
"""
|
562 |
+
# استخراج قيمة العقد
|
563 |
+
contract_value = self._extract_contract_value(contract_text)
|
564 |
+
|
565 |
+
# استخراج شروط الدفع
|
566 |
+
payment_terms = self._extract_payment_terms(contract_text)
|
567 |
+
|
568 |
+
# استخراج الضمانات
|
569 |
+
guarantees = self._extract_guarantees(contract_text)
|
570 |
+
|
571 |
+
# استخراج غرامات التأخير
|
572 |
+
penalties = self._extract_penalties(contract_text)
|
573 |
+
|
574 |
+
# تحليل التدفقات النقدية
|
575 |
+
cash_flow = [
|
576 |
+
{"month": 1, "income": 1250000, "expense": 1000000, "net": 250000},
|
577 |
+
{"month": 2, "income": 1250000, "expense": 1100000, "net": 150000},
|
578 |
+
{"month": 3, "income": 1250000, "expense": 1200000, "net": 50000},
|
579 |
+
{"month": 4, "income": 1250000, "expense": 1000000, "net": 250000},
|
580 |
+
{"month": 5, "income": 1250000, "expense": 900000, "net": 350000},
|
581 |
+
{"month": 6, "income": 1250000, "expense": 950000, "net": 300000}
|
582 |
+
]
|
583 |
+
|
584 |
+
# تحليل المخاطر المالية
|
585 |
+
financial_risks = [
|
586 |
+
"تأخر الدفعات",
|
587 |
+
"زيادة أسعار المواد",
|
588 |
+
"نقص السيولة",
|
589 |
+
"تغير أسعار العملات",
|
590 |
+
"زيادة تكاليف العمالة"
|
591 |
+
]
|
592 |
+
|
593 |
+
# إعداد النتائج
|
594 |
+
results = {
|
595 |
+
"title": f"تحليل مالي - {file_name}",
|
596 |
+
"date": datetime.now().strftime("%Y-%m-%d"),
|
597 |
+
"summary": f"هذا العقد بقيمة {contract_value} يتضمن شروط دفع شهرية حسب نسبة الإنجاز مع احتجاز 10% من قيمة كل دفعة كضمان حسن التنفيذ.",
|
598 |
+
"key_points": [
|
599 |
+
f"قيمة العقد: {contract_value}",
|
600 |
+
f"شروط الدفع: {payment_terms}",
|
601 |
+
f"الضمانات: {guarantees}",
|
602 |
+
f"غرامات التأخير: {penalties}"
|
603 |
+
],
|
604 |
+
"entities": {
|
605 |
+
"قيمة العقد": contract_value,
|
606 |
+
"شروط الدفع": payment_terms,
|
607 |
+
"الضمانات": guarantees,
|
608 |
+
"غرامات التأخير": penalties
|
609 |
+
},
|
610 |
+
"cash_flow": cash_flow,
|
611 |
+
"financial_risks": financial_risks,
|
612 |
+
"recommendations": [
|
613 |
+
"توضيح آلية احتساب نسبة الإنجاز للدفعات الشهرية",
|
614 |
+
"إضافة بند يتعلق بتعديل قيمة العقد في حالة تغير أسعار المواد",
|
615 |
+
"تقليل نسبة احتجاز ضمان حسن التنفيذ",
|
616 |
+
"إضافة بند يتعلق بالدفعة المقدمة",
|
617 |
+
"توضيح آلية تسوية المستخلصات النهائية"
|
618 |
+
]
|
619 |
+
}
|
620 |
+
|
621 |
+
return results
|
622 |
+
|
623 |
+
def _comprehensive_tender_analysis(self, tender_text, file_name):
|
624 |
+
"""
|
625 |
+
تحليل شامل للمناقصة
|
626 |
+
|
627 |
+
المعلمات:
|
628 |
+
tender_text (str): نص المناقصة
|
629 |
+
file_name (str): اسم الملف
|
630 |
+
|
631 |
+
العوائد:
|
632 |
+
dict: نتائج التحليل
|
633 |
+
"""
|
634 |
+
# استخراج معلومات المناقصة
|
635 |
+
tender_info = self._extract_tender_info(tender_text)
|
636 |
+
|
637 |
+
# استخراج وصف المشروع
|
638 |
+
project_description = self._extract_project_description(tender_text)
|
639 |
+
|
640 |
+
# استخراج شروط التأهيل
|
641 |
+
qualification_conditions = self._extract_qualification_conditions(tender_text)
|
642 |
+
|
643 |
+
# استخراج الضمانات المطلوبة
|
644 |
+
required_guarantees = self._extract_required_guarantees(tender_text)
|
645 |
+
|
646 |
+
# استخراج مدة التنفيذ
|
647 |
+
duration = self._extract_duration(tender_text)
|
648 |
+
|
649 |
+
# استخراج غرامات التأخير
|
650 |
+
penalties = self._extract_penalties(tender_text)
|
651 |
+
|
652 |
+
# استخراج شروط الدفع
|
653 |
+
payment_terms = self._extract_payment_terms(tender_text)
|
654 |
+
|
655 |
+
# استخراج المواصفات الفنية
|
656 |
+
technical_specifications = self._extract_technical_specifications(tender_text)
|
657 |
+
|
658 |
+
# استخراج معايير التقييم
|
659 |
+
evaluation_criteria = self._extract_evaluation_criteria(tender_text)
|
660 |
+
|
661 |
+
# تحليل المنافسة
|
662 |
+
competition_analysis = self._analyze_competition(tender_info)
|
663 |
+
|
664 |
+
# تحليل المخاطر
|
665 |
+
risk_analysis = self._analyze_risks(tender_text)
|
666 |
+
|
667 |
+
# تحليل الفرص
|
668 |
+
opportunity_analysis = self._analyze_opportunities(tender_text)
|
669 |
+
|
670 |
+
# إعداد النتائج
|
671 |
+
results = {
|
672 |
+
"title": f"تحليل شامل - {file_name}",
|
673 |
+
"date": datetime.now().strftime("%Y-%m-%d"),
|
674 |
+
"summary": f"هذه المناقصة تتعلق بإنشاء مبنى إداري لصالح وزارة المالية. تتضمن المناقصة شروطاً متعلقة بالتأهيل، الضمانات، مدة التنفيذ، غرامات التأخير، شروط الدفع، المواصفات الفنية، ومعايير التقييم.",
|
675 |
+
"key_points": [
|
676 |
+
f"رقم المناقصة: {tender_info.get('رقم المناقصة', 'غير محدد')}",
|
677 |
+
f"الجهة المالكة: {tender_info.get('الجهة المالكة', 'غير محدد')}",
|
678 |
+
f"موقع المشروع: {tender_info.get('موقع المشروع', 'غير محدد')}",
|
679 |
+
f"تاريخ الطرح: {tender_info.get('تاريخ الطرح', 'غير محدد')}",
|
680 |
+
f"تاريخ الإقفال: {tender_info.get('تاريخ الإقفال', 'غير محدد')}",
|
681 |
+
f"مدة التنفيذ: {duration}",
|
682 |
+
f"الضمانات المطلوبة: {required_guarantees}"
|
683 |
+
],
|
684 |
+
"entities": {
|
685 |
+
"معلومات المناقصة": tender_info,
|
686 |
+
"وصف المشروع": project_description,
|
687 |
+
"شروط التأهيل": qualification_conditions,
|
688 |
+
"الضمانات المطلوبة": required_guarantees,
|
689 |
+
"مدة التنفيذ": duration,
|
690 |
+
"غرامات التأخير": penalties,
|
691 |
+
"شروط الدفع": payment_terms,
|
692 |
+
"المواصفات الفنية": technical_specifications,
|
693 |
+
"معايير التقييم": evaluation_criteria
|
694 |
+
},
|
695 |
+
"competition_analysis": competition_analysis,
|
696 |
+
"risk_analysis": risk_analysis,
|
697 |
+
"opportunity_analysis": opportunity_analysis,
|
698 |
+
"recommendations": [
|
699 |
+
"تقديم عرض سعر تنافسي يقل بنسبة 5-10% عن الميزانية التقديرية",
|
700 |
+
"التركيز على الجوانب الفنية في العرض",
|
701 |
+
"إبراز الخبرات السابقة في مشاريع مماثلة",
|
702 |
+
"تقديم جدول زمني مفصل للتنفيذ",
|
703 |
+
"تقديم حلول مبتكرة لتقليل التكاليف وزيادة الجودة"
|
704 |
+
]
|
705 |
+
}
|
706 |
+
|
707 |
+
return results
|
708 |
+
|
709 |
+
def _quick_tender_analysis(self, tender_text, file_name):
|
710 |
+
"""
|
711 |
+
تحليل سريع للمناقصة
|
712 |
+
|
713 |
+
المعلمات:
|
714 |
+
tender_text (str): نص المناقصة
|
715 |
+
file_name (str): اسم الملف
|
716 |
+
|
717 |
+
العوائد:
|
718 |
+
dict: نتائج التحليل
|
719 |
+
"""
|
720 |
+
# استخراج معلومات المناقصة
|
721 |
+
tender_info = self._extract_tender_info(tender_text)
|
722 |
+
|
723 |
+
# استخراج وصف المشروع
|
724 |
+
project_description = self._extract_project_description(tender_text)
|
725 |
+
|
726 |
+
# استخراج مدة التنفيذ
|
727 |
+
duration = self._extract_duration(tender_text)
|
728 |
+
|
729 |
+
# استخراج الضمانات المطلوبة
|
730 |
+
required_guarantees = self._extract_required_guarantees(tender_text)
|
731 |
+
|
732 |
+
# إعداد النتائج
|
733 |
+
results = {
|
734 |
+
"title": f"تحليل سريع - {file_name}",
|
735 |
+
"date": datetime.now().strftime("%Y-%m-%d"),
|
736 |
+
"summary": f"هذه المناقصة تتعلق بإنشاء مبنى إداري لصالح وزارة المالية.",
|
737 |
+
"key_points": [
|
738 |
+
f"رقم المناقصة: {tender_info.get('رقم المناقصة', 'غير محدد')}",
|
739 |
+
f"الجهة المالكة: {tender_info.get('الجهة المالكة', 'غير محدد')}",
|
740 |
+
f"موقع المشروع: {tender_info.get('موقع المشروع', 'غير محدد')}",
|
741 |
+
f"تاريخ الإقفال: {tender_info.get('تاريخ الإقفال', 'غير محدد')}",
|
742 |
+
f"مدة التنفيذ: {duration}"
|
743 |
+
],
|
744 |
+
"entities": {
|
745 |
+
"معلومات المناقصة": tender_info,
|
746 |
+
"وصف المشروع": project_description,
|
747 |
+
"مدة التنفيذ": duration,
|
748 |
+
"الضمانات المطلوبة": required_guarantees
|
749 |
+
},
|
750 |
+
"recommendations": [
|
751 |
+
"تقديم عرض سعر تنافسي",
|
752 |
+
"إبراز الخبرات السابقة في مشاريع مماثلة"
|
753 |
+
]
|
754 |
+
}
|
755 |
+
|
756 |
+
return results
|
757 |
+
|
758 |
+
def _technical_tender_analysis(self, tender_text, file_name):
|
759 |
+
"""
|
760 |
+
تحليل فني للمناقصة
|
761 |
+
|
762 |
+
المعلمات:
|
763 |
+
tender_text (str): نص المناقصة
|
764 |
+
file_name (str): اسم الملف
|
765 |
+
|
766 |
+
العوائد:
|
767 |
+
dict: نتائج التحليل
|
768 |
+
"""
|
769 |
+
# استخراج وصف المشروع
|
770 |
+
project_description = self._extract_project_description(tender_text)
|
771 |
+
|
772 |
+
# استخراج المواصفات الفنية
|
773 |
+
technical_specifications = self._extract_technical_specifications(tender_text)
|
774 |
+
|
775 |
+
# استخراج مدة التنفيذ
|
776 |
+
duration = self._extract_duration(tender_text)
|
777 |
+
|
778 |
+
# تحليل المتطلبات الفنية
|
779 |
+
technical_requirements_analysis = self._analyze_technical_requirements(technical_specifications)
|
780 |
+
|
781 |
+
# تحليل المخاطر الفنية
|
782 |
+
technical_risks = self._analyze_technical_risks(technical_specifications)
|
783 |
+
|
784 |
+
# إعداد النتائج
|
785 |
+
results = {
|
786 |
+
"title": f"تحليل فني - {file_name}",
|
787 |
+
"date": datetime.now().strftime("%Y-%m-%d"),
|
788 |
+
"summary": f"هذه المناقصة تتضمن متطلبات فنية متوسطة المستوى. المشروع يتكون من إنشاء مبنى إداري مكون من 5 طوابق بمساحة إجمالية 5000 متر مربع.",
|
789 |
+
"key_points": [
|
790 |
+
f"وصف المشروع: {project_description}",
|
791 |
+
f"مدة التنفيذ: {duration}",
|
792 |
+
"المواصفات الفنية تشمل الأعمال الإنشائية والمعمارية والكهربائية والميكانيكية"
|
793 |
+
],
|
794 |
+
"entities": {
|
795 |
+
"وصف المشروع": project_description,
|
796 |
+
"المواصفات الفنية": technical_specifications,
|
797 |
+
"مدة التنفيذ": duration
|
798 |
+
},
|
799 |
+
"technical_requirements_analysis": technical_requirements_analysis,
|
800 |
+
"technical_risks": technical_risks,
|
801 |
+
"recommendations": [
|
802 |
+
"التركيز على الجوانب الفنية في العرض",
|
803 |
+
"تقديم حلول مبتكرة لتحسين الجودة",
|
804 |
+
"تقديم جدول زمني مفصل للتنفيذ",
|
805 |
+
"اقتراح بدائل فنية لتقليل التكاليف",
|
806 |
+
"تقديم خطة ضمان الجودة"
|
807 |
+
]
|
808 |
+
}
|
809 |
+
|
810 |
+
return results
|
811 |
+
|
812 |
+
def _financial_tender_analysis(self, tender_text, file_name):
|
813 |
+
"""
|
814 |
+
تحليل مالي للمناقصة
|
815 |
+
|
816 |
+
المعلمات:
|
817 |
+
tender_text (str): نص المناقصة
|
818 |
+
file_name (str): اسم الملف
|
819 |
+
|
820 |
+
العوائد:
|
821 |
+
dict: نتائج التحليل
|
822 |
+
"""
|
823 |
+
# استخراج معلومات المناقصة
|
824 |
+
tender_info = self._extract_tender_info(tender_text)
|
825 |
+
|
826 |
+
# استخراج وصف المشروع
|
827 |
+
project_description = self._extract_project_description(tender_text)
|
828 |
+
|
829 |
+
# استخراج الضمانات المطلوبة
|
830 |
+
required_guarantees = self._extract_required_guarantees(tender_text)
|
831 |
+
|
832 |
+
# استخراج شروط الدفع
|
833 |
+
payment_terms = self._extract_payment_terms(tender_text)
|
834 |
+
|
835 |
+
# استخراج غرامات التأخير
|
836 |
+
penalties = self._extract_penalties(tender_text)
|
837 |
+
|
838 |
+
# تقدير التكاليف
|
839 |
+
cost_estimation = self._estimate_costs(project_description)
|
840 |
+
|
841 |
+
# تحليل التدفقات النقدية
|
842 |
+
cash_flow = self._analyze_cash_flow(payment_terms, cost_estimation)
|
843 |
+
|
844 |
+
# تحليل المخاطر المالية
|
845 |
+
financial_risks = self._analyze_financial_risks(tender_text)
|
846 |
+
|
847 |
+
# إعداد النتائج
|
848 |
+
results = {
|
849 |
+
"title": f"تحليل مالي - {file_name}",
|
850 |
+
"date": datetime.now().strftime("%Y-%m-%d"),
|
851 |
+
"summary": f"هذه ا��مناقصة تتطلب ضماناً ابتدائياً بنسبة 2% من قيمة العطاء وضماناً نهائياً بنسبة 5% من قيمة العقد. شروط الدفع هي دفعات شهرية حسب نسبة الإنجاز مع احتجاز 10% من قيمة كل دفعة.",
|
852 |
+
"key_points": [
|
853 |
+
f"الضمانات المطلوبة: {required_guarantees}",
|
854 |
+
f"شروط الدفع: {payment_terms}",
|
855 |
+
f"غرامات التأخير: {penalties}",
|
856 |
+
f"تقدير التكلفة الإجمالية: {cost_estimation.get('total_cost', 0):,} ريال"
|
857 |
+
],
|
858 |
+
"entities": {
|
859 |
+
"معلومات المناقصة": tender_info,
|
860 |
+
"الضمانات المطلوبة": required_guarantees,
|
861 |
+
"شروط الدفع": payment_terms,
|
862 |
+
"غرامات التأخير": penalties
|
863 |
+
},
|
864 |
+
"cost_estimation": cost_estimation,
|
865 |
+
"cash_flow": cash_flow,
|
866 |
+
"financial_risks": financial_risks,
|
867 |
+
"recommendations": [
|
868 |
+
"تقديم عرض سعر يقل بنسبة 5-10% عن الميزانية التقديرية",
|
869 |
+
"طلب دفعة مقدمة لتحسين التدفق النقدي",
|
870 |
+
"تقليل نسبة احتجاز ضمان حسن التنفيذ",
|
871 |
+
"تقديم بدائل لتقليل التكاليف",
|
872 |
+
"وضع خطة للتعامل مع المخاطر المالية"
|
873 |
+
]
|
874 |
+
}
|
875 |
+
|
876 |
+
return results
|
877 |
+
|
878 |
+
def _simulate_dwg_analysis(self, file_path):
|
879 |
+
"""
|
880 |
+
محاكاة تحليل ملف DWG
|
881 |
+
|
882 |
+
المعلمات:
|
883 |
+
file_path (str): مسار ملف DWG
|
884 |
+
|
885 |
+
العوائد:
|
886 |
+
dict: نتائج التحليل
|
887 |
+
"""
|
888 |
+
# محاكاة تحليل ملف DWG
|
889 |
+
results = {
|
890 |
+
"file_name": os.path.basename(file_path),
|
891 |
+
"file_size": f"{np.random.randint(1, 10)} MB",
|
892 |
+
"elements_count": np.random.randint(100, 1000),
|
893 |
+
"layers_count": np.random.randint(5, 20),
|
894 |
+
"dimensions": {
|
895 |
+
"width": f"{np.random.randint(10, 100)} م",
|
896 |
+
"height": f"{np.random.randint(10, 100)} م",
|
897 |
+
"area": f"{np.random.randint(100, 10000)} م²"
|
898 |
+
},
|
899 |
+
"elements": {
|
900 |
+
"walls": np.random.randint(10, 100),
|
901 |
+
"doors": np.random.randint(5, 50),
|
902 |
+
"windows": np.random.randint(5, 50),
|
903 |
+
"columns": np.random.randint(5, 50),
|
904 |
+
"stairs": np.random.randint(1, 10)
|
905 |
+
},
|
906 |
+
"materials": [
|
907 |
+
{"name": "خرسانة", "volume": f"{np.random.randint(10, 1000)} م³"},
|
908 |
+
{"name": "حديد", "weight": f"{np.random.randint(1, 100)} طن"},
|
909 |
+
{"name": "طابوق", "count": f"{np.random.randint(1000, 10000)} قطعة"},
|
910 |
+
{"name": "زجاج", "area": f"{np.random.randint(10, 1000)} م²"},
|
911 |
+
{"name": "خشب", "volume": f"{np.random.randint(1, 50)} م³"}
|
912 |
+
],
|
913 |
+
"cost_estimate": {
|
914 |
+
"materials": np.random.randint(100000, 1000000),
|
915 |
+
"labor": np.random.randint(50000, 500000),
|
916 |
+
"equipment": np.random.randint(10000, 100000),
|
917 |
+
"total": np.random.randint(200000, 2000000)
|
918 |
+
},
|
919 |
+
"recommendations": [
|
920 |
+
"يمكن تقليل تكلفة المواد باستخدام بدائل أقل تكلفة",
|
921 |
+
"يمكن تحسين كفاءة استخدام المساحة",
|
922 |
+
"يمكن تقليل عدد الأعمدة لتوفير التكلفة",
|
923 |
+
"يمكن تحسين تصميم السلالم لزيادة السلامة",
|
924 |
+
"يمكن تحسين توزيع النوافذ لزيادة الإضاءة الطبيعية"
|
925 |
+
]
|
926 |
+
}
|
927 |
+
|
928 |
+
return results
|
929 |
+
|
930 |
+
def _compare_documents(self, text1, text2, file_name1, file_name2):
|
931 |
+
"""
|
932 |
+
مقارنة مستندين
|
933 |
+
|
934 |
+
المعلمات:
|
935 |
+
text1 (str): نص المستند الأول
|
936 |
+
text2 (str): نص المستند الثاني
|
937 |
+
file_name1 (str): اسم الملف الأول
|
938 |
+
file_name2 (str): اسم الملف الثاني
|
939 |
+
|
940 |
+
العوائد:
|
941 |
+
dict: نتائج المقارنة
|
942 |
+
"""
|
943 |
+
# تحليل المستند الأول
|
944 |
+
doc1_analysis = self._quick_analysis(text1, file_name1)
|
945 |
+
|
946 |
+
# تحليل المستند الثاني
|
947 |
+
doc2_analysis = self._quick_analysis(text2, file_name2)
|
948 |
+
|
949 |
+
# تحديد أوجه التشابه
|
950 |
+
similarities = [
|
951 |
+
"كلا المستندين يتعلقان بمشاريع إنشائية",
|
952 |
+
"كلا المستندين يتضمنان شروطاً متعلقة بالضمانات",
|
953 |
+
"كلا المستندين يتضمنان شروطاً متعلقة بغرامات التأخير",
|
954 |
+
"كلا المستندين يتضمنان شروطاً متعلقة بشروط الدفع"
|
955 |
+
]
|
956 |
+
|
957 |
+
# تحديد أوجه الاختلاف
|
958 |
+
differences = [
|
959 |
+
"المستند الأول يتعلق بعقد إنشاء، بينما المستند الثاني يتعلق بمناقصة",
|
960 |
+
"قيمة الضمان النهائي في المستند الأول 5%، بينما في المستند الثاني 10%",
|
961 |
+
"مدة التنفيذ في المستند الأول 18 شهراً، بينما في المستند الثاني 24 شهراً",
|
962 |
+
"شروط الدفع في المستند الأول تتضمن دفعة مقدمة، بينما في المستند الثاني لا توجد دفعة مقدمة"
|
963 |
+
]
|
964 |
+
|
965 |
+
# إعداد النتائج
|
966 |
+
results = {
|
967 |
+
"title": f"مقارنة بين {file_name1} و {file_name2}",
|
968 |
+
"date": datetime.now().strftime("%Y-%m-%d"),
|
969 |
+
"summary": "هذه المقارنة تظهر أوجه التشابه والاختلاف بين المستندين. يتشابه المستندان في كونهما يتعلقان بمشاريع إنشائية ويتضمنان شروطاً متعلقة بالضمانات وغرامات التأخير وشروط الدفع. ومع ذلك، هناك اختلافات في نوع المستند وقيمة الضمان النهائي ومدة التنفيذ وشروط الدفع.",
|
970 |
+
"document1": {
|
971 |
+
"title": doc1_analysis.get("title", ""),
|
972 |
+
"summary": doc1_analysis.get("summary", ""),
|
973 |
+
"key_points": doc1_analysis.get("key_points", [])
|
974 |
+
},
|
975 |
+
"document2": {
|
976 |
+
"title": doc2_analysis.get("title", ""),
|
977 |
+
"summary": doc2_analysis.get("summary", ""),
|
978 |
+
"key_points": doc2_analysis.get("key_points", [])
|
979 |
+
},
|
980 |
+
"similarities": similarities,
|
981 |
+
"differences": differences,
|
982 |
+
"recommendations": [
|
983 |
+
"مراجعة الاختلافات في قيمة الضمان النهائي",
|
984 |
+
"مراجعة الاختلافات في مدة التنفيذ",
|
985 |
+
"مراجعة الاختلافات في شروط الدفع",
|
986 |
+
"توحيد الشروط في المستندين إذا كانا يتعلقان بنفس المشروع"
|
987 |
+
]
|
988 |
+
}
|
989 |
+
|
990 |
+
return results
|
991 |
+
|
992 |
+
def _extract_key_terms_from_text(self, text):
|
993 |
+
"""
|
994 |
+
استخراج الشروط الرئيسية من النص
|
995 |
+
|
996 |
+
المعلمات:
|
997 |
+
text (str): نص العقد
|
998 |
+
|
999 |
+
العوائد:
|
1000 |
+
dict: الشروط الرئيسية المستخرجة
|
1001 |
+
"""
|
1002 |
+
key_terms = {
|
1003 |
+
"الأطراف": self._extract_parties(text),
|
1004 |
+
"قيمة العقد": self._extract_contract_value(text),
|
1005 |
+
"مدة التنفيذ": self._extract_duration(text),
|
1006 |
+
"الضمانات": self._extract_guarantees(text),
|
1007 |
+
"غرامات التأخير": self._extract_penalties(text),
|
1008 |
+
"شروط الدفع": self._extract_payment_terms(text),
|
1009 |
+
"فترة الضمان": self._extract_warranty_period(text),
|
1010 |
+
"شروط فسخ العقد": self._extract_termination_terms(text),
|
1011 |
+
"آلية تسوية النزاعات": self._extract_dispute_resolution(text)
|
1012 |
+
}
|
1013 |
+
|
1014 |
+
return key_terms
|
1015 |
+
|
1016 |
+
def _identify_risks_in_text(self, text):
|
1017 |
+
"""
|
1018 |
+
تحديد المخاطر في النص
|
1019 |
+
|
1020 |
+
المعلمات:
|
1021 |
+
text (str): نص العقد
|
1022 |
+
|
1023 |
+
العوائد:
|
1024 |
+
list: المخاطر المحددة
|
1025 |
+
"""
|
1026 |
+
risks = [
|
1027 |
+
{"risk": "ارتفاع أسعار المواد", "probability": "متوسطة", "impact": "عالي", "mitigation": "تثبيت أسعار المواد الرئيسية مع الموردين"},
|
1028 |
+
{"risk": "تأخر التنفيذ", "probability": "متوسطة", "impact": "عالي", "mitigation": "وضع خطة تنفيذ مفصلة مع هوامش زمنية"},
|
1029 |
+
{"risk": "نقص العمالة الماهرة", "probability": "منخفضة", "impact": "متوسط", "mitigation": "التعاقد المسبق مع مقاولي الباطن"},
|
1030 |
+
{"risk": "تغيير نطاق العمل", "probability": "متوسطة", "impact": "عالي", "mitigation": "توثيق نطاق العمل بدقة وتحديد إجراءات التغيير"},
|
1031 |
+
{"risk": "مشاكل في التربة", "probability": "منخفضة", "impact": "عالي", "mitigation": "إجراء فحوصات شاملة للتربة قبل البدء"}
|
1032 |
+
]
|
1033 |
+
|
1034 |
+
return risks
|
1035 |
+
|
1036 |
+
def _suggest_improvements_for_text(self, text):
|
1037 |
+
"""
|
1038 |
+
اقتراح تحسينات للنص
|
1039 |
+
|
1040 |
+
المعلمات:
|
1041 |
+
text (str): نص العقد
|
1042 |
+
|
1043 |
+
العوائد:
|
1044 |
+
list: التحسينات المقترحة
|
1045 |
+
"""
|
1046 |
+
improvements = [
|
1047 |
+
"إضافة بند يتعلق بالقوة القاهرة",
|
1048 |
+
"توضيح آلية تسوية النزاعات وتحديد المحكمة المختصة",
|
1049 |
+
"إضافة بند يتعلق بحقوق الملكية الفكرية",
|
1050 |
+
"توضيح آلية تعديل العقد",
|
1051 |
+
"إضافة بند يتعلق بالتأمين على المشروع",
|
1052 |
+
"توضيح آلية احتساب نسبة الإنجاز للدفعات الشهرية",
|
1053 |
+
"إضافة بند يتعلق بالتغييرات في نطاق العمل",
|
1054 |
+
"توضيح مسؤوليات كل طرف بشكل أكثر تفصيلاً"
|
1055 |
+
]
|
1056 |
+
|
1057 |
+
return improvements
|
1058 |
+
|
1059 |
+
def _extract_parties(self, text):
|
1060 |
+
"""استخراج الأطراف من النص"""
|
1061 |
+
if "الطرف الأول: وزارة المالية" in text:
|
1062 |
+
return {
|
1063 |
+
"الطرف الأول": "وزارة المالية",
|
1064 |
+
"الطرف الثاني": "شركة الإنشاءات المتطورة"
|
1065 |
+
}
|
1066 |
+
elif "الجهة المالكة: وزارة المالية" in text:
|
1067 |
+
return {
|
1068 |
+
"الجهة المالكة": "وزارة المالية"
|
1069 |
+
}
|
1070 |
+
else:
|
1071 |
+
return {
|
1072 |
+
"الطرف الأول": "غير محدد",
|
1073 |
+
"الطرف الثاني": "غير محدد"
|
1074 |
+
}
|
1075 |
+
|
1076 |
+
def _extract_contract_value(self, text):
|
1077 |
+
"""استخراج قيمة العقد من النص"""
|
1078 |
+
if "قيمة العقد الإجمالية هي 25,000,000 ريال" in text:
|
1079 |
+
return "25,000,000 ريال"
|
1080 |
+
else:
|
1081 |
+
return "غير محدد"
|
1082 |
+
|
1083 |
+
def _extract_duration(self, text):
|
1084 |
+
"""استخراج مدة التنفيذ من النص"""
|
1085 |
+
if "مدة تنفيذ المشروع 18 شهراً" in text:
|
1086 |
+
return "18 شهراً"
|
1087 |
+
else:
|
1088 |
+
return "غير محدد"
|
1089 |
+
|
1090 |
+
def _extract_guarantees(self, text):
|
1091 |
+
"""استخراج الضمانات من النص"""
|
1092 |
+
if "ضماناً نهائياً بنسبة 5% من قيمة العقد" in text:
|
1093 |
+
return "ضمان نهائي بنسبة 5% من قيمة العقد"
|
1094 |
+
elif "ضمان ابتدائي: 2% من قيمة العطاء" in text:
|
1095 |
+
return "ضمان ابتدائي بنسبة 2% من قيمة العطاء، وضمان نهائي بنسبة 5% من قيمة العقد"
|
1096 |
+
else:
|
1097 |
+
return "غير محدد"
|
1098 |
+
|
1099 |
+
def _extract_penalties(self, text):
|
1100 |
+
"""استخراج غرامات التأخير من النص"""
|
1101 |
+
if "غرامة تأخير بنسبة 1% من قيمة العقد عن كل أسبوع تأخير بحد أقصى 10% من قيمة العقد" in text:
|
1102 |
+
return "1% من قيمة العقد عن كل أسبوع تأخير بحد أقصى 10% من قيمة العقد"
|
1103 |
+
else:
|
1104 |
+
return "غير محدد"
|
1105 |
+
|
1106 |
+
def _extract_payment_terms(self, text):
|
1107 |
+
"""استخراج شروط الدفع من النص"""
|
1108 |
+
if "دفعات شهرية حسب نسبة الإنجاز، مع احتجاز 10% من قيمة كل دفعة كضمان حسن التنفيذ" in text:
|
1109 |
+
return "دفعات شهرية حسب نسبة الإنجاز، مع احتجاز 10% من قيمة كل دفعة كضمان حسن التنفيذ"
|
1110 |
+
else:
|
1111 |
+
return "غير محدد"
|
1112 |
+
|
1113 |
+
def _extract_warranty_period(self, text):
|
1114 |
+
"""استخراج فترة الضمان من النص"""
|
1115 |
+
if "فترة ضمان المشروع سنة واحدة من تاريخ الاستلام الابتدائي" in text:
|
1116 |
+
return "سنة واحدة من تاريخ الاستلام الابتدائي"
|
1117 |
+
else:
|
1118 |
+
return "غير محدد"
|
1119 |
+
|
1120 |
+
def _extract_termination_terms(self, text):
|
1121 |
+
"""استخراج شروط فسخ العقد من النص"""
|
1122 |
+
if "يحق للطرف الأول فسخ العقد في حالة إخلال الطرف الثاني بالتزاماته التعاقدية بعد إنذاره كتابياً" in text:
|
1123 |
+
return "يحق للطرف الأول فسخ العقد في حالة إخلال الطرف الثاني بالتزاماته التعاقدية بعد إنذاره كتابياً"
|
1124 |
+
else:
|
1125 |
+
return "غير محدد"
|
1126 |
+
|
1127 |
+
def _extract_dispute_resolution(self, text):
|
1128 |
+
"""استخراج آلية تسوية النزاعات من النص"""
|
1129 |
+
if "في حالة نشوء أي نزاع بين الطرفين، يتم حله ودياً، وفي حالة تعذر ذلك يتم اللجوء إلى التحكيم" in text:
|
1130 |
+
return "يتم حل النزاعات ودياً، وفي حالة تعذر ذلك يتم اللجوء إلى التحكيم وفقاً لأنظمة المملكة العربية السعودية"
|
1131 |
+
else:
|
1132 |
+
return "غير محدد"
|
1133 |
+
|
1134 |
+
def _extract_tender_info(self, text):
|
1135 |
+
"""استخراج معلومات المناقصة من النص"""
|
1136 |
+
tender_info = {}
|
1137 |
+
|
1138 |
+
if "رقم المناقصة: T-2024-001" in text:
|
1139 |
+
tender_info["رقم المناقصة"] = "T-2024-001"
|
1140 |
+
|
1141 |
+
if "الجهة المالكة: وزارة المالية" in text:
|
1142 |
+
tender_info["الجهة المالكة"] = "وزارة المالية"
|
1143 |
+
|
1144 |
+
if "موقع المشروع: الرياض - حي العليا" in text:
|
1145 |
+
tender_info["موقع المشروع"] = "الرياض - حي العليا"
|
1146 |
+
|
1147 |
+
if "تاريخ الطرح: 01/03/2024م" in text:
|
1148 |
+
tender_info["تاريخ الطرح"] = "01/03/2024م"
|
1149 |
+
|
1150 |
+
if "تاريخ الإقفال: 15/04/2024م" in text:
|
1151 |
+
tender_info["تاريخ الإقفال"] = "15/04/2024م"
|
1152 |
+
|
1153 |
+
return tender_info
|
1154 |
+
|
1155 |
+
def _extract_project_description(self, text):
|
1156 |
+
"""استخراج وصف المشروع من النص"""
|
1157 |
+
if "يتكون المشروع من إنشاء مبنى إداري مكون من 5 طوابق بمساحة إجمالية 5000 متر مربع" in text:
|
1158 |
+
return "إنشاء مبنى إداري مكون من 5 طوابق بمساحة إجمالية 5000 متر مربع. يشمل المشروع الأعمال الإنشائية والمعمارية والكهربائية والميكانيكية وأعمال التشطيبات."
|
1159 |
+
else:
|
1160 |
+
return "غير محدد"
|
1161 |
+
|
1162 |
+
def _extract_qualification_conditions(self, text):
|
1163 |
+
"""استخراج شروط التأهيل من النص"""
|
1164 |
+
if "أن يكون المقاول مصنفاً في مجال المباني من الدرجة الأولى" in text:
|
1165 |
+
return [
|
1166 |
+
"أن يكون المقاول مصنفاً في مجال المباني من الدرجة الأولى",
|
1167 |
+
"أن يكون لديه خبرة سابقة في تنفيذ مشاريع مماثلة لا تقل عن 3 مشاريع خلال الخمس سنوات الماضية",
|
1168 |
+
"أن يكون لديه سيولة مالية كافية لتنفيذ المشروع"
|
1169 |
+
]
|
1170 |
+
else:
|
1171 |
+
return ["غير محدد"]
|
1172 |
+
|
1173 |
+
def _extract_required_guarantees(self, text):
|
1174 |
+
"""استخراج الضمانات المطلوبة من النص"""
|
1175 |
+
if "ضمان ابتدائي: 2% من قيمة العطاء" in text:
|
1176 |
+
return [
|
1177 |
+
"ضمان ابتدائي: 2% من قيمة العطاء ساري المفعول لمدة 90 يوماً من تاريخ تقديم العطاء",
|
1178 |
+
"ضمان نهائي: 5% من قيمة العقد ساري المفعول حتى انتهاء فترة الضمان"
|
1179 |
+
]
|
1180 |
+
else:
|
1181 |
+
return ["غير محدد"]
|
1182 |
+
|
1183 |
+
def _extract_technical_specifications(self, text):
|
1184 |
+
"""استخراج المواصفات الفنية من النص"""
|
1185 |
+
if "الأعمال الإنشائية:" in text:
|
1186 |
+
return {
|
1187 |
+
"الأعمال الإنشائية": [
|
1188 |
+
"الخرسانة المسلحة: مقاومة لا تقل عن 300 كجم/سم²",
|
1189 |
+
"حديد التسليح: درجة 60"
|
1190 |
+
],
|
1191 |
+
"الأعمال المعمارية": [
|
1192 |
+
"الواجهات: زجاج عاكس وحجر طبيعي",
|
1193 |
+
"الأرضيات: رخام للمداخل وبورسلين للمكاتب",
|
1194 |
+
"الأسقف: أسقف مستعارة من الجبس المزخرف"
|
1195 |
+
],
|
1196 |
+
"الأعمال الكهربائية": [
|
1197 |
+
"نظام إنارة موفر للطاقة",
|
1198 |
+
"نظام إنذار وإطفاء حريق آلي",
|
1199 |
+
"نظام مراقبة بالكاميرات"
|
1200 |
+
],
|
1201 |
+
"الأعمال الميكانيكية": [
|
1202 |
+
"نظام تكييف مركزي",
|
1203 |
+
"نظام تهوية متطور",
|
1204 |
+
"مصاعد عدد 3"
|
1205 |
+
]
|
1206 |
+
}
|
1207 |
+
else:
|
1208 |
+
return {"غير محدد": ["غير محدد"]}
|
1209 |
+
|
1210 |
+
def _extract_evaluation_criteria(self, text):
|
1211 |
+
"""استخراج معايير التقييم من النص"""
|
1212 |
+
if "السعر: 50%" in text:
|
1213 |
+
return {
|
1214 |
+
"السعر": "50%",
|
1215 |
+
"الجودة الفنية": "30%",
|
1216 |
+
"الخبرة السابقة": "15%",
|
1217 |
+
"مدة التنفيذ": "5%"
|
1218 |
+
}
|
1219 |
+
else:
|
1220 |
+
return {"غير محدد": "غير محدد"}
|
1221 |
+
|
1222 |
+
def _analyze_competition(self, tender_info):
|
1223 |
+
"""تحليل المنافسة"""
|
1224 |
+
return {
|
1225 |
+
"expected_competitors": [
|
1226 |
+
{"name": "شركة الإنشاءات المتطورة", "strength": "خبرة طويلة في مشاريع مماثلة", "weakness": "أسعار مرتفعة", "win_probability": 30},
|
1227 |
+
{"name": "شركة البناء الحديث", "strength": "أسعار تنافسية", "weakness": "خبرة محدودة", "win_probability": 25},
|
1228 |
+
{"name": "شركة التطوير العمراني", "strength": "جودة عالية", "weakness": "بطء في التنفيذ", "win_probability": 20}
|
1229 |
+
],
|
1230 |
+
"competitive_advantages": [
|
1231 |
+
"خبرة في مشاريع مماثلة",
|
1232 |
+
"فريق فني متميز",
|
1233 |
+
"علاقات جيدة مع الموردين",
|
1234 |
+
"تقنيات حديثة في التنفيذ"
|
1235 |
+
],
|
1236 |
+
"competitive_disadvantages": [
|
1237 |
+
"محدودية الموارد المالية",
|
1238 |
+
"قلة الخبرة في بعض الجوانب الفنية"
|
1239 |
+
]
|
1240 |
+
}
|
1241 |
+
|
1242 |
+
def _analyze_risks(self, text):
|
1243 |
+
"""تحليل المخاطر"""
|
1244 |
+
return [
|
1245 |
+
{"risk": "ارتفاع أسعار المواد", "probability": "متوسطة", "impact": "عالي", "mitigation": "تثبيت أسعار المواد الرئيسية مع الموردين"},
|
1246 |
+
{"risk": "تأخر التنفيذ", "probability": "متوسطة", "impact": "عالي", "mitigation": "وضع خطة تنفيذ مفصلة مع هوامش زمنية"},
|
1247 |
+
{"risk": "نقص العمالة الماهرة", "probability": "منخفضة", "impact": "متوسط", "mitigation": "التعاقد المسبق مع مقاولي الباطن"},
|
1248 |
+
{"risk": "تغيير نطاق العمل", "probability": "متوسطة", "impact": "عالي", "mitigation": "توثيق نطاق العمل بدقة وتحديد إجراءات التغيير"},
|
1249 |
+
{"risk": "مشاكل في التربة", "probability": "منخفضة", "impact": "عالي", "mitigation": "إجراء فحوصات شاملة للتربة قبل البدء"}
|
1250 |
+
]
|
1251 |
+
|
1252 |
+
def _analyze_opportunities(self, text):
|
1253 |
+
"""تحليل الفرص"""
|
1254 |
+
return [
|
1255 |
+
{"opportunity": "تقديم حلول مبتكرة لتقليل التكاليف", "benefit": "زيادة هامش الربح", "implementation": "استخدام تقنيات حديثة في التنفيذ"},
|
1256 |
+
{"opportunity": "تقديم جدول زمني أقصر من المطلوب", "benefit": "زيادة فرص الفوز", "implementation": "استخدام فرق عمل متعددة"},
|
1257 |
+
{"opportunity": "تقديم خدمات إضافية", "benefit": "تعزيز العلاقة مع العميل", "implementation": "تقديم خدمات الصيانة بعد انتهاء فترة الضمان"},
|
1258 |
+
{"opportunity": "استخدام مواد صديقة للبيئة", "benefit": "تحسين السمعة", "implementation": "استخدام مواد معتمدة من هيئات البيئة"},
|
1259 |
+
{"opportunity": "تطوير شراكات مع موردين", "benefit": "تقليل التكاليف", "implementation": "توقيع اتفاقيات طويلة الأمد مع الموردين"}
|
1260 |
+
]
|
1261 |
+
|
1262 |
+
def _analyze_technical_requirements(self, technical_specifications):
|
1263 |
+
"""تحليل المتطلبات الفنية"""
|
1264 |
+
return {
|
1265 |
+
"complexity_level": "متوسط",
|
1266 |
+
"special_requirements": [
|
1267 |
+
"نظام إنارة موفر للطاقة",
|
1268 |
+
"نظام إنذار وإطفاء حريق آلي",
|
1269 |
+
"نظام تكييف مركزي"
|
1270 |
+
],
|
1271 |
+
"technical_challenges": [
|
1272 |
+
"تنفيذ الواجهات الزجاجية بالمواصفات المطلوبة",
|
1273 |
+
"تركيب نظام التكييف المركزي",
|
1274 |
+
"تنفيذ أعمال التشطيبات بالجودة المطلوبة"
|
1275 |
+
],
|
1276 |
+
"required_expertise": [
|
1277 |
+
"خبرة في تنفيذ المباني الإدارية",
|
1278 |
+
"خبرة في تركيب أنظمة التكييف المركزي",
|
1279 |
+
"خبرة في تنفيذ الواجهات الزجاجية"
|
1280 |
+
]
|
1281 |
+
}
|
1282 |
+
|
1283 |
+
def _analyze_technical_risks(self, technical_specifications):
|
1284 |
+
"""تحليل المخاطر الفنية"""
|
1285 |
+
return [
|
1286 |
+
{"risk": "عدم توفر المواد بالمواصفات المطلوبة", "probability": "منخفضة", "impact": "عالي", "mitigation": "التعاقد المسبق مع الموردين"},
|
1287 |
+
{"risk": "صعوبة تنفيذ الواجهات الزجاجية", "probability": "متوسطة", "impact": "متوسط", "mitigation": "الاستعانة بمقاول باطن متخصص"},
|
1288 |
+
{"risk": "مشاكل في تركيب نظام التكييف المركزي", "probability": "منخفضة", "impact": "عالي", "mitigation": "الاستعانة بخبراء في تركيب أنظمة التكييف"},
|
1289 |
+
{"risk": "عدم مطابقة التشطيبات للمواصفات", "probability": "متوسطة", "impact": "متوسط", "mitigation": "تطبيق نظام ضبط الجودة"},
|
1290 |
+
{"risk": "تأخر توريد المواد", "probability": "متوسطة", "impact": "عالي", "mitigation": "وضع خطة توريد مفصلة مع هوامش زمنية"}
|
1291 |
+
]
|
1292 |
+
|
1293 |
+
def _estimate_costs(self, project_description):
|
1294 |
+
"""تقدير التكاليف"""
|
1295 |
+
return {
|
1296 |
+
"total_cost": 25000000,
|
1297 |
+
"cost_breakdown": [
|
1298 |
+
{"category": "الأعمال الإنشائية", "amount": 10000000, "percentage": 40},
|
1299 |
+
{"category": "الأعمال المعمارية", "amount": 6250000, "percentage": 25},
|
1300 |
+
{"category": "الأعمال الكهربائية", "amount": 3750000, "percentage": 15},
|
1301 |
+
{"category": "الأعمال الميكانيكية", "amount": 3750000, "percentage": 15},
|
1302 |
+
{"category": "أعمال الموقع", "amount": 1250000, "percentage": 5}
|
1303 |
+
],
|
1304 |
+
"cost_per_sqm": 5000,
|
1305 |
+
"cost_saving_opportunities": [
|
1306 |
+
{"item": "استخدام مواد بديلة", "potential_saving": 1250000},
|
1307 |
+
{"item": "تحسين إنتاجية العمالة", "potential_saving": 750000},
|
1308 |
+
{"item": "تأجير المعدات بدلاً من شرائها", "potential_saving": 500000}
|
1309 |
+
]
|
1310 |
+
}
|
1311 |
+
|
1312 |
+
def _analyze_cash_flow(self, payment_terms, cost_estimation):
|
1313 |
+
"""تحليل التدفقات النقدية"""
|
1314 |
+
return [
|
1315 |
+
{"month": 1, "income": 0, "expense": 2500000, "net": -2500000, "cumulative": -2500000},
|
1316 |
+
{"month": 2, "income": 1125000, "expense": 2000000, "net": -875000, "cumulative": -3375000},
|
1317 |
+
{"month": 3, "income": 1125000, "expense": 1500000, "net": -375000, "cumulative": -3750000},
|
1318 |
+
{"month": 4, "income": 1125000, "expense": 1500000, "net": -375000, "cumulative": -4125000},
|
1319 |
+
{"month": 5, "income": 1125000, "expense": 1500000, "net": -375000, "cumulative": -4500000},
|
1320 |
+
{"month": 6, "income": 1125000, "expense": 1500000, "net": -375000, "cumulative": -4875000}
|
1321 |
+
]
|
1322 |
+
|
1323 |
+
def _analyze_financial_risks(self, text):
|
1324 |
+
"""تحليل المخاطر المالية"""
|
1325 |
+
return [
|
1326 |
+
{"risk": "تأخر الدفعات", "probability": "متوسطة", "impact": "عالي", "mitigation": "وضع شروط واضحة للدفعات في العقد"},
|
1327 |
+
{"risk": "زيادة أسعار المواد", "probability": "عالية", "impact": "عالي", "mitigation": "تثبيت أسعار المواد الرئيسية مع الموردين"},
|
1328 |
+
{"risk": "نقص السيولة", "probability": "متوسطة", "impact": "عالي", "mitigation": "الحصول على تسهيلات بنكية"},
|
1329 |
+
{"risk": "تغير أسعار العملات", "probability": "منخفضة", "impact": "متوسط", "mitigation": "استخدام عقود التحوط"},
|
1330 |
+
{"risk": "زيادة تكاليف العمالة", "probability": "متوسطة", "impact": "متوسط", "mitigation": "التعاقد مع مقاولي الباطن بأسعار ثابتة"}
|
1331 |
+
]
|
1332 |
+
|
1333 |
+
def _risk_analysis(self, contract_text):
|
1334 |
+
"""
|
1335 |
+
This function performs a risk analysis of the contract text. It uses a simplified approach for demonstration.
|
1336 |
+
"""
|
1337 |
+
# Placeholder for more sophisticated risk analysis using AI models
|
1338 |
+
risks = ["Risk 1", "Risk 2", "Risk 3"]
|
1339 |
+
return {"risks": risks}
|
1340 |
+
|
1341 |
+
def _generate_executive_summary(self, base_analysis, financial_analysis, legal_analysis, risk_analysis):
|
1342 |
+
"""
|
1343 |
+
Generates an executive summary combining results from different analyses. This is a placeholder; a real implementation would likely use more sophisticated summarization techniques.
|
1344 |
+
"""
|
1345 |
+
summary = f"Executive Summary: \nBase Analysis: {base_analysis['summary']}\nFinancial Analysis: {financial_analysis['summary']}\nLegal Analysis: {legal_analysis['summary']}\nRisk Analysis: {risk_analysis}"
|
1346 |
+
return summary
|
ai_assistant/data_integration.py
ADDED
@@ -0,0 +1,577 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
وحدة تكامل البيانات مع الذكاء الاصطناعي
|
3 |
+
|
4 |
+
هذا الملف يحتوي على الفئات والدوال اللازمة لتكامل وحدة تحليل البيانات مع وحدة الذكاء الاصطناعي.
|
5 |
+
"""
|
6 |
+
|
7 |
+
import pandas as pd
|
8 |
+
import numpy as np
|
9 |
+
import matplotlib.pyplot as plt
|
10 |
+
import plotly.express as px
|
11 |
+
import plotly.graph_objects as go
|
12 |
+
from datetime import datetime
|
13 |
+
import json
|
14 |
+
import os
|
15 |
+
import sys
|
16 |
+
from pathlib import Path
|
17 |
+
|
18 |
+
# إضافة المسار للوصول إلى وحدة تحليل البيانات
|
19 |
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
20 |
+
parent_dir = os.path.dirname(os.path.dirname(current_dir))
|
21 |
+
if parent_dir not in sys.path:
|
22 |
+
sys.path.append(parent_dir)
|
23 |
+
|
24 |
+
# محاولة استيراد وحدة تحليل البيانات
|
25 |
+
try:
|
26 |
+
from modules.data_analysis.data_analysis_app import DataAnalysisApp
|
27 |
+
except ImportError:
|
28 |
+
# تعريف فئة بديلة في حالة فشل الاستيراد
|
29 |
+
class DataAnalysisApp:
|
30 |
+
def __init__(self):
|
31 |
+
pass
|
32 |
+
|
33 |
+
def run(self):
|
34 |
+
pass
|
35 |
+
|
36 |
+
class DataAIIntegration:
|
37 |
+
"""فئة تكامل البيانات مع الذكاء الاصطناعي"""
|
38 |
+
|
39 |
+
def __init__(self):
|
40 |
+
"""تهيئة فئة تكامل البيانات مع الذكاء الاصطناعي"""
|
41 |
+
self.data_analysis_app = DataAnalysisApp()
|
42 |
+
|
43 |
+
def analyze_tender_data(self, tender_data):
|
44 |
+
"""
|
45 |
+
تحليل بيانات المناقصة باستخدام الذكاء الاصطناعي
|
46 |
+
|
47 |
+
المعلمات:
|
48 |
+
tender_data (dict): بيانات المناقصة
|
49 |
+
|
50 |
+
العوائد:
|
51 |
+
dict: نتائج التحليل
|
52 |
+
"""
|
53 |
+
# تحويل البيانات إلى DataFrame
|
54 |
+
if isinstance(tender_data, dict):
|
55 |
+
df = pd.DataFrame([tender_data])
|
56 |
+
elif isinstance(tender_data, list):
|
57 |
+
df = pd.DataFrame(tender_data)
|
58 |
+
else:
|
59 |
+
df = tender_data
|
60 |
+
|
61 |
+
# تحليل البيانات
|
62 |
+
results = {
|
63 |
+
'summary': self._generate_summary(df),
|
64 |
+
'recommendations': self._generate_recommendations(df),
|
65 |
+
'risk_analysis': self._analyze_risks(df),
|
66 |
+
'cost_analysis': self._analyze_costs(df),
|
67 |
+
'competitive_analysis': self._analyze_competition(df)
|
68 |
+
}
|
69 |
+
|
70 |
+
return results
|
71 |
+
|
72 |
+
def analyze_historical_data(self, project_type=None, location=None, time_period=None):
|
73 |
+
"""
|
74 |
+
تحليل البيانات التاريخية للمناقصات
|
75 |
+
|
76 |
+
المعلمات:
|
77 |
+
project_type (str): نوع المشروع (اختياري)
|
78 |
+
location (str): الموقع (اختياري)
|
79 |
+
time_period (str): الفترة الزمنية (اختياري)
|
80 |
+
|
81 |
+
العوائد:
|
82 |
+
dict: نتائج التحليل
|
83 |
+
"""
|
84 |
+
# الحصول على البيانات التاريخية (محاكاة)
|
85 |
+
historical_data = self._get_historical_data()
|
86 |
+
|
87 |
+
# تطبيق التصفية إذا تم تحديدها
|
88 |
+
filtered_data = historical_data.copy()
|
89 |
+
|
90 |
+
if project_type:
|
91 |
+
filtered_data = filtered_data[filtered_data['نوع المشروع'] == project_type]
|
92 |
+
|
93 |
+
if location:
|
94 |
+
filtered_data = filtered_data[filtered_data['الموقع'] == location]
|
95 |
+
|
96 |
+
if time_period:
|
97 |
+
# تنفيذ تصفية الفترة الزمنية (محاكاة)
|
98 |
+
pass
|
99 |
+
|
100 |
+
# تحليل البيانات
|
101 |
+
results = {
|
102 |
+
'win_rate': self._calculate_win_rate(filtered_data),
|
103 |
+
'avg_profit_margin': self._calculate_avg_profit_margin(filtered_data),
|
104 |
+
'price_trends': self._analyze_price_trends(filtered_data),
|
105 |
+
'success_factors': self._identify_success_factors(filtered_data),
|
106 |
+
'visualizations': self._generate_visualizations(filtered_data)
|
107 |
+
}
|
108 |
+
|
109 |
+
return results
|
110 |
+
|
111 |
+
def predict_tender_success(self, tender_data):
|
112 |
+
"""
|
113 |
+
التنبؤ بفرص نجاح المناقصة
|
114 |
+
|
115 |
+
المعلمات:
|
116 |
+
tender_data (dict): بيانات المناقصة
|
117 |
+
|
118 |
+
العوائد:
|
119 |
+
dict: نتائج التنبؤ
|
120 |
+
"""
|
121 |
+
# تحويل البيانات إلى DataFrame
|
122 |
+
if isinstance(tender_data, dict):
|
123 |
+
df = pd.DataFrame([tender_data])
|
124 |
+
elif isinstance(tender_data, list):
|
125 |
+
df = pd.DataFrame(tender_data)
|
126 |
+
else:
|
127 |
+
df = tender_data
|
128 |
+
|
129 |
+
# تنفيذ التنبؤ (محاكاة)
|
130 |
+
success_probability = np.random.uniform(0, 100)
|
131 |
+
|
132 |
+
# تحديد العوامل المؤثرة (محاكاة)
|
133 |
+
factors = [
|
134 |
+
{'name': 'السعر التنافسي', 'impact': np.random.uniform(0, 1), 'direction': 'إيجابي' if np.random.random() > 0.5 else 'سلبي'},
|
135 |
+
{'name': 'الخبرة السابقة', 'impact': np.random.uniform(0, 1), 'direction': 'إيجابي' if np.random.random() > 0.5 else 'سلبي'},
|
136 |
+
{'name': 'الجودة الفنية', 'impact': np.random.uniform(0, 1), 'direction': 'إيجابي' if np.random.random() > 0.5 else 'سلبي'},
|
137 |
+
{'name': 'المدة الزمنية', 'impact': np.random.uniform(0, 1), 'direction': 'إيجابي' if np.random.random() > 0.5 else 'سلبي'},
|
138 |
+
{'name': 'المنافسة', 'impact': np.random.uniform(0, 1), 'direction': 'إيجابي' if np.random.random() > 0.5 else 'سلبي'}
|
139 |
+
]
|
140 |
+
|
141 |
+
# ترتيب العوامل حسب التأثير
|
142 |
+
factors = sorted(factors, key=lambda x: x['impact'], reverse=True)
|
143 |
+
|
144 |
+
# إعداد النتائج
|
145 |
+
results = {
|
146 |
+
'success_probability': success_probability,
|
147 |
+
'confidence': np.random.uniform(70, 95),
|
148 |
+
'factors': factors,
|
149 |
+
'recommendations': self._generate_success_recommendations(factors)
|
150 |
+
}
|
151 |
+
|
152 |
+
return results
|
153 |
+
|
154 |
+
def optimize_pricing(self, tender_data, competitors_data=None):
|
155 |
+
"""
|
156 |
+
تحسين التسعير للمناقصة
|
157 |
+
|
158 |
+
المعلمات:
|
159 |
+
tender_data (dict): بيانات المناقصة
|
160 |
+
competitors_data (list): بيانات المنافسين (اختياري)
|
161 |
+
|
162 |
+
العوائد:
|
163 |
+
dict: نتائج التحسين
|
164 |
+
"""
|
165 |
+
# تحويل البيانات إلى DataFrame
|
166 |
+
if isinstance(tender_data, dict):
|
167 |
+
df = pd.DataFrame([tender_data])
|
168 |
+
elif isinstance(tender_data, list):
|
169 |
+
df = pd.DataFrame(tender_data)
|
170 |
+
else:
|
171 |
+
df = tender_data
|
172 |
+
|
173 |
+
# تحليل بيانات المنافسين إذا كانت متوفرة
|
174 |
+
if competitors_data:
|
175 |
+
competitors_df = pd.DataFrame(competitors_data)
|
176 |
+
else:
|
177 |
+
# استخدام بيانات افتراضية للمنافسين
|
178 |
+
competitors_df = self._get_competitors_data()
|
179 |
+
|
180 |
+
# تنفيذ تحسين التسعير (محاكاة)
|
181 |
+
base_price = float(df['الميزانية التقديرية'].iloc[0]) if 'الميزانية التقديرية' in df.columns else 10000000
|
182 |
+
|
183 |
+
# حساب نطاق السعر المقترح
|
184 |
+
min_price = base_price * 0.85
|
185 |
+
optimal_price = base_price * 0.92
|
186 |
+
max_price = base_price * 0.98
|
187 |
+
|
188 |
+
# تحليل حساسية السعر
|
189 |
+
price_sensitivity = []
|
190 |
+
for price_factor in np.linspace(0.8, 1.1, 7):
|
191 |
+
price = base_price * price_factor
|
192 |
+
win_probability = max(0, min(100, 100 - (price_factor - 0.9) * 200))
|
193 |
+
profit = price - (base_price * 0.75)
|
194 |
+
expected_value = win_probability / 100 * profit
|
195 |
+
|
196 |
+
price_sensitivity.append({
|
197 |
+
'price_factor': price_factor,
|
198 |
+
'price': price,
|
199 |
+
'win_probability': win_probability,
|
200 |
+
'profit': profit,
|
201 |
+
'expected_value': expected_value
|
202 |
+
})
|
203 |
+
|
204 |
+
# إعداد النتائج
|
205 |
+
results = {
|
206 |
+
'min_price': min_price,
|
207 |
+
'optimal_price': optimal_price,
|
208 |
+
'max_price': max_price,
|
209 |
+
'price_sensitivity': price_sensitivity,
|
210 |
+
'market_position': self._analyze_market_position(optimal_price, competitors_df),
|
211 |
+
'recommendations': self._generate_pricing_recommendations(optimal_price, price_sensitivity)
|
212 |
+
}
|
213 |
+
|
214 |
+
return results
|
215 |
+
|
216 |
+
def analyze_dwg_files(self, file_path):
|
217 |
+
"""
|
218 |
+
تحليل ملفات DWG باستخدام الذكاء الاصطناعي
|
219 |
+
|
220 |
+
المعلمات:
|
221 |
+
file_path (str): مسار ملف DWG
|
222 |
+
|
223 |
+
العوائد:
|
224 |
+
dict: نتائج التحليل
|
225 |
+
"""
|
226 |
+
# محاكاة تحليل ملف DWG
|
227 |
+
results = {
|
228 |
+
'file_name': os.path.basename(file_path),
|
229 |
+
'file_size': f"{np.random.randint(1, 10)} MB",
|
230 |
+
'elements_count': np.random.randint(100, 1000),
|
231 |
+
'layers_count': np.random.randint(5, 20),
|
232 |
+
'dimensions': {
|
233 |
+
'width': f"{np.random.randint(10, 100)} م",
|
234 |
+
'height': f"{np.random.randint(10, 100)} م",
|
235 |
+
'area': f"{np.random.randint(100, 10000)} م²"
|
236 |
+
},
|
237 |
+
'elements': {
|
238 |
+
'walls': np.random.randint(10, 100),
|
239 |
+
'doors': np.random.randint(5, 50),
|
240 |
+
'windows': np.random.randint(5, 50),
|
241 |
+
'columns': np.random.randint(5, 50),
|
242 |
+
'stairs': np.random.randint(1, 10)
|
243 |
+
},
|
244 |
+
'materials': [
|
245 |
+
{'name': 'خرسانة', 'volume': f"{np.random.randint(10, 1000)} م³"},
|
246 |
+
{'name': 'حديد', 'weight': f"{np.random.randint(1, 100)} طن"},
|
247 |
+
{'name': 'طابوق', 'count': f"{np.random.randint(1000, 10000)} قطعة"},
|
248 |
+
{'name': 'زجاج', 'area': f"{np.random.randint(10, 1000)} م²"},
|
249 |
+
{'name': 'خشب', 'volume': f"{np.random.randint(1, 50)} م³"}
|
250 |
+
],
|
251 |
+
'cost_estimate': {
|
252 |
+
'materials': np.random.randint(100000, 1000000),
|
253 |
+
'labor': np.random.randint(50000, 500000),
|
254 |
+
'equipment': np.random.randint(10000, 100000),
|
255 |
+
'total': np.random.randint(200000, 2000000)
|
256 |
+
},
|
257 |
+
'recommendations': [
|
258 |
+
'يمكن تقليل تكلفة المواد باستخدام بدائل أقل تكلفة',
|
259 |
+
'يمكن تحسين كفاءة استخدام المساحة',
|
260 |
+
'يمكن تقليل عدد الأعمدة لتوفير التكلفة',
|
261 |
+
'يمكن تحسين تصميم السلالم لزيادة السلامة',
|
262 |
+
'يمكن تحسين توزيع النوافذ لزيادة الإضاءة الطبيعية'
|
263 |
+
]
|
264 |
+
}
|
265 |
+
|
266 |
+
return results
|
267 |
+
|
268 |
+
def integrate_with_ai_assistant(self, ai_assistant):
|
269 |
+
"""
|
270 |
+
تكامل وحدة تحليل البيانات مع وحدة الذكاء الاصطناعي
|
271 |
+
|
272 |
+
المعلمات:
|
273 |
+
ai_assistant: كائن وحدة الذكاء الاصطناعي
|
274 |
+
|
275 |
+
العوائد:
|
276 |
+
bool: نجاح التكامل
|
277 |
+
"""
|
278 |
+
try:
|
279 |
+
# إضافة وظائف تحليل البيانات إلى وحدة الذكاء الاصطناعي
|
280 |
+
ai_assistant.data_integration = self
|
281 |
+
|
282 |
+
# إضافة دوال التحليل إلى وحدة الذكاء الاصطناعي
|
283 |
+
ai_assistant.analyze_tender_data = self.analyze_tender_data
|
284 |
+
ai_assistant.analyze_historical_data = self.analyze_historical_data
|
285 |
+
ai_assistant.predict_tender_success = self.predict_tender_success
|
286 |
+
ai_assistant.optimize_pricing = self.optimize_pricing
|
287 |
+
ai_assistant.analyze_dwg_files = self.analyze_dwg_files
|
288 |
+
|
289 |
+
return True
|
290 |
+
except Exception as e:
|
291 |
+
print(f"خطأ في تكامل وحدة تحليل البيانات مع وحدة الذكاء الاصطناعي: {str(e)}")
|
292 |
+
return False
|
293 |
+
|
294 |
+
# دوال مساعدة داخلية
|
295 |
+
|
296 |
+
def _get_historical_data(self):
|
297 |
+
"""الحصول على البيانات التاريخية"""
|
298 |
+
# محاكاة البيانات التاريخية
|
299 |
+
np.random.seed(42)
|
300 |
+
|
301 |
+
n_tenders = 50
|
302 |
+
tender_ids = [f"T-{2021 + i//20}-{i%20 + 1:03d}" for i in range(n_tenders)]
|
303 |
+
tender_types = np.random.choice(["مبنى إداري", "مبنى سكني", "مدرسة", "مستشفى", "طرق", "جسور", "بنية تحتية"], n_tenders)
|
304 |
+
tender_locations = np.random.choice(["الرياض", "جدة", "الدمام", "مكة", "المدينة", "أبها", "تبوك"], n_tenders)
|
305 |
+
tender_areas = np.random.randint(1000, 10000, n_tenders)
|
306 |
+
tender_durations = np.random.randint(6, 36, n_tenders)
|
307 |
+
tender_budgets = np.random.randint(1000000, 50000000, n_tenders)
|
308 |
+
tender_costs = np.array([budget * np.random.uniform(0.8, 1.1) for budget in tender_budgets])
|
309 |
+
tender_profits = tender_budgets - tender_costs
|
310 |
+
tender_profit_margins = tender_profits / tender_budgets * 100
|
311 |
+
tender_statuses = np.random.choice(["فائز", "خاسر", "قيد التنفيذ", "منجز"], n_tenders)
|
312 |
+
tender_dates = [f"202{1 + i//20}-{np.random.randint(1, 13):02d}-{np.random.randint(1, 29):02d}" for i in range(n_tenders)]
|
313 |
+
|
314 |
+
# إنشاء DataFrame للمناقصات السابقة
|
315 |
+
tenders_data = {
|
316 |
+
"رقم المناقصة": tender_ids,
|
317 |
+
"نوع المشروع": tender_types,
|
318 |
+
"الموقع": tender_locations,
|
319 |
+
"المساحة (م2)": tender_areas,
|
320 |
+
"المدة (شهر)": tender_durations,
|
321 |
+
"الميزانية (ريال)": tender_budgets,
|
322 |
+
"التكلفة (ريال)": tender_costs,
|
323 |
+
"الربح (ريال)": tender_profits,
|
324 |
+
"هامش الربح (%)": tender_profit_margins,
|
325 |
+
"الحالة": tender_statuses,
|
326 |
+
"تاريخ التقديم": tender_dates
|
327 |
+
}
|
328 |
+
|
329 |
+
return pd.DataFrame(tenders_data)
|
330 |
+
|
331 |
+
def _get_competitors_data(self):
|
332 |
+
"""الحصول على بيانات المنافسين"""
|
333 |
+
# محاكاة بيانات المنافسين
|
334 |
+
n_competitors = 10
|
335 |
+
competitor_ids = [f"C-{i+1:02d}" for i in range(n_competitors)]
|
336 |
+
competitor_names = [
|
337 |
+
"شركة الإنشاءات المتطورة", "شركة البناء الحديث", "شركة التطوير العمراني", "شركة الإعمار الدولية",
|
338 |
+
"شركة البنية التحتية المتكاملة", "شركة المقاولات العامة", "شركة التشييد والبناء", "شركة الهندسة والإنشاءات",
|
339 |
+
"شركة المشاريع الكبرى", "شركة التطوير العقاري"
|
340 |
+
]
|
341 |
+
competitor_specialties = np.random.choice(["مباني", "طرق", "جسور", "بنية تحتية", "متعددة"], n_competitors)
|
342 |
+
competitor_sizes = np.random.choice(["صغيرة", "متوسطة", "كبيرة"], n_competitors)
|
343 |
+
competitor_market_shares = np.random.uniform(1, 15, n_competitors)
|
344 |
+
competitor_win_rates = np.random.uniform(10, 60, n_competitors)
|
345 |
+
competitor_avg_margins = np.random.uniform(5, 20, n_competitors)
|
346 |
+
|
347 |
+
# إنشاء DataFrame للمنافسين
|
348 |
+
competitors_data = {
|
349 |
+
"رمز المنافس": competitor_ids,
|
350 |
+
"اسم المنافس": competitor_names,
|
351 |
+
"التخصص": competitor_specialties,
|
352 |
+
"الحجم": competitor_sizes,
|
353 |
+
"حصة السوق (%)": competitor_market_shares,
|
354 |
+
"معدل الفوز (%)": competitor_win_rates,
|
355 |
+
"متوسط هامش الربح (%)": competitor_avg_margins
|
356 |
+
}
|
357 |
+
|
358 |
+
return pd.DataFrame(competitors_data)
|
359 |
+
|
360 |
+
def _generate_summary(self, df):
|
361 |
+
"""توليد ملخص للبيانات"""
|
362 |
+
# محاكاة توليد ملخص
|
363 |
+
return "تحليل البيانات يشير إلى أن هذه المناقصة تتعلق بمشروع إنشائي متوسط الحجم. تتضمن المناقصة متطلبات فنية متوسطة المستوى وشروط تعاقدية معيارية. بناءً على البيانات التاريخية، هناك فرصة جيدة للفوز بهذه المناقصة إذا تم تقديم عرض تنافسي مع التركيز على الجوانب الفنية والجودة."
|
364 |
+
|
365 |
+
def _generate_recommendations(self, df):
|
366 |
+
"""توليد توصيات بناءً على البيانات"""
|
367 |
+
# محاكاة توليد توصيات
|
368 |
+
return [
|
369 |
+
"تقديم عرض سعر تنافسي يقل بنسبة 5-10% عن الميزانية التقديرية",
|
370 |
+
"التركيز على الخبرات السابقة في مشاريع مماثلة",
|
371 |
+
"تقديم حلول مبتكرة لتقليل مدة التنفيذ",
|
372 |
+
"تعزيز الجوانب الفنية في العرض",
|
373 |
+
"تقديم خطة تنفيذ مفصلة مع جدول زمني واضح"
|
374 |
+
]
|
375 |
+
|
376 |
+
def _analyze_risks(self, df):
|
377 |
+
"""تحليل المخاطر"""
|
378 |
+
# محاكاة تحليل المخاطر
|
379 |
+
return [
|
380 |
+
{"risk": "ارتفاع أسعار المواد", "probability": "متوسطة", "impact": "عالي", "mitigation": "تثبيت أسعار المواد الرئيسية مع الموردين"},
|
381 |
+
{"risk": "تأخر التنفيذ", "probability": "متوسطة", "impact": "عالي", "mitigation": "وضع خطة تنفيذ مفصلة مع هوامش زمنية"},
|
382 |
+
{"risk": "نقص العمالة الماهرة", "probability": "منخفضة", "impact": "متوسط", "mitigation": "التعاقد المسبق مع مقاولي الباطن"},
|
383 |
+
{"risk": "تغيير نطاق العمل", "probability": "متوسطة", "impact": "عالي", "mitigation": "توثيق نطاق العمل بدقة وتحديد إجراءات التغيير"},
|
384 |
+
{"risk": "مشاكل في التربة", "probability": "منخفضة", "impact": "عالي", "mitigation": "إجراء فحوصات شاملة للتربة قبل البدء"}
|
385 |
+
]
|
386 |
+
|
387 |
+
def _analyze_costs(self, df):
|
388 |
+
"""تحليل التكاليف"""
|
389 |
+
# محاكاة تحليل التكاليف
|
390 |
+
total_budget = float(df['الميزانية التقديرية'].iloc[0]) if 'الميزانية التقديرية' in df.columns else 10000000
|
391 |
+
|
392 |
+
# توزيع التكاليف
|
393 |
+
materials_cost = total_budget * 0.6
|
394 |
+
labor_cost = total_budget * 0.25
|
395 |
+
equipment_cost = total_budget * 0.1
|
396 |
+
overhead_cost = total_budget * 0.05
|
397 |
+
|
398 |
+
return {
|
399 |
+
"total_budget": total_budget,
|
400 |
+
"cost_breakdown": [
|
401 |
+
{"category": "المواد", "amount": materials_cost, "percentage": 60},
|
402 |
+
{"category": "العمالة", "amount": labor_cost, "percentage": 25},
|
403 |
+
{"category": "المعدات", "amount": equipment_cost, "percentage": 10},
|
404 |
+
{"category": "المصاريف العا��ة", "amount": overhead_cost, "percentage": 5}
|
405 |
+
],
|
406 |
+
"cost_saving_opportunities": [
|
407 |
+
{"item": "استخدام مواد بديلة", "potential_saving": total_budget * 0.05},
|
408 |
+
{"item": "تحسين إنتاجية العمالة", "potential_saving": total_budget * 0.03},
|
409 |
+
{"item": "تأجير المعدات بدلاً من شرائها", "potential_saving": total_budget * 0.02}
|
410 |
+
]
|
411 |
+
}
|
412 |
+
|
413 |
+
def _analyze_competition(self, df):
|
414 |
+
"""تحليل المنافسة"""
|
415 |
+
# محاكاة تحليل المنافسة
|
416 |
+
return {
|
417 |
+
"expected_competitors": [
|
418 |
+
{"name": "شركة الإنشاءات المتطورة", "strength": "خبرة طويلة في مشاريع مماثلة", "weakness": "أسعار مرتفعة", "win_probability": 30},
|
419 |
+
{"name": "شركة البناء الحديث", "strength": "أسعار تنافسية", "weakness": "خبرة محدودة", "win_probability": 25},
|
420 |
+
{"name": "شركة التطوير العمراني", "strength": "جودة عالية", "weakness": "بطء في التنفيذ", "win_probability": 20}
|
421 |
+
],
|
422 |
+
"competitive_advantages": [
|
423 |
+
"خبرة في مشاريع مماثلة",
|
424 |
+
"فريق فني متميز",
|
425 |
+
"علاقات جيدة مع الموردين",
|
426 |
+
"تقنيات حديثة في التنفيذ"
|
427 |
+
],
|
428 |
+
"competitive_disadvantages": [
|
429 |
+
"محدودية الموارد المالية",
|
430 |
+
"قلة الخبرة في بعض الجوانب الفنية"
|
431 |
+
]
|
432 |
+
}
|
433 |
+
|
434 |
+
def _calculate_win_rate(self, df):
|
435 |
+
"""حساب معدل الفوز"""
|
436 |
+
# محاكاة حساب معدل الفوز
|
437 |
+
if 'الحالة' in df.columns:
|
438 |
+
total_tenders = len(df)
|
439 |
+
won_tenders = len(df[df['الحالة'] == 'فائز'])
|
440 |
+
win_rate = won_tenders / total_tenders * 100 if total_tenders > 0 else 0
|
441 |
+
else:
|
442 |
+
win_rate = 35 # قيمة افتراضية
|
443 |
+
|
444 |
+
return {
|
445 |
+
"overall_win_rate": win_rate,
|
446 |
+
"win_rate_by_type": [
|
447 |
+
{"type": "مبنى إداري", "win_rate": 40},
|
448 |
+
{"type": "مبنى سكني", "win_rate": 35},
|
449 |
+
{"type": "مدرسة", "win_rate": 45},
|
450 |
+
{"type": "مستشفى", "win_rate": 30},
|
451 |
+
{"type": "طرق", "win_rate": 25},
|
452 |
+
{"type": "جسور", "win_rate": 20},
|
453 |
+
{"type": "بنية تحتية", "win_rate": 30}
|
454 |
+
],
|
455 |
+
"win_rate_by_location": [
|
456 |
+
{"location": "الرياض", "win_rate": 40},
|
457 |
+
{"location": "جدة", "win_rate": 35},
|
458 |
+
{"location": "الدمام", "win_rate": 30},
|
459 |
+
{"location": "مكة", "win_rate": 25},
|
460 |
+
{"location": "المدينة", "win_rate": 30},
|
461 |
+
{"location": "أبها", "win_rate": 35},
|
462 |
+
{"location": "تبوك", "win_rate": 40}
|
463 |
+
]
|
464 |
+
}
|
465 |
+
|
466 |
+
def _calculate_avg_profit_margin(self, df):
|
467 |
+
"""حساب متوسط هامش الربح"""
|
468 |
+
# محاكاة حساب متوسط هامش الربح
|
469 |
+
if 'هامش الربح (%)' in df.columns:
|
470 |
+
avg_profit_margin = df['هامش الربح (%)'].mean()
|
471 |
+
else:
|
472 |
+
avg_profit_margin = 15 # قيمة افتراضية
|
473 |
+
|
474 |
+
return {
|
475 |
+
"overall_avg_profit_margin": avg_profit_margin,
|
476 |
+
"profit_margin_by_type": [
|
477 |
+
{"type": "مبنى إداري", "profit_margin": 18},
|
478 |
+
{"type": "مبنى سكني", "profit_margin": 15},
|
479 |
+
{"type": "مدرسة", "profit_margin": 20},
|
480 |
+
{"type": "مستشفى", "profit_margin": 12},
|
481 |
+
{"type": "طرق", "profit_margin": 10},
|
482 |
+
{"type": "جسور", "profit_margin": 8},
|
483 |
+
{"type": "بنية تحتية", "profit_margin": 14}
|
484 |
+
],
|
485 |
+
"profit_margin_by_location": [
|
486 |
+
{"location": "الرياض", "profit_margin": 16},
|
487 |
+
{"location": "جدة", "profit_margin": 14},
|
488 |
+
{"location": "الدمام", "profit_margin": 15},
|
489 |
+
{"location": "مكة", "profit_margin": 12},
|
490 |
+
{"location": "المدينة", "profit_margin": 13},
|
491 |
+
{"location": "أبها", "profit_margin": 18},
|
492 |
+
{"location": "تبوك", "profit_margin": 17}
|
493 |
+
]
|
494 |
+
}
|
495 |
+
|
496 |
+
def _analyze_price_trends(self, df):
|
497 |
+
"""تحليل اتجاهات الأسعار"""
|
498 |
+
# محاكاة تحليل اتجاهات الأسعار
|
499 |
+
return {
|
500 |
+
"price_trends_by_year": [
|
501 |
+
{"year": 2021, "avg_price_per_sqm": 3500},
|
502 |
+
{"year": 2022, "avg_price_per_sqm": 3800},
|
503 |
+
{"year": 2023, "avg_price_per_sqm": 4200},
|
504 |
+
{"year": 2024, "avg_price_per_sqm": 4500}
|
505 |
+
],
|
506 |
+
"price_trends_by_material": [
|
507 |
+
{"material": "خرسانة", "price_change": 15},
|
508 |
+
{"material": "حديد", "price_change": 20},
|
509 |
+
{"material": "أسمنت", "price_change": 10},
|
510 |
+
{"material": "طابوق", "price_change": 5},
|
511 |
+
{"material": "ألمنيوم", "price_change": 25}
|
512 |
+
],
|
513 |
+
"price_forecast": [
|
514 |
+
{"year": 2025, "forecasted_price_change": 8},
|
515 |
+
{"year": 2026, "forecasted_price_change": 5},
|
516 |
+
{"year": 2027, "forecasted_price_change": 3}
|
517 |
+
]
|
518 |
+
}
|
519 |
+
|
520 |
+
def _identify_success_factors(self, df):
|
521 |
+
"""تحديد عوامل النجاح"""
|
522 |
+
# محاكاة تحديد عوامل النجاح
|
523 |
+
return [
|
524 |
+
{"factor": "السعر التنافسي", "importance": 0.8, "description": "تقديم أسعار أقل من المنافسين بنسبة 5-10%"},
|
525 |
+
{"factor": "الجودة الفنية", "importance": 0.7, "description": "تقديم حلول فنية متميزة ومبتكرة"},
|
526 |
+
{"factor": "الخبرة السابقة", "importance": 0.6, "description": "إظهار خبرة سابقة في مشاريع مماثلة"},
|
527 |
+
{"factor": "مدة التنفيذ", "importance": 0.5, "description": "تقديم جدول زمني أقصر من المطلوب"},
|
528 |
+
{"factor": "السمعة", "importance": 0.4, "description": "سمعة جيدة في السوق وعلاقات قوية مع العملاء"}
|
529 |
+
]
|
530 |
+
|
531 |
+
def _generate_visualizations(self, df):
|
532 |
+
"""توليد الرسوم البيانية"""
|
533 |
+
# محاكاة توليد الرسوم البيانية
|
534 |
+
return {
|
535 |
+
"visualization_types": [
|
536 |
+
"توزيع المناقصات حسب النوع",
|
537 |
+
"توزيع المناقصات حسب الموقع",
|
538 |
+
"معدل الفوز حسب النوع",
|
539 |
+
"معدل الفوز حسب الموقع",
|
540 |
+
"متوسط هامش الربح حسب النوع",
|
541 |
+
"متوسط هامش الربح حسب الموقع",
|
542 |
+
"اتجاهات الأسعار عبر الزمن"
|
543 |
+
]
|
544 |
+
}
|
545 |
+
|
546 |
+
def _generate_success_recommendations(self, factors):
|
547 |
+
"""توليد توصيات لزيادة فرص النجاح"""
|
548 |
+
# محاكاة توليد توصيات
|
549 |
+
return [
|
550 |
+
"تخفيض السعر بنسبة 5-10% لزيادة التنافسية",
|
551 |
+
"تعزيز الجوانب الفنية في العرض",
|
552 |
+
"إبراز الخبرات السابقة في مشاريع مماثلة",
|
553 |
+
"تقديم جدول زمني أقصر من المطلوب",
|
554 |
+
"تقديم ضمانات إضافية للجودة"
|
555 |
+
]
|
556 |
+
|
557 |
+
def _analyze_market_position(self, price, competitors_df):
|
558 |
+
"""تحليل الموقف التنافسي في السوق"""
|
559 |
+
# محاكاة تحليل الموقف التنافسي
|
560 |
+
return {
|
561 |
+
"market_position": "متوسط",
|
562 |
+
"price_percentile": 45,
|
563 |
+
"competitors_below": 3,
|
564 |
+
"competitors_above": 7,
|
565 |
+
"price_competitiveness": "عالية"
|
566 |
+
}
|
567 |
+
|
568 |
+
def _generate_pricing_recommendations(self, optimal_price, price_sensitivity):
|
569 |
+
"""توليد توصيات التسعير"""
|
570 |
+
# محاكاة توليد توصيات التسعير
|
571 |
+
return [
|
572 |
+
f"السعر الأمثل: {optimal_price:,.0f} ريال",
|
573 |
+
"تقديم خصم إضافي للعميل المتكرر",
|
574 |
+
"تقديم خيارات دفع مرنة",
|
575 |
+
"تضمين خدمات إضافية لتعزيز القيمة",
|
576 |
+
"تقديم ضمانات إضافية لتبرير السعر"
|
577 |
+
]
|
ai_assistant/document_analyzer.py
ADDED
@@ -0,0 +1,507 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: utf-8 -*-
|
2 |
+
"""
|
3 |
+
وحدة تحليل المستندات المتقدمة
|
4 |
+
|
5 |
+
هذا الملف يحتوي على الفئات المسؤولة عن تحليل المستندات بشكل احترافي
|
6 |
+
باستخدام تقنيات الذكاء الاصطناعي المتقدمة.
|
7 |
+
"""
|
8 |
+
|
9 |
+
# استيراد المكتبات القياسية
|
10 |
+
import os
|
11 |
+
import sys
|
12 |
+
import logging
|
13 |
+
import base64
|
14 |
+
import json
|
15 |
+
import time
|
16 |
+
from io import BytesIO
|
17 |
+
from pathlib import Path
|
18 |
+
from urllib.parse import urlparse
|
19 |
+
from tempfile import NamedTemporaryFile
|
20 |
+
|
21 |
+
# استيراد مكتبة Streamlit
|
22 |
+
import streamlit as st
|
23 |
+
|
24 |
+
# استيراد المكتبات الإضافية
|
25 |
+
import requests
|
26 |
+
from PIL import Image
|
27 |
+
import pandas as pd
|
28 |
+
import numpy as np
|
29 |
+
|
30 |
+
# تكوين التسجيل
|
31 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
32 |
+
logger = logging.getLogger(__name__)
|
33 |
+
|
34 |
+
try:
|
35 |
+
# استيراد مكتبة pdf2image للتعامل مع ملفات PDF
|
36 |
+
from pdf2image import convert_from_path
|
37 |
+
pdf_conversion_available = True
|
38 |
+
except ImportError:
|
39 |
+
pdf_conversion_available = False
|
40 |
+
logger.warning("لم يتم العثور على مكتبة pdf2image. لن يمكن تحويل ملفات PDF إلى صور.")
|
41 |
+
|
42 |
+
|
43 |
+
class TextExtractor:
|
44 |
+
"""فئة استخراج النصوص من المستندات"""
|
45 |
+
|
46 |
+
def __init__(self, config=None):
|
47 |
+
"""تهيئة مستخرج النصوص"""
|
48 |
+
self.config = config or {}
|
49 |
+
|
50 |
+
def extract_from_pdf(self, file_path):
|
51 |
+
"""استخراج النص من ملف PDF"""
|
52 |
+
try:
|
53 |
+
# محاكاة استخراج النص من PDF
|
54 |
+
# في التطبيق الحقيقي، يمكن استخدام مكتبات مثل PyPDF2 أو pdfplumber
|
55 |
+
return f"تم استخراج النص من ملف PDF: {file_path}"
|
56 |
+
except Exception as e:
|
57 |
+
logger.error(f"خطأ في استخراج النص من PDF: {str(e)}")
|
58 |
+
return f"حدث خطأ أثناء استخراج النص: {str(e)}"
|
59 |
+
|
60 |
+
def extract_from_docx(self, file_path):
|
61 |
+
"""استخراج النص من ملف DOCX"""
|
62 |
+
try:
|
63 |
+
# محاكاة استخراج النص من DOCX
|
64 |
+
# في التطبيق الحقيقي، يمكن استخدام مكتبة python-docx
|
65 |
+
return f"تم استخراج النص من ملف DOCX: {file_path}"
|
66 |
+
except Exception as e:
|
67 |
+
logger.error(f"خطأ في استخراج النص من DOCX: {str(e)}")
|
68 |
+
return f"حدث خطأ أثناء استخراج النص: {str(e)}"
|
69 |
+
|
70 |
+
def extract_from_image(self, file_path):
|
71 |
+
"""استخراج النص من صورة باستخدام OCR"""
|
72 |
+
try:
|
73 |
+
# محاكاة استخراج النص من صورة
|
74 |
+
# في التطبيق الحقيقي، يمكن استخدام مكتبة pytesseract
|
75 |
+
return f"تم استخراج النص من صورة: {file_path}"
|
76 |
+
except Exception as e:
|
77 |
+
logger.error(f"خطأ في استخراج النص من صورة: {str(e)}")
|
78 |
+
return f"حدث خطأ أثناء استخراج النص: {str(e)}"
|
79 |
+
|
80 |
+
def extract(self, file_path):
|
81 |
+
"""استخراج النص من ملف بناءً على نوعه"""
|
82 |
+
_, ext = os.path.splitext(file_path)
|
83 |
+
ext = ext.lower()
|
84 |
+
|
85 |
+
if ext == '.pdf':
|
86 |
+
return self.extract_from_pdf(file_path)
|
87 |
+
elif ext in ('.doc', '.docx'):
|
88 |
+
return self.extract_from_docx(file_path)
|
89 |
+
elif ext in ('.jpg', '.jpeg', '.png'):
|
90 |
+
return self.extract_from_image(file_path)
|
91 |
+
else:
|
92 |
+
return "نوع ملف غير مدعوم"
|
93 |
+
|
94 |
+
|
95 |
+
class ItemExtractor:
|
96 |
+
"""فئة استخراج العناصر من المستندات"""
|
97 |
+
|
98 |
+
def __init__(self, config=None):
|
99 |
+
"""تهيئة مستخرج العناصر"""
|
100 |
+
self.config = config or {}
|
101 |
+
|
102 |
+
def extract_tables(self, document):
|
103 |
+
"""استخراج الجداول من المستند"""
|
104 |
+
try:
|
105 |
+
# محاكاة استخراج الجداول
|
106 |
+
# في التطبيق الحقيقي، يمكن استخدام مكتبات مثل camelot-py أو tabula-py
|
107 |
+
return [
|
108 |
+
{
|
109 |
+
"عنوان": "جدول البنود والكميات",
|
110 |
+
"بيانات": [
|
111 |
+
{"البند": "أعمال الحفر", "الكمية": 1000, "الوحدة": "م³", "السعر": 50, "الإجمالي": 50000},
|
112 |
+
{"البند": "أعمال الخرسانة", "الكمية": 500, "الوحدة": "م³", "السعر": 300, "الإجمالي": 150000},
|
113 |
+
{"البند": "أعمال التشطيبات", "الكمية": 2000, "الوحدة": "م²", "السعر": 100, "الإجمالي": 200000}
|
114 |
+
]
|
115 |
+
},
|
116 |
+
{
|
117 |
+
"عنوان": "جدول الجدول الزمني",
|
118 |
+
"بيانات": [
|
119 |
+
{"المرحلة": "التصميم", "المدة": "30 يوم", "تاريخ البدء": "2025-04-01", "تاريخ الانتهاء": "2025-04-30"},
|
120 |
+
{"المرحلة": "الإنشاء", "المدة": "180 يوم", "تاريخ البدء": "2025-05-01", "تاريخ الانتهاء": "2025-10-31"},
|
121 |
+
{"المرحلة": "التسليم", "المدة": "30 يوم", "تاريخ البدء": "2025-11-01", "تاريخ الانتهاء": "2025-11-30"}
|
122 |
+
]
|
123 |
+
}
|
124 |
+
]
|
125 |
+
except Exception as e:
|
126 |
+
logger.error(f"خطأ في استخراج الجداول: {str(e)}")
|
127 |
+
return []
|
128 |
+
|
129 |
+
def extract_items(self, file_path):
|
130 |
+
"""استخراج البنود من المستند"""
|
131 |
+
try:
|
132 |
+
# محاكاة استخراج البنود
|
133 |
+
return [
|
134 |
+
{"بند": "أعمال الحفر والردم", "قيمة": 250000, "نسبة": "10%"},
|
135 |
+
{"بند": "أعمال الخرسانة المسلحة", "قيمة": 750000, "نسبة": "30%"},
|
136 |
+
{"بند": "أعمال التشطيبات", "قيمة": 500000, "نسبة": "20%"},
|
137 |
+
{"بند": "أعمال الكهرباء", "قيمة": 350000, "نسبة": "14%"},
|
138 |
+
{"بند": "أعمال السباكة", "قيمة": 300000, "نسبة": "12%"},
|
139 |
+
{"بند": "أعمال التكييف", "قيمة": 350000, "نسبة": "14%"}
|
140 |
+
]
|
141 |
+
except Exception as e:
|
142 |
+
logger.error(f"خطأ في استخراج البنود: {str(e)}")
|
143 |
+
return []
|
144 |
+
|
145 |
+
def extract(self, file_path):
|
146 |
+
"""استخراج جميع العناصر من المستند"""
|
147 |
+
return {
|
148 |
+
"بنود": self.extract_items(file_path),
|
149 |
+
"جداول": self.extract_tables(file_path)
|
150 |
+
}
|
151 |
+
|
152 |
+
|
153 |
+
class DocumentParser:
|
154 |
+
"""فئة تحليل المستندات"""
|
155 |
+
|
156 |
+
def __init__(self, config=None):
|
157 |
+
"""تهيئة محلل المستندات"""
|
158 |
+
self.config = config or {}
|
159 |
+
self.text_extractor = TextExtractor(config)
|
160 |
+
self.item_extractor = ItemExtractor(config)
|
161 |
+
|
162 |
+
def parse_contract(self, file_path):
|
163 |
+
"""تحليل مستند عقد"""
|
164 |
+
try:
|
165 |
+
# محاكاة تحليل عقد
|
166 |
+
return {
|
167 |
+
"نوع المستند": "عقد",
|
168 |
+
"معلومات العقد": {
|
169 |
+
"رقم العقد": "CT-2025-001",
|
170 |
+
"تاريخ العقد": "2025-03-15",
|
171 |
+
"قيمة العقد": "2,500,000 ريال",
|
172 |
+
"مدة العقد": "12 شهر",
|
173 |
+
"تاريخ البدء": "2025-04-01",
|
174 |
+
"تاريخ الانتهاء": "2026-03-31"
|
175 |
+
},
|
176 |
+
"أطراف العقد": {
|
177 |
+
"الطرف الأول": "وزارة الإسكان",
|
178 |
+
"الطرف الثاني": "شركة الإنشاءات المتطورة"
|
179 |
+
},
|
180 |
+
"بنود العقد": [
|
181 |
+
"يلتزم الطرف الثاني بتنفيذ المشروع وفقاً للمواصفات والشروط المرفقة",
|
182 |
+
"مدة تنفيذ المشروع 12 شهراً من تاريخ استلام الموقع",
|
183 |
+
"قيمة العقد 2,500,000 ريال شاملة جميع الضرائب والرسوم",
|
184 |
+
"يتم الدفع على دفعات شهرية حسب نسبة الإنجاز",
|
185 |
+
"غرامة التأخير 1% من قيمة العقد عن كل أسبوع تأخير بحد أقصى 10%"
|
186 |
+
],
|
187 |
+
"المرفقات": [
|
188 |
+
"جدول الكميات",
|
189 |
+
"المواصفات الفنية",
|
190 |
+
"الجدول الزمني",
|
191 |
+
"الضمانات والتأمينات"
|
192 |
+
],
|
193 |
+
"درجة الثقة": "95%"
|
194 |
+
}
|
195 |
+
except Exception as e:
|
196 |
+
logger.error(f"خطأ في تحليل العقد: {str(e)}")
|
197 |
+
return {"error": f"حدث خطأ أثناء تحليل العقد: {str(e)}"}
|
198 |
+
|
199 |
+
def parse_tender(self, file_path):
|
200 |
+
"""تحليل مستند مناقصة"""
|
201 |
+
try:
|
202 |
+
# محاكاة تحليل مناقصة
|
203 |
+
return {
|
204 |
+
"نوع المستند": "مناقصة",
|
205 |
+
"معلومات المناقصة": {
|
206 |
+
"رقم المناقصة": "T-2025-002",
|
207 |
+
"اسم المشروع": "إنشاء مبنى إداري",
|
208 |
+
"الجهة المالكة": "وزارة المالية",
|
209 |
+
"تاريخ الطرح": "2025-03-01",
|
210 |
+
"تاريخ الإقفال": "2025-04-15",
|
211 |
+
"القيمة التقديرية": "3,000,000 ريال"
|
212 |
+
},
|
213 |
+
"شروط المناقصة": [
|
214 |
+
"تصنيف المقاول: الدرجة الأولى في مجال المباني",
|
215 |
+
"خبرة سابقة: 5 مشاريع مماثلة خلال الـ 10 سنوات الماضية",
|
216 |
+
"الضمان الابتدائي: 2% من قيمة العطاء",
|
217 |
+
"الضمان النهائي: 5% من قيمة العقد",
|
218 |
+
"مدة تنفيذ المشروع: 18 شهراً"
|
219 |
+
],
|
220 |
+
"المستندات المطلوبة": [
|
221 |
+
"شهادة التصنيف",
|
222 |
+
"السجل التجاري",
|
223 |
+
"شهادة الزكاة والدخل",
|
224 |
+
"شهادة التأمينات الاجتماعية",
|
225 |
+
"قائمة المشاريع المماثلة"
|
226 |
+
],
|
227 |
+
"معايير التقييم": [
|
228 |
+
{"المعيار": "السعر", "الوزن": "50%"},
|
229 |
+
{"المعيار": "الخبرة الفنية", "الوزن": "25%"},
|
230 |
+
{"المعيار": "الجدول الزمني", "الوزن": "15%"},
|
231 |
+
{"المعيار": "فريق العمل", "الوزن": "10%"}
|
232 |
+
],
|
233 |
+
"درجة الثقة": "92%"
|
234 |
+
}
|
235 |
+
except Exception as e:
|
236 |
+
logger.error(f"خطأ في تحليل المناقصة: {str(e)}")
|
237 |
+
return {"error": f"حدث خطأ أثناء تحليل المناقصة: {str(e)}"}
|
238 |
+
|
239 |
+
def parse_specifications(self, file_path):
|
240 |
+
"""تحليل كراسة الشروط والمواصفات"""
|
241 |
+
try:
|
242 |
+
# محاكاة تحليل كراسة الشروط والمواصفات
|
243 |
+
return {
|
244 |
+
"نوع المستند": "كراسة شروط ومواصفات",
|
245 |
+
"معلومات المشروع": {
|
246 |
+
"اسم المشروع": "إنشاء مبنى إداري",
|
247 |
+
"الموقع": "الرياض - حي العليا",
|
248 |
+
"المساحة": "5000 متر مربع",
|
249 |
+
"عدد الطوابق": "5 طوابق"
|
250 |
+
},
|
251 |
+
"المواصفات الفنية": {
|
252 |
+
"الهيكل الإنشائي": "خرسانة مسلحة",
|
253 |
+
"الواجهات": "زجاج عاكس وحجر طبيعي",
|
254 |
+
"التشطيبات الداخلية": "رخام للأرضيات، جبس للأسقف، دهانات عالية الجودة للجدران",
|
255 |
+
"أنظمة الكهرباء": "نظام إنارة LED موفر للطاقة، نظام تحكم ذكي",
|
256 |
+
"أنظمة التكييف": "نظام تكييف مركزي مع تحكم منفصل لكل منطقة",
|
257 |
+
"أنظمة السلامة": "نظام إنذار وإطفاء حريق آلي، كاميرات مراقبة، نظام تحكم في الدخول"
|
258 |
+
},
|
259 |
+
"الشروط العامة": [
|
260 |
+
"الالتزام بكود البناء السعودي",
|
261 |
+
"الالتزام بمتطلبات الدفاع المدني",
|
262 |
+
"الالتزام بمتطلبات الاستدامة وكفاءة الطاقة",
|
263 |
+
"تقديم مخططات تنفيذية معتمدة قبل البدء في التنفيذ",
|
264 |
+
"تقديم عينات للمواد للاعتماد قبل التوريد"
|
265 |
+
],
|
266 |
+
"المرفقات": [
|
267 |
+
"المخططات المعمارية",
|
268 |
+
"المخططات الإنشائية",
|
269 |
+
"مخططات الكهرباء",
|
270 |
+
"مخططات التكييف",
|
271 |
+
"مخططات السباكة",
|
272 |
+
"جدول الكميات"
|
273 |
+
],
|
274 |
+
"درجة الثقة": "90%"
|
275 |
+
}
|
276 |
+
except Exception as e:
|
277 |
+
logger.error(f"خطأ في تحليل كراسة الشروط والمواصفات: {str(e)}")
|
278 |
+
return {"error": f"حدث خطأ أثناء تحليل كراسة الشروط والمواصفات: {str(e)}"}
|
279 |
+
|
280 |
+
def parse_dwg(self, file_path):
|
281 |
+
"""تحليل ملف DWG"""
|
282 |
+
try:
|
283 |
+
# محاكاة تحليل ملف DWG
|
284 |
+
return {
|
285 |
+
"نوع المستند": "ملف DWG",
|
286 |
+
"معلومات الملف": {
|
287 |
+
"اسم الملف": os.path.basename(file_path),
|
288 |
+
"حجم الملف": f"{os.path.getsize(file_path) / (1024*1024):.2f} ميجابايت",
|
289 |
+
"تاريخ التعديل": time.ctime(os.path.getmtime(file_path))
|
290 |
+
},
|
291 |
+
"محتويات الملف": {
|
292 |
+
"عدد الطبقات": 15,
|
293 |
+
"عدد الكائنات": 1250,
|
294 |
+
"أبعاد الرسم": "50م × 30م"
|
295 |
+
},
|
296 |
+
"تحليل المساحات": {
|
297 |
+
"المساحة الإجمالية": "4,500 م²",
|
298 |
+
"مساحة البناء": "3,200 م²",
|
299 |
+
"مساحة الخدمات": "800 م²",
|
300 |
+
"مساحة الممرات": "500 م²"
|
301 |
+
},
|
302 |
+
"تحليل العناصر": {
|
303 |
+
"عدد الغرف": 25,
|
304 |
+
"عدد الأبواب": 40,
|
305 |
+
"عدد النوافذ": 30,
|
306 |
+
"عدد الأعمدة": 20
|
307 |
+
},
|
308 |
+
"ملاحظات": [
|
309 |
+
"تصميم يتوافق مع متطلبات كود البناء السعودي",
|
310 |
+
"توزيع جيد للمساحات",
|
311 |
+
"تصميم يراعي متطلبات ذوي الاحتياجات الخاصة",
|
312 |
+
"تصميم يراعي متطلبات السلامة والإخلاء"
|
313 |
+
],
|
314 |
+
"درجة الثقة": "85%"
|
315 |
+
}
|
316 |
+
except Exception as e:
|
317 |
+
logger.error(f"خطأ في تحليل ملف DWG: {str(e)}")
|
318 |
+
return {"error": f"حدث خطأ أثناء تحليل ملف DWG: {str(e)}"}
|
319 |
+
|
320 |
+
def parse(self, file_path):
|
321 |
+
"""تحليل المستند بناءً على نوعه"""
|
322 |
+
try:
|
323 |
+
_, ext = os.path.splitext(file_path)
|
324 |
+
ext = ext.lower()
|
325 |
+
|
326 |
+
# تحديد نوع المستند بناءً على محتواه (محاكاة)
|
327 |
+
file_name = os.path.basename(file_path).lower()
|
328 |
+
|
329 |
+
if ext == '.dwg':
|
330 |
+
return self.parse_dwg(file_path)
|
331 |
+
elif 'contract' in file_name or 'عقد' in file_name:
|
332 |
+
return self.parse_contract(file_path)
|
333 |
+
elif 'tender' in file_name or 'مناقصة' in file_name:
|
334 |
+
return self.parse_tender(file_path)
|
335 |
+
elif 'spec' in file_name or 'شروط' in file_name or 'مواصفات' in file_name:
|
336 |
+
return self.parse_specifications(file_path)
|
337 |
+
else:
|
338 |
+
# تحليل عام للمستند
|
339 |
+
return {
|
340 |
+
"نوع المستند": "مستند عام",
|
341 |
+
"معلومات الملف": {
|
342 |
+
"اسم الملف": os.path.basename(file_path),
|
343 |
+
"حجم الملف": f"{os.path.getsize(file_path) / (1024*1024):.2f} ميجابايت",
|
344 |
+
"تاريخ التعديل": time.ctime(os.path.getmtime(file_path))
|
345 |
+
},
|
346 |
+
"محتوى المستند": {
|
347 |
+
"نص": self.text_extractor.extract(file_path),
|
348 |
+
"عناصر": self.item_extractor.extract(file_path)
|
349 |
+
},
|
350 |
+
"درجة الثقة": "75%"
|
351 |
+
}
|
352 |
+
except Exception as e:
|
353 |
+
logger.error(f"خطأ في تحليل المستند: {str(e)}")
|
354 |
+
return {"error": f"حدث خطأ أثناء تحليل المستند: {str(e)}"}
|
355 |
+
|
356 |
+
|
357 |
+
class AIDocumentAnalyzer:
|
358 |
+
"""فئة تحليل المستندات باستخدام الذكاء الاصطناعي"""
|
359 |
+
|
360 |
+
def __init__(self):
|
361 |
+
"""تهيئة محلل المستندات الذكي"""
|
362 |
+
self.document_parser = DocumentParser()
|
363 |
+
self.api_keys = {}
|
364 |
+
|
365 |
+
def set_api_key(self, provider, key):
|
366 |
+
"""تعيين مفتاح API لمزود خدمة الذكاء الاصطناعي"""
|
367 |
+
self.api_keys[provider] = key
|
368 |
+
|
369 |
+
def get_api_key(self, provider):
|
370 |
+
"""الحصول على مفتاح API لمزود خدمة الذكاء الاصطناعي"""
|
371 |
+
return self.api_keys.get(provider)
|
372 |
+
|
373 |
+
def analyze_document(self, file_path, provider="local"):
|
374 |
+
"""تحليل المستند باستخدام الذكاء الاصطناعي"""
|
375 |
+
try:
|
376 |
+
# تحليل محلي للمستند
|
377 |
+
local_analysis = self.document_parser.parse(file_path)
|
378 |
+
|
379 |
+
if provider == "local":
|
380 |
+
return local_analysis
|
381 |
+
|
382 |
+
# تحليل باستخدام خدمات الذكاء الاصطناعي السحابية
|
383 |
+
if provider == "openai":
|
384 |
+
# محاكاة تحليل باستخدام OpenAI
|
385 |
+
enhanced_analysis = self._enhance_with_openai(local_analysis)
|
386 |
+
return enhanced_analysis
|
387 |
+
elif provider == "claude":
|
388 |
+
# محاكاة تحليل باستخدام Claude
|
389 |
+
enhanced_analysis = self._enhance_with_claude(local_analysis)
|
390 |
+
return enhanced_analysis
|
391 |
+
else:
|
392 |
+
return local_analysis
|
393 |
+
except Exception as e:
|
394 |
+
logger.error(f"خطأ في تحليل المستند: {str(e)}")
|
395 |
+
return {"error": f"حدث خطأ أثناء تحليل المستند: {str(e)}"}
|
396 |
+
|
397 |
+
def _enhance_with_openai(self, analysis):
|
398 |
+
"""تحسين التحليل باستخدام OpenAI"""
|
399 |
+
# محاكاة تحسين التحليل باستخدام OpenAI
|
400 |
+
analysis["مصدر التحليل"] = "OpenAI"
|
401 |
+
analysis["درجة الثقة"] = "98%"
|
402 |
+
|
403 |
+
# إضافة تحليل المخاطر
|
404 |
+
if "تحليل المخاطر" not in analysis:
|
405 |
+
analysis["تحليل المخاطر"] = [
|
406 |
+
{"المخاطرة": "تأخر التوريدات", "الاحتمالية": "متوسطة", "التأثير": "عالي", "استراتيجية التخفيف": "وضع خطة توريدات بديلة"},
|
407 |
+
{"المخاطرة": "زيادة أسعار المواد", "الاحتمالية": "عالية", "التأثير": "عالي", "استراتيجية التخفيف": "تثبيت أسعار المواد الرئيسية مع الموردين"},
|
408 |
+
{"المخاطرة": "نقص العمالة الماهرة", "الاحتمالية": "متوسطة", "التأثير": "متوسط", "استراتيجية التخفيف": "التعاقد المسبق مع مقاولي الباطن"},
|
409 |
+
{"المخاطرة": "تغيير نطاق العمل", "الاحتمالية": "منخفضة", "التأثير": "عالي", "استراتيجية التخفيف": "توثيق نطاق العمل بدقة وإدارة التغيير"}
|
410 |
+
]
|
411 |
+
|
412 |
+
# إضافة توصيات
|
413 |
+
if "التوصيات" not in analysis:
|
414 |
+
analysis["التوصيات"] = [
|
415 |
+
"مراجعة بنود العقد بدقة قبل التوقيع",
|
416 |
+
"التأكد من وضوح نطاق العمل وعدم وجود غموض",
|
417 |
+
"التحقق من توافق المواصفات الفنية مع المعايير المحلية",
|
418 |
+
"وضع خطة إدارة مخاطر شاملة للمشروع",
|
419 |
+
"تخصيص احتياطي للطوارئ بنسبة 10-15% من قيمة المشروع"
|
420 |
+
]
|
421 |
+
|
422 |
+
return analysis
|
423 |
+
|
424 |
+
def _enhance_with_claude(self, analysis):
|
425 |
+
"""تحسين التحليل باستخدام Claude"""
|
426 |
+
# محاكاة تحسين التحليل باستخدام Claude
|
427 |
+
analysis["مصدر التحليل"] = "Claude"
|
428 |
+
analysis["درجة الثقة"] = "97%"
|
429 |
+
|
430 |
+
# إضافة تحليل الفرص
|
431 |
+
if "تحليل الفرص" not in analysis:
|
432 |
+
analysis["تحليل الفرص"] = [
|
433 |
+
{"الفرصة": "تحسين التصميم", "الفائدة": "تقليل التكلفة بنسبة 5-10%", "المتطلبات": "مراجعة هندسية شاملة"},
|
434 |
+
{"الفرصة": "استخدام مواد بديلة", "الفائدة": "تقليل وقت التنفيذ", "المتطلبات": "اعتماد المواصفات الجديدة"},
|
435 |
+
{"الفرصة": "زيادة المحتوى المحلي", "الفائدة": "تحسين التصنيف في برنامج القيمة المضافة", "المتطلبات": "تحديد الموردين المحليين"},
|
436 |
+
{"الفرصة": "تطبيق تقنيات البناء الحديثة", "الفائدة": "تحسين الجودة وتقليل الهدر", "المتطلبات": "تدريب فريق العمل"}
|
437 |
+
]
|
438 |
+
|
439 |
+
# إضافة ملخص تنفيذي
|
440 |
+
if "الملخص التنفيذي" not in analysis:
|
441 |
+
analysis["الملخص التنفيذي"] = """
|
442 |
+
يتضمن هذا المستند تفاصيل مشروع إنشاء مبنى إداري بمساحة إجمالية 5000 متر مربع.
|
443 |
+
المشروع يتكون من 5 طوابق ويتضمن مواصفات فنية عالية الجودة.
|
444 |
+
تقدر تكلفة المشروع بحوالي 3 مليون ريال ومدة التنفيذ 18 شهراً.
|
445 |
+
يتميز المشروع بتصميم يراعي متطلبات الاستدامة وكفاءة الطاقة.
|
446 |
+
تم تحديد عدة مخاطر محتملة للمشروع مع استراتيجيات التخفيف المناسبة.
|
447 |
+
كما تم تحديد عدة فرص لتحسين المشروع من حيث التكلفة والجودة ووقت التنفيذ.
|
448 |
+
"""
|
449 |
+
|
450 |
+
return analysis
|
451 |
+
|
452 |
+
def analyze_dwg(self, file_path, provider="local"):
|
453 |
+
"""تحليل ملف DWG باستخدام الذكاء الاصطناعي"""
|
454 |
+
try:
|
455 |
+
# تحليل محلي لملف DWG
|
456 |
+
local_analysis = self.document_parser.parse_dwg(file_path)
|
457 |
+
|
458 |
+
if provider == "local":
|
459 |
+
return local_analysis
|
460 |
+
|
461 |
+
# تحسين التحليل باستخدام خدمات الذكاء الاصطناعي
|
462 |
+
if provider == "openai" or provider == "claude":
|
463 |
+
# إض��فة تحليل متقدم
|
464 |
+
local_analysis["تحليل متقدم"] = {
|
465 |
+
"تقييم التصميم": "جيد جداً",
|
466 |
+
"كفاءة استخدام المساحة": "90%",
|
467 |
+
"توافق مع المعايير": "متوافق مع كود البناء السعودي",
|
468 |
+
"اقتراحات التحسين": [
|
469 |
+
"تحسين توزيع المساحات لزيادة كفاءة استخدام المساحة",
|
470 |
+
"تحسين تصميم الممرات لتسهيل الحركة",
|
471 |
+
"إضافة عناصر تصميم مستدامة لتقليل استهلاك الطاقة",
|
472 |
+
"تحسين تصميم الواجهات لزيادة الإضاءة الطبيعية"
|
473 |
+
]
|
474 |
+
}
|
475 |
+
|
476 |
+
# إضافة تقدير التكلفة
|
477 |
+
local_analysis["تقدير التكلفة"] = {
|
478 |
+
"التكلفة الإجمالية التقديرية": "3,200,000 ريال",
|
479 |
+
"تكلفة المتر المربع": "800 ريال",
|
480 |
+
"توزيع التكلفة": [
|
481 |
+
{"البند": "الهيكل الإنشائي", "النسبة": "35%", "القيمة": "1,120,000 ريال"},
|
482 |
+
{"البند": "التشطيبات", "النسبة": "25%", "القيمة": "800,000 ريال"},
|
483 |
+
{"البند": "الأنظمة الكهربائية", "النسبة": "15%", "القيمة": "480,000 ريال"},
|
484 |
+
{"البند": "الأنظمة الميكانيكية", "النسبة": "15%", "القيمة": "480,000 ريال"},
|
485 |
+
{"البند": "الأعمال الخارجية", "النسبة": "10%", "القيمة": "320,000 ريال"}
|
486 |
+
]
|
487 |
+
}
|
488 |
+
|
489 |
+
# إضافة الجدول الزمني
|
490 |
+
local_analysis["الجدول الزمني التقديري"] = {
|
491 |
+
"المدة الإجمالية": "18 شهر",
|
492 |
+
"المراحل": [
|
493 |
+
{"المرحلة": "أعمال الحفر والأساسات", "المدة": "3 أشهر", "النسبة": "15%"},
|
494 |
+
{"المرحلة": "الهيكل الإنشائي", "المدة": "6 أشهر", "النسبة": "35%"},
|
495 |
+
{"المرحلة": "التشطيبات الداخلية", "المدة": "5 أشهر", "النسبة": "25%"},
|
496 |
+
{"المرحلة": "الأنظمة الكهربائية والميكانيكية", "المدة": "3 أشهر", "النسبة": "15%"},
|
497 |
+
{"المرحلة": "الأعمال الخارجية والتسليم", "المدة": "1 شهر", "النسبة": "10%"}
|
498 |
+
]
|
499 |
+
}
|
500 |
+
|
501 |
+
local_analysis["مصدر التحليل"] = provider.capitalize()
|
502 |
+
local_analysis["درجة الثقة"] = "95%"
|
503 |
+
|
504 |
+
return local_analysis
|
505 |
+
except Exception as e:
|
506 |
+
logger.error(f"خطأ في تحليل ملف DWG: {str(e)}")
|
507 |
+
return {"error": f"حدث خطأ أثناء تحليل ملف DWG: {str(e)}"}
|
ai_assistant/engineering_drawing_analyzer.py
ADDED
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import cv2
|
3 |
+
import numpy as np
|
4 |
+
from PIL import Image
|
5 |
+
import pytesseract
|
6 |
+
import logging
|
7 |
+
from datetime import datetime
|
8 |
+
|
9 |
+
class EngineeringDrawingAnalyzer:
|
10 |
+
"""محلل الرسومات الهندسية المتقدم"""
|
11 |
+
|
12 |
+
def __init__(self, claude_client=None):
|
13 |
+
self.claude_client = claude_client
|
14 |
+
self.supported_formats = ['.dwg', '.dxf', '.pdf', '.jpg', '.jpeg', '.png']
|
15 |
+
|
16 |
+
def analyze_drawing(self, file_path):
|
17 |
+
"""تحليل الرسم الهندسي"""
|
18 |
+
try:
|
19 |
+
# قراءة الصورة
|
20 |
+
image = cv2.imread(file_path)
|
21 |
+
|
22 |
+
# تحليل الخطوط والأشكال
|
23 |
+
lines, shapes = self._analyze_shapes(image)
|
24 |
+
|
25 |
+
# استخراج النص والأبعاد
|
26 |
+
text, dimensions = self._extract_text_and_dimensions(image)
|
27 |
+
|
28 |
+
# تحليل متقدم باستخدام Claude
|
29 |
+
if self.claude_client:
|
30 |
+
drawing_info = {
|
31 |
+
'lines': lines,
|
32 |
+
'shapes': shapes,
|
33 |
+
'text': text,
|
34 |
+
'dimensions': dimensions
|
35 |
+
}
|
36 |
+
|
37 |
+
analysis_prompt = f"""
|
38 |
+
قم بتحليل معلومات الرسم الهندسي التالية وتقديم:
|
39 |
+
1. تحليل شامل للرسم
|
40 |
+
2. تحديد التخصص (إنشائي، معماري، ميكانيكي، كهربائي)
|
41 |
+
3. تحليل التفاصيل الفنية
|
42 |
+
4. توصيات للتحسين
|
43 |
+
5. تقدير التكاليف
|
44 |
+
|
45 |
+
معلومات الرسم:
|
46 |
+
{json.dumps(drawing_info, indent=2, ensure_ascii=False)}
|
47 |
+
"""
|
48 |
+
|
49 |
+
response = self.claude_client.messages.create(
|
50 |
+
model="claude-3-sonnet-20240229",
|
51 |
+
max_tokens=4000,
|
52 |
+
temperature=0.7,
|
53 |
+
messages=[{"role": "user", "content": analysis_prompt}]
|
54 |
+
)
|
55 |
+
|
56 |
+
ai_analysis = response.content[0].text
|
57 |
+
else:
|
58 |
+
ai_analysis = "تحليل الذكاء الاصطناعي غير متاح"
|
59 |
+
|
60 |
+
return {
|
61 |
+
'file_info': {
|
62 |
+
'path': file_path,
|
63 |
+
'type': os.path.splitext(file_path)[1],
|
64 |
+
'size': os.path.getsize(file_path)
|
65 |
+
},
|
66 |
+
'analysis': {
|
67 |
+
'lines': lines,
|
68 |
+
'shapes': shapes,
|
69 |
+
'text': text,
|
70 |
+
'dimensions': dimensions,
|
71 |
+
'ai_analysis': ai_analysis
|
72 |
+
}
|
73 |
+
}
|
74 |
+
|
75 |
+
except Exception as e:
|
76 |
+
logging.error(f"خطأ في تحليل الرسم: {str(e)}")
|
77 |
+
raise
|
78 |
+
|
79 |
+
def _analyze_shapes(self, image):
|
80 |
+
"""تحليل الخطوط والأشكال"""
|
81 |
+
# تحويل الصورة إلى تدرج رمادي
|
82 |
+
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
83 |
+
|
84 |
+
# كشف الحواف
|
85 |
+
edges = cv2.Canny(gray, 50, 150)
|
86 |
+
|
87 |
+
# كشف الخطوط
|
88 |
+
lines = cv2.HoughLinesP(edges, 1, np.pi/180, 100)
|
89 |
+
|
90 |
+
# كشف الأشكال
|
91 |
+
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
92 |
+
|
93 |
+
shapes = []
|
94 |
+
for contour in contours:
|
95 |
+
approx = cv2.approxPolyDP(contour, 0.04 * cv2.arcLength(contour, True), True)
|
96 |
+
if len(approx) == 3:
|
97 |
+
shapes.append('triangle')
|
98 |
+
elif len(approx) == 4:
|
99 |
+
shapes.append('rectangle')
|
100 |
+
elif len(approx) == 5:
|
101 |
+
shapes.append('pentagon')
|
102 |
+
elif len(approx) > 5:
|
103 |
+
shapes.append('circle')
|
104 |
+
|
105 |
+
return {
|
106 |
+
'line_count': len(lines) if lines is not None else 0,
|
107 |
+
'line_angles': self._analyze_line_angles(lines) if lines is not None else [],
|
108 |
+
'line_lengths': self._analyze_line_lengths(lines) if lines is not None else []
|
109 |
+
}, {
|
110 |
+
'shape_count': len(shapes),
|
111 |
+
'shape_types': dict(Counter(shapes))
|
112 |
+
}
|
113 |
+
|
114 |
+
def _extract_text_and_dimensions(self, image):
|
115 |
+
"""استخراج النص والأبعاد"""
|
116 |
+
# استخراج النص
|
117 |
+
text = pytesseract.image_to_string(image, lang='ara+eng')
|
118 |
+
|
119 |
+
# تحليل الأبعاد
|
120 |
+
dimensions = self._analyze_dimensions_from_text(text)
|
121 |
+
|
122 |
+
return text, dimensions
|
123 |
+
|
124 |
+
def _analyze_dimensions_from_text(self, text):
|
125 |
+
"""تحليل الأبعاد من النص"""
|
126 |
+
import re
|
127 |
+
|
128 |
+
# نمط للبحث عن الأبعاد
|
129 |
+
dimension_pattern = r'(\d+(?:\.\d+)?)\s*(م|متر|سم|مم|m|cm|mm)'
|
130 |
+
|
131 |
+
# البحث عن جميع ��لأبعاد
|
132 |
+
dimensions = re.findall(dimension_pattern, text)
|
133 |
+
|
134 |
+
# تحويل الأبعاد إلى قائمة منظمة
|
135 |
+
processed_dimensions = []
|
136 |
+
for value, unit in dimensions:
|
137 |
+
processed_dimensions.append({
|
138 |
+
'value': float(value),
|
139 |
+
'unit': unit
|
140 |
+
})
|
141 |
+
|
142 |
+
return processed_dimensions
|
document_analysis/analyzer.py
ADDED
@@ -0,0 +1,434 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
وحدة تحليل المستندات لنظام إدارة المناقصات - Hybrid Face
|
3 |
+
"""
|
4 |
+
|
5 |
+
import os
|
6 |
+
import re
|
7 |
+
import logging
|
8 |
+
import threading
|
9 |
+
from pathlib import Path
|
10 |
+
import datetime
|
11 |
+
import json
|
12 |
+
|
13 |
+
# تهيئة السجل
|
14 |
+
logging.basicConfig(
|
15 |
+
level=logging.INFO,
|
16 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
17 |
+
)
|
18 |
+
logger = logging.getLogger('document_analysis')
|
19 |
+
|
20 |
+
class DocumentAnalyzer:
|
21 |
+
"""فئة تحليل المستندات"""
|
22 |
+
|
23 |
+
def __init__(self, config=None):
|
24 |
+
"""تهيئة محلل المستندات"""
|
25 |
+
self.config = config
|
26 |
+
self.analysis_in_progress = False
|
27 |
+
self.current_document = None
|
28 |
+
self.analysis_results = {}
|
29 |
+
|
30 |
+
# إنشاء مجلد المستندات إذا لم يكن موجوداً
|
31 |
+
if config and hasattr(config, 'DOCUMENTS_PATH'):
|
32 |
+
self.documents_path = Path(config.DOCUMENTS_PATH)
|
33 |
+
else:
|
34 |
+
self.documents_path = Path('data/documents')
|
35 |
+
|
36 |
+
if not self.documents_path.exists():
|
37 |
+
self.documents_path.mkdir(parents=True, exist_ok=True)
|
38 |
+
|
39 |
+
def analyze_document(self, document_path, document_type="tender", callback=None):
|
40 |
+
"""تحليل مستند"""
|
41 |
+
if self.analysis_in_progress:
|
42 |
+
logger.warning("هناك عملية تحليل جارية بالفعل")
|
43 |
+
return False
|
44 |
+
|
45 |
+
if not os.path.exists(document_path):
|
46 |
+
logger.error(f"المستند غير موجود: {document_path}")
|
47 |
+
return False
|
48 |
+
|
49 |
+
self.analysis_in_progress = True
|
50 |
+
self.current_document = document_path
|
51 |
+
self.analysis_results = {
|
52 |
+
"document_path": document_path,
|
53 |
+
"document_type": document_type,
|
54 |
+
"analysis_start_time": datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
55 |
+
"status": "جاري التحليل",
|
56 |
+
"items": [],
|
57 |
+
"entities": [],
|
58 |
+
"dates": [],
|
59 |
+
"amounts": [],
|
60 |
+
"risks": []
|
61 |
+
}
|
62 |
+
|
63 |
+
# بدء التحليل في خيط منفصل
|
64 |
+
thread = threading.Thread(
|
65 |
+
target=self._analyze_document_thread,
|
66 |
+
args=(document_path, document_type, callback)
|
67 |
+
)
|
68 |
+
thread.daemon = True
|
69 |
+
thread.start()
|
70 |
+
|
71 |
+
return True
|
72 |
+
|
73 |
+
def _analyze_document_thread(self, document_path, document_type, callback):
|
74 |
+
"""خيط تحليل المستند"""
|
75 |
+
try:
|
76 |
+
# تحديد نوع المستند
|
77 |
+
file_extension = os.path.splitext(document_path)[1].lower()
|
78 |
+
|
79 |
+
if file_extension == '.pdf':
|
80 |
+
self.analysis_results = self._analyze_pdf(document_path, document_type)
|
81 |
+
elif file_extension == '.docx':
|
82 |
+
self._analyze_docx(document_path, document_type)
|
83 |
+
elif file_extension == '.xlsx':
|
84 |
+
self._analyze_xlsx(document_path, document_type)
|
85 |
+
elif file_extension == '.txt':
|
86 |
+
self._analyze_txt(document_path, document_type)
|
87 |
+
else:
|
88 |
+
logger.error(f"نوع المستند غير مدعوم: {file_extension}")
|
89 |
+
self.analysis_results["status"] = "فشل التحليل"
|
90 |
+
self.analysis_results["error"] = "نوع المستند غير مدعوم"
|
91 |
+
|
92 |
+
# تحديث حالة التحليل
|
93 |
+
if self.analysis_results["status"] != "فشل التحليل":
|
94 |
+
self.analysis_results["status"] = "اكتمل التحليل"
|
95 |
+
self.analysis_results["analysis_end_time"] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
96 |
+
|
97 |
+
logger.info(f"اكتمل تحليل المستند: {document_path}")
|
98 |
+
|
99 |
+
except Exception as e:
|
100 |
+
logger.error(f"خطأ في تحليل المستند: {str(e)}")
|
101 |
+
self.analysis_results["status"] = "فشل التحليل"
|
102 |
+
self.analysis_results["error"] = str(e)
|
103 |
+
|
104 |
+
finally:
|
105 |
+
self.analysis_in_progress = False
|
106 |
+
|
107 |
+
# استدعاء دالة الاستجابة إذا تم توفيرها
|
108 |
+
if callback and callable(callback):
|
109 |
+
callback(self.analysis_results)
|
110 |
+
|
111 |
+
def _analyze_pdf(self, document_path, document_type):
|
112 |
+
"""تحليل مستند PDF باستخدام الذكاء الاصطناعي"""
|
113 |
+
try:
|
114 |
+
# استخراج النص من PDF
|
115 |
+
text = self._extract_text_from_pdf(document_path)
|
116 |
+
|
117 |
+
# تحليل متقدم للمستند
|
118 |
+
analysis = {
|
119 |
+
"file_info": {
|
120 |
+
"name": os.path.basename(document_path),
|
121 |
+
"type": "PDF",
|
122 |
+
"size": os.path.getsize(document_path),
|
123 |
+
"pages": self._count_pages(document_path),
|
124 |
+
"create_date": self._get_creation_date(document_path),
|
125 |
+
"modify_date": time.ctime(os.path.getmtime(document_path))
|
126 |
+
},
|
127 |
+
"content_analysis": {
|
128 |
+
"contract_terms": self._analyze_contract_terms(text),
|
129 |
+
"financial_analysis": self._analyze_financial_terms(text),
|
130 |
+
"legal_analysis": self._analyze_legal_terms(text),
|
131 |
+
"risk_analysis": self._analyze_risks(text),
|
132 |
+
"conditions_analysis": self._analyze_conditions(text),
|
133 |
+
"technical_specs": self._analyze_technical_specs(text),
|
134 |
+
"key_dates": self._extract_key_dates(text),
|
135 |
+
"important_figures": self._extract_figures(text),
|
136 |
+
"entities": self._extract_entities(text)
|
137 |
+
},
|
138 |
+
"statistical_analysis": {
|
139 |
+
"word_count": len(text.split()),
|
140 |
+
"unique_terms": self._analyze_unique_terms(text),
|
141 |
+
"topic_distribution": self._analyze_topics(text),
|
142 |
+
"complexity_score": self._calculate_complexity(text)
|
143 |
+
},
|
144 |
+
"compliance_check": {
|
145 |
+
"missing_sections": self._check_missing_sections(text),
|
146 |
+
"required_terms": self._check_required_terms(text),
|
147 |
+
"compliance_score": self._calculate_compliance_score(text)
|
148 |
+
},
|
149 |
+
"summary": self._generate_summary(text),
|
150 |
+
"recommendations": self._generate_recommendations(text),
|
151 |
+
"related_documents": self._find_related_documents(document_path),
|
152 |
+
"version_info": self._get_version_info(document_path)
|
153 |
+
}
|
154 |
+
|
155 |
+
# إضافة تحليل متخصص حسب نوع المستند
|
156 |
+
if document_type == "tender":
|
157 |
+
analysis["tender_analysis"] = self._analyze_tender_specifics(text)
|
158 |
+
elif document_type == "contract":
|
159 |
+
analysis["contract_analysis"] = self._analyze_contract_specifics(text)
|
160 |
+
elif document_type == "technical":
|
161 |
+
analysis["technical_analysis"] = self._analyze_technical_specifics(text)
|
162 |
+
|
163 |
+
return analysis
|
164 |
+
|
165 |
+
except Exception as e:
|
166 |
+
logger.error(f"خطأ في تحليل PDF: {str(e)}")
|
167 |
+
raise
|
168 |
+
|
169 |
+
def _extract_text_from_pdf(self, document_path):
|
170 |
+
"""استخراج النص من ملف PDF (تحتاج إلى مكتبة مثل PyPDF2 أو pdfplumber)"""
|
171 |
+
# Implementation using a PDF processing library like PyPDF2 or pdfplumber is needed here.
|
172 |
+
# This is a placeholder. Replace with actual PDF text extraction.
|
173 |
+
return "Placeholder text extracted from PDF"
|
174 |
+
|
175 |
+
def _analyze_contract_terms(self, text):
|
176 |
+
"""تحليل بنود العقد"""
|
177 |
+
# Implementation for contract term analysis is needed here. This is a placeholder.
|
178 |
+
return "Placeholder contract terms analysis"
|
179 |
+
|
180 |
+
def _analyze_financial_terms(self, text):
|
181 |
+
"""تحليل الجزء المالي"""
|
182 |
+
# Implementation for financial term analysis is needed here. This is a placeholder.
|
183 |
+
return "Placeholder financial terms analysis"
|
184 |
+
|
185 |
+
def _analyze_legal_terms(self, text):
|
186 |
+
"""تحليل القانوني للعقد"""
|
187 |
+
# Implementation for legal term analysis is needed here. This is a placeholder.
|
188 |
+
return "Placeholder legal terms analysis"
|
189 |
+
|
190 |
+
def _analyze_risks(self, text):
|
191 |
+
"""تحليل المخاطر"""
|
192 |
+
# Implementation for risk analysis is needed here. This is a placeholder.
|
193 |
+
return "Placeholder risk analysis"
|
194 |
+
|
195 |
+
def _analyze_conditions(self, text):
|
196 |
+
"""دراسة كراسة الشروط"""
|
197 |
+
# Implementation for conditions analysis is needed here. This is a placeholder.
|
198 |
+
return "Placeholder conditions analysis"
|
199 |
+
|
200 |
+
def _generate_summary(self, text):
|
201 |
+
"""توليد ملخص"""
|
202 |
+
# Implementation for summary generation is needed here. This is a placeholder.
|
203 |
+
return "Placeholder summary"
|
204 |
+
|
205 |
+
def _generate_recommendations(self, text):
|
206 |
+
"""توليد التوصيات"""
|
207 |
+
# Implementation for recommendation generation is needed here. This is a placeholder.
|
208 |
+
return "Placeholder recommendations"
|
209 |
+
|
210 |
+
|
211 |
+
|
212 |
+
def _analyze_docx(self, document_path, document_type):
|
213 |
+
"""تحليل مستند Word"""
|
214 |
+
try:
|
215 |
+
# محاكاة تحليل مستند Word
|
216 |
+
logger.info(f"تحليل مستند Word: {document_path}")
|
217 |
+
|
218 |
+
# في التطبيق الفعلي، سيتم استخدام مكتبة مثل python-docx
|
219 |
+
# لاستخراج النص من ملف Word وتحليله
|
220 |
+
|
221 |
+
# محاكاة استخراج البنود والكيانات والتواريخ والمبالغ والمخاطر
|
222 |
+
# (مشابه لتحليل PDF)
|
223 |
+
self.analysis_results["items"] = [
|
224 |
+
{"id": 1, "name": "توريد معدات", "description": "توريد معدات المشروع", "unit": "مجموعة", "estimated_quantity": 10},
|
225 |
+
{"id": 2, "name": "تركيب المعدات", "description": "تركيب وتشغيل المعدات", "unit": "مجموعة", "estimated_quantity": 10},
|
226 |
+
{"id": 3, "name": "التدريب", "description": "تدريب الموظفين على استخدام المعدات", "unit": "يوم", "estimated_quantity": 20}
|
227 |
+
]
|
228 |
+
|
229 |
+
# محاكاة استخراج الكيانات والتواريخ والمبالغ والمخاطر
|
230 |
+
# (مشابه لتحليل PDF)
|
231 |
+
|
232 |
+
except Exception as e:
|
233 |
+
logger.error(f"خطأ في تحليل مستند Word: {str(e)}")
|
234 |
+
raise
|
235 |
+
|
236 |
+
def _analyze_xlsx(self, document_path, document_type):
|
237 |
+
"""تحليل مستند Excel"""
|
238 |
+
try:
|
239 |
+
# محاكاة تحليل مستند Excel
|
240 |
+
logger.info(f"تحليل مستند Excel: {document_path}")
|
241 |
+
|
242 |
+
# في التطبيق الفعلي، سيتم استخدام مكتبة مثل pandas أو openpyxl
|
243 |
+
# لاستخراج البيانات من ملف Excel وتحليلها
|
244 |
+
|
245 |
+
# محاكاة استخراج البنود
|
246 |
+
self.analysis_results["items"] = [
|
247 |
+
{"id": 1, "name": "بند 1", "description": "وصف البند 1", "unit": "وحدة", "estimated_quantity": 100},
|
248 |
+
{"id": 2, "name": "بند 2", "description": "وصف البند 2", "unit": "وحدة", "estimated_quantity": 200},
|
249 |
+
{"id": 3, "name": "بند 3", "description": "وصف البند 3", "unit": "وحدة", "estimated_quantity": 300}
|
250 |
+
]
|
251 |
+
|
252 |
+
# محاكاة استخراج المبالغ
|
253 |
+
self.analysis_results["amounts"] = [
|
254 |
+
{"type": "item_cost", "amount": 10000, "currency": "SAR", "description": "تكلفة البند 1"},
|
255 |
+
{"type": "item_cost", "amount": 20000, "currency": "SAR", "description": "تكلفة البند 2"},
|
256 |
+
{"type": "item_cost", "amount": 30000, "currency": "SAR", "description": "تكلفة البند 3"}
|
257 |
+
]
|
258 |
+
|
259 |
+
except Exception as e:
|
260 |
+
logger.error(f"خطأ في تحليل مستند Excel: {str(e)}")
|
261 |
+
raise
|
262 |
+
|
263 |
+
def _analyze_txt(self, document_path, document_type):
|
264 |
+
"""تحليل مستند نصي"""
|
265 |
+
try:
|
266 |
+
# محاكاة تحليل مستند نصي
|
267 |
+
logger.info(f"تحليل مستند نصي: {document_path}")
|
268 |
+
|
269 |
+
# في التطبيق الفعلي، سيتم قراءة الملف النصي وتحليله
|
270 |
+
|
271 |
+
# محاكاة استخراج البنود والكيانات والتواريخ والمبالغ والمخاطر
|
272 |
+
# (مشابه للتحليلات الأخرى)
|
273 |
+
|
274 |
+
except Exception as e:
|
275 |
+
logger.error(f"خطأ في تحليل مستند نصي: {str(e)}")
|
276 |
+
raise
|
277 |
+
|
278 |
+
def get_analysis_status(self):
|
279 |
+
"""الحصول على حالة التحليل الحالي"""
|
280 |
+
if not self.analysis_in_progress:
|
281 |
+
if not self.analysis_results:
|
282 |
+
return {"status": "لا يوجد تحليل جارٍ"}
|
283 |
+
else:
|
284 |
+
return {"status": self.analysis_results.get("status", "غير معروف")}
|
285 |
+
|
286 |
+
return {
|
287 |
+
"status": "جاري التحليل",
|
288 |
+
"document_path": self.current_document,
|
289 |
+
"start_time": self.analysis_results.get("analysis_start_time")
|
290 |
+
}
|
291 |
+
|
292 |
+
def get_analysis_results(self):
|
293 |
+
"""الحصول على نتائج التحليل"""
|
294 |
+
return self.analysis_results
|
295 |
+
|
296 |
+
def export_analysis_results(self, output_path=None):
|
297 |
+
"""تصدير نتائج التحليل إلى ملف JSON"""
|
298 |
+
if not self.analysis_results:
|
299 |
+
logger.warning("لا توجد نتائج تحليل للتصدير")
|
300 |
+
return None
|
301 |
+
|
302 |
+
if not output_path:
|
303 |
+
# إنشاء اسم ملف افتراضي
|
304 |
+
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
|
305 |
+
filename = f"analysis_results_{timestamp}.json"
|
306 |
+
output_path = os.path.join(self.documents_path, filename)
|
307 |
+
|
308 |
+
try:
|
309 |
+
with open(output_path, 'w', encoding='utf-8') as f:
|
310 |
+
json.dump(self.analysis_results, f, ensure_ascii=False, indent=4)
|
311 |
+
|
312 |
+
logger.info(f"تم تصدير نتائج التحليل إلى: {output_path}")
|
313 |
+
return output_path
|
314 |
+
|
315 |
+
except Exception as e:
|
316 |
+
logger.error(f"خطأ في تصدير نتائج التحليل: {str(e)}")
|
317 |
+
return None
|
318 |
+
|
319 |
+
def import_analysis_results(self, input_path):
|
320 |
+
"""استيراد نتائج التحليل من ملف JSON"""
|
321 |
+
if not os.path.exists(input_path):
|
322 |
+
logger.error(f"ملف نتائج التحليل غير موجود: {input_path}")
|
323 |
+
return False
|
324 |
+
|
325 |
+
try:
|
326 |
+
with open(input_path, 'r', encoding='utf-8') as f:
|
327 |
+
self.analysis_results = json.load(f)
|
328 |
+
|
329 |
+
logger.info(f"تم استيراد نتائج التحليل من: {input_path}")
|
330 |
+
return True
|
331 |
+
|
332 |
+
except Exception as e:
|
333 |
+
logger.error(f"خطأ في استيراد نتائج التحليل: {str(e)}")
|
334 |
+
return False
|
335 |
+
|
336 |
+
def _count_pages(self, document_path):
|
337 |
+
"""حساب عدد صفحات المستند"""
|
338 |
+
try:
|
339 |
+
import PyPDF2
|
340 |
+
with open(document_path, 'rb') as file:
|
341 |
+
reader = PyPDF2.PdfReader(file)
|
342 |
+
return len(reader.pages)
|
343 |
+
except:
|
344 |
+
return 0
|
345 |
+
|
346 |
+
def _get_creation_date(self, document_path):
|
347 |
+
"""استخراج تاريخ إنشاء المستند"""
|
348 |
+
try:
|
349 |
+
import PyPDF2
|
350 |
+
with open(document_path, 'rb') as file:
|
351 |
+
reader = PyPDF2.PdfReader(file)
|
352 |
+
if '/CreationDate' in reader.metadata:
|
353 |
+
return reader.metadata['/CreationDate']
|
354 |
+
return "غير متوفر"
|
355 |
+
except:
|
356 |
+
return "غير متوفر"
|
357 |
+
|
358 |
+
def _analyze_technical_specs(self, text):
|
359 |
+
"""تحليل المواصفات الفنية"""
|
360 |
+
specs = {
|
361 |
+
"materials": self._extract_materials(text),
|
362 |
+
"measurements": self._extract_measurements(text),
|
363 |
+
"standards": self._extract_standards(text)
|
364 |
+
}
|
365 |
+
return specs
|
366 |
+
|
367 |
+
def _extract_key_dates(self, text):
|
368 |
+
"""استخراج التواريخ المهمة"""
|
369 |
+
import re
|
370 |
+
date_pattern = r'\d{1,2}[-/]\d{1,2}[-/]\d{2,4}'
|
371 |
+
dates = re.findall(date_pattern, text)
|
372 |
+
return list(set(dates))
|
373 |
+
|
374 |
+
def _extract_figures(self, text):
|
375 |
+
"""استخراج الأرقام والقيم المهمة"""
|
376 |
+
import re
|
377 |
+
# البحث عن القيم النقدية
|
378 |
+
currency_pattern = r'[\d,]+\.?\d*\s*(?:ريال|دولار|SAR|USD)'
|
379 |
+
currencies = re.findall(currency_pattern, text)
|
380 |
+
|
381 |
+
# البحث عن النسب المئوية
|
382 |
+
percentage_pattern = r'\d+\.?\d*\s*%'
|
383 |
+
percentages = re.findall(percentage_pattern, text)
|
384 |
+
|
385 |
+
return {
|
386 |
+
"currencies": currencies,
|
387 |
+
"percentages": percentages
|
388 |
+
}
|
389 |
+
|
390 |
+
def _analyze_unique_terms(self, text):
|
391 |
+
"""تحليل المصطلحات الفريدة"""
|
392 |
+
words = set(text.split())
|
393 |
+
return list(words)
|
394 |
+
|
395 |
+
def _calculate_complexity(self, text):
|
396 |
+
"""حساب مستوى تعقيد النص"""
|
397 |
+
words = text.split()
|
398 |
+
avg_word_length = sum(len(word) for word in words) / len(words)
|
399 |
+
sentences = text.split('.')
|
400 |
+
avg_sentence_length = len(words) / len(sentences)
|
401 |
+
|
402 |
+
# حساب درجة التعقيد (1-10)
|
403 |
+
complexity = min((avg_word_length * 0.5 + avg_sentence_length * 0.2), 10)
|
404 |
+
return round(complexity, 2)
|
405 |
+
|
406 |
+
def _check_missing_sections(self, text):
|
407 |
+
"""التحقق من الأقسام المفقودة"""
|
408 |
+
required_sections = [
|
409 |
+
"نطاق العمل",
|
410 |
+
"المواصفات الفنية",
|
411 |
+
"الشروط العامة",
|
412 |
+
"الضمانات",
|
413 |
+
"الغرامات",
|
414 |
+
"شروط الدفع"
|
415 |
+
]
|
416 |
+
|
417 |
+
missing = []
|
418 |
+
for section in required_sections:
|
419 |
+
if section not in text:
|
420 |
+
missing.append(section)
|
421 |
+
|
422 |
+
return missing
|
423 |
+
|
424 |
+
def _find_related_documents(self, document_path):
|
425 |
+
"""البحث عن المستندات المرتبطة"""
|
426 |
+
directory = os.path.dirname(document_path)
|
427 |
+
base_name = os.path.basename(document_path)
|
428 |
+
related = []
|
429 |
+
|
430 |
+
for file in os.listdir(directory):
|
431 |
+
if file != base_name and file.startswith(base_name.split('_')[0]):
|
432 |
+
related.append(file)
|
433 |
+
|
434 |
+
return related
|
document_analysis/document_app.py
ADDED
@@ -0,0 +1,440 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
وحدة تحليل البيانات - التطبيق الرئيسي
|
3 |
+
"""
|
4 |
+
|
5 |
+
import streamlit as st
|
6 |
+
import pandas as pd
|
7 |
+
import numpy as np
|
8 |
+
import matplotlib.pyplot as plt
|
9 |
+
import plotly.express as px
|
10 |
+
import plotly.graph_objects as go
|
11 |
+
from datetime import datetime
|
12 |
+
import time
|
13 |
+
import io
|
14 |
+
import os
|
15 |
+
import json
|
16 |
+
import base64
|
17 |
+
from pathlib import Path
|
18 |
+
|
19 |
+
class DataAnalysisApp:
|
20 |
+
"""وحدة تحليل البيانات"""
|
21 |
+
|
22 |
+
def __init__(self):
|
23 |
+
"""تهيئة وحدة تحليل البيانات"""
|
24 |
+
|
25 |
+
# تهيئة حالة الجلسة
|
26 |
+
if 'uploaded_data' not in st.session_state:
|
27 |
+
st.session_state.uploaded_data = None
|
28 |
+
|
29 |
+
if 'data_sources' not in st.session_state:
|
30 |
+
st.session_state.data_sources = [
|
31 |
+
{
|
32 |
+
'id': 1,
|
33 |
+
'name': 'بيانات المناقصات السابقة',
|
34 |
+
'type': 'CSV',
|
35 |
+
'rows': 250,
|
36 |
+
'columns': 15,
|
37 |
+
'last_updated': '2024-03-01',
|
38 |
+
'description': 'بيانات المناقصات السابقة للشركة خلال الثلاث سنوات الماضية'
|
39 |
+
},
|
40 |
+
{
|
41 |
+
'id': 2,
|
42 |
+
'name': 'بيانات المنافسين',
|
43 |
+
'type': 'Excel',
|
44 |
+
'rows': 120,
|
45 |
+
'columns': 10,
|
46 |
+
'last_updated': '2024-02-15',
|
47 |
+
'description': 'بيانات المنافسين الرئيسيين في السوق وأسعارهم التنافسية'
|
48 |
+
},
|
49 |
+
{
|
50 |
+
'id': 3,
|
51 |
+
'name': 'بيانات أسعار المواد',
|
52 |
+
'type': 'CSV',
|
53 |
+
'rows': 500,
|
54 |
+
'columns': 8,
|
55 |
+
'last_updated': '2024-03-10',
|
56 |
+
'description': 'بيانات أسعار المواد الرئيسية المستخدمة في المشاريع'
|
57 |
+
},
|
58 |
+
{
|
59 |
+
'id': 4,
|
60 |
+
'name': 'بيانات الموردين',
|
61 |
+
'type': 'Excel',
|
62 |
+
'rows': 80,
|
63 |
+
'columns': 12,
|
64 |
+
'last_updated': '2024-02-20',
|
65 |
+
'description': 'بيانات الموردين الرئيسيين وأسعارهم وجودة منتجاتهم'
|
66 |
+
},
|
67 |
+
{
|
68 |
+
'id': 5,
|
69 |
+
'name': 'بيانات المشاريع المنجزة',
|
70 |
+
'type': 'CSV',
|
71 |
+
'rows': 150,
|
72 |
+
'columns': 20,
|
73 |
+
'last_updated': '2024-03-15',
|
74 |
+
'description': 'بيانات المشاريع المنجزة وتكاليفها الفعلية ومدة تنفيذها'
|
75 |
+
}
|
76 |
+
]
|
77 |
+
|
78 |
+
if 'sample_data' not in st.session_state:
|
79 |
+
# إنشاء بيانات افتراضية للمناقصات السابقة
|
80 |
+
np.random.seed(42)
|
81 |
+
|
82 |
+
# إنشاء بيانات المناقصات السابقة
|
83 |
+
n_tenders = 50
|
84 |
+
tender_ids = [f"T-{2021 + i//20}-{i%20 + 1:03d}" for i in range(n_tenders)]
|
85 |
+
tender_types = np.random.choice(["مبنى إداري", "مبنى سكني", "مدرسة", "مستشفى", "طرق", "جسور", "بنية تحتية"], n_tenders)
|
86 |
+
tender_locations = np.random.choice(["الرياض", "جدة", "الدمام", "مكة", "المدينة", "أبها", "تبوك"], n_tenders)
|
87 |
+
tender_areas = np.random.randint(1000, 10000, n_tenders)
|
88 |
+
tender_durations = np.random.randint(6, 36, n_tenders)
|
89 |
+
tender_budgets = np.random.randint(1000000, 50000000, n_tenders)
|
90 |
+
tender_costs = np.array([budget * np.random.uniform(0.8, 1.1) for budget in tender_budgets])
|
91 |
+
tender_profits = tender_budgets - tender_costs
|
92 |
+
tender_profit_margins = tender_profits / tender_budgets * 100
|
93 |
+
tender_statuses = np.random.choice(["فائز", "خاسر", "قيد التنفيذ", "منجز"], n_tenders)
|
94 |
+
tender_dates = [f"202{1 + i//20}-{np.random.randint(1, 13):02d}-{np.random.randint(1, 29):02d}" for i in range(n_tenders)]
|
95 |
+
|
96 |
+
# إنشاء DataFrame للمناقصات السابقة
|
97 |
+
tenders_data = {
|
98 |
+
"رقم المناقصة": tender_ids,
|
99 |
+
"نوع المشروع": tender_types,
|
100 |
+
"الموقع": tender_locations,
|
101 |
+
"المساحة (م2)": tender_areas,
|
102 |
+
"المدة (شهر)": tender_durations,
|
103 |
+
"الميزانية (ريال)": tender_budgets,
|
104 |
+
"التكلفة (ريال)": tender_costs,
|
105 |
+
"الربح (ريال)": tender_profits,
|
106 |
+
"هامش الربح (%)": tender_profit_margins,
|
107 |
+
"الحالة": tender_statuses,
|
108 |
+
"تاريخ التقديم": tender_dates
|
109 |
+
}
|
110 |
+
|
111 |
+
st.session_state.sample_data = {
|
112 |
+
"tenders": pd.DataFrame(tenders_data)
|
113 |
+
}
|
114 |
+
|
115 |
+
# إنشاء بيانات أسعار المواد
|
116 |
+
n_materials = 30
|
117 |
+
material_ids = [f"M-{i+1:03d}" for i in range(n_materials)]
|
118 |
+
material_names = [
|
119 |
+
"خرسانة جاهزة", "حديد تسليح", "طابوق", "أسمنت", "رمل", "بحص", "خشب", "ألمنيوم", "زجاج", "دهان",
|
120 |
+
"سيراميك", "رخام", "جبس", "عازل مائي", "عازل حراري", "أنابيب PVC", "أسلاك كهربائية", "مفاتيح كهربائية",
|
121 |
+
"إنارة", "تكييف", "مصاعد", "أبواب خشبية", "أبواب حديدية", "نوافذ ألمنيوم", "نوافذ زجاجية",
|
122 |
+
"أرضيات خشبية", "أرضيات بلاط", "أرضيات رخام", "أرضيات سيراميك", "أرضيات بورسلين"
|
123 |
+
]
|
124 |
+
material_units = np.random.choice(["م3", "طن", "م2", "كجم", "لتر", "قطعة", "متر"], n_materials)
|
125 |
+
material_prices_2021 = np.random.randint(50, 5000, n_materials)
|
126 |
+
material_prices_2022 = np.array([price * np.random.uniform(1.0, 1.2) for price in material_prices_2021])
|
127 |
+
material_prices_2023 = np.array([price * np.random.uniform(1.0, 1.15) for price in material_prices_2022])
|
128 |
+
material_prices_2024 = np.array([price * np.random.uniform(0.95, 1.1) for price in material_prices_2023])
|
129 |
+
|
130 |
+
# إنشاء DataFrame لأسعار المواد
|
131 |
+
materials_data = {
|
132 |
+
"رمز المادة": material_ids,
|
133 |
+
"اسم المادة": material_names,
|
134 |
+
"الوحدة": material_units,
|
135 |
+
"سعر 2021 (ريال)": material_prices_2021,
|
136 |
+
"سعر 2022 (ريال)": material_prices_2022,
|
137 |
+
"سعر 2023 (ريال)": material_prices_2023,
|
138 |
+
"سعر 2024 (ريال)": material_prices_2024,
|
139 |
+
"نسبة التغير 2021-2024 (%)": (material_prices_2024 - material_prices_2021) / material_prices_2021 * 100
|
140 |
+
}
|
141 |
+
|
142 |
+
st.session_state.sample_data["materials"] = pd.DataFrame(materials_data)
|
143 |
+
|
144 |
+
# إنشاء بيانات المنافسين
|
145 |
+
n_competitors = 10
|
146 |
+
competitor_ids = [f"C-{i+1:02d}" for i in range(n_competitors)]
|
147 |
+
competitor_names = [
|
148 |
+
"شركة الإنشاءات المتطورة", "شركة البناء الحديث", "شركة التطوير العمراني", "شركة الإعمار الدولية",
|
149 |
+
"شركة البنية التحتية المتكاملة", "شركة المقاولات العامة", "شركة التشييد والبناء", "شركة الهندسة والإنشاءات",
|
150 |
+
"شركة المشاريع الكبرى", "شركة التطوير العقاري"
|
151 |
+
]
|
152 |
+
competitor_specialties = np.random.choice(["مباني", "طرق", "جسور", "بنية تحتية", "متعددة"], n_competitors)
|
153 |
+
competitor_sizes = np.random.choice(["صغيرة", "متوسطة", "كبيرة"], n_competitors)
|
154 |
+
competitor_market_shares = np.random.uniform(1, 15, n_competitors)
|
155 |
+
competitor_win_rates = np.random.uniform(10, 60, n_competitors)
|
156 |
+
competitor_avg_margins = np.random.uniform(5, 20, n_competitors)
|
157 |
+
|
158 |
+
# إنشاء DataFrame للمنافسين
|
159 |
+
competitors_data = {
|
160 |
+
"رمز المنافس": competitor_ids,
|
161 |
+
"اسم المنافس": competitor_names,
|
162 |
+
"التخصص": competitor_specialties,
|
163 |
+
"الحجم": competitor_sizes,
|
164 |
+
"حصة السوق (%)": competitor_market_shares,
|
165 |
+
"معدل الفوز (%)": competitor_win_rates,
|
166 |
+
"متوسط هامش الربح (%)": competitor_avg_margins
|
167 |
+
}
|
168 |
+
|
169 |
+
st.session_state.sample_data["competitors"] = pd.DataFrame(competitors_data)
|
170 |
+
|
171 |
+
def run(self):
|
172 |
+
"""
|
173 |
+
تشغيل وحدة تحليل البيانات
|
174 |
+
|
175 |
+
هذه الدالة هي نقطة الدخول الرئيسية لوحدة تحليل البيانات.
|
176 |
+
تقوم بتهيئة واجهة المستخدم وعرض البيانات والتحليلات.
|
177 |
+
"""
|
178 |
+
try:
|
179 |
+
# استخدام مدير التكوين لضبط إعدادات الصفحة
|
180 |
+
from config_manager import ConfigManager
|
181 |
+
config_manager = ConfigManager()
|
182 |
+
config_manager.set_page_config_if_needed(
|
183 |
+
page_title="وحدة تحليل البيانات - نظام المناقصات",
|
184 |
+
page_icon="📊",
|
185 |
+
layout="wide",
|
186 |
+
initial_sidebar_state="expanded"
|
187 |
+
)
|
188 |
+
|
189 |
+
# تطبيق التنسيق المخصص
|
190 |
+
st.markdown("""
|
191 |
+
<style>
|
192 |
+
.module-title {
|
193 |
+
color: #2c3e50;
|
194 |
+
text-align: center;
|
195 |
+
font-size: 2.5rem;
|
196 |
+
margin-bottom: 1rem;
|
197 |
+
padding-bottom: 1rem;
|
198 |
+
border-bottom: 2px solid #3498db;
|
199 |
+
}
|
200 |
+
.stTabs [data-baseweb="tab-list"] {
|
201 |
+
gap: 10px;
|
202 |
+
}
|
203 |
+
.stTabs [data-baseweb="tab"] {
|
204 |
+
height: 50px;
|
205 |
+
white-space: pre-wrap;
|
206 |
+
background-color: #f8f9fa;
|
207 |
+
border-radius: 4px 4px 0px 0px;
|
208 |
+
gap: 1px;
|
209 |
+
padding-top: 10px;
|
210 |
+
padding-bottom: 10px;
|
211 |
+
}
|
212 |
+
.stTabs [aria-selected="true"] {
|
213 |
+
background-color: #3498db;
|
214 |
+
color: white;
|
215 |
+
}
|
216 |
+
</style>
|
217 |
+
""", unsafe_allow_html=True)
|
218 |
+
|
219 |
+
# عرض الشريط الجانبي
|
220 |
+
with st.sidebar:
|
221 |
+
st.image("assets/images/logo.png", width=200)
|
222 |
+
st.markdown("## نظام تحليل المناقصات")
|
223 |
+
st.markdown("### وحدة تحليل البيانات")
|
224 |
+
|
225 |
+
st.markdown("---")
|
226 |
+
|
227 |
+
# إضافة خيارات التصفية العامة
|
228 |
+
st.markdown("### خيارات التصفية العامة")
|
229 |
+
|
230 |
+
# إضافة مزيد من الخيارات حسب الحاجة
|
231 |
+
st.markdown("---")
|
232 |
+
|
233 |
+
# إضافة معلومات المستخدم
|
234 |
+
st.markdown("### معلومات المستخدم")
|
235 |
+
st.markdown("**المستخدم:** مهندس تامر الجوهري")
|
236 |
+
st.markdown("**الدور:** محلل بيانات")
|
237 |
+
st.markdown("**تاريخ آخر دخول:** " + datetime.now().strftime("%Y-%m-%d %H:%M"))
|
238 |
+
|
239 |
+
# عرض واجهة وحدة تحليل البيانات
|
240 |
+
self.render()
|
241 |
+
|
242 |
+
# إضافة معلومات في أسفل الصفحة
|
243 |
+
st.markdown("---")
|
244 |
+
st.markdown("### نظام تحليل المناقصات - وحدة تحليل البيانات")
|
245 |
+
st.markdown("**الإصدار:** 2.0.0")
|
246 |
+
st.markdown("**تاريخ التحديث:** 2024-03-31")
|
247 |
+
st.markdown("**جميع الحقوق محفوظة © 2024**")
|
248 |
+
|
249 |
+
return True
|
250 |
+
|
251 |
+
except Exception as e:
|
252 |
+
st.error(f"حدث خطأ أثناء تشغيل وحدة تحليل البيانات: {str(e)}")
|
253 |
+
return False
|
254 |
+
|
255 |
+
def render(self):
|
256 |
+
"""عرض واجهة وحدة تحليل البيانات"""
|
257 |
+
|
258 |
+
st.markdown("<h1 class='module-title'>وحدة تحليل البيانات</h1>", unsafe_allow_html=True)
|
259 |
+
|
260 |
+
tabs = st.tabs([
|
261 |
+
"لوحة المعلومات",
|
262 |
+
"تحليل المناقصات",
|
263 |
+
"تحليل الأسعار",
|
264 |
+
"تحليل المنافسين",
|
265 |
+
"استيراد وتصدير البيانات"
|
266 |
+
])
|
267 |
+
|
268 |
+
with tabs[0]:
|
269 |
+
self._render_dashboard_tab()
|
270 |
+
|
271 |
+
with tabs[1]:
|
272 |
+
self._render_tenders_analysis_tab()
|
273 |
+
|
274 |
+
with tabs[2]:
|
275 |
+
self._render_price_analysis_tab()
|
276 |
+
|
277 |
+
with tabs[3]:
|
278 |
+
self._render_competitors_analysis_tab()
|
279 |
+
|
280 |
+
with tabs[4]:
|
281 |
+
self._render_import_export_tab()
|
282 |
+
|
283 |
+
def _render_dashboard_tab(self):
|
284 |
+
"""عرض تبويب لوحة المعلومات"""
|
285 |
+
|
286 |
+
st.markdown("### لوحة المعلومات")
|
287 |
+
|
288 |
+
# عرض مؤشرات الأداء الرئيسية
|
289 |
+
st.markdown("#### مؤشرات الأداء الرئيسية")
|
290 |
+
|
291 |
+
# استخراج البيانات اللازمة للمؤشرات
|
292 |
+
tenders_df = st.session_state.sample_data["tenders"]
|
293 |
+
|
294 |
+
# حساب المؤشرات
|
295 |
+
total_tenders = len(tenders_df)
|
296 |
+
won_tenders = len(tenders_df[tenders_df["الحالة"] == "فائز"])
|
297 |
+
win_rate = won_tenders / total_tenders * 100
|
298 |
+
avg_profit_margin = tenders_df["هامش الربح (%)"].mean()
|
299 |
+
total_profit = tenders_df["الربح (ريال)"].sum()
|
300 |
+
|
301 |
+
# عرض المؤشرات
|
302 |
+
col1, col2, col3, col4 = st.columns(4)
|
303 |
+
|
304 |
+
with col1:
|
305 |
+
st.metric("إجمالي المناقصات", f"{total_tenders}")
|
306 |
+
|
307 |
+
with col2:
|
308 |
+
st.metric("معدل الفوز", f"{win_rate:.1f}%")
|
309 |
+
|
310 |
+
with col3:
|
311 |
+
st.metric("متوسط هامش الربح", f"{avg_profit_margin:.1f}%")
|
312 |
+
|
313 |
+
with col4:
|
314 |
+
st.metric("إجمالي الربح", f"{total_profit:,.0f} ريال")
|
315 |
+
|
316 |
+
# عرض توزيع المناقصات حسب الحالة
|
317 |
+
st.markdown("#### توزيع المناقصات حسب الحالة")
|
318 |
+
|
319 |
+
status_counts = tenders_df["الحالة"].value_counts().reset_index()
|
320 |
+
status_counts.columns = ["الحالة", "العدد"]
|
321 |
+
|
322 |
+
fig = px.pie(
|
323 |
+
status_counts,
|
324 |
+
values="العدد",
|
325 |
+
names="الحالة",
|
326 |
+
title="توزيع المناقصات حسب الحالة",
|
327 |
+
color="الحالة",
|
328 |
+
color_discrete_map={
|
329 |
+
"فائز": "#2ecc71",
|
330 |
+
"خاسر": "#e74c3c",
|
331 |
+
"قيد التنفيذ": "#3498db",
|
332 |
+
"منجز": "#f39c12"
|
333 |
+
}
|
334 |
+
)
|
335 |
+
|
336 |
+
st.plotly_chart(fig, use_container_width=True)
|
337 |
+
|
338 |
+
# عرض توزيع المناقصات حسب نوع المشروع
|
339 |
+
st.markdown("#### توزيع المناقصات حسب نوع المشروع")
|
340 |
+
|
341 |
+
type_counts = tenders_df["نوع المشروع"].value_counts().reset_index()
|
342 |
+
type_counts.columns = ["نوع المشروع", "العدد"]
|
343 |
+
|
344 |
+
fig = px.bar(
|
345 |
+
type_counts,
|
346 |
+
x="نوع المشروع",
|
347 |
+
y="العدد",
|
348 |
+
title="توزيع المناقصات حسب نوع المشروع",
|
349 |
+
color="نوع المشروع",
|
350 |
+
text_auto=True
|
351 |
+
)
|
352 |
+
|
353 |
+
st.plotly_chart(fig, use_container_width=True)
|
354 |
+
|
355 |
+
# عرض تطور هامش الربح عبر الزمن
|
356 |
+
st.markdown("#### تطور هامش الربح عبر الزمن")
|
357 |
+
|
358 |
+
# إضافة عمود السنة
|
359 |
+
tenders_df["السنة"] = tenders_df["تاريخ التقديم"].str[:4]
|
360 |
+
|
361 |
+
# حساب متوسط هامش الربح لكل سنة
|
362 |
+
profit_margin_by_year = tenders_df.groupby("السنة")["هامش الربح (%)"].mean().reset_index()
|
363 |
+
|
364 |
+
fig = px.line(
|
365 |
+
profit_margin_by_year,
|
366 |
+
x="السنة",
|
367 |
+
y="هامش الربح (%)",
|
368 |
+
title="تطور متوسط هامش الربح عبر السنوات",
|
369 |
+
markers=True
|
370 |
+
)
|
371 |
+
|
372 |
+
st.plotly_chart(fig, use_container_width=True)
|
373 |
+
|
374 |
+
# عرض توزيع المناقصات حسب الموقع
|
375 |
+
st.markdown("#### توزيع المناقصات حسب الموقع")
|
376 |
+
|
377 |
+
location_counts = tenders_df["الموقع"].value_counts().reset_index()
|
378 |
+
location_counts.columns = ["الموقع", "العدد"]
|
379 |
+
|
380 |
+
fig = px.bar(
|
381 |
+
location_counts,
|
382 |
+
x="الموقع",
|
383 |
+
y="العدد",
|
384 |
+
title="توزيع المناقصات حسب الموقع",
|
385 |
+
color="الموقع",
|
386 |
+
text_auto=True
|
387 |
+
)
|
388 |
+
|
389 |
+
st.plotly_chart(fig, use_container_width=True)
|
390 |
+
|
391 |
+
# عرض العلاقة بين الميزانية والتكلفة
|
392 |
+
st.markdown("#### العلاقة بين الميزانية والتكلفة")
|
393 |
+
|
394 |
+
fig = px.scatter(
|
395 |
+
tenders_df,
|
396 |
+
x="الميزانية (ريال)",
|
397 |
+
y="التكلفة (ريال)",
|
398 |
+
color="الحالة",
|
399 |
+
size="المساحة (م2)",
|
400 |
+
hover_name="رقم المناقصة",
|
401 |
+
hover_data=["نوع المشروع", "الموقع", "هامش الربح (%)"],
|
402 |
+
title="العلاقة بين الميزانية والتكلفة",
|
403 |
+
color_discrete_map={
|
404 |
+
"فائز": "#2ecc71",
|
405 |
+
"خاسر": "#e74c3c",
|
406 |
+
"قيد التنفيذ": "#3498db",
|
407 |
+
"منجز": "#f39c12"
|
408 |
+
}
|
409 |
+
)
|
410 |
+
|
411 |
+
# إضافة خط الميزانية = التكلفة
|
412 |
+
max_value = max(tenders_df["الميزانية (ريال)"].max(), tenders_df["التكلفة (ريال)"].max())
|
413 |
+
fig.add_trace(
|
414 |
+
go.Scatter(
|
415 |
+
x=[0, max_value],
|
416 |
+
y=[0, max_value],
|
417 |
+
mode="lines",
|
418 |
+
line=dict(color="gray", dash="dash"),
|
419 |
+
name="الميزانية = التكلفة"
|
420 |
+
)
|
421 |
+
)
|
422 |
+
st.plotly_chart(fig, use_container_width=True)
|
423 |
+
|
424 |
+
def _render_tenders_analysis_tab(self):
|
425 |
+
"""عرض تبويب تحليل المناقصات"""
|
426 |
+
st.markdown("### تحليل المناقصات")
|
427 |
+
|
428 |
+
def _render_price_analysis_tab(self):
|
429 |
+
"""عرض تبويب تحليل الأسعار"""
|
430 |
+
st.markdown("### تحليل الأسعار")
|
431 |
+
|
432 |
+
def _render_competitors_analysis_tab(self):
|
433 |
+
"""عرض تبويب تحليل المنافسين"""
|
434 |
+
st.markdown("### تحليل المنافسين")
|
435 |
+
|
436 |
+
def _render_import_export_tab(self):
|
437 |
+
"""عرض تبويب استيراد وتصدير البيانات"""
|
438 |
+
st.markdown("### استيراد وتصدير البيانات")
|
439 |
+
|
440 |
+
DocumentAnalysisApp = DataAnalysisApp
|