Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -19,21 +19,6 @@ class UniversalAnonymizer:
|
|
| 19 |
'percent': 0
|
| 20 |
}
|
| 21 |
self.api_key = os.getenv("OPENAI_API_KEY", "")
|
| 22 |
-
|
| 23 |
-
# لیست عبارات عمومی که نباید ناشناسسازی شوند
|
| 24 |
-
self.common_phrases = [
|
| 25 |
-
'مجمع عمومی عادی سالیانه',
|
| 26 |
-
'مجمع عمومی فوقالعاده',
|
| 27 |
-
'هیئت مدیره',
|
| 28 |
-
'سهامداران محترم',
|
| 29 |
-
'صورتهای مالی',
|
| 30 |
-
'شرکت اصلی',
|
| 31 |
-
'درآمد عملیاتی',
|
| 32 |
-
'سود عملیاتی',
|
| 33 |
-
'زیان انباشته',
|
| 34 |
-
'محصولات گرم',
|
| 35 |
-
'محصولات سرد',
|
| 36 |
-
]
|
| 37 |
|
| 38 |
def anonymize_text(self, original_text, lang='fa'):
|
| 39 |
"""ناشناسسازی جامع با تشخیص خودکار الگوها"""
|
|
@@ -47,17 +32,24 @@ class UniversalAnonymizer:
|
|
| 47 |
|
| 48 |
anonymized = original_text
|
| 49 |
|
| 50 |
-
#
|
| 51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
|
| 53 |
-
# مرحله
|
| 54 |
anonymized = self._anonymize_amounts(anonymized)
|
| 55 |
|
| 56 |
-
# مرحله
|
| 57 |
-
anonymized = self.
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
-
# مرحله
|
| 60 |
-
anonymized = self.
|
| 61 |
|
| 62 |
logger.info(f"✅ Anonymization completed. Found {len(self.mapping_table)} entities.")
|
| 63 |
return anonymized
|
|
@@ -65,329 +57,200 @@ class UniversalAnonymizer:
|
|
| 65 |
except Exception as e:
|
| 66 |
return f"⚠ Error in anonymization: {str(e)}" if lang == 'en' else f"⚠ خطا در ناشناسسازی: {str(e)}"
|
| 67 |
|
| 68 |
-
def
|
| 69 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
r'کمتر از \d+\.?\d* درصد',
|
| 92 |
-
r'بالای \d+\.?\d* درصد',
|
| 93 |
-
r'زیر \d+\.?\d* درصد',
|
| 94 |
-
r'حداقل \d+\.?\d* درصد',
|
| 95 |
-
r'حداکثر \d+\.?\d* درصد',
|
| 96 |
-
|
| 97 |
-
# نمادهای درصد
|
| 98 |
-
r'\d+\.?\d*%',
|
| 99 |
-
r'\d+\.?\d*٪',
|
| 100 |
-
|
| 101 |
-
# درصدهای دهگانی
|
| 102 |
-
r'\d+\.\d+ درصد',
|
| 103 |
-
|
| 104 |
-
# بازههایی با خط فاصله
|
| 105 |
-
r'\d+–\d+٪',
|
| 106 |
]
|
| 107 |
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
def _anonymize_amounts(self, text):
|
| 111 |
-
"""تشخیص و ناشناسسازی
|
| 112 |
-
|
| 113 |
-
# الگوهای جامع مبالغ مالی
|
| 114 |
amount_patterns = [
|
| 115 |
-
# مبالغ با
|
| 116 |
-
r'\d+
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
r'\d
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
r'\d
|
| 126 |
-
r'\d
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
r'\d
|
| 131 |
-
r'\d
|
| 132 |
-
r'
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
r'
|
| 136 |
-
r'بالغ بر \d+\.?\d* همت',
|
| 137 |
-
|
| 138 |
-
# مبالغ ترکیبی با هزار
|
| 139 |
-
r'\d+ هزار و \d+ میلیارد تومان',
|
| 140 |
-
r'\d+ هزار و \d+ میلیارد ریال',
|
| 141 |
-
r'\d+ هزار و \d+ میلیون تومان',
|
| 142 |
-
r'\d+ هزار و \d+ میلیون ریال',
|
| 143 |
-
r'\d+ هزار و \d+ دستگاه',
|
| 144 |
-
r'\d+ هزار و \d+ تن',
|
| 145 |
-
r'\d+ هزار و \d+ نفر',
|
| 146 |
-
|
| 147 |
-
# مبالغ با توضیحات
|
| 148 |
-
r'بیش از \d+ هزار میلیارد تومان',
|
| 149 |
-
r'نزدیک به \d+ هزار میلیارد تومان',
|
| 150 |
-
r'حدود \d+ هزار میلیارد تومان',
|
| 151 |
-
r'کمتر از \d+ هزار میلیارد تومان',
|
| 152 |
-
r'بالغ بر \d+ هزار میلیارد تومان',
|
| 153 |
-
|
| 154 |
-
r'بیش از \d+ میلیارد تومان',
|
| 155 |
-
r'نزدیک به \d+ میلیارد تومان',
|
| 156 |
-
r'حدود \d+ میلیارد تومان',
|
| 157 |
-
r'کمتر از \d+ میلیارد تومان',
|
| 158 |
-
r'بالغ بر \d+ میلیارد تومان',
|
| 159 |
-
|
| 160 |
-
r'بیش از \d+ میلیون تومان',
|
| 161 |
-
r'حدود \d+ میلیون تومان',
|
| 162 |
-
r'نزدیک به \d+ میلیون تومان',
|
| 163 |
-
|
| 164 |
-
# مبالغ ارزی
|
| 165 |
-
r'حدود \d+ میلیون دلار',
|
| 166 |
-
r'بیش از \d+ میلیون دلار',
|
| 167 |
-
r'نزدیک به \d+ میلیون دلار',
|
| 168 |
-
r'\d+ میلیون دلار',
|
| 169 |
-
r'\d+ میلیون یورو',
|
| 170 |
-
|
| 171 |
-
# مبالغ خاص
|
| 172 |
-
r'یکهزار میلیارد تومان',
|
| 173 |
-
r'یکهزار میلیارد تومان',
|
| 174 |
-
r'دویست میلیارد تومان',
|
| 175 |
-
r'سیصد میلیارد تومان',
|
| 176 |
-
r'چهارصد میلیارد تومان',
|
| 177 |
-
r'پانصد میلیارد تومان',
|
| 178 |
-
|
| 179 |
-
# مبالغ با کاما
|
| 180 |
-
r'\d+,\d+ میلیارد تومان',
|
| 181 |
-
r'\d+,\d+ میلیارد ریال',
|
| 182 |
-
r'\d+,\d+ میلیون تومان',
|
| 183 |
-
r'\d+,\d+ میلیون ریال',
|
| 184 |
-
r'\d+,\d+ هزار میلیارد تومان',
|
| 185 |
-
r'\d+,\d+ هزار میلیارد ریال',
|
| 186 |
|
| 187 |
# مبالغ ساده
|
| 188 |
-
r'\d+
|
| 189 |
-
r'\d+ میلیارد ریال',
|
| 190 |
-
r'\d+ میلیون تومان',
|
| 191 |
-
r'\d+ میلیون ریال',
|
| 192 |
-
r'\d+ هزار میلیارد تومان',
|
| 193 |
-
r'\d+ هزار میلیارد ریال',
|
| 194 |
-
r'\d+ هزارمیلیارد تومان',
|
| 195 |
-
r'\d+ هزارمیلیون تومان',
|
| 196 |
-
r'\d+ ریال',
|
| 197 |
-
r'\d+ تومان',
|
| 198 |
-
|
| 199 |
-
# واحدهای تولیدی و اندازهگیری
|
| 200 |
-
r'\d+ هزار تن',
|
| 201 |
-
r'\d+,\d+ هزار تن',
|
| 202 |
-
r'\d+ میلیون تن',
|
| 203 |
-
r'\d+ تن',
|
| 204 |
-
r'\d+ مگا واتی',
|
| 205 |
-
r'\d+ مگاوات',
|
| 206 |
-
r'\d+ کیلووات',
|
| 207 |
-
r'\d+ واحد',
|
| 208 |
-
r'\d+ دستگاه',
|
| 209 |
-
r'\d+ میلیون دستگاه',
|
| 210 |
-
r'\d+ هزار دستگاه',
|
| 211 |
-
|
| 212 |
-
# تعداد افراد
|
| 213 |
-
r'\d+\.?\d* میلیون نفر',
|
| 214 |
-
r'\d+ میلیون نفر',
|
| 215 |
-
r'\d+ هزار نفر',
|
| 216 |
-
r'\d+ نفر',
|
| 217 |
-
|
| 218 |
-
# مبالغ اعشاری
|
| 219 |
-
r'\d+\.\d+ میلیارد تومان',
|
| 220 |
-
r'\d+\.\d+ میلیارد ریال',
|
| 221 |
-
r'\d+\.\d+ میلیون تومان',
|
| 222 |
-
r'\d+\.\d+ میلیون ریال',
|
| 223 |
-
r'\d+\.\d+ هزار میلیارد تومان',
|
| 224 |
-
r'\d+\.\d+ هزار میلیارد ریال',
|
| 225 |
-
r'\d+\.\d+ همت',
|
| 226 |
-
|
| 227 |
-
# بازههای مقداری
|
| 228 |
-
r'\d+ تا \d+ میلیارد تومان',
|
| 229 |
-
r'\d+ تا \d+ میلیون تومان',
|
| 230 |
-
r'\d+ تا \d+ ماه',
|
| 231 |
-
r'\d+ تا \d+ سال',
|
| 232 |
-
r'\d+ الی \d+ میلیارد تومان',
|
| 233 |
]
|
| 234 |
|
| 235 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
|
| 237 |
-
def
|
| 238 |
-
"""تشخیص و ناشناسسازی
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
|
| 240 |
-
|
|
|
|
|
|
|
|
|
|
| 241 |
person_patterns = [
|
| 242 |
-
#
|
| 243 |
-
r'
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
r'
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
r'
|
| 250 |
-
|
| 251 |
-
# نامهای کامل با سید
|
| 252 |
-
r'سید\s+[آ-ی]+\s+[آ-ی]+',
|
| 253 |
-
r'سید\s+[آ-ی]+\s+[آ-ی]+\s+[آ-ی]+',
|
| 254 |
-
r'سیدعلی\s+[آ-ی]+',
|
| 255 |
-
r'سیدمحمد\s+[آ-ی]+',
|
| 256 |
-
r'سیدحسن\s+[آ-ی]+',
|
| 257 |
|
| 258 |
# نامهایی که با مدیرعامل همراه هستند
|
| 259 |
-
r'[آ-ی]+\s+[آ-ی]
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
r'[آ-ی]+\s+[آ-ی]+\s*،?\s*رئیس',
|
| 264 |
-
r'[آ-ی]+\s+[آ-ی]+\s*،?\s*مدیر',
|
| 265 |
-
r'[آ-ی]+\s+[آ-ی]+\s*،?\s*نایب',
|
| 266 |
-
|
| 267 |
-
# نامهای خاص (باید در آخر باشند تا اولویت کمتری داشته باشند)
|
| 268 |
-
r'محمدرضا\s+[آ-ی]+',
|
| 269 |
-
r'علیرضا\s+[آ-ی]+',
|
| 270 |
-
r'محمدعلی\s+[آ-ی]+',
|
| 271 |
-
r'حسینعلی\s+[آ-ی]+',
|
| 272 |
-
|
| 273 |
-
# نام و نام خانوادگی ساده - حداقل 3 حرف برای هر کلمه
|
| 274 |
-
r'[آ-ی]{3,}\s+[آ-ی]{3,}(?:\s+[آ-ی]{3,})?',
|
| 275 |
]
|
| 276 |
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 281 |
|
| 282 |
-
|
|
|
|
|
|
|
|
|
|
| 283 |
company_patterns = [
|
| 284 |
-
#
|
| 285 |
-
r'
|
| 286 |
-
r'
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
r'
|
| 292 |
-
r'
|
| 293 |
-
r'
|
| 294 |
-
r'هلدینگ\s+[آ-ی\s]+\([آ-ی\s]+\)',
|
| 295 |
-
|
| 296 |
-
# شرکتهای با کلمه شرکت
|
| 297 |
-
r'شرکت\s+[آ-ی\s]{5,}',
|
| 298 |
-
r'شرکت\s+گروه\s+[آ-ی\s]+',
|
| 299 |
-
r'شرکت\s+سرمایهگذاری\s+[آ-ی\s]+',
|
| 300 |
-
r'شرکت\s+سرمایه گذاری\s+[آ-ی\s]+',
|
| 301 |
-
r'شرکت\s+بیمه\s+[آ-ی\s]+',
|
| 302 |
-
r'شرکت\s+پتروشیمی\s+[آ-ی\s]+',
|
| 303 |
-
r'شرکت\s+فولاد\s+[آ-ی\s]+',
|
| 304 |
-
r'شرکت\s+پالایش\s+[آ-ی\s]+',
|
| 305 |
-
r'شرکت\s+انرژی\s+[آ-ی\s]+',
|
| 306 |
-
r'شرکت\s+ملی\s+[آ-ی\s]+',
|
| 307 |
-
|
| 308 |
-
# بانکها
|
| 309 |
-
r'بانک\s+[آ-ی\s]{3,}',
|
| 310 |
-
r'موسسه\s+اعتباری\s+[آ-ی\s]+',
|
| 311 |
-
r'موسسه\s+مالی\s+[آ-ی\s]+',
|
| 312 |
-
|
| 313 |
-
# گروهها و هلدینگها
|
| 314 |
-
r'گروه\s+[آ-ی\s]{3,}',
|
| 315 |
-
r'هلدینگ\s+[آ-ی\s]{3,}',
|
| 316 |
-
r'گروه\s+مالی\s+[آ-ی\s]+',
|
| 317 |
-
r'گروه\s+صنعتی\s+[آ-ی\s]+',
|
| 318 |
-
|
| 319 |
-
# صندوقها و سازمانها
|
| 320 |
-
r'صندوق\s+[آ-ی\s]{3,}',
|
| 321 |
-
r'سازمان\s+[آ-ی\s]{3,}',
|
| 322 |
-
r'موسسه\s+[آ-ی\s]{3,}',
|
| 323 |
|
| 324 |
# شرکتهای خاص
|
| 325 |
-
r'[آ-ی]+\s+خودرو',
|
| 326 |
-
r'[آ-ی]+\s+فولاد',
|
| 327 |
-
r'بیمه\s+[آ-ی]+',
|
| 328 |
-
r'[آ-ی]+\s+انرژی',
|
| 329 |
-
r'[آ-ی]+\s+پتروشیمی',
|
| 330 |
-
|
| 331 |
-
# نامهای کوتاه شرکتها (باید در آخر باشند)
|
| 332 |
-
r'همراه\s+اول',
|
| 333 |
-
r'ایرانخودرو',
|
| 334 |
-
r'ایران خودرو',
|
| 335 |
-
r'فولاد\s+مبارکه',
|
| 336 |
-
|
| 337 |
-
# مجلهها و نشریات
|
| 338 |
-
r'مجله\s+[آ-ی\s]+',
|
| 339 |
-
r'نشریه\s+[آ-ی\s]+',
|
| 340 |
-
r'روزنامه\s+[آ-ی\s]+',
|
| 341 |
]
|
| 342 |
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
def _apply_patterns(self, text, patterns, category):
|
| 346 |
-
"""اعمال الگوها و جایگزینی با کدهای ناشناس"""
|
| 347 |
-
|
| 348 |
-
# مرتبسازی الگوها بر اساس طول (طولانیترین اول)
|
| 349 |
-
patterns.sort(key=len, reverse=True)
|
| 350 |
|
| 351 |
-
for pattern in
|
| 352 |
-
matches = list(re.finditer(pattern, text
|
| 353 |
-
|
| 354 |
-
for match in matches:
|
| 355 |
matched_text = match.group(0)
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
if self.
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
self.counters[category] += 1
|
| 366 |
-
code = f"{category}-{self.counters[category]:02d}"
|
| 367 |
-
self.mapping_table[matched_text] = code
|
| 368 |
-
text = text.replace(matched_text, code)
|
| 369 |
-
logger.info(f"{category.title()} replaced: {matched_text} -> {code}")
|
| 370 |
|
| 371 |
return text
|
| 372 |
-
|
| 373 |
-
def _is_common_phrase(self, text):
|
| 374 |
-
"""بررسی اینکه آیا عبارت جزو عبارات عمومی است"""
|
| 375 |
-
for phrase in self.common_phrases:
|
| 376 |
-
if phrase.lower() in text.lower():
|
| 377 |
-
return True
|
| 378 |
-
return False
|
| 379 |
-
|
| 380 |
-
def _is_complete_word(self, text, match):
|
| 381 |
-
"""بررسی اینکه آیا کلمه کامل است یا بخشی از کلمه بزرگتر"""
|
| 382 |
-
start, end = match.span()
|
| 383 |
-
|
| 384 |
-
# بررسی کاراکتر قبل و بعد
|
| 385 |
-
if start > 0 and text[start-1].isalnum():
|
| 386 |
-
return False
|
| 387 |
-
if end < len(text) and text[end].isalnum():
|
| 388 |
-
return False
|
| 389 |
-
|
| 390 |
-
return True
|
| 391 |
|
| 392 |
def send_to_chatgpt(self, anonymized_text, lang='fa'):
|
| 393 |
"""ارسال به ChatGPT"""
|
|
@@ -791,18 +654,57 @@ with gr.Blocks(title="📊 Universal Anonymization System", theme=gr.themes.Soft
|
|
| 791 |
)
|
| 792 |
|
| 793 |
if __name__ == "__main__":
|
| 794 |
-
|
| 795 |
-
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 799 |
|
| 800 |
anonymizer_test = UniversalAnonymizer()
|
| 801 |
-
|
| 802 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 803 |
print(result)
|
| 804 |
-
|
|
|
|
|
|
|
| 805 |
for original, code in anonymizer_test.mapping_table.items():
|
| 806 |
-
print(f"{
|
|
|
|
|
|
|
|
|
|
|
|
|
| 807 |
|
| 808 |
app.launch()
|
|
|
|
| 19 |
'percent': 0
|
| 20 |
}
|
| 21 |
self.api_key = os.getenv("OPENAI_API_KEY", "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
def anonymize_text(self, original_text, lang='fa'):
|
| 24 |
"""ناشناسسازی جامع با تشخیص خودکار الگوها"""
|
|
|
|
| 32 |
|
| 33 |
anonymized = original_text
|
| 34 |
|
| 35 |
+
# ترتیب مهم است: از خاص به عام
|
| 36 |
+
# مرحله 1: نامهای خاص اشخاص (اول از همه)
|
| 37 |
+
anonymized = self._anonymize_specific_persons(anonymized)
|
| 38 |
+
|
| 39 |
+
# مرحله 2: نامهای خاص شرکتها
|
| 40 |
+
anonymized = self._anonymize_specific_companies(anonymized)
|
| 41 |
|
| 42 |
+
# مرحله 3: مبالغ مالی
|
| 43 |
anonymized = self._anonymize_amounts(anonymized)
|
| 44 |
|
| 45 |
+
# مرحله 4: درصدها
|
| 46 |
+
anonymized = self._anonymize_percentages(anonymized)
|
| 47 |
+
|
| 48 |
+
# مرحله 5: نامهای عمومی اشخاص
|
| 49 |
+
anonymized = self._anonymize_general_persons(anonymized)
|
| 50 |
|
| 51 |
+
# مرحله 6: نامهای عمومی شرکتها
|
| 52 |
+
anonymized = self._anonymize_general_companies(anonymized)
|
| 53 |
|
| 54 |
logger.info(f"✅ Anonymization completed. Found {len(self.mapping_table)} entities.")
|
| 55 |
return anonymized
|
|
|
|
| 57 |
except Exception as e:
|
| 58 |
return f"⚠ Error in anonymization: {str(e)}" if lang == 'en' else f"⚠ خطا در ناشناسسازی: {str(e)}"
|
| 59 |
|
| 60 |
+
def _anonymize_specific_persons(self, text):
|
| 61 |
+
"""ناشناسسازی نامهای خاص اشخاص"""
|
| 62 |
+
# نامهای خاص که حتماً باید ناشناس شوند
|
| 63 |
+
specific_names = [
|
| 64 |
+
'مهدی اخوان بهابادی',
|
| 65 |
+
# میتوانید نامهای خاص دیگر را اینجا اضافه کنید
|
| 66 |
+
]
|
| 67 |
|
| 68 |
+
for name in specific_names:
|
| 69 |
+
if name in text:
|
| 70 |
+
if name not in self.mapping_table:
|
| 71 |
+
self.counters['person'] += 1
|
| 72 |
+
code = f"person-{self.counters['person']:02d}"
|
| 73 |
+
self.mapping_table[name] = code
|
| 74 |
+
text = text.replace(name, code)
|
| 75 |
+
logger.info(f"Person replaced: {name} -> {code}")
|
| 76 |
+
|
| 77 |
+
return text
|
| 78 |
+
|
| 79 |
+
def _anonymize_specific_companies(self, text):
|
| 80 |
+
"""ناشناسسازی نامهای خاص شرکتها"""
|
| 81 |
+
# نامهای خاص شرکتها
|
| 82 |
+
specific_companies = [
|
| 83 |
+
'شرکت سرمایه گذاری پارسیان',
|
| 84 |
+
'شرکت سرمایهگذاری پارسیان',
|
| 85 |
+
'بانک پارسیان',
|
| 86 |
+
'گروه مالی پارسیان',
|
| 87 |
+
# میتوانید نامهای خاص دیگر را اینجا اضافه کنید
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
]
|
| 89 |
|
| 90 |
+
for company in specific_companies:
|
| 91 |
+
if company in text:
|
| 92 |
+
if company not in self.mapping_table:
|
| 93 |
+
self.counters['company'] += 1
|
| 94 |
+
code = f"company-{self.counters['company']:02d}"
|
| 95 |
+
self.mapping_table[company] = code
|
| 96 |
+
text = text.replace(company, code)
|
| 97 |
+
logger.info(f"Company replaced: {company} -> {code}")
|
| 98 |
+
|
| 99 |
+
return text
|
| 100 |
|
| 101 |
def _anonymize_amounts(self, text):
|
| 102 |
+
"""تشخیص و ناشناسسازی مبالغ مالی"""
|
| 103 |
+
# الگوهای مبالغ - ترتیب از خاص به عام
|
|
|
|
| 104 |
amount_patterns = [
|
| 105 |
+
# مبالغ با "تومانی" در انتها
|
| 106 |
+
(r'(\d+(?:\.\d+)?)\s+(میلیارد|میلیون|��زار)\s+تومانی', 'amount'),
|
| 107 |
+
|
| 108 |
+
# مبالغ با همت
|
| 109 |
+
(r'(\d+(?:\.\d+)?)\s+همت', 'amount'),
|
| 110 |
+
|
| 111 |
+
# مبالغ با هزار تن
|
| 112 |
+
(r'(\d+(?:\.\d+)?)\s+هزار\s+تن', 'amount'),
|
| 113 |
+
|
| 114 |
+
# مبالغ عادی با میلیارد/میلیون
|
| 115 |
+
(r'(\d+(?:\.\d+)?)\s+(هزار\s+)?میلیارد\s+(تومان|ریال)', 'amount'),
|
| 116 |
+
(r'(\d+(?:\.\d+)?)\s+(هزار\s+)?میلیون\s+(تومان|ریال)', 'amount'),
|
| 117 |
+
(r'(\d+(?:\.\d+)?)\s+هزار\s+(تومان|ریال)', 'amount'),
|
| 118 |
+
|
| 119 |
+
# مبالغ با عبارات اضافی
|
| 120 |
+
(r'بیش از\s+(\d+(?:\.\d+)?)\s+(میلیارد|میلیون|هزار)\s+(تومان|ریال)', 'amount'),
|
| 121 |
+
(r'حدود\s+(\d+(?:\.\d+)?)\s+(میلیارد|میلیون|هزار)\s+(تومان|ریال)', 'amount'),
|
| 122 |
+
(r'نزدیک به\s+(\d+(?:\.\d+)?)\s+(میلیارد|میلیون|هزار)\s+(تومان|ریال)', 'amount'),
|
| 123 |
+
|
| 124 |
+
# واحدهای دیگر
|
| 125 |
+
(r'(\d+(?:\.\d+)?)\s+(تن|کیلوگرم|متر|لیتر|دستگاه|واحد|نفر)', 'amount'),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
|
| 127 |
# مبالغ ساده
|
| 128 |
+
(r'(\d+(?:\.\d+)?)\s+(تومان|ریال)(?!\w)', 'amount'),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
]
|
| 130 |
|
| 131 |
+
for pattern, category in amount_patterns:
|
| 132 |
+
matches = list(re.finditer(pattern, text))
|
| 133 |
+
# از آخر به اول جایگزین میکنیم تا موقعیتها تغییر نکنند
|
| 134 |
+
for match in reversed(matches):
|
| 135 |
+
matched_text = match.group(0)
|
| 136 |
+
if matched_text not in self.mapping_table:
|
| 137 |
+
self.counters[category] += 1
|
| 138 |
+
code = f"{category}-{self.counters[category]:02d}"
|
| 139 |
+
self.mapping_table[matched_text] = code
|
| 140 |
+
# جایگزینی دقیق با استفاده از موقعیت
|
| 141 |
+
start, end = match.span()
|
| 142 |
+
text = text[:start] + code + text[end:]
|
| 143 |
+
logger.info(f"Amount replaced: {matched_text} -> {code}")
|
| 144 |
+
|
| 145 |
+
return text
|
| 146 |
|
| 147 |
+
def _anonymize_percentages(self, text):
|
| 148 |
+
"""تشخیص و ناشناسسازی درصدها"""
|
| 149 |
+
percent_patterns = [
|
| 150 |
+
(r'(\d+(?:\.\d+)?)\s+درصدی', 'percent'),
|
| 151 |
+
(r'(\d+(?:\.\d+)?)\s+درصد', 'percent'),
|
| 152 |
+
(r'(\d+(?:\.\d+)?)\s*%', 'percent'),
|
| 153 |
+
(r'(\d+(?:\.\d+)?)\s*٪', 'percent'),
|
| 154 |
+
(r'منفی\s+(\d+(?:\.\d+)?)\s+درصد', 'percent'),
|
| 155 |
+
(r'بیش از\s+(\d+(?:\.\d+)?)\s+درصد', 'percent'),
|
| 156 |
+
(r'حدود\s+(\d+(?:\.\d+)?)\s+درصد', 'percent'),
|
| 157 |
+
(r'کمتر از\s+(\d+(?:\.\d+)?)\s+درصد', 'percent'),
|
| 158 |
+
]
|
| 159 |
+
|
| 160 |
+
for pattern, category in percent_patterns:
|
| 161 |
+
matches = list(re.finditer(pattern, text))
|
| 162 |
+
for match in reversed(matches):
|
| 163 |
+
matched_text = match.group(0)
|
| 164 |
+
if matched_text not in self.mapping_table:
|
| 165 |
+
self.counters[category] += 1
|
| 166 |
+
code = f"{category}-{self.counters[category]:02d}"
|
| 167 |
+
self.mapping_table[matched_text] = code
|
| 168 |
+
start, end = match.span()
|
| 169 |
+
text = text[:start] + code + text[end:]
|
| 170 |
+
logger.info(f"Percent replaced: {matched_text} -> {code}")
|
| 171 |
|
| 172 |
+
return text
|
| 173 |
+
|
| 174 |
+
def _anonymize_general_persons(self, text):
|
| 175 |
+
"""ناشناسسازی نامهای عمومی اشخاص"""
|
| 176 |
person_patterns = [
|
| 177 |
+
# نام با عنوان
|
| 178 |
+
(r'دکتر\s+[آ-ی]+\s+[آ-ی]+(?:\s+[آ-ی]+)?', 'person'),
|
| 179 |
+
(r'مهندس\s+[آ-ی]+\s+[آ-ی]+(?:\s+[آ-ی]+)?', 'person'),
|
| 180 |
+
(r'آقای\s+[آ-ی]+\s+[آ-ی]+(?:\s+[آ-ی]+)?', 'person'),
|
| 181 |
+
(r'خانم\s+[آ-ی]+\s+[آ-ی]+(?:\s+[آ-ی]+)?', 'person'),
|
| 182 |
+
|
| 183 |
+
# نام با سید
|
| 184 |
+
(r'سید\s*[آ-ی]+\s+[آ-ی]+(?:\s+[آ-ی]+)?', 'person'),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
|
| 186 |
# نامهایی که با مدیرعامل همراه هستند
|
| 187 |
+
(r'[آ-ی]+\s+[آ-ی]+(?:\s+[آ-ی]+)?\s*،?\s*مدیرعامل', 'person'),
|
| 188 |
+
|
| 189 |
+
# نام و نام خانوادگی - حداقل 3 حرف
|
| 190 |
+
(r'(?<!\S)[آ-ی]{3,}\s+[آ-ی]{3,}(?:\s+[آ-ی]{3,})?(?!\S)', 'person'),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
]
|
| 192 |
|
| 193 |
+
# عباراتی که نباید به عنوان نام تشخیص داده شوند
|
| 194 |
+
exclude_phrases = [
|
| 195 |
+
'مجمع عمومی', 'عادی سالیانه', 'شرکت اصلی', 'درآمد عملیاتی',
|
| 196 |
+
'سود عملیاتی', 'زیان انباشته', 'محصولات گرم', 'محصولات سرد',
|
| 197 |
+
'صورت مالی', 'سال گذشته', 'سال جاری', 'هیئت مدیره'
|
| 198 |
+
]
|
| 199 |
+
|
| 200 |
+
for pattern, category in person_patterns:
|
| 201 |
+
matches = list(re.finditer(pattern, text))
|
| 202 |
+
for match in reversed(matches):
|
| 203 |
+
matched_text = match.group(0)
|
| 204 |
+
# بررسی که جزو عبارات مستثنی نباشد
|
| 205 |
+
is_excluded = any(phrase in matched_text for phrase in exclude_phrases)
|
| 206 |
+
if not is_excluded and matched_text not in self.mapping_table:
|
| 207 |
+
self.counters[category] += 1
|
| 208 |
+
code = f"{category}-{self.counters[category]:02d}"
|
| 209 |
+
self.mapping_table[matched_text] = code
|
| 210 |
+
start, end = match.span()
|
| 211 |
+
text = text[:start] + code + text[end:]
|
| 212 |
+
logger.info(f"Person replaced: {matched_text} -> {code}")
|
| 213 |
|
| 214 |
+
return text
|
| 215 |
+
|
| 216 |
+
def _anonymize_general_companies(self, text):
|
| 217 |
+
"""ناشناسسازی نامهای عمومی شرکتها"""
|
| 218 |
company_patterns = [
|
| 219 |
+
# شرکتها با پرانتز
|
| 220 |
+
(r'شرکت\s+[آ-ی][آ-ی\s]+\([آ-ی\s]+\)', 'company'),
|
| 221 |
+
(r'بانک\s+[آ-ی][آ-ی\s]+\([آ-ی\s]+\)', 'company'),
|
| 222 |
+
|
| 223 |
+
# شرکتها با انواع مختلف
|
| 224 |
+
(r'شرکت\s+[آ-ی][آ-ی\s]{4,}', 'company'),
|
| 225 |
+
(r'بانک\s+[آ-ی][آ-ی\s]{2,}', 'company'),
|
| 226 |
+
(r'گروه\s+[آ-ی][آ-ی\s]{2,}', 'company'),
|
| 227 |
+
(r'موسسه\s+[آ-ی][آ-ی\s]{2,}', 'company'),
|
| 228 |
+
(r'سازمان\s+[آ-ی][آ-ی\s]{2,}', 'company'),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
|
| 230 |
# شرکتهای خاص
|
| 231 |
+
(r'[آ-ی]+\s+خودرو', 'company'),
|
| 232 |
+
(r'[آ-ی]+\s+فولاد', 'company'),
|
| 233 |
+
(r'بیمه\s+[آ-ی]+', 'company'),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
]
|
| 235 |
|
| 236 |
+
# عباراتی که نباید به عنوان شرکت تشخیص داده شوند
|
| 237 |
+
exclude_company_phrases = ['شرکت اصلی']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
|
| 239 |
+
for pattern, category in company_patterns:
|
| 240 |
+
matches = list(re.finditer(pattern, text))
|
| 241 |
+
for match in reversed(matches):
|
|
|
|
| 242 |
matched_text = match.group(0)
|
| 243 |
+
# بررسی که جزو عبارات مستثنی نباشد
|
| 244 |
+
is_excluded = any(phrase in matched_text for phrase in exclude_company_phrases)
|
| 245 |
+
if not is_excluded and matched_text not in self.mapping_table:
|
| 246 |
+
self.counters[category] += 1
|
| 247 |
+
code = f"{category}-{self.counters[category]:02d}"
|
| 248 |
+
self.mapping_table[matched_text] = code
|
| 249 |
+
start, end = match.span()
|
| 250 |
+
text = text[:start] + code + text[end:]
|
| 251 |
+
logger.info(f"Company replaced: {matched_text} -> {code}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
|
| 253 |
return text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
|
| 255 |
def send_to_chatgpt(self, anonymized_text, lang='fa'):
|
| 256 |
"""ارسال به ChatGPT"""
|
|
|
|
| 654 |
)
|
| 655 |
|
| 656 |
if __name__ == "__main__":
|
| 657 |
+
print("=" * 80)
|
| 658 |
+
print("تست سیستم ناشناسسازی جامع با نمونههای ذکر شده:")
|
| 659 |
+
print("=" * 80)
|
| 660 |
+
|
| 661 |
+
# نمونههای تست
|
| 662 |
+
test_samples = [
|
| 663 |
+
"مهدی اخوان بهابادی باید یک اسم حساب شود.",
|
| 664 |
+
"در مجمع عمومی عادی سالیانه اعلام کرد درآمد عملیاتی شرکت اصلی",
|
| 665 |
+
"به معنای درآمد روزانه 178 میلیارد تومانی این اپراتور بوده",
|
| 666 |
+
"در خودروسازان حالا از مرز 305 همت عبور کرده و به 305 همت رسیده است.",
|
| 667 |
+
"زیان انباشته این شرکت 7.6 همت زیاد شده است.",
|
| 668 |
+
"تولید محصولات گرم این شرکت به 1000 هزار تن و محصولات سرد به 1378 هزار تن رسید",
|
| 669 |
+
"شرکت سرمایه گذاری پارسیان را اعلام کرد",
|
| 670 |
+
"بانک پارسیان و گروه مالی پارسیان"
|
| 671 |
+
]
|
| 672 |
+
|
| 673 |
+
# تست کامل
|
| 674 |
+
full_test = """مهدی اخوان بهابادی در مجمع عمومی عادی سالیانه اعلام کرد درآمد عملیاتی شرکت اصلی به 178 میلیارد تومانی رسیده است.
|
| 675 |
+
در خودروسازان حالا از مرز 305 همت عبور کرده و سود عملیاتی داشته اما زیان انباشته این شرکت 7.6 همت زیاد شده است.
|
| 676 |
+
تولید محصولات گرم این شرکت به 1000 هزار تن و محصولات سرد به 1378 هزار تن رسید.
|
| 677 |
+
شرکت سرمایه گذاری پارسیان سود خوبی را نشان داد. بانک پارسیان و گروه مالی پارسیان هم عملکرد مثبتی داشتند."""
|
| 678 |
|
| 679 |
anonymizer_test = UniversalAnonymizer()
|
| 680 |
+
|
| 681 |
+
# تست نمونههای جداگانه
|
| 682 |
+
print("\n📌 تست نمونههای جداگانه:")
|
| 683 |
+
print("-" * 40)
|
| 684 |
+
for i, sample in enumerate(test_samples, 1):
|
| 685 |
+
anonymizer_test = UniversalAnonymizer() # ریست برای هر تست
|
| 686 |
+
result = anonymizer_test.anonymize_text(sample)
|
| 687 |
+
print(f"{i}. اصلی: {sample}")
|
| 688 |
+
print(f" ناشناس: {result}")
|
| 689 |
+
print()
|
| 690 |
+
|
| 691 |
+
# تست کامل
|
| 692 |
+
print("\n📌 تست کامل:")
|
| 693 |
+
print("-" * 40)
|
| 694 |
+
anonymizer_test = UniversalAnonymizer()
|
| 695 |
+
result = anonymizer_test.anonymize_text(full_test)
|
| 696 |
+
print("متن اصلی:")
|
| 697 |
+
print(full_test)
|
| 698 |
+
print("\nمتن ناشناسشده:")
|
| 699 |
print(result)
|
| 700 |
+
|
| 701 |
+
print("\n📊 جدول نگاشت:")
|
| 702 |
+
print("-" * 40)
|
| 703 |
for original, code in anonymizer_test.mapping_table.items():
|
| 704 |
+
print(f"{code} ← {original}")
|
| 705 |
+
|
| 706 |
+
print("\n" + "=" * 80)
|
| 707 |
+
print("✅ برنامه آماده اجراست!")
|
| 708 |
+
print("=" * 80)
|
| 709 |
|
| 710 |
app.launch()
|