Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -13,8 +13,8 @@ logger = logging.getLogger(__name__)
|
|
| 13 |
|
| 14 |
class ArabicContentModerator:
|
| 15 |
"""
|
| 16 |
-
|
| 17 |
-
Checks for cultural violations
|
| 18 |
"""
|
| 19 |
|
| 20 |
def __init__(self, deepseek_api_key: str = None):
|
|
@@ -34,329 +34,66 @@ class ArabicContentModerator:
|
|
| 34 |
"Content-Type": "application/json"
|
| 35 |
}
|
| 36 |
|
| 37 |
-
#
|
| 38 |
-
self.profanity_terms = {
|
| 39 |
-
# Sexual terms (explicit)
|
| 40 |
-
'explicit_sexual': [
|
| 41 |
-
'زب', 'زبر', 'زبي', 'كس', 'كسك', 'كسها', 'كسي', 'طيز', 'طيزك', 'طيزها',
|
| 42 |
-
'نيك', 'نيكه', 'نيكها', 'ناك', 'منيوك', 'منيوكة', 'مناك', 'متناك',
|
| 43 |
-
'لحس', 'لحسه', 'لحسها', 'لاحس', 'ملحوس', 'ملحوسة', 'عرص', 'عرصه',
|
| 44 |
-
'شرموط', 'شرموطه', 'قحبه', 'قحبة', 'عاهر', 'عاهرة', 'بغي', 'بغيه',
|
| 45 |
-
'ديوث', 'ديوثه', 'قواد', 'قوادة', 'فاجر', 'فاجرة', 'فاسق', 'فاسقة'
|
| 46 |
-
],
|
| 47 |
-
|
| 48 |
-
# Bathroom/excretory terms
|
| 49 |
-
'excretory': [
|
| 50 |
-
'خرا', 'خراء', 'خرة', 'زفت', 'زفته', 'بول', 'بوله', 'غائط', 'براز',
|
| 51 |
-
'تبول', 'تبرز', 'حقير', 'نجس', 'قذر', 'قذره', 'وسخ', 'وسخه'
|
| 52 |
-
],
|
| 53 |
-
|
| 54 |
-
# Family honor insults
|
| 55 |
-
'family_honor': [
|
| 56 |
-
'ابن الكلب', 'ابن الكلبه', 'ابن الحرام', 'ابن الزنا', 'ابن القحبه',
|
| 57 |
-
'بنت الكلب', 'بنت الكلبه', 'بنت الحرام', 'بنت الزنا', 'بنت القحبه',
|
| 58 |
-
'يلعن ابوك', 'يلعن امك', 'يلعن اختك', 'يلعن اهلك', 'يلعن عرضك',
|
| 59 |
-
'كلب', 'كلبه', 'حيوان', 'حيوانه', 'وضيع', 'وضيعه', 'حقير', 'حقيره'
|
| 60 |
-
],
|
| 61 |
-
|
| 62 |
-
# Religious blasphemy
|
| 63 |
-
'religious_blasphemy': [
|
| 64 |
-
'يلعن الله', 'لعنة الله', 'يلعن الدين', 'لعنة الدين', 'يلعن الرسول',
|
| 65 |
-
'كسم الله', 'كسم الدين', 'كسم الرسول', 'كسم المسيح', 'كسم النبي',
|
| 66 |
-
'تف على الله', 'تف على الدين', 'تف على الرسول', 'خرا على الله',
|
| 67 |
-
'زب الله', 'زب الدين', 'زب الرسول', 'طيز الله', 'طيز الدين'
|
| 68 |
-
],
|
| 69 |
-
|
| 70 |
-
# Ethnic/racial slurs
|
| 71 |
-
'ethnic_slurs': [
|
| 72 |
-
'عبد', 'عبده', 'زنجي', 'زنجيه', 'اسود', 'سوده', 'خواجه', 'خواجة',
|
| 73 |
-
'يهودي نجس', 'يهوديه نجسه', 'مسيحي كافر', 'مسيحيه كافره',
|
| 74 |
-
'بدوي', 'بدويه', 'فلاح', 'فلاحه', 'صعيدي', 'صعيديه'
|
| 75 |
-
]
|
| 76 |
-
}
|
| 77 |
-
|
| 78 |
-
# Religious terms that require careful handling
|
| 79 |
-
self.religious_sensitive_terms = [
|
| 80 |
-
'الله', 'محمد', 'الرسول', 'النبي', 'القرآن', 'الإسلام', 'المسيح', 'عيسى',
|
| 81 |
-
'موسى', 'إبراهيم', 'الأنبياء', 'الصحابة', 'الخلفاء', 'الإمام', 'الشيخ',
|
| 82 |
-
'المسجد', 'الكعبة', 'مكة', 'المدينة', 'الحج', 'الصلاة', 'الصوم', 'الزكاة'
|
| 83 |
-
]
|
| 84 |
-
|
| 85 |
-
# Cultural taboos in Arabic society
|
| 86 |
-
self.cultural_taboos = [
|
| 87 |
-
'عري', 'عريان', 'عريانه', 'عاري', 'عاريه', 'مثلي', 'مثليه', 'شاذ', 'شاذه',
|
| 88 |
-
'خمر', 'خمرة', 'مخمور', 'مخمورة', 'سكران', 'سكرانه', 'مسكر', 'مسكرة',
|
| 89 |
-
'قمار', 'مقامر', 'مقامرة', 'ميسر', 'رهان', 'مراهنة', 'خنزير', 'خنزيرة'
|
| 90 |
-
]
|
| 91 |
-
|
| 92 |
-
# Enhanced Arabic Content Moderation with Comprehensive Checking
|
| 93 |
self.moderation_prompt = """
|
| 94 |
-
أنت
|
| 95 |
-
|
| 96 |
-
## معايير الرفض الصارمة:
|
| 97 |
-
|
| 98 |
-
### 1. المحتوى الإخباري والصحفي - رفض فوري:
|
| 99 |
-
**يجب رفض النصوص التي تحتوي على:**
|
| 100 |
-
|
| 101 |
-
**أ) التقارير الرياضية:**
|
| 102 |
-
- "بعد المباراة خرج وقال"
|
| 103 |
-
- "اللاعب تألق ومنع أهداف"
|
| 104 |
-
- "فاز بجائزة رجل المباراة"
|
| 105 |
-
- "المباراة انتهت بنتيجة"
|
| 106 |
-
- "في الشوط الأول"
|
| 107 |
-
- "المدرب صرح"
|
| 108 |
-
|
| 109 |
-
**ب) المؤتمرات الصحفية:**
|
| 110 |
-
- "في مؤتمر صحفي"
|
| 111 |
-
- "صرح الوزير"
|
| 112 |
-
- "أعلن المسؤول"
|
| 113 |
-
- "في تصريحات خاصة"
|
| 114 |
-
- "قال النائب"
|
| 115 |
-
- "أكد الخبير"
|
| 116 |
-
|
| 117 |
-
**ج) الاجتماعات والفعاليات:**
|
| 118 |
-
- "في اجتماع اليوم"
|
| 119 |
-
- "خلال الجلسة"
|
| 120 |
-
- "في المنتدى"
|
| 121 |
-
- "أثناء المؤتمر"
|
| 122 |
-
- "في الورشة"
|
| 123 |
-
- "خلال اللقاء"
|
| 124 |
-
|
| 125 |
-
### 2. الانتهاكات الدينية - رفض صارم ومطلق:
|
| 126 |
-
**رفض فوري ونهائي للمحتوى الذي يحتوي على:**
|
| 127 |
-
|
| 128 |
-
**أ) التجديف والكفر:**
|
| 129 |
-
- أي استهزاء أو تهكم على الله سبحانه وتعالى
|
| 130 |
-
- السخرية من الأنبياء أو الرسل (محمد، عيسى، موسى، إبراهيم)
|
| 131 |
-
- انتقاد أو تشكيك في القرآن الكريم أو الأحاديث النبوية
|
| 132 |
-
- التطاول على الصحابة أو أمهات المؤمنين
|
| 133 |
-
- السب أو اللعن بالدين أو الله أو الرسول
|
| 134 |
-
|
| 135 |
-
**ب) الاستهزاء بالشعائر:**
|
| 136 |
-
- السخرية من الصلاة، الصوم، الحج، الزكاة
|
| 137 |
-
- التهكم على المساجد أو الأماكن المقدسة
|
| 138 |
-
- الاستهزاء بالحجاب أو اللباس الإسلامي
|
| 139 |
-
- انتقاد الأحكام الشرعية بطريقة مسيئة
|
| 140 |
-
|
| 141 |
-
**ج) التشكيك في العقيدة:**
|
| 142 |
-
- إنكار وجود الله أو صفاته
|
| 143 |
-
- التشكيك في الآخرة أو يوم القيامة
|
| 144 |
-
- إنكار النبوة أو الوحي
|
| 145 |
-
- الترويج للإلحاد أو الكفر
|
| 146 |
-
|
| 147 |
-
### 3. الانتهاكات الثقافية العربية - رفض صارم:
|
| 148 |
|
| 149 |
-
|
| 150 |
-
- الوصف الجنسي الصريح أو المبطن
|
| 151 |
-
- المشاهد الإباحية أو الإيحاءات الجنسية
|
| 152 |
-
- الحديث عن العلاقات غير الشرعية بتفصيل
|
| 153 |
-
- وصف الأجساد بطريقة مثيرة أو فاضحة
|
| 154 |
-
- الترويج للعري أو السفور
|
| 155 |
|
| 156 |
-
|
| 157 |
-
- تمجيد العلاقات خارج إطار الزواج
|
| 158 |
-
- السخرية من الزواج أو الأسرة
|
| 159 |
-
- الترويج للشذوذ الجنسي أو المثلية
|
| 160 |
-
- انتهاك احترام الوالدين أو كبار السن
|
| 161 |
-
- تشجيع العقوق أو قطيعة الرحم
|
| 162 |
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
- الطعن في الشرف أو العرض
|
| 169 |
|
| 170 |
-
|
|
|
|
| 171 |
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
- الكلمات الجنسية الفاحشة
|
| 175 |
-
- السب بالعرض أو الشرف
|
| 176 |
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
- السب بالقذارة أو الوسخ
|
| 180 |
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
|
|
|
| 185 |
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
|
| 191 |
-
|
|
|
|
|
|
|
|
|
|
| 192 |
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
- قصص الأنبياء والصالحين (بأدب واحترام)
|
| 197 |
-
- الحكايات التراثية الراقية
|
| 198 |
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
- احترام الثقافة والتقاليد العربية
|
| 203 |
-
- التأكيد على أهمية العلم والتعلم
|
| 204 |
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
- قصص الأطفال التربوية
|
| 208 |
-
- الخيال العلمي المناسب ثقافياً
|
| 209 |
-
- قصص المغامرات النظيفة
|
| 210 |
|
| 211 |
-
|
| 212 |
|
| 213 |
-
|
| 214 |
-
-
|
| 215 |
-
- تجنب الألفاظ الركيكة أو المبتذلة
|
| 216 |
-
- الحفاظ ��لى جمال اللغة وبلاغتها
|
| 217 |
|
| 218 |
-
|
| 219 |
-
- تجنب الإثارة أو الإسفاف
|
| 220 |
-
- الحفاظ على كرامة الشخصيات
|
| 221 |
-
- احترام المشاعر والأحاسيس
|
| 222 |
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
**انتهاكات دينية (يجب رفضها):**
|
| 226 |
-
- أي نص يحتوي على سب أو استهزاء بالله أو الرسول
|
| 227 |
-
- أي نص يشكك في العقيدة الإسلامية
|
| 228 |
-
- أي نص يسخر من الشعائر الدينية
|
| 229 |
-
|
| 230 |
-
**انتهاكات ثقافية (يجب رفضها):**
|
| 231 |
-
- أي نص يروج للعلاقات غير الشرعية
|
| 232 |
-
- أي نص يحتوي على وصف جنسي صريح
|
| 233 |
-
- أي نص يروج للخمر أو المخدرات
|
| 234 |
-
|
| 235 |
-
**سب وشتم (يجب رفضه):**
|
| 236 |
-
- أي نص يحتوي على ألفاظ نابية أو فاحشة
|
| 237 |
-
- أي نص يسب الأسرة أو العرض
|
| 238 |
-
- أي نص يحتوي على تحقير عرقي أو طائفي
|
| 239 |
-
|
| 240 |
-
## أمثلة للقبول:
|
| 241 |
-
|
| 242 |
-
**قصة أدبية راقية (يجب قبولها):**
|
| 243 |
-
"كان رجل صالح يعيش في قرية صغيرة، يساعد الفقراء ويعطف على اليتامى. في يوم من الأيام، مرض ابنه الوحيد، فصبر واحتسب، ولم يفقد إيمانه بالله..."
|
| 244 |
-
|
| 245 |
-
**حكاية تراثية (يجب قبولها):**
|
| 246 |
-
"يحكى أن ملكاً عادلاً كان يتنكر في زي فقير ليتفقد أحوال رعيته. في إحدى جولاته، رأى رجلاً يطعم الطيور من قوت يومه..."
|
| 247 |
-
|
| 248 |
-
## الاستجابة المطلوبة:
|
| 249 |
-
بعد المراجعة الدقيقة والشاملة، أجب بكلمة واحدة فقط:
|
| 250 |
-
- "true" - إذا كان النص قصة أدبية إبداعية راقية خالية من جميع الانتهاكات الدينية والثقافية والأخلاقية
|
| 251 |
-
- "no" - إذا كان النص إخبارياً أو يحتوي على أي انتهاك ديني أو ثقافي أو أخلاقي مهما كان بسيطاً
|
| 252 |
-
|
| 253 |
-
**تذكر: المعايير صارمة جداً، وأي شك في المحتوى يستوجب الرفض الفوري**
|
| 254 |
-
|
| 255 |
-
النص المطلوب مراجعته:
|
| 256 |
"""
|
| 257 |
|
| 258 |
-
def _check_profanity_and_violations(self, content: str) -> Dict[str, Any]:
|
| 259 |
-
"""
|
| 260 |
-
Enhanced pre-check for profanity and cultural violations
|
| 261 |
-
|
| 262 |
-
Args:
|
| 263 |
-
content: Content to check
|
| 264 |
-
|
| 265 |
-
Returns:
|
| 266 |
-
Dictionary with violation details
|
| 267 |
-
"""
|
| 268 |
-
violations = {
|
| 269 |
-
'has_violations': False,
|
| 270 |
-
'violation_types': [],
|
| 271 |
-
'severity': 'none',
|
| 272 |
-
'details': []
|
| 273 |
-
}
|
| 274 |
-
|
| 275 |
-
content_lower = content.lower()
|
| 276 |
-
|
| 277 |
-
# Check for explicit profanity
|
| 278 |
-
for category, terms in self.profanity_terms.items():
|
| 279 |
-
for term in terms:
|
| 280 |
-
if term in content_lower:
|
| 281 |
-
violations['has_violations'] = True
|
| 282 |
-
violations['violation_types'].append(category)
|
| 283 |
-
violations['severity'] = 'critical'
|
| 284 |
-
violations['details'].append(f"Found {category} term: {term}")
|
| 285 |
-
|
| 286 |
-
# Check for inappropriate religious content
|
| 287 |
-
religious_violations = [
|
| 288 |
-
'يلعن الله', 'لعنة الله', 'يلعن الدين', 'تف على الله', 'كسم الله',
|
| 289 |
-
'خرا على الله', 'زب الله', 'طيز الله', 'كسم الدين', 'يلعن الرسول'
|
| 290 |
-
]
|
| 291 |
-
|
| 292 |
-
for violation in religious_violations:
|
| 293 |
-
if violation in content_lower:
|
| 294 |
-
violations['has_violations'] = True
|
| 295 |
-
violations['violation_types'].append('religious_blasphemy')
|
| 296 |
-
violations['severity'] = 'critical'
|
| 297 |
-
violations['details'].append(f"Religious violation detected: {violation}")
|
| 298 |
-
|
| 299 |
-
# Check for cultural taboos
|
| 300 |
-
for taboo in self.cultural_taboos:
|
| 301 |
-
if taboo in content_lower:
|
| 302 |
-
violations['has_violations'] = True
|
| 303 |
-
violations['violation_types'].append('cultural_taboo')
|
| 304 |
-
violations['severity'] = 'high' if violations['severity'] != 'critical' else 'critical'
|
| 305 |
-
violations['details'].append(f"Cultural taboo detected: {taboo}")
|
| 306 |
-
|
| 307 |
-
# Check for inappropriate sexual content patterns
|
| 308 |
-
sexual_patterns = [
|
| 309 |
-
r'نيك', r'ناك', r'منيوك', r'لحس', r'ملحوس', r'عرص', r'شرموط',
|
| 310 |
-
r'قحبة', r'عاهر', r'بغي', r'ديوث', r'قواد'
|
| 311 |
-
]
|
| 312 |
-
|
| 313 |
-
for pattern in sexual_patterns:
|
| 314 |
-
if re.search(pattern, content_lower):
|
| 315 |
-
violations['has_violations'] = True
|
| 316 |
-
violations['violation_types'].append('sexual_content')
|
| 317 |
-
violations['severity'] = 'critical'
|
| 318 |
-
violations['details'].append(f"Sexual content pattern detected: {pattern}")
|
| 319 |
-
|
| 320 |
-
return violations
|
| 321 |
-
|
| 322 |
-
def _check_religious_sensitivity(self, content: str) -> Dict[str, Any]:
|
| 323 |
-
"""
|
| 324 |
-
Check for inappropriate use of religious terms
|
| 325 |
-
|
| 326 |
-
Args:
|
| 327 |
-
content: Content to check
|
| 328 |
-
|
| 329 |
-
Returns:
|
| 330 |
-
Dictionary with religious sensitivity analysis
|
| 331 |
-
"""
|
| 332 |
-
sensitivity = {
|
| 333 |
-
'has_issues': False,
|
| 334 |
-
'religious_terms_found': [],
|
| 335 |
-
'context_issues': [],
|
| 336 |
-
'severity': 'none'
|
| 337 |
-
}
|
| 338 |
-
|
| 339 |
-
content_lower = content.lower()
|
| 340 |
-
|
| 341 |
-
# Find religious terms
|
| 342 |
-
for term in self.religious_sensitive_terms:
|
| 343 |
-
if term.lower() in content_lower:
|
| 344 |
-
sensitivity['religious_terms_found'].append(term)
|
| 345 |
-
|
| 346 |
-
# Check for inappropriate contexts with religious terms
|
| 347 |
-
if sensitivity['religious_terms_found']:
|
| 348 |
-
inappropriate_contexts = [
|
| 349 |
-
'يلعن', 'تف على', 'خرا على', 'كسم', 'زب', 'طيز', 'نيك', 'لعنة'
|
| 350 |
-
]
|
| 351 |
-
|
| 352 |
-
for context in inappropriate_contexts:
|
| 353 |
-
if context in content_lower:
|
| 354 |
-
sensitivity['has_issues'] = True
|
| 355 |
-
sensitivity['context_issues'].append(context)
|
| 356 |
-
sensitivity['severity'] = 'critical'
|
| 357 |
-
|
| 358 |
-
return sensitivity
|
| 359 |
-
|
| 360 |
def _call_deepseek_api(self, story_content: str) -> Dict[str, Any]:
|
| 361 |
"""
|
| 362 |
Call Deepseek API for content moderation
|
|
@@ -373,21 +110,21 @@ class ArabicContentModerator:
|
|
| 373 |
"messages": [
|
| 374 |
{
|
| 375 |
"role": "system",
|
| 376 |
-
"content": "أنت
|
| 377 |
},
|
| 378 |
{
|
| 379 |
-
"role": "user",
|
| 380 |
"content": f"{self.moderation_prompt}\n\n{story_content}"
|
| 381 |
}
|
| 382 |
],
|
| 383 |
"max_tokens": 10,
|
| 384 |
-
"temperature": 0.0,
|
| 385 |
"stream": False
|
| 386 |
}
|
| 387 |
|
| 388 |
response = requests.post(
|
| 389 |
-
self.api_url,
|
| 390 |
-
headers=self.headers,
|
| 391 |
json=payload,
|
| 392 |
timeout=30
|
| 393 |
)
|
|
@@ -398,13 +135,132 @@ class ArabicContentModerator:
|
|
| 398 |
logger.error(f"API Error: {response.status_code} - {response.text}")
|
| 399 |
return {"error": f"API Error: {response.status_code}"}
|
| 400 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 401 |
except Exception as e:
|
| 402 |
logger.error(f"Error processing API response: {str(e)}")
|
| 403 |
return {
|
| 404 |
"approved": False,
|
| 405 |
"response": "no",
|
| 406 |
"reason": "خطأ في معالجة نتيجة المراجعة",
|
| 407 |
-
"violation_type": "processing_error",
|
| 408 |
"error": str(e),
|
| 409 |
"timestamp": datetime.now().isoformat()
|
| 410 |
}
|
|
@@ -416,7 +272,7 @@ app = Flask(__name__)
|
|
| 416 |
# Initialize the moderator (API key will be set via environment variable)
|
| 417 |
try:
|
| 418 |
moderator = ArabicContentModerator()
|
| 419 |
-
logger.info("
|
| 420 |
except ValueError as e:
|
| 421 |
logger.error(f"Failed to initialize moderator: {e}")
|
| 422 |
moderator = None
|
|
@@ -425,71 +281,34 @@ except ValueError as e:
|
|
| 425 |
def home():
|
| 426 |
"""Home endpoint with API documentation"""
|
| 427 |
return jsonify({
|
| 428 |
-
"service": "مراجع المحتوى الأدبي العربي المحسن
|
| 429 |
-
"service_en": "Enhanced Arabic Literary Content Moderator
|
| 430 |
-
"version": "
|
| 431 |
-
"description": "AI-powered professional literary critic for Arabic short stories with comprehensive
|
| 432 |
-
"description_ar": "ناقد أدبي محترف مدعوم بالذكاء الاصطناعي للقصص العربية القصيرة مع
|
| 433 |
"endpoints": {
|
| 434 |
"/health": "Health check",
|
| 435 |
"/moderate": "POST - Moderate single story",
|
| 436 |
-
"/moderate/batch": "POST - Moderate multiple stories"
|
| 437 |
-
"/violations/check": "POST - Check for specific violations without full moderation"
|
| 438 |
},
|
| 439 |
"features": [
|
| 440 |
-
"Comprehensive profanity detection
|
| 441 |
-
"
|
| 442 |
-
"Cultural taboo identification and rejection",
|
| 443 |
-
"Enhanced news content detection and rejection",
|
| 444 |
-
"Family honor and respect enforcement",
|
| 445 |
-
"Ethnic and racial slur detection",
|
| 446 |
-
"Sexual content and inappropriate material filtering",
|
| 447 |
-
"Religious sensitivity analysis",
|
| 448 |
"Professional literary criticism standards",
|
| 449 |
-
"
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
"
|
| 453 |
-
"
|
| 454 |
-
"
|
| 455 |
-
"
|
| 456 |
-
"
|
| 457 |
-
"cultural_taboo - Cultural taboos and inappropriate content",
|
| 458 |
-
"sexual_content - Sexual content patterns",
|
| 459 |
-
"religious_violation - Inappropriate religious content usage"
|
| 460 |
-
],
|
| 461 |
-
"rejected_content_types": [
|
| 462 |
-
"Sports reports and match analysis",
|
| 463 |
-
"Press conferences and official statements",
|
| 464 |
-
"Meeting minutes and proceedings",
|
| 465 |
-
"Political news and announcements",
|
| 466 |
-
"Economic reports and market updates",
|
| 467 |
-
"Technical reviews and product launches",
|
| 468 |
-
"Local news and municipal updates",
|
| 469 |
-
"Content with profanity or offensive language",
|
| 470 |
-
"Religious blasphemy or disrespectful content",
|
| 471 |
-
"Culturally inappropriate material",
|
| 472 |
-
"Sexual or adult content",
|
| 473 |
-
"Family honor violations"
|
| 474 |
],
|
| 475 |
-
"cultural_standards": {
|
| 476 |
-
"religious_respect": "Strict enforcement of Islamic values and respect for all religions",
|
| 477 |
-
"family_values": "Protection of family honor and traditional values",
|
| 478 |
-
"language_purity": "Rejection of profanity and offensive language",
|
| 479 |
-
"cultural_sensitivity": "Adherence to Arab cultural norms and traditions",
|
| 480 |
-
"moral_guidelines": "Enforcement of high moral and ethical standards"
|
| 481 |
-
},
|
| 482 |
"usage": {
|
| 483 |
"moderate": {
|
| 484 |
"method": "POST",
|
| 485 |
"payload": {"story_content": "Arabic story text"},
|
| 486 |
-
"response": {
|
| 487 |
-
"approved": "boolean",
|
| 488 |
-
"response": "true/no",
|
| 489 |
-
"reason": "detailed reason in Arabic",
|
| 490 |
-
"violation_type": "type of violation if any",
|
| 491 |
-
"violation_details": "detailed violation analysis"
|
| 492 |
-
}
|
| 493 |
}
|
| 494 |
},
|
| 495 |
"status": "healthy" if moderator else "service unavailable"
|
|
@@ -500,77 +319,15 @@ def health_check():
|
|
| 500 |
"""Health check endpoint"""
|
| 501 |
return jsonify({
|
| 502 |
"status": "healthy" if moderator else "unhealthy",
|
| 503 |
-
"service": "Enhanced Arabic Content Moderator
|
| 504 |
"timestamp": datetime.now().isoformat(),
|
| 505 |
-
"api_available": moderator is not None
|
| 506 |
-
"features_active": [
|
| 507 |
-
"profanity_detection",
|
| 508 |
-
"religious_filtering",
|
| 509 |
-
"cultural_compliance",
|
| 510 |
-
"news_detection",
|
| 511 |
-
"ai_moderation"
|
| 512 |
-
] if moderator else []
|
| 513 |
})
|
| 514 |
|
| 515 |
-
@app.route('/violations/check', methods=['POST'])
|
| 516 |
-
def check_violations():
|
| 517 |
-
"""
|
| 518 |
-
Check for specific violations without full moderation
|
| 519 |
-
|
| 520 |
-
Expected JSON payload:
|
| 521 |
-
{
|
| 522 |
-
"content": "Text to check for violations"
|
| 523 |
-
}
|
| 524 |
-
|
| 525 |
-
Returns detailed violation analysis
|
| 526 |
-
"""
|
| 527 |
-
if not moderator:
|
| 528 |
-
return jsonify({
|
| 529 |
-
"error": "خدمة المراجعة غير متوفرة - لم يتم تكوين مفتاح API",
|
| 530 |
-
"error_en": "Moderation service not available - API key not configured"
|
| 531 |
-
}), 500
|
| 532 |
-
|
| 533 |
-
try:
|
| 534 |
-
data = request.get_json()
|
| 535 |
-
|
| 536 |
-
if not data or 'content' not in data:
|
| 537 |
-
return jsonify({
|
| 538 |
-
"error": "المحتوى مفقود في الطلب",
|
| 539 |
-
"error_en": "Missing content in request"
|
| 540 |
-
}), 400
|
| 541 |
-
|
| 542 |
-
content = data['content']
|
| 543 |
-
|
| 544 |
-
# Check for violations
|
| 545 |
-
profanity_check = moderator._check_profanity_and_violations(content)
|
| 546 |
-
religious_check = moderator._check_religious_sensitivity(content)
|
| 547 |
-
news_check = moderator._pre_check_news_content(content)
|
| 548 |
-
|
| 549 |
-
return jsonify({
|
| 550 |
-
"content_analysis": {
|
| 551 |
-
"profanity_violations": profanity_check,
|
| 552 |
-
"religious_sensitivity": religious_check,
|
| 553 |
-
"news_content_detected": news_check,
|
| 554 |
-
"overall_safe": not (profanity_check['has_violations'] or
|
| 555 |
-
religious_check['has_issues'] or
|
| 556 |
-
news_check),
|
| 557 |
-
"content_length": len(content),
|
| 558 |
-
"timestamp": datetime.now().isoformat()
|
| 559 |
-
}
|
| 560 |
-
})
|
| 561 |
-
|
| 562 |
-
except Exception as e:
|
| 563 |
-
logger.error(f"Error in check_violations: {str(e)}")
|
| 564 |
-
return jsonify({
|
| 565 |
-
"error": "خطأ داخلي في الخادم",
|
| 566 |
-
"error_en": "Internal server error",
|
| 567 |
-
"details": str(e)
|
| 568 |
-
}), 500
|
| 569 |
-
|
| 570 |
@app.route('/moderate', methods=['POST'])
|
| 571 |
def moderate_content():
|
| 572 |
"""
|
| 573 |
-
Enhanced moderation endpoint
|
| 574 |
|
| 575 |
Expected JSON payload:
|
| 576 |
{
|
|
@@ -581,9 +338,7 @@ def moderate_content():
|
|
| 581 |
{
|
| 582 |
"approved": true/false,
|
| 583 |
"response": "true"/"no",
|
| 584 |
-
"reason": "
|
| 585 |
-
"violation_type": "type of violation",
|
| 586 |
-
"violation_details": "detailed analysis",
|
| 587 |
"timestamp": "ISO timestamp"
|
| 588 |
}
|
| 589 |
"""
|
|
@@ -609,11 +364,6 @@ def moderate_content():
|
|
| 609 |
story_content = data['story_content']
|
| 610 |
result = moderator.moderate_story(story_content)
|
| 611 |
|
| 612 |
-
# Add additional metadata
|
| 613 |
-
result["moderation_version"] = "4.0.0"
|
| 614 |
-
result["strict_mode"] = True
|
| 615 |
-
result["cultural_compliance"] = "enforced"
|
| 616 |
-
|
| 617 |
return jsonify(result)
|
| 618 |
|
| 619 |
except Exception as e:
|
|
@@ -623,14 +373,13 @@ def moderate_content():
|
|
| 623 |
"error_en": "Internal server error",
|
| 624 |
"approved": False,
|
| 625 |
"response": "no",
|
| 626 |
-
"violation_type": "system_error",
|
| 627 |
"details": str(e)
|
| 628 |
}), 500
|
| 629 |
|
| 630 |
@app.route('/moderate/batch', methods=['POST'])
|
| 631 |
def moderate_batch():
|
| 632 |
"""
|
| 633 |
-
Enhanced batch moderation endpoint
|
| 634 |
|
| 635 |
Expected JSON payload:
|
| 636 |
{
|
|
@@ -661,21 +410,14 @@ def moderate_batch():
|
|
| 661 |
|
| 662 |
results = []
|
| 663 |
approved_count = 0
|
| 664 |
-
violation_stats = {}
|
| 665 |
|
| 666 |
for i, story in enumerate(stories):
|
| 667 |
logger.info(f"Moderating story {i+1}/{len(stories)}")
|
| 668 |
result = moderator.moderate_story(story)
|
| 669 |
-
|
| 670 |
-
# Track violation statistics
|
| 671 |
-
violation_type = result.get("violation_type", "none")
|
| 672 |
-
violation_stats[violation_type] = violation_stats.get(violation_type, 0) + 1
|
| 673 |
-
|
| 674 |
results.append({
|
| 675 |
"story_index": i,
|
| 676 |
"result": result
|
| 677 |
})
|
| 678 |
-
|
| 679 |
if result.get("approved", False):
|
| 680 |
approved_count += 1
|
| 681 |
|
|
@@ -685,14 +427,7 @@ def moderate_batch():
|
|
| 685 |
"total_processed": len(results),
|
| 686 |
"approved_count": approved_count,
|
| 687 |
"rejected_count": len(results) - approved_count,
|
| 688 |
-
"approval_rate": f"{(approved_count/len(results)*100):.1f}%" if results else "0%"
|
| 689 |
-
"violation_statistics": violation_stats
|
| 690 |
-
},
|
| 691 |
-
"moderation_info": {
|
| 692 |
-
"version": "4.0.0",
|
| 693 |
-
"strict_mode": True,
|
| 694 |
-
"cultural_compliance": "enforced",
|
| 695 |
-
"religious_filtering": "active"
|
| 696 |
},
|
| 697 |
"timestamp": datetime.now().isoformat()
|
| 698 |
})
|
|
@@ -705,274 +440,7 @@ def moderate_batch():
|
|
| 705 |
"details": str(e)
|
| 706 |
}), 500
|
| 707 |
|
| 708 |
-
@app.route('/standards', methods=['GET'])
|
| 709 |
-
def get_standards():
|
| 710 |
-
"""
|
| 711 |
-
Get detailed information about moderation standards and criteria
|
| 712 |
-
"""
|
| 713 |
-
return jsonify({
|
| 714 |
-
"moderation_standards": {
|
| 715 |
-
"version": "4.0.0",
|
| 716 |
-
"enforcement_level": "strict",
|
| 717 |
-
"cultural_compliance": {
|
| 718 |
-
"religious_respect": {
|
| 719 |
-
"description": "Strict enforcement of respect for Islamic values and all religions",
|
| 720 |
-
"violations_include": [
|
| 721 |
-
"Blasphemy against Allah, prophets, or religious figures",
|
| 722 |
-
"Mockery of religious practices or symbols",
|
| 723 |
-
"Inappropriate use of religious terms",
|
| 724 |
-
"Disrespect towards religious texts or teachings"
|
| 725 |
-
]
|
| 726 |
-
},
|
| 727 |
-
"family_values": {
|
| 728 |
-
"description": "Protection of family honor and traditional values",
|
| 729 |
-
"violations_include": [
|
| 730 |
-
"Insults targeting family members",
|
| 731 |
-
"Disrespect towards parents or elders",
|
| 732 |
-
"Inappropriate sexual content",
|
| 733 |
-
"Promotion of immoral relationships"
|
| 734 |
-
]
|
| 735 |
-
},
|
| 736 |
-
"language_purity": {
|
| 737 |
-
"description": "Rejection of profanity and offensive language",
|
| 738 |
-
"categories": [
|
| 739 |
-
"Sexual explicit terms",
|
| 740 |
-
"Excretory language",
|
| 741 |
-
"Family honor insults",
|
| 742 |
-
"Ethnic and racial slurs"
|
| 743 |
-
]
|
| 744 |
-
}
|
| 745 |
-
},
|
| 746 |
-
"content_requirements": {
|
| 747 |
-
"acceptable_genres": [
|
| 748 |
-
"Educational moral stories",
|
| 749 |
-
"Traditional folk tales",
|
| 750 |
-
"Religious stories (respectful)",
|
| 751 |
-
"Social stories with positive messages",
|
| 752 |
-
"Children's educational content",
|
| 753 |
-
"Historical narratives (appropriate)",
|
| 754 |
-
"Fantasy and adventure (culturally appropriate)"
|
| 755 |
-
],
|
| 756 |
-
"rejected_content": [
|
| 757 |
-
"News reports and journalism",
|
| 758 |
-
"Sports commentary and analysis",
|
| 759 |
-
"Political statements and speeches",
|
| 760 |
-
"Commercial product reviews",
|
| 761 |
-
"Technical documentation",
|
| 762 |
-
"Adult or sexual content",
|
| 763 |
-
"Content with profanity",
|
| 764 |
-
"Religious blasphemy",
|
| 765 |
-
"Cultural violations"
|
| 766 |
-
]
|
| 767 |
-
}
|
| 768 |
-
},
|
| 769 |
-
"violation_categories": {
|
| 770 |
-
"critical": [
|
| 771 |
-
"religious_blasphemy",
|
| 772 |
-
"explicit_sexual",
|
| 773 |
-
"family_honor_severe"
|
| 774 |
-
],
|
| 775 |
-
"high": [
|
| 776 |
-
"cultural_taboo",
|
| 777 |
-
"ethnic_slurs",
|
| 778 |
-
"sexual_content"
|
| 779 |
-
],
|
| 780 |
-
"medium": [
|
| 781 |
-
"excretory",
|
| 782 |
-
"inappropriate_language"
|
| 783 |
-
]
|
| 784 |
-
},
|
| 785 |
-
"enforcement_policy": {
|
| 786 |
-
"zero_tolerance": [
|
| 787 |
-
"Religious blasphemy or disrespect",
|
| 788 |
-
"Explicit sexual content",
|
| 789 |
-
"Severe family honor violations",
|
| 790 |
-
"Ethnic or racial discrimination"
|
| 791 |
-
],
|
| 792 |
-
"strict_filtering": [
|
| 793 |
-
"All forms of profanity",
|
| 794 |
-
"Cultural taboos",
|
| 795 |
-
"Inappropriate religious content usage",
|
| 796 |
-
"News and journalistic content"
|
| 797 |
-
]
|
| 798 |
-
}
|
| 799 |
-
})
|
| 800 |
-
|
| 801 |
if __name__ == '__main__':
|
| 802 |
# For local testing
|
| 803 |
port = int(os.environ.get('PORT', 7860))
|
| 804 |
-
app.run(host='0.0.0.0', port=port, debug=False)
|
| 805 |
-
return {"error": str(e)}
|
| 806 |
-
|
| 807 |
-
def _pre_check_news_content(self, story_content: str) -> bool:
|
| 808 |
-
"""
|
| 809 |
-
Pre-check for obvious news content patterns
|
| 810 |
-
|
| 811 |
-
Args:
|
| 812 |
-
story_content: Content to check
|
| 813 |
-
|
| 814 |
-
Returns:
|
| 815 |
-
True if appears to be news content, False otherwise
|
| 816 |
-
"""
|
| 817 |
-
# News indicators in Arabic
|
| 818 |
-
news_patterns = [
|
| 819 |
-
r'بعد المباراة.*قال',
|
| 820 |
-
r'في مؤتمر صحفي',
|
| 821 |
-
r'صرح.*الوزير',
|
| 822 |
-
r'أعلن.*المسؤول',
|
| 823 |
-
r'فاز.*بجائزة.*رجل المباراة',
|
| 824 |
-
r'تألق.*ومنع.*أهداف',
|
| 825 |
-
r'خلال.*الاجتماع',
|
| 826 |
-
r'في.*الجلسة',
|
| 827 |
-
r'الرئيس.*التقى',
|
| 828 |
-
r'البرلمان.*ناقش',
|
| 829 |
-
r'الحكومة.*قررت',
|
| 830 |
-
r'البورصة.*ارتفعت',
|
| 831 |
-
r'أسعار.*النفط',
|
| 832 |
-
r'الشركة.*حققت',
|
| 833 |
-
r'المحافظ.*افتتح',
|
| 834 |
-
r'بلدية.*المدينة',
|
| 835 |
-
r'التطبيق.*الجديد',
|
| 836 |
-
r'الهاتف.*يتميز',
|
| 837 |
-
r'في.*محافظة'
|
| 838 |
-
]
|
| 839 |
-
|
| 840 |
-
# Check for news patterns
|
| 841 |
-
for pattern in news_patterns:
|
| 842 |
-
if re.search(pattern, story_content, re.IGNORECASE):
|
| 843 |
-
return True
|
| 844 |
-
|
| 845 |
-
# Check for sports-specific terms
|
| 846 |
-
sports_terms = ['المباراة', 'اللاعب', 'المدرب', 'الفريق', 'الهدف', 'الشوط']
|
| 847 |
-
news_verbs = ['صرح', 'أعلن', 'أكد', 'قال', 'فاز', 'تألق']
|
| 848 |
-
|
| 849 |
-
has_sports = any(term in story_content for term in sports_terms)
|
| 850 |
-
has_news_verbs = any(verb in story_content for verb in news_verbs)
|
| 851 |
-
|
| 852 |
-
if has_sports and has_news_verbs:
|
| 853 |
-
return True
|
| 854 |
-
|
| 855 |
-
return False
|
| 856 |
-
|
| 857 |
-
def _validate_story_format(self, story_content: str) -> bool:
|
| 858 |
-
"""
|
| 859 |
-
Enhanced validation of story format and content
|
| 860 |
-
|
| 861 |
-
Args:
|
| 862 |
-
story_content: Story content to validate
|
| 863 |
-
|
| 864 |
-
Returns:
|
| 865 |
-
Boolean indicating if format is valid
|
| 866 |
-
"""
|
| 867 |
-
if not story_content or not isinstance(story_content, str):
|
| 868 |
-
return False
|
| 869 |
-
|
| 870 |
-
# Check minimum length (at least 50 characters for a meaningful story)
|
| 871 |
-
if len(story_content.strip()) < 50:
|
| 872 |
-
return False
|
| 873 |
-
|
| 874 |
-
# Check for Arabic characters (must have substantial Arabic content)
|
| 875 |
-
arabic_pattern = re.compile(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]')
|
| 876 |
-
arabic_chars = len(arabic_pattern.findall(story_content))
|
| 877 |
-
|
| 878 |
-
# Arabic characters should be at least 30% of total characters
|
| 879 |
-
if arabic_chars < len(story_content.strip()) * 0.3:
|
| 880 |
-
return False
|
| 881 |
-
|
| 882 |
-
# Pre-check for obvious news content
|
| 883 |
-
if self._pre_check_news_content(story_content):
|
| 884 |
-
return False
|
| 885 |
-
|
| 886 |
-
return True
|
| 887 |
-
|
| 888 |
-
def moderate_story(self, story_content: str) -> Dict[str, Any]:
|
| 889 |
-
"""
|
| 890 |
-
Main method to moderate Arabic story content with enhanced validation and strict enforcement
|
| 891 |
-
|
| 892 |
-
Args:
|
| 893 |
-
story_content: The Arabic story to moderate
|
| 894 |
-
|
| 895 |
-
Returns:
|
| 896 |
-
Dictionary with moderation result
|
| 897 |
-
"""
|
| 898 |
-
# Enhanced validation
|
| 899 |
-
if not self._validate_story_format(story_content):
|
| 900 |
-
return {
|
| 901 |
-
"approved": False,
|
| 902 |
-
"response": "no",
|
| 903 |
-
"reason": "المحتوى يبدو أنه تقرير إخباري أو صحفي وليس قصة أدبية، أو فشل في التحقق من صحة التنسيق",
|
| 904 |
-
"violation_type": "format_violation",
|
| 905 |
-
"timestamp": datetime.now().isoformat()
|
| 906 |
-
}
|
| 907 |
-
|
| 908 |
-
# Clean and prepare content
|
| 909 |
-
cleaned_content = story_content.strip()
|
| 910 |
-
|
| 911 |
-
# Check for profanity and violations first
|
| 912 |
-
violation_check = self._check_profanity_and_violations(cleaned_content)
|
| 913 |
-
if violation_check['has_violations']:
|
| 914 |
-
return {
|
| 915 |
-
"approved": False,
|
| 916 |
-
"response": "no",
|
| 917 |
-
"reason": "المحتوى يحتوي على انتهاكات صريحة للقيم الدينية أو الثقافية أو ألفاظ نابية",
|
| 918 |
-
"violation_type": "content_violation",
|
| 919 |
-
"violation_details": violation_check,
|
| 920 |
-
"timestamp": datetime.now().isoformat()
|
| 921 |
-
}
|
| 922 |
-
|
| 923 |
-
# Check for religious sensitivity issues
|
| 924 |
-
religious_check = self._check_religious_sensitivity(cleaned_content)
|
| 925 |
-
if religious_check['has_issues']:
|
| 926 |
-
return {
|
| 927 |
-
"approved": False,
|
| 928 |
-
"response": "no",
|
| 929 |
-
"reason": "المحتوى يحتوي على استخدام غير مناسب للمصطلحات الدينية أو انتهاكات دينية",
|
| 930 |
-
"violation_type": "religious_violation",
|
| 931 |
-
"religious_details": religious_check,
|
| 932 |
-
"timestamp": datetime.now().isoformat()
|
| 933 |
-
}
|
| 934 |
-
|
| 935 |
-
# Call Deepseek API for final check
|
| 936 |
-
api_response = self._call_deepseek_api(cleaned_content)
|
| 937 |
-
|
| 938 |
-
if "error" in api_response:
|
| 939 |
-
logger.error(f"Moderation failed: {api_response['error']}")
|
| 940 |
-
return {
|
| 941 |
-
"approved": False,
|
| 942 |
-
"response": "no",
|
| 943 |
-
"reason": "خطأ في خدمة المراجعة",
|
| 944 |
-
"violation_type": "service_error",
|
| 945 |
-
"error": api_response["error"],
|
| 946 |
-
"timestamp": datetime.now().isoformat()
|
| 947 |
-
}
|
| 948 |
-
|
| 949 |
-
try:
|
| 950 |
-
# Extract the moderation decision
|
| 951 |
-
ai_response = api_response.get("choices", [{}])[0].get("message", {}).get("content", "").strip().lower()
|
| 952 |
-
|
| 953 |
-
# Clean the response (remove any extra whitespace or characters)
|
| 954 |
-
ai_response = re.sub(r'[^\w]', '', ai_response)
|
| 955 |
-
|
| 956 |
-
# Determine if content is approved (be very strict)
|
| 957 |
-
approved = ai_response == "true"
|
| 958 |
-
response_value = "true" if approved else "no"
|
| 959 |
-
|
| 960 |
-
result = {
|
| 961 |
-
"approved": approved,
|
| 962 |
-
"response": response_value,
|
| 963 |
-
"ai_decision": ai_response,
|
| 964 |
-
"timestamp": datetime.now().isoformat(),
|
| 965 |
-
"content_length": len(cleaned_content),
|
| 966 |
-
"violation_type": "none" if approved else "ai_detected"
|
| 967 |
-
}
|
| 968 |
-
|
| 969 |
-
if not approved:
|
| 970 |
-
result["reason"] = "المحتوى ينتهك المعايير الصارمة للثقافة العربية والإسلامية، أو أنه ليس قصة أدبية حقيقية بل محتوى إخباري أو غير مناسب"
|
| 971 |
-
else:
|
| 972 |
-
result["reason"] = "المحتوى مقبول ويلتزم بالمعايير الصارمة المطلوبة للثقافة العربية والإسلامية"
|
| 973 |
-
|
| 974 |
-
logger.info(f"Moderation completed: {response_value} for content of length {len(cleaned_content)}")
|
| 975 |
-
return result
|
| 976 |
-
|
| 977 |
-
except Exception as e:
|
| 978 |
-
logger.error(
|
|
|
|
| 13 |
|
| 14 |
class ArabicContentModerator:
|
| 15 |
"""
|
| 16 |
+
Arabic Story Content Moderation Model using Deepseek API
|
| 17 |
+
Checks for cultural violations and inappropriate content
|
| 18 |
"""
|
| 19 |
|
| 20 |
def __init__(self, deepseek_api_key: str = None):
|
|
|
|
| 34 |
"Content-Type": "application/json"
|
| 35 |
}
|
| 36 |
|
| 37 |
+
# The Definitive Prompt for Word-Level Contextual Analysis
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
self.moderation_prompt = """
|
| 39 |
+
أنت ناقد أدبي فائق الدقة، خبير في الأدب العربي والثقافة الإسلامية. مهمتك ليست مجرد المراجعة، بل التشريح الأدبي للنص. يجب أن تركز في كل كلمة، وتفهم موقعها ودلالتها داخل السياق السردي العام قبل إصدار أي حكم. قرارك هو نتاج تحليل عميق، وليس رد فعل سطحي تجاه مفردات معينة.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
+
**المنهجية التحليلية: من الكلمة إلى النص**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
+
اتبع هذه المنهجية الصارمة، حيث كل كلمة هي وحدة تحليل أساسية:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
+
**أولاً: التحليل الدقيق للسياق والغرض**
|
| 46 |
+
قبل أن تبدأ، اقرأ النص بالكامل لتفهم عالمه. ثم أعد القراءة مع التركيز على بناء الجمل واختيار الكلمات. اسأل نفسك باستمرار:
|
| 47 |
+
* **ما وظيفة هذه الكلمة هنا؟** هل هي جزء من حوار يكشف عن شخصية منحرفة؟ هل هي استعارة أدبية تغير المعنى الظاهري؟ أم هي صوت مباشر من السرد يروج لانتهاك؟
|
| 48 |
+
* **ما هي نبرة القصة؟** هل هي ساخرة، مأساوية، نقدية؟ النبرة تغير دلالة الكلمات بشكل جذري.
|
| 49 |
+
* تذكر، قصة تصور شخصية تنطق بالكفر للكشف عن ضلالها تختلف جذرياً عن نص يدعو إلى الكفر. الفهم الدقيق لهذه النية هو جوهر عملك.
|
|
|
|
| 50 |
|
| 51 |
+
**ثانياً: التحقق من الهوية الأدبية (هل البنية هي بنية قصة؟)**
|
| 52 |
+
بعد فهم السياق، تأكد من أن النص عمل سردي حقيقي يمتلك العناصر الأساسية: حبكة، شخصيات، حوار، وصف. ارفض بشكل قاطع أي محتوى غير قصصي (مقالات، تعليقات، مواد دراسية، إلخ).
|
| 53 |
|
| 54 |
+
**ثالثاً: تطبيق معايير المراجعة بناءً على التحليل السياقي (مدونة السلوك للناقد)**
|
| 55 |
+
استخدم هذه المدونة كمرجع لتحديد الانتهاكات. لا تقم بتطبيقها بشكل آلي. يجب أن يمر كل انتهاك محتمل عبر فلتر "التحليل السياقي للكلمة" الذي قمت به في الخطوة الأولى.
|
|
|
|
|
|
|
| 56 |
|
| 57 |
+
---
|
| 58 |
+
**(((مدونة السلوك والمحتوى المرفوض)))**
|
|
|
|
| 59 |
|
| 60 |
+
**1. الإساءة الدينية والثقافية:**
|
| 61 |
+
* **أ) سب الذات الإلهية أو الأديان:** أي ازدراء أو سب مباشر لله تعالى، القرآن، النبي محمد ﷺ، الصحابة، أو الرسل والأنبياء في الديانات السماوية الأخرى.
|
| 62 |
+
* **ب) الاستهزاء بالشعائر والرموز الدينية:** السخرية من الصلاة، الصيام، الحج، الزكاة، المساجد، الكعبة، إلخ.
|
| 63 |
+
* **ج) الكفر والتجديف الصريح:** إنكار وجود الله، إنكار البعث والحساب، أو إنكار النبوة.
|
| 64 |
+
* **د) التحريض الطائفي والمذهبي:** إثارة الفتنة بين المذاهب أو الأديان.
|
| 65 |
|
| 66 |
+
**2. الألفاظ النابية والبذاءة:**
|
| 67 |
+
* **أ) الشتائم الجنسية الصريحة:** استخدام أي لفظ يصف الأعضاء التناسلية أو الأفعال الجنسية بشكل مبتذل.
|
| 68 |
+
* **ب) سب الأهل والأعراض:** الشتائم التي تتضمن الأب أو الأم أو الأخت.
|
| 69 |
+
* **ج) الشتائم العامة المهينة:** الألفاظ التي تحط من الكرامة الإنسانية.
|
| 70 |
|
| 71 |
+
**3. المحتوى الجنسي والأخلاقي:**
|
| 72 |
+
* **أ) الوصف الجنسي الفاضح:** وصف تفصيلي للأعضاء التناسلية أو الأفعال الجنسية بهدف الإثارة.
|
| 73 |
+
* **ب) تمجيد العلاقات المحرمة والشذوذ الجنسي:** الترويج للزنا، الخيانة، أو العلاقات المثلية وتقديمها على أنها أمر إيجابي.
|
| 74 |
+
* **ج) الإيحاءات والتلميحات المبتذلة:** العبارات التي تحمل معانٍ جنسية مبطنة ورخيصة.
|
| 75 |
|
| 76 |
+
**4. العنف والكراهية:**
|
| 77 |
+
* **أ) العنف المفرط والمقزز:** الوصف التصويري المفصل لأعمال القتل والتعذيب والتشويه.
|
| 78 |
+
* **ب) تمجيد الجريمة والكراهية:** الدعوة الصريحة للقتل، الانتقام، إيذاء النفس، أو التحريض العرقي.
|
|
|
|
|
|
|
| 79 |
|
| 80 |
+
**5. المحتوى المخالف للقانون والأخلاق العامة:**
|
| 81 |
+
* **أ) الترويج للمخدرات والمسكرات:** التشجيع على تعاطي المخدرات أو شرب الخمر.
|
| 82 |
+
* **ب) الإساءة للمرأة وتحقيرها:** العبارات التي تصور المرأة ككائن ناقص أو مجرد أداة.
|
|
|
|
|
|
|
| 83 |
|
| 84 |
+
---
|
| 85 |
+
**رابعاً: القرار النهائي المبني على التحليل الدقيق**
|
|
|
|
|
|
|
|
|
|
| 86 |
|
| 87 |
+
بعد تشريح النص كلمة بكلمة وفهم سياقه الأدبي العميق، أصدر حكمك النهائي:
|
| 88 |
|
| 89 |
+
- **إذا كان النص قصة أدبية حقيقية، وبعد فحص كل كلمة حساسة في سياقها، تأكدت من أنها لا تمثل انتهاكاً مباشراً من قبل صوت السرد نفسه، أجب بـ `true` فقط.**
|
| 90 |
+
- **إذا كان النص غير قصصي، أو إذا كانت كلماته أو عباراته، حتى بعد تحليل السياق، تشكل انتهاكاً واضحاً ومباشراً للمعايير، أجب بـ `no` فقط.**
|
|
|
|
|
|
|
| 91 |
|
| 92 |
+
إجابتك يجب أن تكون `true` أو `no` فقط. لا مجال للتردد. حكمك يعتمد على تحليل دقيق وليس على انطباع عام.
|
|
|
|
|
|
|
|
|
|
| 93 |
|
| 94 |
+
**النص المطلوب مراجعته:**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
"""
|
| 96 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
def _call_deepseek_api(self, story_content: str) -> Dict[str, Any]:
|
| 98 |
"""
|
| 99 |
Call Deepseek API for content moderation
|
|
|
|
| 110 |
"messages": [
|
| 111 |
{
|
| 112 |
"role": "system",
|
| 113 |
+
"content": "أنت ناقد أدبي محترف متخصص في الأدب العربي والثقافة الإسلامية. مهمتك مراجعة النصوص بدقة شديدة للتأكد من التزامها بالقيم الدينية والثقافية العربية الإسلامية ومن كونها قصصاً أدبية حقيقية."
|
| 114 |
},
|
| 115 |
{
|
| 116 |
+
"role": "user",
|
| 117 |
"content": f"{self.moderation_prompt}\n\n{story_content}"
|
| 118 |
}
|
| 119 |
],
|
| 120 |
"max_tokens": 10,
|
| 121 |
+
"temperature": 0.0, # Changed to 0.0 for more consistent results
|
| 122 |
"stream": False
|
| 123 |
}
|
| 124 |
|
| 125 |
response = requests.post(
|
| 126 |
+
self.api_url,
|
| 127 |
+
headers=self.headers,
|
| 128 |
json=payload,
|
| 129 |
timeout=30
|
| 130 |
)
|
|
|
|
| 135 |
logger.error(f"API Error: {response.status_code} - {response.text}")
|
| 136 |
return {"error": f"API Error: {response.status_code}"}
|
| 137 |
|
| 138 |
+
except Exception as e:
|
| 139 |
+
logger.error(f"Exception calling Deepseek API: {str(e)}")
|
| 140 |
+
return {"error": str(e)}
|
| 141 |
+
|
| 142 |
+
def _validate_story_format(self, story_content: str) -> bool:
|
| 143 |
+
"""
|
| 144 |
+
Enhanced validation of story format and content
|
| 145 |
+
|
| 146 |
+
Args:
|
| 147 |
+
story_content: Story content to validate
|
| 148 |
+
|
| 149 |
+
Returns:
|
| 150 |
+
Boolean indicating if format is valid
|
| 151 |
+
"""
|
| 152 |
+
if not story_content or not isinstance(story_content, str):
|
| 153 |
+
return False
|
| 154 |
+
|
| 155 |
+
# Check minimum length (at least 50 characters for a meaningful story)
|
| 156 |
+
if len(story_content.strip()) < 50:
|
| 157 |
+
return False
|
| 158 |
+
|
| 159 |
+
# Check for Arabic characters (must have substantial Arabic content)
|
| 160 |
+
arabic_pattern = re.compile(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]')
|
| 161 |
+
arabic_chars = len(arabic_pattern.findall(story_content))
|
| 162 |
+
|
| 163 |
+
# Arabic characters should be at least 30% of total characters
|
| 164 |
+
if arabic_chars < len(story_content.strip()) * 0.3:
|
| 165 |
+
return False
|
| 166 |
+
|
| 167 |
+
# Quick pre-screening for obvious violations (before API call)
|
| 168 |
+
content_lower = story_content.lower()
|
| 169 |
+
|
| 170 |
+
# Check for common profanity patterns across dialects AND religious violations
|
| 171 |
+
profanity_patterns = [
|
| 172 |
+
# Sexual profanity
|
| 173 |
+
r'\bكس\b', r'\bزب\b', r'\bنيك\b', r'\bمنيوك\b', r'\bشرموط\b',
|
| 174 |
+
r'\bقحب\b', r'\bلحس\b', r'\bمص\b', r'\bطيز\b', r'\bبزاز\b',
|
| 175 |
+
r'\bتبن\b', r'\bنعل\b', r'\bكوس\b', r'\bأير\b', r'\bمنايك\b',
|
| 176 |
+
r'\bخرا\b', r'\bكداب\b', r'\bعرص\b', r'\bخول\b', r'\bحيوان\b',
|
| 177 |
+
|
| 178 |
+
# Religious violations and blasphemy
|
| 179 |
+
r'الله كذاب', r'لعن الله', r'تبا لله', r'يلعن رب', r'الله غبي',
|
| 180 |
+
r'القرآن كذب', r'كتاب مزور', r'خرافات القرآن', r'قرآن مؤلف',
|
| 181 |
+
r'محمد كذاب', r'الرسول دجال', r'نبي كاذب', r'لعنة على محمد',
|
| 182 |
+
r'أبو بكر منافق', r'عمر ظالم', r'علي مخادع', r'الصحابة كذابون',
|
| 183 |
+
r'الصلاة مضيعة', r'رمضان تخلف', r'الحج طقوس', r'الكعبة صنم',
|
| 184 |
+
r'المسيح كذاب', r'الإنجيل محرف', r'موسى دجال', r'التوراة مزورة',
|
| 185 |
+
r'لا إله', r'الله وهم', r'الجنة وهم', r'النار خيال', r'القيامة كذبة',
|
| 186 |
+
r'لعنة الله', r'الله يلعنك', r'ملعون من رب', r'بيوت خرافة',
|
| 187 |
+
r'السنة نواصب', r'الشيعة رافضة', r'الصوفية مشركون', r'الوهابيون كفار'
|
| 188 |
+
]
|
| 189 |
+
|
| 190 |
+
for pattern in profanity_patterns:
|
| 191 |
+
if re.search(pattern, content_lower):
|
| 192 |
+
return False
|
| 193 |
+
|
| 194 |
+
return True
|
| 195 |
+
|
| 196 |
+
def moderate_story(self, story_content: str) -> Dict[str, Any]:
|
| 197 |
+
"""
|
| 198 |
+
Main method to moderate Arabic story content with enhanced validation
|
| 199 |
+
|
| 200 |
+
Args:
|
| 201 |
+
story_content: The Arabic story to moderate
|
| 202 |
+
|
| 203 |
+
Returns:
|
| 204 |
+
Dictionary with moderation result
|
| 205 |
+
"""
|
| 206 |
+
# Enhanced validation
|
| 207 |
+
if not self._validate_story_format(story_content):
|
| 208 |
+
return {
|
| 209 |
+
"approved": False,
|
| 210 |
+
"response": "no",
|
| 211 |
+
"reason": "فشل في التحقق من صحة تنسيق القصة أو عدم وجود محتوى عربي كافٍ",
|
| 212 |
+
"timestamp": datetime.now().isoformat()
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
# Clean and prepare content
|
| 216 |
+
cleaned_content = story_content.strip()
|
| 217 |
+
|
| 218 |
+
# Call Deepseek API
|
| 219 |
+
api_response = self._call_deepseek_api(cleaned_content)
|
| 220 |
+
|
| 221 |
+
if "error" in api_response:
|
| 222 |
+
logger.error(f"Moderation failed: {api_response['error']}")
|
| 223 |
+
return {
|
| 224 |
+
"approved": False,
|
| 225 |
+
"response": "no",
|
| 226 |
+
"reason": "خطأ في خدمة المراجعة",
|
| 227 |
+
"error": api_response["error"],
|
| 228 |
+
"timestamp": datetime.now().isoformat()
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
try:
|
| 232 |
+
# Extract the moderation decision
|
| 233 |
+
ai_response = api_response.get("choices", [{}])[0].get("message", {}).get("content", "").strip().lower()
|
| 234 |
+
|
| 235 |
+
# Clean the response (remove any extra whitespace or characters)
|
| 236 |
+
ai_response = re.sub(r'[^\w]', '', ai_response)
|
| 237 |
+
|
| 238 |
+
# Determine if content is approved (be more strict)
|
| 239 |
+
approved = ai_response == "true"
|
| 240 |
+
response_value = "true" if approved else "no"
|
| 241 |
+
|
| 242 |
+
result = {
|
| 243 |
+
"approved": approved,
|
| 244 |
+
"response": response_value,
|
| 245 |
+
"ai_decision": ai_response,
|
| 246 |
+
"timestamp": datetime.now().isoformat(),
|
| 247 |
+
"content_length": len(cleaned_content)
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
if not approved:
|
| 251 |
+
result["reason"] = "المحتوى ينتهك القواعد المجتمعية أو الثقافية أو الدينية، أو أنه ليس قصة أدبية حقيقية"
|
| 252 |
+
else:
|
| 253 |
+
result["reason"] = "المحتوى مقبول ويلتزم بالمعايير المطلوبة"
|
| 254 |
+
|
| 255 |
+
logger.info(f"Moderation completed: {response_value} for content of length {len(cleaned_content)}")
|
| 256 |
+
return result
|
| 257 |
+
|
| 258 |
except Exception as e:
|
| 259 |
logger.error(f"Error processing API response: {str(e)}")
|
| 260 |
return {
|
| 261 |
"approved": False,
|
| 262 |
"response": "no",
|
| 263 |
"reason": "خطأ في معالجة نتيجة المراجعة",
|
|
|
|
| 264 |
"error": str(e),
|
| 265 |
"timestamp": datetime.now().isoformat()
|
| 266 |
}
|
|
|
|
| 272 |
# Initialize the moderator (API key will be set via environment variable)
|
| 273 |
try:
|
| 274 |
moderator = ArabicContentModerator()
|
| 275 |
+
logger.info("Arabic Content Moderator initialized successfully")
|
| 276 |
except ValueError as e:
|
| 277 |
logger.error(f"Failed to initialize moderator: {e}")
|
| 278 |
moderator = None
|
|
|
|
| 281 |
def home():
|
| 282 |
"""Home endpoint with API documentation"""
|
| 283 |
return jsonify({
|
| 284 |
+
"service": "مراجع المحتوى الأدبي العربي المحسن",
|
| 285 |
+
"service_en": "Enhanced Arabic Literary Content Moderator",
|
| 286 |
+
"version": "2.0.0",
|
| 287 |
+
"description": "AI-powered professional literary critic for Arabic short stories with comprehensive dialect-aware moderation",
|
| 288 |
+
"description_ar": "ناقد أدبي محترف مدعوم بالذكاء الاصطناعي للقصص العربية القصيرة مع مراجعة شاملة لجميع اللهجات العربية",
|
| 289 |
"endpoints": {
|
| 290 |
"/health": "Health check",
|
| 291 |
"/moderate": "POST - Moderate single story",
|
| 292 |
+
"/moderate/batch": "POST - Moderate multiple stories"
|
|
|
|
| 293 |
},
|
| 294 |
"features": [
|
| 295 |
+
"Comprehensive profanity detection across all Arabic dialects",
|
| 296 |
+
"Enhanced religious and cultural compliance checking with specific examples",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 297 |
"Professional literary criticism standards",
|
| 298 |
+
"Strict content type validation (stories only)",
|
| 299 |
+
"Multi-dialect offensive content detection (Gulf, Levantine, Egyptian, Maghrebi, Iraqi, Sudanese, Yemeni)",
|
| 300 |
+
"Comprehensive religious blasphemy detection (Islam, Christianity, Judaism, other faiths)",
|
| 301 |
+
"Advanced sectarian and inter-religious conflict prevention",
|
| 302 |
+
"Takfir and religious defamation blocking",
|
| 303 |
+
"Advanced pre-screening before API calls",
|
| 304 |
+
"Arabic language purity validation",
|
| 305 |
+
"Context-aware violation detection"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 307 |
"usage": {
|
| 308 |
"moderate": {
|
| 309 |
"method": "POST",
|
| 310 |
"payload": {"story_content": "Arabic story text"},
|
| 311 |
+
"response": {"approved": "boolean", "response": "true/no"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 312 |
}
|
| 313 |
},
|
| 314 |
"status": "healthy" if moderator else "service unavailable"
|
|
|
|
| 319 |
"""Health check endpoint"""
|
| 320 |
return jsonify({
|
| 321 |
"status": "healthy" if moderator else "unhealthy",
|
| 322 |
+
"service": "Enhanced Arabic Content Moderator",
|
| 323 |
"timestamp": datetime.now().isoformat(),
|
| 324 |
+
"api_available": moderator is not None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 325 |
})
|
| 326 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 327 |
@app.route('/moderate', methods=['POST'])
|
| 328 |
def moderate_content():
|
| 329 |
"""
|
| 330 |
+
Enhanced moderation endpoint
|
| 331 |
|
| 332 |
Expected JSON payload:
|
| 333 |
{
|
|
|
|
| 338 |
{
|
| 339 |
"approved": true/false,
|
| 340 |
"response": "true"/"no",
|
| 341 |
+
"reason": "reason in Arabic",
|
|
|
|
|
|
|
| 342 |
"timestamp": "ISO timestamp"
|
| 343 |
}
|
| 344 |
"""
|
|
|
|
| 364 |
story_content = data['story_content']
|
| 365 |
result = moderator.moderate_story(story_content)
|
| 366 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 367 |
return jsonify(result)
|
| 368 |
|
| 369 |
except Exception as e:
|
|
|
|
| 373 |
"error_en": "Internal server error",
|
| 374 |
"approved": False,
|
| 375 |
"response": "no",
|
|
|
|
| 376 |
"details": str(e)
|
| 377 |
}), 500
|
| 378 |
|
| 379 |
@app.route('/moderate/batch', methods=['POST'])
|
| 380 |
def moderate_batch():
|
| 381 |
"""
|
| 382 |
+
Enhanced batch moderation endpoint
|
| 383 |
|
| 384 |
Expected JSON payload:
|
| 385 |
{
|
|
|
|
| 410 |
|
| 411 |
results = []
|
| 412 |
approved_count = 0
|
|
|
|
| 413 |
|
| 414 |
for i, story in enumerate(stories):
|
| 415 |
logger.info(f"Moderating story {i+1}/{len(stories)}")
|
| 416 |
result = moderator.moderate_story(story)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 417 |
results.append({
|
| 418 |
"story_index": i,
|
| 419 |
"result": result
|
| 420 |
})
|
|
|
|
| 421 |
if result.get("approved", False):
|
| 422 |
approved_count += 1
|
| 423 |
|
|
|
|
| 427 |
"total_processed": len(results),
|
| 428 |
"approved_count": approved_count,
|
| 429 |
"rejected_count": len(results) - approved_count,
|
| 430 |
+
"approval_rate": f"{(approved_count/len(results)*100):.1f}%" if results else "0%"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
},
|
| 432 |
"timestamp": datetime.now().isoformat()
|
| 433 |
})
|
|
|
|
| 440 |
"details": str(e)
|
| 441 |
}), 500
|
| 442 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 443 |
if __name__ == '__main__':
|
| 444 |
# For local testing
|
| 445 |
port = int(os.environ.get('PORT', 7860))
|
| 446 |
+
app.run(host='0.0.0.0', port=port, debug=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|