""" 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) @app.get("/", response_class=HTMLResponse) def root(): with open("index_extended.html", encoding="utf-8") as f: return f.read() @app.get("/api/stats") def get_stats(): return load_json("stats.json") @app.get("/api/articles") 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)} @app.get("/api/sources") 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} @app.get("/api/timeline") 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], } @app.get("/api/fake-score-distribution") 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} @app.get("/api/stance-distribution") 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": {}} @app.get("/api/benchmark") 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, } } } @app.get("/api/crosslingual") 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} @app.get("/api/entities") 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} @app.get("/api/validation-results") def get_validation(): return {"has_data": False} @app.get("/api/confusion-matrix") 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} @app.get("/api/temporal-detailed") 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}