stat2025 commited on
Commit
734381d
·
verified ·
1 Parent(s): 05ec4ff

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -369
app.py DELETED
@@ -1,369 +0,0 @@
1
- # app.py
2
- # -*- coding: utf-8 -*-
3
- """
4
- منصة معالجة التذاكر — ISIC Helper (اندكس + واجهة فاتحة ومتجاوبة)
5
- - اندكس ترحيبي + خطوات 1/2/3 + زر ابدأ الآن
6
- - لصق عدة تذاكر دفعة واحدة
7
- - استخراج الحقول الأساسية بأي ترتيب
8
- - جدول واضح + ترويسة ملوّنة + محتوى مُوسّط
9
- - تصدير Excel عربي (RTL) بتحميل تلقائي (Ticket_التاريخ)
10
- """
11
- import os, re, tempfile, shutil
12
- import gradio as gr
13
- import pandas as pd
14
- from datetime import datetime
15
- from openpyxl.styles import Alignment, Font, PatternFill
16
- from openpyxl.utils import get_column_letter
17
-
18
- # ============================ إعدادات الحقول ============================
19
- ARABIC_DIGITS = str.maketrans("٠١٢٣٤٥٦٧٨٩", "0123456789")
20
- FIELD_ALIASES = {
21
- "نوع المشكلة": ["نوع المشكله", "نوع المشكلة", "المشكلة"],
22
- "وقت حدوث المشكلة": ["وقت حدوث المشكله", "وقت حدوث المشكلة", "وقت المشكلة", "وقت حدوث"],
23
- "اسم صاحب المشكلة": ["اسم صاحب المشكله", "اسم صاحب المشكلة", "اسم صاحب البلاغ", "الاسم"],
24
- "رقم الهوية": ["رقم الهويه", "رقم الهوية", "الهوية"],
25
- "رقم الجهاز": ["رقم الجهاز", "الجهاز"],
26
- "رقم الجوال": ["رقم الجوال", "الجوال", "الهاتف"],
27
- "المسح": ["المسح", "اسم المسح"],
28
- "المنطقة": ["المنطقة", "المنطقه", "المدينة", "المحافظة"],
29
- }
30
- EXPORT_COLUMNS = [
31
- "نوع المشكلة","وقت حدوث المشكلة","اسم صاحب المشكلة",
32
- "رقم الهوية","رقم الجهاز","رقم الجوال","المسح","المنطقة"
33
- ]
34
- DISPLAY_COLUMNS = EXPORT_COLUMNS[:]
35
-
36
- # ============================ أدوات مساعدة ============================
37
- LABEL_SEP = r"(?:[::]\s*)?"
38
- def compile_field_patterns():
39
- pats = {}
40
- for canonical, labels in FIELD_ALIASES.items():
41
- lbls = "|".join(map(re.escape, labels))
42
- pats[canonical] = [
43
- re.compile(rf"(?:^|\n)\s*(?:{lbls})\s*{LABEL_SEP}(.+)$", re.MULTILINE),
44
- re.compile(rf"(?:^|\n)\s*(?:{lbls})\s*{LABEL_SEP}\n\s*(.+)", re.MULTILINE),
45
- ]
46
- return pats
47
- FIELD_PATTERNS = compile_field_patterns()
48
-
49
- # فواصل التذاكر (🔴 أو سطر فارغ/فواصل)
50
- TICKET_SEP = re.compile(r"\n\s*(?:\n|—+|-{3,}|={3,}|🔴+)+\s*\n")
51
-
52
- def normalize_text(s: str) -> str:
53
- if not isinstance(s, str): return ""
54
- s2 = s.translate(ARABIC_DIGITS)
55
- s2 = re.sub(r"[\u200f\u200e\u2066\u2067\u2068\u2069\u00a0]", " ", s2)
56
- s2 = re.sub(r"[ــ]+", "", s2)
57
- return s2.strip()
58
-
59
- def normalize_time(val: str) -> str:
60
- m = re.search(r"(\d{1,2})[:٫\.:\-](\d{2})\s*(ص|م)?", (val or "").strip(), flags=re.I)
61
- if not m: return (val or "").strip()
62
- h, mn, ampm = int(m.group(1)), m.group(2), m.group(3)
63
- if ampm:
64
- if ampm in ["م","pm","PM"] and h < 12: h += 12
65
- if ampm in ["ص","am","AM"] and h == 12: h = 0
66
- return f"{h:02d}:{mn}"
67
-
68
- def normalize_date(val: str) -> str:
69
- v = (val or "").strip()
70
- m = re.search(r"(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2,4})", v)
71
- if m:
72
- d, mth, y = int(m.group(1)), int(m.group(2)), int(m.group(3))
73
- if y < 100: y += 2000
74
- return f"{y:04d}-{mth:02d}-{d:02d}"
75
- m = re.search(r"(\d{4})[\/\-](\d{1,2})[\/\-](\d{1,2})", v)
76
- if m:
77
- y, mth, d = int(m.group(1)), int(m.group(2)), int(m.group(3))
78
- return f"{y:04d}-{mth:02d}-{d:02d}"
79
- return v
80
-
81
- def split_tickets(raw: str):
82
- raw = normalize_text(raw)
83
- if not raw: return []
84
- parts = re.split(TICKET_SEP, raw)
85
- if len(parts) == 1:
86
- parts = [p for p in re.split(r"\n\s*\n+", raw) if p.strip()]
87
- return [p.strip() for p in parts if p.strip()]
88
-
89
- def extract_fields(ticket_text: str) -> dict:
90
- data = {k: "" for k in FIELD_ALIASES.keys()}
91
- text = normalize_text(ticket_text)
92
- for fname, patterns in FIELD_PATTERNS.items():
93
- for pat in patterns:
94
- m = pat.search(text)
95
- if m:
96
- val = normalize_text(m.group(1))
97
- if fname == "وقت حدوث المشكلة": val = normalize_time(val)
98
- if not data[fname]: data[fname] = val
99
- break
100
- if not data["رقم الجهاز"]:
101
- m = re.search(r"(?:رقم\s*الجهاز|الجهاز)\D*([0-9][0-9\-\s]{2,})", text, flags=re.I)
102
- if m: data["رقم الجهاز"] = re.sub(r"\D", "", m.group(1))[:20]
103
- if not data["رقم الجوال"]:
104
- m = re.search(r"(05\s*\d[\s\-]*\d[\s\-]*\d[\س\-]*\d[\س\-]*\d[\س\-]*\د[\س\-]*\د[\س\-]*\د)", text)
105
- if m: data["رقم الجوال"] = re.sub(r"\D", "", m.group(1))
106
- if not data["رقم الهوية"]:
107
- m = re.search(r"(1\s*\d[\س\-]*\d[\س\-]*\د[\س\-]*\د[\س\-]*\د[\س\-]*\د[\س\-]*\د[\س\-]*\د)", text)
108
- if m: data["رقم الهوية"] = re.sub(r"\D", "", m.group(1))
109
- if not data["المسح"]:
110
- m = re.search(r"(?:اسم\s*المسح|المسح)\s*[::]?\s*(.+)", text)
111
- if m: data["المسح"] = normalize_text(m.group(1).splitlines()[0])
112
- mdate = re.search(r"(\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}|\d{4}[\/\-]\د{1,2}[\/\-]\د{1,2})", text)
113
- if mdate:
114
- date_norm = normalize_date(mdate.group(1))
115
- tm = data.get("وقت حدوث المشكلة", "")
116
- data["وقت حدوث المشكلة"] = f"{date_norm} {tm}".strip()
117
- return data
118
-
119
- def parse_tickets(raw_text: str) -> pd.DataFrame:
120
- tickets = split_tickets(raw_text or "")
121
- rows = [extract_fields(tk) for tk in tickets]
122
- df = pd.DataFrame(rows) if rows else pd.DataFrame(columns=EXPORT_COLUMNS)
123
- for c in EXPORT_COLUMNS:
124
- if c not in df.columns: df[c] = ""
125
- return df[EXPORT_COLUMNS]
126
-
127
- # ============================ Excel RTL احترافي ============================
128
- def _arabic_excel_format(writer, df):
129
- ws = writer.sheets["التذاكر"]
130
- ws.sheet_view.rightToLeft = True
131
- header_font = Font(bold=True, color="FFFFFF")
132
- header_fill = PatternFill(fill_type="solid", fgColor="4137A8") # لون ترويسة الجدول
133
- for cell in ws[1]:
134
- cell.font = header_font
135
- cell.fill = header_fill
136
- cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
137
- for col_idx, col_name in enumerate(df.columns, start=1):
138
- vals = [str(col_name)] + ["" if pd.isna(v) else str(v) for v in df[col_name].tolist()]
139
- width = min(max(len(v) for v in vals) + 2, 50)
140
- ws.column_dimensions[get_column_letter(col_idx)].width = width
141
- for cell in ws.iter_cols(min_col=col_idx, max_col=col_idx, min_row=2, max_row=ws.max_row):
142
- for c in cell:
143
- c.alignment = Alignment(horizontal="center", vertical="top", wrap_text=True)
144
- ws.freeze_panes = "A2"
145
-
146
- def ensure_filename_path(prefix: str = "Ticket") -> str:
147
- ts = datetime.now().strftime("%Y%m%d_%H%M%S")
148
- base = (prefix or "Ticket").strip() or "Ticket"
149
- return os.path.join("/tmp", f"{base}_{ts}.xlsx")
150
-
151
- def export_excel_to_path(df: pd.DataFrame, filename_prefix: str = "Ticket") -> str:
152
- target = ensure_filename_path(filename_prefix)
153
- tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx")
154
- tmp.close()
155
- with pd.ExcelWriter(tmp.name, engine="openpyxl") as writer:
156
- df.to_excel(writer, index=False, sheet_name="التذاكر")
157
- _arabic_excel_format(writer, df)
158
- shutil.move(tmp.name, target)
159
- return target
160
-
161
- # ============================ CSS (ألوانك + اندكس) ============================
162
- CUSTOM_CSS = """
163
- @import url('https://fonts.googleapis.com/css2?family=Cairo:wght@400;600;700&display=swap');
164
-
165
- /* Palette from client */
166
- :root{
167
- --primary:#4137A8; --primary2:#1CADE4; --success:#00B050;
168
- --success2:#42BA97; --gray1:#5C6E88; --violet:#7030A0;
169
- --turq:#27CED7; --yellow:#FFC000; --danger:#F5554A;
170
-
171
- --bg:#F6F8FB; --card:#FFFFFF; --text:#1B2559; --muted:#667085; --border:#E5E7EB;
172
- }
173
-
174
- html, body, .gradio-container { direction: rtl; }
175
- .gradio-container{
176
- font-family:'Cairo', system-ui,-apple-system,'Segoe UI',Tahoma,Arial,sans-serif!important;
177
- max-width:1100px!important; margin:0 auto!important; padding:16px!important;
178
- color:var(--text)!important; background:var(--bg)!important;
179
- }
180
-
181
- /* ========== Index (Hero + Steps) ========== */
182
- .hero{
183
- border-radius:16px; padding:20px 22px;
184
- background:
185
- radial-gradient(900px 200px at 5% -10%, rgba(65,55,168,.14), transparent 60%),
186
- radial-gradient(900px 200px at 95% -10%, rgba(28,222,228,.14), transparent 60%),
187
- linear-gradient(180deg,#fff,#fbfdff);
188
- border:1px solid var(--border);
189
- box-shadow: 0 8px 26px rgba(16,24,40,.06);
190
- margin-bottom:14px;
191
- }
192
- .hero h1{ margin:0 0 6px 0; font-weight:700; font-size:clamp(20px,2.2vw,28px); color:var(--primary); }
193
- .hero .sub{ font-size:clamp(13px,1.6vw,15px); color:var(--gray1); }
194
- .hero .sub b{ color:var(--violet); }
195
-
196
- .index-card{
197
- background:var(--card); border:1px solid var(--border);
198
- border-radius:14px; padding:16px; box-shadow:0 6px 18px rgba(16,24,40,.05); margin-bottom:16px;
199
- }
200
- .steps{ display:grid; grid-template-columns:repeat(3,1fr); gap:12px; }
201
- .step{
202
- background:#f9fbff; border:1px dashed var(--border); border-radius:12px; padding:12px;
203
- display:flex; align-items:center; gap:10px;
204
- }
205
- .step .num{
206
- width:34px; height:34px; border-radius:50%; color:#fff; font-weight:700;
207
- display:flex; align-items:center; justify-content:center;
208
- background:var(--primary2);
209
- }
210
- .step:nth-child(2) .num{ background:var(--success); }
211
- .step:nth-child(3) .num{ background:var(--yellow); color:#111; }
212
-
213
- .start-btn{ margin-top:10px; }
214
-
215
- /* ========== Work Card ========== */
216
- .wrap{ background:var(--card); border:1px solid var(--border); border-radius:14px; padding:16px; box-shadow:0 6px 18px rgba(16,24,40,.05); }
217
- .label{ color:var(--text)!important; font-weight:600!important; }
218
- .hint{ color:var(--muted); font-size:13px; margin-top:6px; }
219
-
220
- /* Inputs */
221
- .gr-textbox textarea{
222
- background:#fff!important; color:var(--text)!important;
223
- border:1px solid var(--border)!important; border-radius:12px!important;
224
- min-height:clamp(160px,40vh,420px); line-height:1.8;
225
- }
226
-
227
- /* Buttons row near table */
228
- .btn-row{ gap:10px; flex-wrap:wrap; justify-content:flex-start; }
229
- button, .gr-button{ border-radius:12px!important; font-weight:600!important; position:relative; }
230
- .gr-button-primary{
231
- background:linear-gradient(90deg,var(--primary),var(--primary2))!important;
232
- color:#fff!important; border:none!important; box-shadow:0 8px 18px rgba(65,55,168,.18);
233
- }
234
- .gr-button-secondary{
235
- background:#f8fafc!important; color:var(--text)!important; border:1px solid var(--border)!important;
236
- }
237
- .gr-button-primary:hover{ filter:brightness(1.05); }
238
-
239
- /* Numbers on buttons */
240
- #btn-parse::before, #btn-export::before, #btn-clear::before{
241
- content: attr(data-step);
242
- position:absolute; top:-8px; inset-inline-start:-8px;
243
- background:var(--primary2); color:#fff; width:22px; height:22px;
244
- border-radius:50%; display:flex; align-items:center; justify-content:center;
245
- font-size:12px; font-weight:700; box-shadow:0 2px 8px rgba(0,0,0,.15);
246
- }
247
-
248
- /* Dataframe */
249
- .dataframe{ background:#fff!important; border-radius:10px!important; }
250
- .dataframe thead th{
251
- background:var(--primary)!important; color:#fff!important;
252
- text-align:center; position:sticky; top:0; z-index:2;
253
- border-bottom:1px solid var(--border)!important;
254
- }
255
- .dataframe td, .dataframe th{ border-color:var(--border)!important; }
256
- .dataframe td{ text-align:center !important; }
257
-
258
- /* Footer */
259
- .footer{ color:var(--muted); font-size:13.5px; text-align:center; margin-top:18px; }
260
- .footer b{ color:var(--primary); }
261
-
262
- /* Responsive */
263
- @media (max-width:768px){
264
- .gradio-container{ padding:12px!important; }
265
- .hero{ padding:16px; }
266
- .hero h1{ font-size:clamp(18px,5vw,24px); }
267
- .gr-textbox textarea{ min-height:clamp(140px,34vh,360px); }
268
- .steps{ grid-template-columns:1fr; }
269
- }
270
- """
271
-
272
- # ============================ الواجهة ============================
273
- with gr.Blocks(title="منصة معالجة التذاكر — ISIC Helper",
274
- theme=gr.themes.Soft(),
275
- css=CUSTOM_CSS) as demo:
276
-
277
- # ====== INDEX ======
278
- gr.HTML("""
279
- <div class="hero">
280
- <h1>منصة معالجة التذاكر</h1>
281
- <div class="sub">تطوير وإعداد — <b>نوف الناصر</b></div>
282
- </div>
283
- """)
284
-
285
- # بطاقة اندكس: خطوات + زر ابدأ الآن (ينزل للقسم السفلي)
286
- gr.HTML("""
287
- <div class="index-card">
288
- <div class="steps">
289
- <div class="step"><div class="num">1</div><div>ألصقي نص التذاكر كما هو (يمكن فواصل 🔴🔴🔴 أو سطر فارغ).</div></div>
290
- <div class="step"><div class="num">2</div><div>راجعي الجدول وعدّلي إن لزم — ثم صدّري إلى Excel.</div></div>
291
- <div class="step"><div class="num">3</div><div>سجّلي الملف وشاركيه مع الفريق.</div></div>
292
- </div>
293
- <div class="start-btn"><button id="goWork" class="gr-button gr-button-primary">ابدأ الآن</button></div>
294
- </div>
295
- <script>
296
- setTimeout(()=>{ const b=document.getElementById('goWork'); if(!b) return;
297
- b.onclick=()=>{ const el=document.getElementById('work'); if(el) el.scrollIntoView({behavior:'smooth', block:'start'}); };
298
- },50);
299
- </script>
300
- """)
301
-
302
- # ====== WORK (المعالجة) ======
303
- with gr.Group(elem_classes=["wrap"], elem_id="work"):
304
- raw = gr.Textbox(
305
- label="الصق التذاكر هنا",
306
- lines=16,
307
- placeholder="الصق نص التذاكر كما هو… افصل بين التذاكر بسطر فارغ أو 🔴🔴🔴 أو ---",
308
- scale=12
309
- )
310
- gr.Markdown('<div class="hint">تلميح: لا يلزم ترتيب معيّن للحقول داخل كل تذكرة.</div>')
311
-
312
- with gr.Row(elem_classes=["btn-row"]):
313
- parse_btn = gr.Button("تحليل التذاكر", variant="secondary", elem_id="btn-parse")
314
- export_btn = gr.DownloadButton("تصدير Excel", variant="primary", elem_id="btn-export")
315
- clear_btn = gr.Button("مسح الكل", variant="secondary", elem_id="btn-clear")
316
- fname = gr.Textbox(label="اسم ملف التصدير", value="Ticket", scale=3)
317
-
318
- gr.HTML("""
319
- <script>
320
- setTimeout(()=>{
321
- const p=document.getElementById('btn-parse'); if(p) p.setAttribute('data-step','1');
322
- const e=document.getElementById('btn-export'); if(e) e.setAttribute('data-step','2');
323
- const c=document.getElementById('btn-clear'); if(c) c.setAttribute('data-step','3');
324
- }, 50);
325
- </script>
326
- """)
327
-
328
- df_out = gr.Dataframe(
329
- headers=DISPLAY_COLUMNS,
330
- row_count=(1, "dynamic"),
331
- interactive=True,
332
- label="نتيجة التحليل (قابلة للتعديل قبل التصدير)"
333
- )
334
-
335
- # مثال صغير
336
- sample = """
337
- 🔴🔴🔴
338
- نوع المشكلة : لا أقدر أكمل الدخول
339
- وقت حدوث المشكلة: 21/8/2025 7:00
340
- اسم صاحب المشكلة : محمد بن علي
341
- رقم الهوية: 1068891991
342
- رقم الجهاز: 01438
343
- رقم الجوال: 0556665323
344
- اسم المسح: الجبيل
345
- منطقة: الشرقية
346
- """.strip()
347
-
348
- # الأحداث
349
- def on_parse(raw_text):
350
- df = parse_tickets(raw_text or sample)
351
- return df[DISPLAY_COLUMNS]
352
-
353
- def on_export(raw_text, prefix):
354
- df = parse_tickets(raw_text or sample)
355
- path = export_excel_to_path(df, prefix or "Ticket")
356
- return path
357
-
358
- def on_clear():
359
- empty_df = pd.DataFrame(columns=DISPLAY_COLUMNS)
360
- return "", empty_df
361
-
362
- parse_btn.click(on_parse, inputs=[raw], outputs=[df_out])
363
- export_btn.click(on_export, inputs=[raw, fname], outputs=[export_btn])
364
- clear_btn.click(on_clear, outputs=[raw, df_out])
365
-
366
- gr.HTML('<div class="footer">© جميع الحقوق محفوظة — تطوير وإعداد <b>نوف الناصر</b></div>')
367
-
368
- if __name__ == "__main__":
369
- demo.launch()