Reza-galaxy21 commited on
Commit
fa59319
·
verified ·
1 Parent(s): 26ffd74

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +197 -64
app.py CHANGED
@@ -2,17 +2,20 @@ import gradio as gr
2
  import pandas as pd
3
  import json
4
  import os
5
- from typing import Dict, List, Optional
 
 
6
 
7
- # مسیر ذخیره دیتابیس
8
  DB_PATH = "material_db.json"
 
9
 
10
  class ElectricPoleEstimator:
11
  def __init__(self):
12
- # ایجاد فایل دیتابیس اگر وجود ندارد
13
  self._initialize_db()
14
  self.db = self._load_db()
15
  self.rule_engine = RuleEngine()
 
16
 
17
  def _initialize_db(self):
18
  """ایجاد فایل دیتابیس اولیه"""
@@ -49,7 +52,10 @@ class ElectricPoleEstimator:
49
  'quantity_expr': str(row['تعداد']) if pd.notna(row['تعداد']) else None,
50
  'pole_height': float(row['ارتفاع پایه']) if pd.notna(row['ارتفاع پایه']) else None,
51
  'pole_capacity': float(row['توان پایه']) if pd.notna(row['توان پایه']) else None,
52
- 'conductor_type': row['نوع هادی'] if pd.notna(row['نوع هادی']) else None
 
 
 
53
  }
54
  new_items.append(item)
55
 
@@ -59,76 +65,176 @@ class ElectricPoleEstimator:
59
  except Exception as e:
60
  return f"❌ خطا: {str(e)}"
61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  def estimate(self,
63
  pole_height: Optional[float] = None,
64
  pole_capacity: Optional[float] = None,
65
  conductor_type: Optional[str] = None,
66
  config_type: Optional[str] = None) -> List[Dict]:
67
- """محاسبه برآورد"""
68
  context = {
69
  'pole_height': pole_height,
70
  'pole_capacity': pole_capacity,
71
  'conductor_type': conductor_type,
72
  'configuration': config_type
73
  }
 
 
 
 
 
 
 
74
 
75
- # اعمال قوانین
76
- overrides = self.rule_engine.apply_rules(context)
77
- context.update(overrides)
78
-
 
 
 
79
  results = []
80
  for item in self.db:
81
- if self._is_item_required(item, **context):
82
  qty = self._calculate_quantity(item, context)
83
  results.append({
84
- 'کد': item['code'],
85
- 'عنوان': item['title'],
86
- 'مقدار': qty,
87
- 'واحد': item['unit'],
88
- 'شرایط': self._format_conditions(item)
89
  })
90
  return results
91
 
92
- def _is_item_required(self, item: Dict, **context) -> bool:
93
- """بررسی نیاز به آیتم"""
94
- height_ok = (item['pole_height'] is None) or (item['pole_height'] == context.get('pole_height'))
95
- capacity_ok = (item['pole_capacity'] is None) or (item['pole_capacity'] == context.get('pole_capacity'))
96
- conductor_ok = (item['conductor_type'] is None) or (item['conductor_type'] == context.get('conductor_type'))
97
- return height_ok and capacity_ok and conductor_ok
 
 
 
 
 
 
 
 
 
 
 
98
 
99
  def _calculate_quantity(self, item: Dict, context: Dict) -> float:
100
- """محاسبه مقدار"""
101
- if not item['quantity_expr']:
102
- return 1.0
103
  try:
104
- if item['quantity_expr'].startswith('='):
105
- return eval(item['quantity_expr'][1:], {}, context)
106
- return float(item['quantity_expr'])
 
 
 
 
 
 
 
107
  except:
108
- return 1.0
 
109
 
110
- def _format_conditions(self, item: Dict) -> str:
111
  """قالب‌بندی شرایط برای نمایش"""
112
  conditions = []
113
- if item['pole_height']: conditions.append(f"ارتفاع: {item['pole_height']}m")
114
- if item['pole_capacity']: conditions.append(f"توان: {item['pole_capacity']}kVA")
115
- if item['conductor_type']: conditions.append(f"هادی: {item['conductor_type']}")
116
- return " - ".join(conditions) if conditions else "عمومی"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
  class RuleEngine:
119
- """موتور قوانین پیشرفته"""
120
  def __init__(self):
121
  self.rules = [
122
  self._minck_rule,
123
- self._passing_config_rule
 
124
  ]
125
 
126
  def apply_rules(self, context: Dict) -> Dict:
127
- """اعمال تمام قوانین"""
128
  overrides = {}
129
  for rule in self.rules:
130
  overrides.update(rule(context))
131
- return overrides
132
 
133
  def _minck_rule(self, context: Dict) -> Dict:
134
  """قانون سیم مینک"""
@@ -141,22 +247,63 @@ class RuleEngine:
141
  if context.get('configuration') == 'عبوری':
142
  return {'pole_capacity': 400}
143
  return {}
 
 
 
 
 
 
 
 
144
 
145
- # ایجاد نمونه Estimator
146
  estimator = ElectricPoleEstimator()
147
 
148
  # رابط کاربری Gradio
149
- with gr.Blocks(title="برآورد هوشمند مصالح تاسیسات برقی") as demo:
150
- gr.Markdown("## سیستم برآورد هوشمند مصالح تاسیسات برقی")
151
 
152
- with gr.Tab("بارگذاری داده‌ها"):
153
  gr.Markdown("### بارگذاری فایل اکسل جدید")
154
- file_input = gr.File(label="فایل اکسل برآورد", type="filepath")
155
- upload_btn = gr.Button("بارگذاری و ذخیره در دیتابیس")
156
  upload_output = gr.Textbox(label="نتایج بارگذاری")
157
  upload_btn.click(estimator.add_materials, inputs=file_input, outputs=upload_output)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
 
159
- with gr.Tab("محاسبه برآورد"):
160
  with gr.Row():
161
  height = gr.Number(label="ارتفاع پایه (متر)", value=12)
162
  capacity = gr.Number(label="توان پایه (kVA)", value=600)
@@ -170,31 +317,17 @@ with gr.Blocks(title="برآورد هوشمند مصالح تاسیسات برق
170
  choices=["", "عبوری", "انتهایی", "زانویی"],
171
  value=""
172
  )
173
- estimate_btn = gr.Button("محاسبه برآورد")
174
 
175
- results_table = gr.Dataframe(
176
- headers=["کد", "عنوان", "مقدار", "واحد", "شرایط"],
177
- datatype=["str", "str", "number", "str", "str"],
178
- wrap=True
179
  )
180
 
181
- estimate_btn.click(
182
  estimator.estimate,
183
  inputs=[height, capacity, conductor, config],
184
- outputs=results_table
185
- )
186
-
187
- with gr.Tab("مشاهده دیتابیس"):
188
- gr.Markdown("### محتوای فعلی دیتابیس")
189
- db_viewer = gr.Dataframe(
190
- headers=["کد", "عنوان", "واحد", "ارتفاع", "توان", "هادی"],
191
- datatype=["str", "str", "str", "number", "number", "str"],
192
- interactive=False
193
- )
194
- refresh_btn = gr.Button("بروزرسانی نمایش")
195
- refresh_btn.click(
196
- lambda: pd.DataFrame(estimator.db)[['code', 'title', 'unit', 'pole_height', 'pole_capacity', 'conductor_type']],
197
- outputs=db_viewer
198
  )
199
 
200
  if __name__ == "__main__":
 
2
  import pandas as pd
3
  import json
4
  import os
5
+ import re
6
+ import math
7
+ from typing import Dict, List, Optional, Union
8
 
9
+ # تنظیمات پایه
10
  DB_PATH = "material_db.json"
11
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
12
 
13
  class ElectricPoleEstimator:
14
  def __init__(self):
 
15
  self._initialize_db()
16
  self.db = self._load_db()
17
  self.rule_engine = RuleEngine()
18
+ self.nlp_processor = NLPProcessor()
19
 
20
  def _initialize_db(self):
21
  """ایجاد فایل دیتابیس اولیه"""
 
52
  'quantity_expr': str(row['تعداد']) if pd.notna(row['تعداد']) else None,
53
  'pole_height': float(row['ارتفاع پایه']) if pd.notna(row['ارتفاع پایه']) else None,
54
  'pole_capacity': float(row['توان پایه']) if pd.notna(row['توان پایه']) else None,
55
+ 'conductor_type': row['نوع هادی'] if pd.notna(row['نوع هادی']) else None,
56
+ 'conditions': self._parse_conditions(row),
57
+ 'description': row['عنوان'], # استفاده از عنوان به عنوان توضیحات اولیه
58
+ 'keywords': self._generate_keywords(row['عنوان'])
59
  }
60
  new_items.append(item)
61
 
 
65
  except Exception as e:
66
  return f"❌ خطا: {str(e)}"
67
 
68
+ def _parse_conditions(self, row) -> Optional[Dict]:
69
+ """استخراج شرایط از ردیف اکسل"""
70
+ conditions = {}
71
+ if pd.notna(row['تعداد']) and '=' in str(row['تعداد']):
72
+ conditions.update({
73
+ 'type': 'formula',
74
+ 'formula': str(row['تعداد'])[1:] if str(row['تعداد']).startswith('=') else str(row['تعداد'])
75
+ })
76
+ return conditions if conditions else None
77
+
78
+ def _generate_keywords(self, text: str) -> List[str]:
79
+ """تولید کلمات کلیدی از عنوان"""
80
+ persian_keywords = re.findall(r'[\w\-]+', text)
81
+ return list(set(persian_keywords))
82
+
83
  def estimate(self,
84
  pole_height: Optional[float] = None,
85
  pole_capacity: Optional[float] = None,
86
  conductor_type: Optional[str] = None,
87
  config_type: Optional[str] = None) -> List[Dict]:
88
+ """محاسبه برآورد پارامتریک"""
89
  context = {
90
  'pole_height': pole_height,
91
  'pole_capacity': pole_capacity,
92
  'conductor_type': conductor_type,
93
  'configuration': config_type
94
  }
95
+ return self._estimate_with_context(context)
96
+
97
+ def estimate_from_text(self, project_description: str) -> Dict:
98
+ """محاسبه برآورد از توضیحات متنی"""
99
+ features = self.nlp_processor.extract_features(project_description)
100
+ context = self.rule_engine.apply_rules(features)
101
+ estimates = self._estimate_with_context(context)
102
 
103
+ return {
104
+ 'extracted_features': features,
105
+ 'estimates': estimates
106
+ }
107
+
108
+ def _estimate_with_context(self, context: Dict) -> List[Dict]:
109
+ """هسته اصلی محاسبه برآورد"""
110
  results = []
111
  for item in self.db:
112
+ if self._is_item_required(item, context):
113
  qty = self._calculate_quantity(item, context)
114
  results.append({
115
+ 'code': item['code'],
116
+ 'title': item['title'],
117
+ 'quantity': qty,
118
+ 'unit': item['unit'],
119
+ 'conditions': self._format_conditions(item, context)
120
  })
121
  return results
122
 
123
+ def _is_item_required(self, item: Dict, context: Dict) -> bool:
124
+ """بررسی نیاز به آیتم براساس شرایط"""
125
+ # شرایط عمومی
126
+ height_ok = (item.get('pole_height') is None) or (item.get('pole_height') == context.get('pole_height'))
127
+ capacity_ok = (item.get('pole_capacity') is None) or (item.get('pole_capacity') == context.get('pole_capacity'))
128
+ conductor_ok = (item.get('conductor_type') is None) or (item.get('conductor_type') == context.get('conductor_type'))
129
+
130
+ # شرایط پیشرفته
131
+ advanced_ok = True
132
+ if 'conditions' in item and item['conditions']:
133
+ if item['conditions']['type'] == 'formula':
134
+ try:
135
+ advanced_ok = eval(item['conditions'].get('rule', 'True'), {}, context)
136
+ except:
137
+ advanced_ok = True
138
+
139
+ return height_ok and capacity_ok and conductor_ok and advanced_ok
140
 
141
  def _calculate_quantity(self, item: Dict, context: Dict) -> float:
142
+ """محاسبه مقدار با پشتیبانی از تمام حالت‌ها"""
 
 
143
  try:
144
+ # فرمول‌های پیشرفته
145
+ if 'conditions' in item and item['conditions']:
146
+ if item['conditions']['type'] == 'formula':
147
+ return eval(item['conditions']['formula'], {'math': math}, context)
148
+
149
+ # فرمول‌های ساده
150
+ if item.get('quantity_expr'):
151
+ if item['quantity_expr'].startswith('='):
152
+ return eval(item['quantity_expr'][1:], {}, context)
153
+ return float(item['quantity_expr'])
154
  except:
155
+ pass
156
+ return 1.0
157
 
158
+ def _format_conditions(self, item: Dict, context: Dict) -> str:
159
  """قالب‌بندی شرایط برای نمایش"""
160
  conditions = []
161
+ if item.get('pole_height'): conditions.append(f"ارتفاع: {item['pole_height']}m")
162
+ if item.get('pole_capacity'): conditions.append(f"توان: {item['pole_capacity']}kVA")
163
+ if item.get('conductor_type'): conditions.append(f"هادی: {item['conductor_type']}")
164
+
165
+ # نمایش فرمول اگر وجود دارد
166
+ if 'conditions' in item and item['conditions']:
167
+ if item['conditions']['type'] == 'formula':
168
+ conditions.append(f"فرمول: {item['conditions']['formula']}")
169
+
170
+ return " | ".join(conditions) if conditions else "عمومی"
171
+
172
+ class NLPProcessor:
173
+ """پردازشگر هوشمند متن ورودی"""
174
+ def __init__(self):
175
+ self.keyword_rules = {
176
+ 'مینک': ['مینک', 'mink'],
177
+ 'هاینا': ['هاینا', 'haina'],
178
+ 'عبوری': ['عبوری', 'passing'],
179
+ 'خاکی': ['خاکی', 'soil'],
180
+ 'اسفالت': ['اسفالت', 'asphalt']
181
+ }
182
+
183
+ def extract_features(self, text: str) -> Dict:
184
+ """استخراج هوشمند ویژگی‌ها از متن"""
185
+ return {
186
+ 'length': self._extract_length(text),
187
+ 'road_type': self._extract_road_type(text),
188
+ 'conductor_type': self._extract_conductor_type(text),
189
+ 'pole_height': self._extract_pole_height(text),
190
+ 'pole_capacity': self._extract_pole_capacity(text),
191
+ 'keywords': self._extract_keywords(text)
192
+ }
193
+
194
+ def _extract_length(self, text: str) -> Optional[float]:
195
+ match = re.search(r'(\d+)\s*متری', text)
196
+ return float(match.group(1)) if match else None
197
+
198
+ def _extract_road_type(self, text: str) -> Optional[str]:
199
+ for rt, keywords in self.keyword_rules.items():
200
+ if any(kw in text for kw in keywords):
201
+ return rt
202
+ return None
203
+
204
+ def _extract_conductor_type(self, text: str) -> Optional[str]:
205
+ for ct, keywords in [('مینک', self.keyword_rules['مینك']),
206
+ ('هاینا', self.keyword_rules['هاینا'])]:
207
+ if any(kw in text for kw in keywords):
208
+ return ct
209
+ return None
210
+
211
+ def _extract_pole_height(self, text: str) -> Optional[float]:
212
+ matches = re.findall(r'پایه\s*(\d+)', text) or re.findall(r'ارتفاع\s*(\d+)', text)
213
+ return float(matches[0]) if matches else None
214
+
215
+ def _extract_pole_capacity(self, text: str) -> Optional[float]:
216
+ matches = re.findall(r'(\d+)\s*کیلوولت', text) or re.findall(r'(\d+)\s*kV', text)
217
+ return float(matches[0]) if matches else None
218
+
219
+ def _extract_keywords(self, text: str) -> List[str]:
220
+ return [kw for kw, keywords in self.keyword_rules.items()
221
+ if any(k in text for k in keywords)]
222
 
223
  class RuleEngine:
224
+ """موتور قوانین هوشمند"""
225
  def __init__(self):
226
  self.rules = [
227
  self._minck_rule,
228
+ self._passing_config_rule,
229
+ self._road_type_rule
230
  ]
231
 
232
  def apply_rules(self, context: Dict) -> Dict:
233
+ """اعمال تمام قوانین به ترتیب"""
234
  overrides = {}
235
  for rule in self.rules:
236
  overrides.update(rule(context))
237
+ return {**context, **overrides}
238
 
239
  def _minck_rule(self, context: Dict) -> Dict:
240
  """قانون سیم مینک"""
 
247
  if context.get('configuration') == 'عبوری':
248
  return {'pole_capacity': 400}
249
  return {}
250
+
251
+ def _road_type_rule(self, context: Dict) -> Dict:
252
+ """قانون نوع معبر"""
253
+ if context.get('road_type') == 'خاکی':
254
+ return {'length_coefficient': 1.2}
255
+ elif context.get('road_type') == 'اسفالت':
256
+ return {'length_coefficient': 0.9}
257
+ return {}
258
 
259
+ # نمونه اصلی برنامه
260
  estimator = ElectricPoleEstimator()
261
 
262
  # رابط کاربری Gradio
263
+ with gr.Blocks(title="سیستم هوشمند برآورد مصالح تاسیسات برقی", css=".gradio-container {direction: rtl}") as demo:
264
+ gr.Markdown("## 🏗️ سیستم هوشمند برآورد مصالح تاسیسات برقی")
265
 
266
+ with gr.Tab("📤 بارگذاری داده‌ها"):
267
  gr.Markdown("### بارگذاری فایل اکسل جدید")
268
+ file_input = gr.File(label="فایل اکسل برآورد", file_types=[".xlsx"])
269
+ upload_btn = gr.Button("بارگذاری و ذخیره در دیتابیس", variant="primary")
270
  upload_output = gr.Textbox(label="نتایج بارگذاری")
271
  upload_btn.click(estimator.add_materials, inputs=file_input, outputs=upload_output)
272
+
273
+ gr.Markdown("### محتوای فعلی دیتابیس")
274
+ db_viewer = gr.Dataframe(
275
+ headers=["کد", "عنوان", "واحد", "شرایط"],
276
+ datatype=["str", "str", "str", "str"],
277
+ interactive=False
278
+ )
279
+ refresh_btn = gr.Button("بروزرسانی نمایش")
280
+ refresh_btn.click(
281
+ lambda: [{"کد": x["code"], "عنوان": x["title"], "واحد": x["unit"],
282
+ "شرایط": estimator._format_conditions(x, {})} for x in estimator.db],
283
+ outputs=db_viewer
284
+ )
285
+
286
+ with gr.Tab("📝 ورود متنی پروژه"):
287
+ gr.Markdown("### توضیحات کامل پروژه را وارد کنید")
288
+ project_desc = gr.Textbox(label="مثال: نصب ترانس 75 کیلوولت با کابل‌کشی 20 متری در معبر خاکی")
289
+ text_submit = gr.Button("محاسبه برآورد", variant="primary")
290
+
291
+ gr.Markdown("### ویژگی‌های استخراج شده")
292
+ features_output = gr.JSON(label="")
293
+
294
+ gr.Markdown("### لیست مصالح مورد نیاز")
295
+ estimates_output = gr.Dataframe(
296
+ headers=["کد", "عنوان", "مقدار", "واحد"],
297
+ datatype=["str", "str", "number", "str"]
298
+ )
299
+
300
+ text_submit.click(
301
+ estimator.estimate_from_text,
302
+ inputs=project_desc,
303
+ outputs=[features_output, estimates_output]
304
+ )
305
 
306
+ with gr.Tab("⚙️ ورود پارامتری"):
307
  with gr.Row():
308
  height = gr.Number(label="ارتفاع پایه (متر)", value=12)
309
  capacity = gr.Number(label="توان پایه (kVA)", value=600)
 
317
  choices=["", "عبوری", "انتهایی", "زانویی"],
318
  value=""
319
  )
320
+ param_submit = gr.Button("محاسبه برآورد", variant="primary")
321
 
322
+ param_output = gr.Dataframe(
323
+ headers=["کد", "عنوان", "مقدار", "واحد"],
324
+ datatype=["str", "str", "number", "str"]
 
325
  )
326
 
327
+ param_submit.click(
328
  estimator.estimate,
329
  inputs=[height, capacity, conductor, config],
330
+ outputs=param_output
 
 
 
 
 
 
 
 
 
 
 
 
 
331
  )
332
 
333
  if __name__ == "__main__":