Spaces:
Sleeping
Sleeping
Delete app.py
Browse files
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 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|