File size: 3,555 Bytes
f89a3d6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import asyncio
from typing import Any

from SPARQLWrapper import JSON, SPARQLWrapper

WIKIDATA_ENDPOINT = "https://query.wikidata.org/sparql"

ARABIC_STOP_WORDS = {
    "في", "من", "على", "إلى", "عن", "مع", "هذا", "هذه", "أن", "إن",
    "كان", "كانت", "التي", "الذي", "وقد", "قد", "أو", "و", "ثم",
    "لكن", "بل", "حتى", "إذا", "لما", "بأن", "إنه", "أنه", "التقى",
    "أعلن", "أعلنت", "قال", "قالت", "أكد", "أكدت", "أشار", "أشارت",
}


def extract_entity_candidates(claim: str, *, limit: int = 3) -> list[str]:
    """Return likely entity tokens from an Arabic claim."""
    tokens = [token for token in claim.split() if len(token) > 3 and token not in ARABIC_STOP_WORDS]
    return tokens[:limit]


def wikidata_lookup(entity_name: str) -> list[dict[str, Any]]:
    """Synchronously search Wikidata by Arabic label, then English fallback."""
    sparql = SPARQLWrapper(WIKIDATA_ENDPOINT)
    sparql.addCustomHttpHeader(
        "User-Agent",
        "ArabicNewsAnalyzer/1.0 (graduation-project)",
    )

    query_ar = f"""
    SELECT DISTINCT ?item ?itemLabel ?itemDescription ?article WHERE {{
        ?item rdfs:label "{entity_name}"@ar .
        OPTIONAL {{
            ?article schema:about ?item ;
                     schema:inLanguage "ar" ;
                     schema:isPartOf <https://ar.wikipedia.org/> .
        }}
        SERVICE wikibase:label {{
            bd:serviceParam wikibase:language "ar,en" .
        }}
    }}
    LIMIT 3
    """
    sparql.setQuery(query_ar)
    sparql.setReturnFormat(JSON)

    try:
        raw = sparql.query().convert()
        bindings = raw.get("results", {}).get("bindings", [])
    except Exception:
        bindings = []

    if not bindings:
        query_en = f"""
        SELECT DISTINCT ?item ?itemLabel ?itemDescription WHERE {{
            ?item rdfs:label "{entity_name}"@en .
            SERVICE wikibase:label {{
                bd:serviceParam wikibase:language "ar,en" .
            }}
        }}
        LIMIT 2
        """
        sparql.setQuery(query_en)
        try:
            raw = sparql.query().convert()
            bindings = raw.get("results", {}).get("bindings", [])
        except Exception:
            bindings = []

    evidence: list[dict[str, Any]] = []
    for binding in bindings:
        label = binding.get("itemLabel", {}).get("value", entity_name)
        description = binding.get("itemDescription", {}).get("value", "")
        article_url = binding.get("article", {}).get("value", "")
        item_url = binding.get("item", {}).get("value", "")

        if not description:
            continue
        evidence.append(
            {
                "snippet": f"{label}: {description}",
                "source_url": article_url or item_url,
                "source": "wikidata",
                "rating": None,
            }
        )

    return evidence


async def search_wikidata(claim: str) -> list[dict[str, Any]]:
    """Search Wikidata for likely entities mentioned in a claim."""
    candidates = extract_entity_candidates(claim)
    if not candidates:
        return []

    loop = asyncio.get_event_loop()
    evidence: list[dict[str, Any]] = []

    for entity_name in candidates:
        try:
            results = await loop.run_in_executor(None, wikidata_lookup, entity_name)
            evidence.extend(results)
        except Exception as exc:
            print(f"[Wikidata] Error for '{entity_name}': {exc}")

    return evidence