GoshawkVortexAI commited on
Commit
e15715d
·
verified ·
1 Parent(s): cbe33d1

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -491
app.py DELETED
@@ -1,491 +0,0 @@
1
- import os
2
- import re
3
- import urllib.request
4
- import xml.etree.ElementTree as ET
5
- from email.utils import parsedate_to_datetime
6
- from urllib.parse import quote
7
-
8
- import gradio as gr
9
- import pandas as pd
10
- import torch
11
- from peft import PeftModel
12
- from transformers import AutoModelForSequenceClassification, AutoTokenizer
13
-
14
- # ================================================================
15
- # CONFIG
16
- # ================================================================
17
-
18
- BASE_MODEL = "distilbert-base-uncased"
19
- ADAPTER_PATH = "."
20
- MAX_LENGTH = 128
21
- BATCH_SIZE = 32
22
-
23
- LABELS = {0: "negative", 1: "neutral", 2: "positive"}
24
-
25
- DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
26
-
27
- # ================================================================
28
- # MODEL LOADING (DistilBERT + LoRA)
29
- # ================================================================
30
-
31
- print("Device:", DEVICE)
32
-
33
- tokenizer = AutoTokenizer.from_pretrained(ADAPTER_PATH, use_fast=True)
34
-
35
- base_model = AutoModelForSequenceClassification.from_pretrained(
36
- BASE_MODEL,
37
- num_labels=3,
38
- id2label=LABELS,
39
- label2id={v: k for k, v in LABELS.items()},
40
- ignore_mismatched_sizes=True,
41
- )
42
-
43
- model = PeftModel.from_pretrained(base_model, ADAPTER_PATH)
44
- try:
45
- model = model.merge_and_unload()
46
- except Exception as e:
47
- print("Merge skipped:", e)
48
-
49
- model = model.to(DEVICE)
50
- model.eval()
51
- print("Model ready.")
52
-
53
-
54
- # ================================================================
55
- # SENTIMENT (batch)
56
- # ================================================================
57
-
58
- def predict_batch(texts):
59
- """Her metin için (p_neg, p_neu, p_pos) döndürür."""
60
- out = []
61
- for i in range(0, len(texts), BATCH_SIZE):
62
- chunk = texts[i:i + BATCH_SIZE]
63
- inputs = tokenizer(
64
- chunk,
65
- return_tensors="pt",
66
- truncation=True,
67
- max_length=MAX_LENGTH,
68
- padding=True,
69
- )
70
- inputs = {k: v.to(DEVICE) for k, v in inputs.items()}
71
- with torch.inference_mode():
72
- probs = torch.softmax(model(**inputs).logits, dim=-1).cpu().tolist()
73
- out.extend(probs)
74
- return out
75
-
76
-
77
- # ================================================================
78
- # RISK KURALLARI (anahtar kelime tabanlı sinyaller)
79
- # ================================================================
80
-
81
- RISK_SIGNALS = {
82
- "Hasar / Claim": [
83
- "claim", "claims", "payout", "payouts", "loss", "losses",
84
- "combined ratio", "reserve", "reserves",
85
- ],
86
- "Doğal afet": [
87
- "hurricane", "earthquake", "flood", "flooding", "wildfire", "storm",
88
- "catastrophe", "cyclone", "typhoon", "tornado", "hailstorm", "drought",
89
- ],
90
- "Büyük kaza": [
91
- "crash", "explosion", "collapse", "derailment", "blast", "accident",
92
- "fire", "sinking", "spill",
93
- ],
94
- "Dava / Fraud": [
95
- "lawsuit", "sued", "litigation", "fraud", "fine", "fined", "penalty",
96
- "investigation", "probe", "settlement", "class action", "regulator",
97
- ],
98
- "Finansal stres": [
99
- "downgrade", "downgraded", "profit warning", "solvency", "writedown",
100
- "write-down", "impairment", "insolvency", "bankruptcy", "capital shortfall",
101
- "negative outlook", "slump", "plunge",
102
- ],
103
- "Siber / Operasyonel": [
104
- "cyberattack", "cyber attack", "data breach", "breach", "ransomware",
105
- "outage",
106
- ],
107
- }
108
-
109
- TOPICS = {
110
- "Motor insurance": ["motor", "auto insurance", "car insurance", "vehicle", "automobile"],
111
- "Health insurance": ["health insurance", "health", "medical", "hospital"],
112
- "Catastrophe risk": ["catastrophe", "hurricane", "earthquake", "flood", "wildfire",
113
- "storm", "cyclone", "typhoon", "tornado", "hailstorm"],
114
- "Life insurance": ["life insurance", "life insurer", "annuity", "annuities", "pension", "mortality"],
115
- "Reinsurance": ["reinsur", "retrocession", "cat bond", "catastrophe bond"],
116
- "Property": ["property insurance", "homeowner", "home insurance", "real estate", "fire"],
117
- "Cyber": ["cyber", "ransomware", "data breach"],
118
- }
119
-
120
- COMPANIES = [
121
- "Allianz", "AXA", "Zurich", "Generali", "Aviva", "Munich Re", "Swiss Re",
122
- "Hannover Re", "Chubb", "AIG", "MetLife", "Prudential", "Allstate",
123
- "Progressive", "State Farm", "Mapfre", "Ageas", "Lloyd's", "Travelers",
124
- "Liberty Mutual", "Tokio Marine", "Ping An",
125
- ]
126
-
127
-
128
- def _compile(words):
129
- return re.compile(r"\b(?:%s)" % "|".join(re.escape(w) for w in words), re.I)
130
-
131
-
132
- SIGNAL_RE = {cat: _compile(words) for cat, words in RISK_SIGNALS.items()}
133
- TOPIC_RE = {t: _compile(words) for t, words in TOPICS.items()}
134
- COMPANY_RE = {c: re.compile(r"\b%s\b" % re.escape(c), re.I) for c in COMPANIES}
135
-
136
-
137
- def find_signals(text):
138
- hits = []
139
- for cat, rx in SIGNAL_RE.items():
140
- m = rx.findall(text)
141
- if m:
142
- hits.append((cat, sorted({x.lower() for x in m})[:2]))
143
- return hits
144
-
145
-
146
- def find_topics(text):
147
- return [t for t, rx in TOPIC_RE.items() if rx.search(text)]
148
-
149
-
150
- def find_companies(text):
151
- return [c for c, rx in COMPANY_RE.items() if rx.search(text)]
152
-
153
-
154
- def risk_level(score, high=60, medium=35):
155
- if score >= high:
156
- return "🔴 HIGH"
157
- if score >= medium:
158
- return "🟠 MEDIUM"
159
- return "🟢 LOW"
160
-
161
-
162
- # ================================================================
163
- # TEK HABER ANALİZİ
164
- # ================================================================
165
-
166
- def score_item(text, probs):
167
- p_neg, p_neu, p_pos = probs
168
- sentiment = p_pos - p_neg # -1 .. +1
169
- signals = find_signals(text)
170
- n_sig = len(signals)
171
- # Risk indeksi: %65 negatif olasılık + %35 risk sinyali yoğunluğu
172
- risk = 100 * (0.65 * p_neg + 0.35 * min(n_sig, 3) / 3)
173
- label = LABELS[max(range(3), key=lambda i: probs[i])]
174
- reason = "; ".join(f"{c} ({', '.join(k)})" for c, k in signals) or "-"
175
- return {
176
- "label": label,
177
- "sentiment": round(sentiment, 3),
178
- "signals": n_sig,
179
- "risk_score": round(risk, 1),
180
- "risk": risk_level(risk),
181
- "reason": reason,
182
- }
183
-
184
-
185
- def analyze_news(text):
186
- text = (text or "").strip()
187
- if not text:
188
- return "Lütfen bir sigorta haber başlığı girin.", "", ""
189
-
190
- probs = predict_batch([text])[0]
191
- s = score_item(text, probs)
192
-
193
- companies = find_companies(text) or ["-"]
194
- topics = find_topics(text) or ["-"]
195
-
196
- result = (
197
- f"Sentiment: {s['label'].upper()}\n"
198
- f"Skor: {s['sentiment']:+.2f}\n"
199
- f"Güven: {max(probs) * 100:.1f}%"
200
- )
201
- prob_text = (
202
- f"Negative: {probs[0] * 100:.1f}%\n"
203
- f"Neutral: {probs[1] * 100:.1f}%\n"
204
- f"Positive: {probs[2] * 100:.1f}%"
205
- )
206
- risk_text = (
207
- f"Şirket: {', '.join(companies)}\n"
208
- f"Risk: {s['risk']} ({s['risk_score']}/100)\n"
209
- f"Risk sinyali: {s['signals']}\n"
210
- f"Neden: {s['reason']}\n"
211
- f"Konu: {', '.join(topics)}"
212
- )
213
- return result, prob_text, risk_text
214
-
215
-
216
- # ================================================================
217
- # TOPLU ANALİZ PIPELINE
218
- # ================================================================
219
-
220
- def analyze_items(items):
221
- """items: [{'headline':str, 'date':datetime|None, 'company':str|None}]"""
222
- if not items:
223
- return None, None, None
224
-
225
- texts = [it["headline"] for it in items]
226
- probs = predict_batch(texts)
227
-
228
- rows = []
229
- for it, p in zip(items, probs):
230
- s = score_item(it["headline"], p)
231
- companies = [it["company"]] if it.get("company") else find_companies(it["headline"])
232
- rows.append({
233
- "date": it.get("date"),
234
- "headline": it["headline"],
235
- "companies": companies or ["(Sektör)"],
236
- "topics": find_topics(it["headline"]),
237
- **s,
238
- })
239
- df = pd.DataFrame(rows)
240
-
241
- has_dates = df["date"].notna().sum() >= 4
242
- if has_dates:
243
- df["date"] = pd.to_datetime(df["date"], utc=True, errors="coerce")
244
- mid = df["date"].dropna().sort_values().iloc[len(df["date"].dropna()) // 2]
245
- df["recent"] = df["date"] >= mid
246
- else:
247
- df["recent"] = False
248
-
249
- # ---- Şirket tablosu ----
250
- ex = df.explode("companies")
251
- comp_rows = []
252
- for comp, g in ex.groupby("companies"):
253
- risk = g["risk_score"].mean()
254
- trend = "—"
255
- if has_dates:
256
- r, e = g[g["recent"]], g[~g["recent"]]
257
- if len(r) and len(e):
258
- d = r["sentiment"].mean() - e["sentiment"].mean()
259
- trend = ("Improving ↑" if d > 0.15
260
- else "Deteriorating ↓" if d < -0.15 else "Stable →")
261
- comp_rows.append({
262
- "Company": comp,
263
- "News": len(g),
264
- "Avg sentiment": round(g["sentiment"].mean(), 2),
265
- "Risk signals": int(g["signals"].sum()),
266
- "High-risk news": int((g["risk_score"] >= 60).sum()),
267
- "Risk score": round(risk, 1),
268
- "Risk": risk_level(risk, high=55, medium=30),
269
- "Trend": trend,
270
- })
271
- comp_df = pd.DataFrame(comp_rows).sort_values("Risk score", ascending=False)
272
-
273
- # ---- Konu / trend tablosu ----
274
- topic_rows = []
275
- n_recent = max(int(df["recent"].sum()), 1)
276
- n_early = max(int((~df["recent"]).sum()), 1)
277
- for topic in TOPICS:
278
- mask = df["topics"].apply(lambda t: topic in t)
279
- if not mask.any():
280
- continue
281
- arrow = "—"
282
- if has_dates:
283
- share_r = (mask & df["recent"]).sum() / n_recent
284
- share_e = (mask & ~df["recent"]).sum() / n_early
285
- if share_e == 0:
286
- arrow = "↑↑"
287
- else:
288
- ratio = share_r / share_e
289
- arrow = ("↑↑" if ratio >= 1.5 else "↑" if ratio >= 1.15
290
- else "↓" if ratio <= 0.8 else "→")
291
- topic_rows.append({
292
- "Topic": topic,
293
- "News": int(mask.sum()),
294
- "Avg sentiment": round(df.loc[mask, "sentiment"].mean(), 2),
295
- "Trend": arrow,
296
- })
297
- topic_df = pd.DataFrame(topic_rows).sort_values("News", ascending=False) \
298
- if topic_rows else pd.DataFrame(columns=["Topic", "News", "Avg sentiment", "Trend"])
299
-
300
- # ---- Haber tablosu ----
301
- news_df = df.assign(
302
- companies=df["companies"].apply(", ".join),
303
- topics=df["topics"].apply(", ".join),
304
- date=df["date"].apply(lambda d: d.strftime("%Y-%m-%d") if pd.notna(d) else ""),
305
- )[["date", "companies", "headline", "label", "sentiment", "risk", "risk_score", "reason", "topics"]]
306
- news_df = news_df.sort_values("risk_score", ascending=False)
307
-
308
- return news_df, comp_df, topic_df
309
-
310
-
311
- # ================================================================
312
- # TOPLU GİRİŞ (metin / CSV)
313
- # ================================================================
314
-
315
- def _pick(cols, names):
316
- for n in names:
317
- if n in cols:
318
- return n
319
- return None
320
-
321
-
322
- def batch_analyze(text, file):
323
- items = []
324
-
325
- path = file if isinstance(file, str) else getattr(file, "name", None)
326
- if path:
327
- df = pd.read_csv(path)
328
- df.columns = [c.strip().lower() for c in df.columns]
329
- h = _pick(df.columns, ["headline", "title", "text", "news", "haber", "baslik"])
330
- d = _pick(df.columns, ["date", "published", "pubdate", "tarih"])
331
- c = _pick(df.columns, ["company", "sirket", "şirket"])
332
- if h is None:
333
- raise gr.Error("CSV'de 'headline' (veya title/text/news) kolonu bulunamadı.")
334
- for _, r in df.iterrows():
335
- if pd.isna(r[h]) or not str(r[h]).strip():
336
- continue
337
- items.append({
338
- "headline": str(r[h]).strip(),
339
- "date": pd.to_datetime(r[d], utc=True, errors="coerce") if d else None,
340
- "company": str(r[c]).strip() if c and pd.notna(r[c]) else None,
341
- })
342
-
343
- for line in (text or "").splitlines():
344
- line = line.strip()
345
- if line:
346
- items.append({"headline": line, "date": None, "company": None})
347
-
348
- if not items:
349
- raise gr.Error("Haber girin (her satıra bir başlık) veya CSV yükleyin.")
350
- return analyze_items(items)
351
-
352
-
353
- # ================================================================
354
- # CANLI TARAMA (Google News RSS)
355
- # ================================================================
356
-
357
- def fetch_news(company, days, limit):
358
- q = quote(f'"{company}" insurance when:{int(days)}d')
359
- url = f"https://news.google.com/rss/search?q={q}&hl=en-US&gl=US&ceid=US:en"
360
- req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
361
- with urllib.request.urlopen(req, timeout=15) as resp:
362
- root = ET.fromstring(resp.read())
363
-
364
- items = []
365
- for it in root.iter("item"):
366
- title = (it.findtext("title") or "").strip()
367
- title = re.sub(r"\s+-\s+[^-]+$", "", title) # " - Kaynak" kısmını at
368
- try:
369
- date = parsedate_to_datetime(it.findtext("pubDate"))
370
- except Exception:
371
- date = None
372
- if title:
373
- items.append({"headline": title, "date": date, "company": company})
374
- if len(items) >= limit:
375
- break
376
- return items
377
-
378
-
379
- def live_scan(company_text, days, limit):
380
- companies = [c.strip() for c in (company_text or "").split(",") if c.strip()]
381
- if not companies:
382
- raise gr.Error("En az bir şirket adı girin.")
383
-
384
- items, errors = [], []
385
- for c in companies:
386
- try:
387
- items += fetch_news(c, days, int(limit))
388
- except Exception as e:
389
- errors.append(f"{c}: {e}")
390
-
391
- if not items:
392
- raise gr.Error("Haber alınamadı. İnternet erişimini kontrol edin. " + " | ".join(errors))
393
- return analyze_items(items)
394
-
395
-
396
- # ================================================================
397
- # GRADIO UI
398
- # ================================================================
399
-
400
- EXAMPLES = [
401
- ["Insurance company reports record premium growth"],
402
- ["Allianz faces major claims increase after severe floods"],
403
- ["Zurich maintained stable operations in the third quarter."],
404
- ["AXA downgraded after reserve shortfall and fraud investigation"],
405
- ["Hurricane losses push reinsurance rates higher"],
406
- ]
407
-
408
- BATCH_PLACEHOLDER = (
409
- "Allianz reports record premium growth\n"
410
- "Generali hit by wildfire claims surge\n"
411
- "Zurich faces lawsuit over auto claims"
412
- )
413
-
414
- NEWS_COLS = ["date", "companies", "headline", "label", "sentiment", "risk", "risk_score", "reason", "topics"]
415
-
416
- with gr.Blocks(title="Insurance News Risk Scanner") as demo:
417
-
418
- gr.Markdown(
419
- "# 🛡️ Insurance News Risk Scanner\n"
420
- "Sigorta haberleri için **sentiment + risk sinyali + erken uyarı**. \n"
421
- "Model: DistilBERT + LoRA · Val Macro F1: 78.06% · "
422
- "Haber başlıkları **İngilizce** olmalı."
423
- )
424
-
425
- # ---------------- TAB 1 ----------------
426
- with gr.Tab("📰 Tek Haber"):
427
- with gr.Row():
428
- with gr.Column():
429
- text_input = gr.Textbox(label="Insurance News", lines=4,
430
- placeholder="Allianz faces major claims increase...")
431
- btn = gr.Button("🔍 Analiz Et", variant="primary")
432
- with gr.Column():
433
- out_pred = gr.Textbox(label="Sentiment", lines=3)
434
- out_prob = gr.Textbox(label="Olasılıklar", lines=3)
435
- out_risk = gr.Textbox(label="Risk Raporu", lines=5)
436
- outs = [out_pred, out_prob, out_risk]
437
- btn.click(analyze_news, text_input, outs)
438
- text_input.submit(analyze_news, text_input, outs)
439
- gr.Examples(EXAMPLES, inputs=text_input)
440
-
441
- # ---------------- TAB 2 ----------------
442
- with gr.Tab("📚 Toplu Analiz"):
443
- gr.Markdown(
444
- "Her satıra bir başlık yazın **veya** CSV yükleyin "
445
- "(kolonlar: `headline`, opsiyonel `date`, `company`). "
446
- "Tarih varsa **trend** hesaplanır."
447
- )
448
- b_text = gr.Textbox(label="Başlıklar (satır başına bir haber)", lines=6,
449
- placeholder=BATCH_PLACEHOLDER)
450
- b_file = gr.File(label="CSV (opsiyonel)", file_types=[".csv"])
451
- b_btn = gr.Button("🚀 Toplu Analiz", variant="primary")
452
- gr.Markdown("### 🏢 Şirket Risk Tablosu (Underwriting / Rakip İstihbaratı)")
453
- b_comp = gr.Dataframe(wrap=True)
454
- gr.Markdown("### 📈 Sektör Konu Trendi")
455
- b_topic = gr.Dataframe(wrap=True)
456
- gr.Markdown("### 🗞️ Haber Detayı (riskten düşüğe)")
457
- b_news = gr.Dataframe(wrap=True)
458
- b_btn.click(batch_analyze, [b_text, b_file], [b_news, b_comp, b_topic])
459
-
460
- # ---------------- TAB 3 ----------------
461
- with gr.Tab("🌐 Canlı Tarama"):
462
- gr.Markdown(
463
- "Google News RSS'ten şirket haberlerini çekip otomatik analiz eder "
464
- "(sunucunun internet erişimi gerekir)."
465
- )
466
- l_comp = gr.Textbox(label="Şirketler (virgülle)", value="Allianz, AXA, Zurich, Generali")
467
- with gr.Row():
468
- l_days = gr.Slider(7, 90, value=30, step=1, label="Son kaç gün")
469
- l_limit = gr.Slider(5, 50, value=20, step=1, label="Şirket başına haber")
470
- l_btn = gr.Button("🔎 Haberleri Tara", variant="primary")
471
- gr.Markdown("### 🏢 Rakip Karşılaştırma")
472
- l_compdf = gr.Dataframe(wrap=True)
473
- gr.Markdown("### 📈 Konu Trendi")
474
- l_topic = gr.Dataframe(wrap=True)
475
- gr.Markdown("### 🗞️ Haberler")
476
- l_news = gr.Dataframe(wrap=True)
477
- l_btn.click(live_scan, [l_comp, l_days, l_limit], [l_news, l_compdf, l_topic])
478
-
479
- gr.Markdown(
480
- "---\n"
481
- "**Not:** Risk skoru = %65 model negatif olasılığı + %35 anahtar-kelime risk sinyali. "
482
- "Karar destek amaçlıdır; tek başına underwriting kararı için kullanılmamalıdır."
483
- )
484
-
485
-
486
- if __name__ == "__main__":
487
- demo.launch(
488
- server_name="0.0.0.0",
489
- server_port=int(os.environ.get("PORT", 7860)),
490
- show_error=True,
491
- )