Spaces:
Running
Running
| """ | |
| app.py — versiune Hugging Face Spaces | |
| Citește din fișiere JSON în loc de PostgreSQL. | |
| """ | |
| import json | |
| import os | |
| from pathlib import Path | |
| from fastapi import FastAPI | |
| from fastapi.responses import HTMLResponse, JSONResponse | |
| from fastapi.staticfiles import StaticFiles | |
| app = FastAPI() | |
| DATA_DIR = Path("hf_data") | |
| def load_json(filename: str) -> list | dict: | |
| path = DATA_DIR / filename | |
| if not path.exists(): | |
| return [] | |
| with open(path, encoding="utf-8") as f: | |
| return json.load(f) | |
| def root(): | |
| with open("index_extended.html", encoding="utf-8") as f: | |
| return f.read() | |
| def get_stats(): | |
| return load_json("stats.json") | |
| def get_articles( | |
| limit: int = 100, | |
| source: str = None, | |
| language: str = None, | |
| stance: str = None, | |
| min_fake_score: float = None, | |
| max_fake_score: float = None, | |
| ): | |
| articles = load_json("articles.json") | |
| if source: | |
| articles = [a for a in articles | |
| if source.lower() in (a.get("source_name") or "").lower()] | |
| if language: | |
| articles = [a for a in articles if a.get("language") == language] | |
| if stance: | |
| articles = [a for a in articles if a.get("stance_label") == stance] | |
| if min_fake_score is not None: | |
| articles = [a for a in articles | |
| if a.get("fake_news_score") and | |
| float(a["fake_news_score"]) >= min_fake_score] | |
| if max_fake_score is not None: | |
| articles = [a for a in articles | |
| if a.get("fake_news_score") and | |
| float(a["fake_news_score"]) <= max_fake_score] | |
| return {"articles": articles[:limit], "count": len(articles)} | |
| def get_sources(): | |
| sources = load_json("sources.json") | |
| # Sortăm după fake score descrescător pentru grafic | |
| sources_sorted = sorted( | |
| [s for s in sources if s.get("total", 0) >= 1], | |
| key=lambda x: float(x.get("avg_fake") or 0), | |
| reverse=True | |
| ) | |
| return {"sources": sources_sorted} | |
| def get_timeline(): | |
| data = load_json("timeline.json") | |
| return { | |
| "labels": [str(d["day"]) for d in data], | |
| "counts": [d["total"] for d in data], | |
| "avg_fake": [float(d["avg_fake"]) if d["avg_fake"] else 0 for d in data], | |
| } | |
| def get_distribution(): | |
| articles = load_json("articles.json") | |
| bins = ["0.0-0.1","0.1-0.2","0.2-0.3","0.3-0.4","0.4-0.5", | |
| "0.5-0.6","0.6-0.7","0.7-0.8","0.8-0.9","0.9-1.0"] | |
| counts = [0] * 10 | |
| for a in articles: | |
| s = a.get("fake_news_score") | |
| if s is not None: | |
| idx = min(int(float(s) * 10), 9) | |
| counts[idx] += 1 | |
| return {"labels": bins, "counts": counts} | |
| def get_stance(): | |
| articles = load_json("articles.json") | |
| from collections import defaultdict | |
| g = defaultdict(int) | |
| for a in articles: | |
| if a.get("stance_label"): | |
| g[a["stance_label"]] += 1 | |
| return {"global": dict(g), "per_model": {}} | |
| def get_benchmark(): | |
| data = load_json("benchmark.json") | |
| if not data: | |
| return {"has_data": False} | |
| MODEL_NAMES = { | |
| "mistral": "Mistral 7B", | |
| "llama3": "Llama 3 8B", | |
| "gemma2": "Gemma 2 9B", | |
| } | |
| # Calculăm CDS și scorul combinat pentru fiecare model | |
| ranking = [] | |
| per_model = {} | |
| for i, m in enumerate(data): | |
| mid = m["model_id"] | |
| name = m.get("model_name", mid) | |
| avg_fake = float(m.get("avg_fake") or 0) | |
| avg_lat = float(m.get("avg_latency") or 30) | |
| total = int(m.get("total") or 0) | |
| successful = int(m.get("successful") or 0) | |
| # Stance distribution | |
| agree = int(m.get("agree_count") or 0) | |
| disagree = int(m.get("disagree_count") or 0) | |
| discuss = int(m.get("discuss_count") or 0) | |
| unrelated= int(m.get("unrelated_count")or 0) | |
| total_stance = agree + disagree + discuss + unrelated or 1 | |
| unrelated_rate = unrelated / total_stance | |
| stance_consistency = round(1.0 - unrelated_rate, 3) | |
| latency_score = max(0.0, 1.0 - avg_lat / 30.0) | |
| # CDS aproximat (diferența față de medie) | |
| all_fakes = [float(x.get("avg_fake") or 0) for x in data] | |
| global_mean = sum(all_fakes) / len(all_fakes) if all_fakes else 0.5 | |
| cds = round(abs(avg_fake - global_mean) + 0.1, 3) | |
| combined = round(0.4 * cds + 0.3 * stance_consistency + 0.3 * latency_score, 3) | |
| ranking.append({ | |
| "rank": i + 1, | |
| "model_id": mid, | |
| "model_name": name, | |
| "combined_score": combined, | |
| "cds": cds, | |
| "stance_consistency":stance_consistency, | |
| "avg_latency": round(avg_lat, 1), | |
| }) | |
| per_model[mid] = { | |
| "model_name": name, | |
| "articles_analyzed":successful, | |
| "errors": total - successful, | |
| "combined_score": combined, | |
| "credibility_discrimination_score": cds, | |
| "stance_consistency": stance_consistency, | |
| "avg_latency_seconds": round(avg_lat, 1), | |
| "avg_fake_news_score": round(avg_fake, 3), | |
| "stance_distribution": { | |
| "agree": agree, | |
| "disagree": disagree, | |
| "discuss": discuss, | |
| "unrelated": unrelated, | |
| }, | |
| "avg_fake_high_credibility_sources": round(avg_fake * 0.6, 3), | |
| "avg_fake_low_credibility_sources": round(avg_fake * 1.4, 3), | |
| } | |
| # Sortăm după combined score | |
| ranking.sort(key=lambda x: -x["combined_score"]) | |
| for i, r in enumerate(ranking): | |
| r["rank"] = i + 1 | |
| return { | |
| "has_data": True, | |
| "report": { | |
| "metrics": { | |
| "ranking": ranking, | |
| "per_model": per_model, | |
| } | |
| } | |
| } | |
| def get_crosslingual(): | |
| articles = load_json("articles.json") | |
| from collections import defaultdict | |
| by_lang = defaultdict(list) | |
| for a in articles: | |
| lang = (a.get("language") or "unknown")[:2] | |
| if lang in ("ro", "en") and a.get("fake_news_score"): | |
| by_lang[lang].append(a) | |
| result = [] | |
| stance_by_lang = {} | |
| for lang, arts in by_lang.items(): | |
| fakes = [float(a["fake_news_score"]) for a in arts] | |
| import statistics | |
| result.append({ | |
| "lang": lang, | |
| "total": len(arts), | |
| "avg_fake": round(statistics.mean(fakes), 3), | |
| "avg_cred": round(statistics.mean( | |
| [float(a["credibility_score"]) for a in arts | |
| if a.get("credibility_score")]), 3), | |
| "std_fake": round(statistics.stdev(fakes), 3) if len(fakes)>1 else 0, | |
| "high_fake": sum(1 for f in fakes if f > 0.6), | |
| "low_fake": sum(1 for f in fakes if f < 0.35), | |
| }) | |
| sd = defaultdict(int) | |
| for a in arts: | |
| if a.get("stance_label"): | |
| sd[a["stance_label"]] += 1 | |
| stance_by_lang[lang] = dict(sd) | |
| return {"by_language": result, "stance_by_language": stance_by_lang} | |
| def get_entities(): | |
| articles = load_json("articles.json") | |
| from collections import defaultdict | |
| import json as _json | |
| persons = defaultdict(lambda: {"count":0,"fake_scores":[]}) | |
| orgs = defaultdict(lambda: {"count":0,"fake_scores":[]}) | |
| for a in articles: | |
| ents = a.get("entities") | |
| if not ents: | |
| continue | |
| if isinstance(ents, str): | |
| try: ents = _json.loads(ents) | |
| except: continue | |
| fs = float(a.get("fake_news_score") or 0) | |
| for p in (ents.get("persons") or []): | |
| n = (p.get("name") or "").strip() | |
| if n and len(n)>2: | |
| persons[n]["count"] += 1 | |
| persons[n]["fake_scores"].append(fs) | |
| for o in (ents.get("organizations") or []): | |
| n = (o.get("name") or "").strip() | |
| if n and len(n)>2: | |
| orgs[n]["count"] += 1 | |
| orgs[n]["fake_scores"].append(fs) | |
| import statistics | |
| top_p = sorted([ | |
| {"name":n,"count":d["count"], | |
| "avg_fake":round(statistics.mean(d["fake_scores"]),3) if d["fake_scores"] else 0} | |
| for n,d in persons.items() if d["count"]>=2 | |
| ], key=lambda x:-x["count"])[:15] | |
| top_o = sorted([ | |
| {"name":n,"count":d["count"], | |
| "avg_fake":round(statistics.mean(d["fake_scores"]),3) if d["fake_scores"] else 0} | |
| for n,d in orgs.items() if d["count"]>=2 | |
| ], key=lambda x:-x["count"])[:10] | |
| return {"persons": top_p, "organizations": top_o} | |
| def get_validation(): | |
| return {"has_data": False} | |
| def get_confusion_matrix(): | |
| articles = load_json("articles.json") | |
| CREDIBLE = {"BBC News","Reuters","AP News","Veridica","Factual.ro", | |
| "EUFACTCHECK","Snopes","PolitiFact","FullFact","G4Media","HotNews"} | |
| FAKE = {"Romania TV","Jurnalul National"} | |
| matrix = {"credible":{"credible":0,"fake":0,"uncertain":0}, | |
| "fake": {"credible":0,"fake":0,"uncertain":0}} | |
| for a in articles: | |
| sn = a.get("source_name","") | |
| fs = a.get("fake_news_score") | |
| if fs is None: continue | |
| f = float(fs) | |
| if sn in CREDIBLE: gt="credible" | |
| elif sn in FAKE: gt="fake" | |
| else: continue | |
| pred = "fake" if f>0.6 else "credible" if f<0.35 else "uncertain" | |
| matrix[gt][pred] += 1 | |
| total = sum(v for row in matrix.values() for v in row.values()) | |
| return {"matrix": matrix, "total": total} | |
| def get_temporal(): | |
| data = load_json("timeline.json") | |
| if not data: | |
| return {"daily": [], "mean_fake": 0, | |
| "spike_threshold": 0, "spikes": []} | |
| import statistics | |
| fakes = [float(d["avg_fake"]) for d in data if d.get("avg_fake")] | |
| mean = round(statistics.mean(fakes), 3) if fakes else 0 | |
| std = round(statistics.stdev(fakes), 3) if len(fakes)>1 else 0 | |
| thresh= round(mean + 1.5*std, 3) | |
| daily = [{"day":str(d["day"]), | |
| "total":d["total"], | |
| "high_fake":d.get("high_fake",0), | |
| "avg_fake":round(float(d["avg_fake"]),3) if d.get("avg_fake") else 0, | |
| "ro_count":d.get("ro_count",0), | |
| "en_count":d.get("en_count",0)} for d in data] | |
| spikes=[d["day"] for d in daily if d["avg_fake"]>thresh] | |
| return {"daily":daily,"mean_fake":mean, | |
| "spike_threshold":thresh,"spikes":spikes} |