Spaces:
Runtime error
Runtime error
| """Inverted Pyramid Structure Analyzer | |
| ======================================= | |
| Checks whether an article follows the inverted pyramid journalism structure | |
| used in Filipino (Tagalog), Cebuano, and English news writing. | |
| Real journalism hallmarks: | |
| 1. Lead answers the 5W+1H (Who/What/Where/When/Why/How) | |
| 2. Body contains attributed quotes and named sources | |
| 3. Transitions mark flow between sections | |
| 4. Formal journalism markers appear (bylines, "exclusive", etc.) | |
| Fake news / social media posts typically: | |
| - Lack WHERE, WHEN, HOW answers in the lead | |
| - Have no named source attribution | |
| - Use no formal transitions | |
| - Score < 30 on the formalism scale | |
| Returns a formalism score (0β100) and a per-signal breakdown. | |
| """ | |
| import re | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # LANGUAGE DETECTION | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Shared Filipino words that appear in BOTH Tagalog and Cebuano. | |
| # These are NOT counted toward either language individually β only used | |
| # to confirm that the text is Filipino (not English). | |
| _SHARED_FILIPINO = { | |
| "ang", "sa", "na", "ni", "si", "kay", "mga", | |
| } | |
| # Tagalog-EXCLUSIVE markers (do NOT appear meaningfully in Cebuano) | |
| _TAGALOG_EXCLUSIVE = { | |
| # ββ Function words unique to Tagalog ββ | |
| "ng", "at", "ay", "naman", "kasi", "kung", "hindi", "pero", | |
| "lang", "daw", "raw", "pa", "din", "rin", "siya", "niya", | |
| "kanila", "nila", "kami", "tayo", "ito", "iyon", "noon", | |
| "talaga", "lamang", "habang", "kapag", "upang", "para", | |
| "subalit", "gayunpaman", "bagaman", "datapwat", "sapagkat", | |
| "samakatuwid", "kaya", "pati", "maging", "sila", "atin", | |
| # ββ Tagalog news / crime verbs ββ | |
| "ayon", "aniya", "sinabi", "namatay", "patay", | |
| "inaresto", "kinumpirma", "ulat", "samantala", "dagdag", | |
| "inihayag", "idineklara", "nagbitiw", "nagresign", | |
| "nahuli", "nasugatan", "natagpuan", "naaksidente", | |
| # ββ Political / diplomatic / economic verbs ββ | |
| "pinirmahan", "inaprubahan", "ipinasa", "inilunsad", | |
| "binigyang-diin", "ipinag-utos", "inilipat", "ipinagbawal", | |
| "pinagtibay", "pinangunahan", "pinasinayaan", "ipinagkaloob", | |
| "itinakda", "isinulong", "ginawaran", "tinugunan", | |
| "nagdeklara", "nagproklama", "nagpatupad", "nagsampa", | |
| "naghain", "naglabas", "nagtalaga", "nagbigay", | |
| # ββ Disaster / calamity verbs ββ | |
| "nasalanta", "binaha", "lumindol", "niyanig", "tinangay", | |
| "nawasak", "gumuho", "bumaha", "naglandslide", "lumikas", | |
| "nilikas", "inilikas", "nasira", "natumba", "napinsala", | |
| # ββ Health / medical verbs ββ | |
| "nagpositibo", "nagnegatibo", "naospital", "namatay", | |
| "nagpabakuna", "nakumpirma", "na-confine", "gumaling", | |
| "nagkaroon", "dumarami", "kumalat", "tumataas", "bumababa", | |
| # ββ Economic / labor verbs ββ | |
| "nagtaas", "bumaba", "tumaas", "bumagsak", "namuhunan", | |
| "nagwelga", "nag-rally", "umakyat", "pumalo", "sumirit", | |
| "naglabas", "naglaan", "nangutang", "nagbukas", "nagsara", | |
| # ββ Social / education verbs ββ | |
| "nagreklamo", "nagprotesta", "nagmartsa", "sumuko", | |
| "nagtapos", "pumasa", "bumagsak", "nag-aral", "nagturo", | |
| "nag-enroll", "pinauwi", "na-deport", | |
| # ββ Elections / political process ββ | |
| "nanumpa", "nanalo", "natalo", "tumakbo", "kumandidato", | |
| "nagfile", "nag-withdraw", "nagkampanya", "ibinoto", | |
| "nanungkulan", "pinalitan", "sinibak", "tinanggal", | |
| # ββ Government/political titles ββ | |
| "pangulo", "senador", "alkalde", "gobernador", "kalihim", | |
| "embahador", "kinatawan", "ahensya", "tanggapan", | |
| "kagawaran", "komisyon", "lungsod", "bayan", "munisipyo", | |
| "kongresista", "mambabatas", "hukom", "piskal", "abogado", | |
| "hepe", "heneral", "koronel", "kapitan", "konsehal", | |
| # ββ Professionals / roles ββ | |
| "guro", "doktor", "nars", "inhinyero", "mangingisda", | |
| "magsasaka", "empleyado", "manggagawa", "negosyante", | |
| "pasahero", "estudyante", "residente", "mamamayan", | |
| # ββ Tagalog temporal / causal ββ | |
| "kahapon", "kagabi", "ngayon", "noong", "matapos", "dahil", | |
| "bunga", "kasunod", "kaugnay", "kamakailan", "kanina", | |
| } | |
| # Cebuano-EXCLUSIVE markers (do NOT appear meaningfully in Tagalog) | |
| _CEBUANO_EXCLUSIVE = { | |
| # ββ Strong unique Cebuano function words ββ | |
| "nga", "ug", "diin", "asa", "kinsa", "unsa", "ngano", | |
| "giunsa", "kanus-a", "karon", "karong", | |
| "apan", "busa", "mao", "maong", "tungod", "lamang", | |
| "diha", "didto", "dinhi", "gani", "bitaw", "lagi", | |
| # ββ Cebuano news / crime verbs ββ | |
| "sumala", "gikumpirmar", "samtang", "hinuon", "nidugang", | |
| "dugang", "matud", "niingon", "gisulti", "giingon", | |
| "kuyog", "usab", "pud", "bisan", "gibuhat", "gitaho", | |
| "gireport", "magbuhat", | |
| # ββ Deep Visayan vocabulary ββ | |
| "dakbayan", "lalawigan", "buhatan", "pagdakop", "dinalagan", | |
| "nabsan", "gipusil", "gidakop", "nasakitan", "gipatay", | |
| "niadtong", "kagab-i", | |
| # ββ Government/political titles ββ | |
| "sekretaryo", "embahador", "tinugyanan", "gobernador", | |
| "konsehal", "kapitan", "punoan", | |
| "kongresista", "maghuhukom", "piskal", "abogado", | |
| "heneral", "koronel", "direktor", | |
| # ββ Political / diplomatic / economic verbs ββ | |
| "gipirmahan", "giaprubahan", "gitangtang", "gipasa", | |
| "gilansad", "gibug-aton", "gimandoan", "gipatuman", | |
| "klarohon", "namatyan", "gisakit", | |
| "gideklara", "giproklamar", "giablihan", "gisirado", | |
| "gihatag", "gipagawas", "gipakaylap", "gitugutan", | |
| "gitakda", "gipadayon", "giduso", "gipalig-on", | |
| "gihatagan", "gipanguluhan", "gipasinayaan", | |
| # ββ Disaster / calamity verbs ββ | |
| "gianud", "gilunopan", "gibanlas", "nabungkag", "nahugno", | |
| "gidaot", "gikuha sa baha", "napukan", "nagun-ob", | |
| "gilikas", "gipalikay", "napiang", "nadaot", "naangol", | |
| # ββ Health / medical verbs ββ | |
| "nagpositibo", "nagnegatibo", "giospital", "gipa-confine", | |
| "nagpabakuna", "naayo", "nagkadaghan", "mikaylap", "misaka", | |
| # ββ Economic / labor verbs ββ | |
| "misaka", "miubos", "mitubo", "mihagbong", "mipalit", | |
| "nagwelga", "nag-rally", "giablihan", "gisirhan", | |
| "namuhunan", "gibaligya", "gipalit", | |
| # ββ Social / education verbs ββ | |
| "nagreklamo", "nagprotesta", "nagmartsa", "misurender", | |
| "nakagraduate", "nipasa", "nahagbong", "nagtuon", | |
| "gipauli", "gi-deport", | |
| # ββ Elections / political process ββ | |
| "nanumpa", "midaog", "napildi", "mikandidato", | |
| "nagfile", "mi-withdraw", "nagkampanya", "giboto", | |
| "milingkod", "gipulihan", "gisibak", "gitangtang", | |
| # ββ Professionals / roles ββ | |
| "magtutudlo", "doktor", "nars", "inhinyero", "mangingisda", | |
| "mag-uuma", "empleyado", "mamumuo", "negosyante", | |
| "pasahero", "estudyante", "residente", "lungsuranon", | |
| } | |
| # Strong Cebuano signals β words that are DEFINITIVELY Cebuano (never Tagalog) | |
| # Any single match here β definitively Cebuano | |
| _CEBUANO_STRONG = { | |
| # Interrogatives / function words (unique to Cebuano) | |
| "kinsa", "unsa", "ngano", "giunsa", "kanus-a", "diin", | |
| "samtang", "hinuon", "busa", "apan", "maong", | |
| # Cebuano gi-/mi- passive/active verb forms (never appear in Tagalog) | |
| "gikumpirmar", "gipusil", "gidakop", "gipatay", "gibuhat", | |
| "gitaho", "gireport", "giingon", "gibug-aton", "gimandoan", | |
| "gipatuman", "gipirmahan", "giaprubahan", "gilansad", | |
| "gianud", "gilunopan", "gibanlas", "gidaot", "giospital", | |
| "gilikas", "gipalikay", "gisirhan", "giboto", "gipulihan", | |
| "gisibak", "gipauli", "gibutyag", "gipahibalo", "giklarohan", | |
| # Cebuano attribution / temporal | |
| "sumala", "matud", "niingon", "nidugang", "niadtong", | |
| "dakbayan", "nabsan", "kagab-i", | |
| # Cebuano pronouns/adverbs (never Tagalog) | |
| "kuyog", "usab", "pud", "bisan", | |
| # Cebuano mi- verb forms | |
| "midaog", "misaka", "miubos", "mitubo", "mihagbong", | |
| "mikandidato", "misurender", "milingkod", "mikaylap", | |
| } | |
| def _detect_language(text: str) -> str: | |
| """Return 'tagalog', 'cebuano', or 'english' based on keyword heuristics. | |
| Uses EXCLUSIVE markers per language to avoid false matches from | |
| shared Filipino words like 'ang', 'sa', 'na', 'si', 'ni', 'kay'. | |
| """ | |
| words = set(re.findall(r"\b[\w-]+\b", text.lower())) | |
| # Count exclusive markers | |
| tl_hits = len(words & _TAGALOG_EXCLUSIVE) | |
| ceb_hits = len(words & _CEBUANO_EXCLUSIVE) | |
| ceb_strong = len(words & _CEBUANO_STRONG) | |
| shared_hits = len(words & _SHARED_FILIPINO) | |
| # Priority 1: ANY single exclusive deep Visayan term β definitively Cebuano | |
| if ceb_strong >= 1: | |
| return "cebuano" | |
| # Priority 2: Cebuano exclusive majority (even without strong terms) | |
| if ceb_hits >= 2 and ceb_hits > tl_hits: | |
| return "cebuano" | |
| # Priority 3: Tagalog exclusive majority | |
| if tl_hits >= 2 and tl_hits > ceb_hits: | |
| return "tagalog" | |
| # Priority 4: If shared Filipino markers are present but no exclusive ones, | |
| # check for Tagalog-leaning shared words like 'at', 'ay', 'ng' | |
| if shared_hits >= 2: | |
| # 'at' (and), 'ay' (is), 'ng' (of) are uniquely Tagalog among shared | |
| tl_shared = len(words & {"at", "ay", "ng"}) | |
| ceb_shared = len(words & {"nga", "ug"}) | |
| if tl_shared > ceb_shared: | |
| return "tagalog" | |
| elif ceb_shared > tl_shared: | |
| return "cebuano" | |
| # If tied, need at least some exclusive markers | |
| if tl_hits >= 1: | |
| return "tagalog" | |
| if ceb_hits >= 1: | |
| return "cebuano" | |
| return "english" | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # 5W+1H SIGNAL SETS (per language) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _5W1H = { | |
| "english": { | |
| "who": [ | |
| # Government / political roles | |
| r"\b(president|senator|official|police|army|government|spokesperson" | |
| r"|mayor|governor|minister|secretary|officer|suspect|victim|resident" | |
| r"|woman|man|person|group|company|department|commission" | |
| # Diplomatic / international roles | |
| r"|ambassador|envoy|diplomat|delegation|administration|agency" | |
| r"|council|foreign affairs|representative|commander|general" | |
| r"|chief|director|chairman|chairperson|head of state" | |
| # Professionals / civilian roles | |
| r"|teacher|doctor|nurse|engineer|farmer|fisherman|worker" | |
| r"|employee|student|driver|pilot|seafarer|businessman" | |
| r"|lawyer|judge|prosecutor|journalist|reporter|analyst" | |
| # Organizations | |
| r"|DFA|DOJ|DOH|DSWD|PNP|AFP|DILG|DBM|DOE|DOF|DOLE|DepEd" | |
| r"|PAGASA|NDRRMC|PHIVOLCS|BFP|PCG|BOC|BIR|NBI|CIDG" | |
| r"|United Nations|UN|WHO|ASEAN|NATO|IMF|World Bank" | |
| r"|Red Cross|UNICEF|EU|ICC|WTO|ADB|World Health)\b", | |
| # Proper name (two capitalized words) | |
| r"\b[A-Z][a-z]+ [A-Z][a-z]+\b", | |
| ], | |
| "what": [ | |
| # Crime / incident verbs | |
| r"\b(killed|arrested|died|fired|resigned|signed|announced|launched" | |
| r"|found|caught|charged|convicted|indicted|released|confirmed" | |
| r"|injured|evacuated|repatriated|rescued|seized" | |
| # Diplomatic / political action verbs | |
| r"|secured|negotiated|pledged|assured|dismissed|classified" | |
| r"|designated|imposed|warned|emphasized|implemented|deployed" | |
| r"|directed|ordered|reached|passed|approved|granted|ratified" | |
| r"|revoked|enacted|mandated|urged|demanded|condemned|praised" | |
| r"|denounced|recognized|established|forged|brokered|mediated" | |
| # Economic / social verbs | |
| r"|invested|allocated|distributed|donated|raised|collected" | |
| r"|privatized|subsidized|regulated|deregulated|imported|exported" | |
| r"|suspended|resumed|inaugurated|commissioned|expanded" | |
| # Disaster / health verbs | |
| r"|struck|devastated|flooded|collapsed|erupted|hit|damaged" | |
| r"|destroyed|displaced|stranded|trapped|missing|recovered" | |
| r"|tested positive|hospitalized|vaccinated|quarantined" | |
| # Governance / institutional verbs | |
| r"|filed|submitted|vetoed|overturned|upheld|repealed" | |
| r"|proposed|endorsed|recommended|certified|validated)\b", | |
| ], | |
| "where": [ | |
| r"\b(in|at|near|from|inside|outside|across|within|throughout)\s+[A-Z][a-zA-Z]", | |
| r"\b(city|province|barangay|street|avenue|district|region|country" | |
| r"|town|village|municipality|island|strait|corridor|border|port" | |
| r"|embassy|consulate|capitol|palace|headquarters|territory" | |
| r"|hospital|school|university|church|market|airport|seaport" | |
| r"|river|mountain|coast|bay|ocean|sea|pier|terminal)\b", | |
| # Philippine locations | |
| r"\b(Manila|Cebu|Davao|Quezon City|Makati|Pasay|Pasig" | |
| r"|Philippines|Metro Manila|Mindanao|Visayas|Luzon" | |
| r"|Taguig|Caloocan|Zamboanga|Iloilo|Bacolod|Baguio" | |
| r"|Tacloban|General Santos|Cagayan de Oro|Butuan)\b", | |
| # International locations | |
| r"\b(Washington|Beijing|Tokyo|Seoul|Jakarta|Tehran|Moscow" | |
| r"|Hormuz|Middle East|Asia|Europe|Pacific|South China Sea" | |
| r"|West Philippine Sea|ASEAN|United States|China|Japan" | |
| r"|Singapore|Malaysia|Indonesia|Vietnam|Thailand|India" | |
| r"|North Korea|South Korea|Taiwan|Hong Kong|Australia)\b", | |
| ], | |
| "when": [ | |
| r"\b(yesterday|today|this morning|tonight|last (night|week|month|year)" | |
| r"|on (Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)" | |
| r"|on [A-Z][a-z]+ \d+|at \d+:\d+|around \d+" | |
| r"|earlier this (month|week|year)|earlier today|recently" | |
| r"|this (month|week|year)|over the (weekend|past)" | |
| r"|in (January|February|March|April|May|June|July|August" | |
| r"|September|October|November|December)" | |
| r"|ago|as of|since then|prior to this" | |
| r"|following a|after a|during the)\b", | |
| ], | |
| "why": [ | |
| r"\b(after|due to|because( of)?|following|as a result|amid" | |
| r"|over|in response to|allegedly|reportedly|in connection with" | |
| r"|in light of|amid escalating|in the wake of|in order to" | |
| r"|to address|to ensure|to protect|to prevent|to support" | |
| r"|citing|on the grounds|as part of|pursuant to" | |
| r"|in compliance with|under the terms|triggered by" | |
| r"|stemming from|arising from|prompted by|sparked by)\b", | |
| ], | |
| "how": [ | |
| # Physical methods | |
| r"\b(shot|stabbed|strangled|burned|drowned|hanged|beaten|poisoned)\b", | |
| # Process / mechanism | |
| r"\b(via|through|using|by means of|with a|by a" | |
| r"|through diplomatic|through a|via executive|under the" | |
| r"|by issuing|by signing|by conducting|by deploying" | |
| r"|in coordination with|in partnership with" | |
| r"|by filing|by passing|by enacting|by ratifying" | |
| r"|by negotiating|by securing|through negotiations" | |
| r"|under an agreement|under the arrangement" | |
| r"|by phone|by letter|by resolution|by proclamation" | |
| r"|through legislation|through a memorandum" | |
| r"|with the support|with the approval|with the help)\b", | |
| ], | |
| }, | |
| "tagalog": { | |
| "who": [ | |
| r"\b(si|ni|kay)\s+[A-Z][a-zA-Z]", | |
| r"\b(ang\s+)?(lalaki|babae|opisyal|pulis|sundalo|suspek|biktima" | |
| r"|pangulo|senador|alkalde|gobernador|miyembro|grupo|ahensya" | |
| # Government / political titles | |
| r"|kalihim|embahador|kinatawan|tanggapan|kagawaran|komisyon" | |
| r"|konsehal|kapitan|direktor|hepe|pinuno|lider" | |
| r"|kongresista|mambabatas|hukom|piskal|abogado" | |
| r"|heneral|koronel|mayor|komandante" | |
| # Titles / prefixes | |
| r"|punong|bise|dating|bagong" | |
| # Professionals / roles | |
| r"|guro|doktor|nars|inhinyero|mangingisda|magsasaka" | |
| r"|empleyado|manggagawa|negosyante|pasahero|estudyante" | |
| r"|residente|mamamayan|bata|magulang|pamilya" | |
| r"|drayber|piloto|tripulante|seaman|OFW|marino" | |
| # Organizations | |
| r"|PNP|AFP|DILG|DOH|DepEd|DSWD|DOJ|DOLE|DOE|DBM|DOF" | |
| r"|PAGASA|NDRRMC|PHIVOLCS|BFP|PCG|BOC|BIR|NBI|CIDG" | |
| r"|kagawaran ng|tanggapan ng|komisyon sa)\b", | |
| ], | |
| "what": [ | |
| # Crime / incident verbs | |
| r"\b(patay|namatay|inaresto|nahuli|nasugatan|natagpuan|nagresign" | |
| r"|nagbitiw|inihayag|idineklara|sinabi|naaksidente|nasunog" | |
| r"|nabuntis|kinuha|dinakip|nakulong|nailigtas|napatay" | |
| r"|hinoldap|ninakawan|ginahasa|pinagbabaril|sumabog" | |
| # Political / diplomatic verbs | |
| r"|pinirmahan|inaprubahan|itinanggal|ipinasa|inilunsad" | |
| r"|binigyang-diin|ipinag-utos|inilipat|ipinagbawal" | |
| r"|tinanggap|ibinigay|inilabas|ikinalat|ipinagkaloob" | |
| r"|itinakda|isinulong|ipinaabot|pinagtibay|ginawaran" | |
| r"|tinugunan|tinuldukan|pinangunahan|pinasinayaan" | |
| r"|nagdeklara|nagproklama|nagpatupad|nagsampa" | |
| r"|naghain|naglabas|nagtalaga|nagbigay" | |
| # Disaster / calamity verbs | |
| r"|nasalanta|binaha|lumindol|niyanig|tinangay|nawasak" | |
| r"|gumuho|bumaha|naglandslide|lumikas|nilikas|inilikas" | |
| r"|nasira|natumba|napinsala|naanod|natabunan" | |
| # Health / medical verbs | |
| r"|nagpositibo|nagnegatibo|naospital|na-confine|gumaling" | |
| r"|nagpabakuna|nakumpirma|nagkaroon|dumarami|kumalat" | |
| r"|tumataas|bumababa|na-quarantine|nagpa-test" | |
| # Economic / labor verbs | |
| r"|nagtaas|bumaba|tumaas|bumagsak|namuhunan|nagwelga" | |
| r"|nag-rally|umakyat|pumalo|sumirit|naglaan|nangutang" | |
| r"|nagsara|nagbukas|nagtanggal|nag-layoff" | |
| # Elections / political process | |
| r"|nanumpa|nanalo|natalo|tumakbo|kumandidato" | |
| r"|nagfile|nag-withdraw|nagkampanya|ibinoto" | |
| r"|nanungkulan|pinalitan|sinibak|tinanggal" | |
| # Social / education | |
| r"|nagreklamo|nagprotesta|nagmartsa|sumuko" | |
| r"|nagtapos|pumasa|nag-enroll|pinauwi|na-deport" | |
| # Sports / weather | |
| r"|nanalo|natalo|nagwagi|nagtala|tumama)\b", | |
| # ββ Broad morphological verb patterns (catch translated verbs) ββ | |
| # nag-/naka-/nakapag- prefix verbs (actor focus, completed) | |
| r"\b(nag|naka|nakapag)(bigay|secure|protekta|siguro|tanggol" | |
| r"|tustos|buo|tayo|hatid|dala|lagay|lunsad|bukas|sara" | |
| r"|paalala|babala|pahayag|patupad|sampa|hain|taas" | |
| r"|baba|lakad|takbo|labas|pasok|balik|uwi|alis" | |
| r"|pirmahan|apruba|deklara|proklama|negosyo|kampanya)\b", | |
| # ipinag-/ipinahayag/ipinag-utos (object focus, completed) | |
| r"\bipina(g-utos|hayag|laban|gtibay|gkaloob|lagay|bawal|tawag)\b", | |
| # Diplomatic/political nouns (common in translations) | |
| r"\b(kasunduan|kasiguruhan|kalayaan|kapayapaan|seguridad" | |
| r"|diplomatiko|negosasyon|exemption|pag-eexempt|tratado" | |
| r"|alyansa|kooperasyon|pakikipagtulungan|misyon|delegasyon" | |
| r"|proteksyon|garantiya|pagsang-ayon|pag-apruba)\b", | |
| ], | |
| "where": [ | |
| r"\bsa\s+[A-Z][a-zA-Z]", | |
| r"\b(barangay|lungsod|lalawigan|bayan|munisipyo|probinsya|isla" | |
| r"|kalye|abenida|distrito|rehiyon|kalsada|palasyo|kapitolyo" | |
| r"|embahada|konsulado|himpilan|kampo|daungan|pantalan" | |
| r"|ospital|paaralan|eskwelahan|simbahan|palengke|merkado" | |
| r"|estasyon|terminal|paliparan|airport|seaport|pier" | |
| r"|karagatan|dagat|ilog|bundok|kabundukan|baybayin)\b", | |
| # Philippine locations | |
| r"\b(Maynila|Pilipinas|Metro Manila|Mindanao|Visayas|Luzon" | |
| r"|Quezon City|Makati|Pasig|Taguig|Pasay|Caloocan" | |
| r"|Cebu|Davao|Zamboanga|Iloilo|Bacolod|Cagayan de Oro" | |
| r"|Baguio|Tacloban|Legazpi|Naga|General Santos" | |
| r"|NCR|CALABARZON|Central Luzon|Western Visayas)\b", | |
| ], | |
| "when": [ | |
| r"\b(kagabi|kaninang|ngayong umaga|kahapon" | |
| r"|nitong (Lunes|Martes|Miyerkules|Huwebes|Biyernes|Sabado|Linggo)" | |
| r"|noong|ngayon|nitong|kamakailan" | |
| r"|noong nakaraang (buwan|linggo|taon|gabi|umaga)" | |
| r"|ngayong (buwan|linggo|taon|araw|gabi)" | |
| r"|sa (Lunes|Martes|Miyerkules|Huwebes|Biyernes|Sabado|Linggo)" | |
| r"|kanina|sa umaga|sa hapon|sa gabi|sa tanghali|sa madaling-araw" | |
| r"|bandang (alas|tanghali|gabi|umaga|hapon)" | |
| r"|alas \d+|dakong|nang (umaga|hapon|gabi)" | |
| r"|sa (Enero|Pebrero|Marso|Abril|Mayo|Hunyo" | |
| r"|Hulyo|Agosto|Setyembre|Oktubre|Nobyembre|Disyembre)" | |
| r"|taong \d{4}|ika-\d+" | |
| # Translation-common temporal phrases | |
| r"|nitong buwan|sa loob ng|bago ang|pagkatapos ng" | |
| r"|kamakailan lang|sa nakaraang|sa susunod na" | |
| r"|simula noong|mula noong|hanggang sa|sa panahon ng)\b", | |
| ], | |
| "why": [ | |
| r"\b(matapos|dahil( sa)?|kasunod|bunga ng|bilang tugon|kaugnay ng" | |
| r"|ayon sa|umano|diumano|matapos ang|sa gitna ng|upang|alinsunod sa" | |
| r"|para sa|alang-alang sa|bilang bahagi ng|patungkol sa" | |
| r"|sa kadahilanang|sanhi ng|dulot ng|dahilan sa" | |
| r"|sa kabila ng|sa harap ng|nang dahil sa" | |
| r"|bilang pagtugon|kaugnay ng|may kinalaman sa" | |
| r"|sa ilalim ng|batay sa|ayon na rin sa" | |
| r"|laban sa|kontra sa|pabor sa|sang-ayon sa)\b", | |
| ], | |
| "how": [ | |
| # Physical methods | |
| r"\b(binaril|sinaksak|sinakal|sinunog|nalunod|pinalo|pinagtataga" | |
| r"|sinagasaan|binangga|nasagasaan|nabaril|nasaksak)\b", | |
| # Process / mechanism | |
| r"\b(sa pamamagitan ng|gamit ang|sa tulong ng|sa pakikipagtulungan" | |
| r"|sa pakikipag-ugnayan|sa pamamaraang|sa prosesong" | |
| r"|sa bisa ng|ayon sa proseso|sa ilalim ng batas" | |
| r"|sa pamamagitan ng resolusyon|sa executive order" | |
| r"|sa tulong ng teknolohiya|gamit ang mga kagamitan" | |
| # Diplomatic / institutional process | |
| r"|sa pakikipag-negosasyon|sa pakikipag-alyansa" | |
| r"|sa diplomatikong paraan|sa pamamagitan ng usapan" | |
| r"|sa pormal na kasunduan|sa bilateral na usapan" | |
| r"|sa tawag sa telepono|sa liham na opisyal" | |
| r"|sa bisa ng kautusan|sa atas ng|sa panawagan" | |
| r"|sa direktiba|sa kautusan|sa resolusyon" | |
| r"|sa high.level.+(usapan|pulong|pag-uusap))\b", | |
| ], | |
| }, | |
| "cebuano": { | |
| "who": [ | |
| r"\b(si|ni|kang)\s+[A-Z][a-zA-Z]", | |
| r"\b(lalaki|babaye|opisyal|pulis|sundalo|suspetsado|biktima" | |
| r"|presidente|senador|alkalde|gobernador|miyembro|grupo" | |
| r"|drayber|tripulante" | |
| # Government / political titles | |
| r"|sekretaryo|embahador|tinugyanan|konsehal|kapitan" | |
| r"|direktor|punoan|pangulo|lider" | |
| r"|kongresista|maghuhukom|piskal|abogado" | |
| r"|heneral|koronel|mayor|komandante" | |
| # Titles / prefixes | |
| r"|punong|bise|kanhing|bag-ong" | |
| # Professionals / roles | |
| r"|magtutudlo|doktor|nars|inhinyero|mangingisda|mag-uuma" | |
| r"|empleyado|mamumuo|negosyante|pasahero|estudyante" | |
| r"|residente|lungsuranon|bata|ginikanan|pamilya" | |
| r"|piloto|seaman|OFW|marino" | |
| # Organizations | |
| r"|PNP|AFP|DILG|DOH|DepEd|DSWD|DOJ|DOLE|DOE|DBM|DOF" | |
| r"|PAGASA|NDRRMC|PHIVOLCS|BFP|PCG|BOC|BIR|NBI|CIDG" | |
| r"|buhatan sa|ahensya sa)\b", | |
| ], | |
| "what": [ | |
| # Crime / incident verbs | |
| r"\b(namatay|gidakop|nasakitan|nakit-an|nagdaog|nagbitiw|gipatay" | |
| r"|giaresto|naigo|napusil|nasunogan|nalunod|gisalibay|nakaplagan" | |
| r"|nabsan|nakulang|nawad-an sa kinabuhi|nasamad" | |
| r"|giholdap|gikawatan|gilugos|gipusil|mibuto" | |
| # Political / diplomatic verbs | |
| r"|gipirmahan|giaprubahan|gitangtang|gipasa|gilansad" | |
| r"|gibug-aton|gimandoan|gipatuman|klarohon|giablihan" | |
| r"|gisirado|gihatag|gipagawas|gipakaylap|gitugutan" | |
| r"|gitakda|gipadayon|giduso|gipalig-on|gihatagan" | |
| r"|gipanguluhan|gipasinayaan|gideklara|giproklamar" | |
| # Disaster / calamity verbs | |
| r"|gianud|gilunopan|gibanlas|nabungkag|nahugno" | |
| r"|gidaot|napukan|nagun-ob|gilikas|gipalikay" | |
| r"|napiang|nadaot|naangol|natabunan|gilubong" | |
| # Health / medical verbs | |
| r"|nagpositibo|nagnegatibo|giospital|gipa-confine" | |
| r"|nagpabakuna|naayo|nagkadaghan|mikaylap|misaka" | |
| r"|na-quarantine|nagpa-test" | |
| # Economic / labor verbs | |
| r"|misaka|miubos|mitubo|mihagbong|mipalit" | |
| r"|nagwelga|nag-rally|giablihan|gisirhan" | |
| r"|namuhunan|gibaligya|gipalit|gitangtangan|gi-layoff" | |
| # Elections / political process | |
| r"|nanumpa|midaog|napildi|mikandidato" | |
| r"|nagfile|mi-withdraw|nagkampanya|giboto" | |
| r"|milingkod|gipulihan|gisibak|gitangtang" | |
| # Social / education | |
| r"|nagreklamo|nagprotesta|nagmartsa|misurender" | |
| r"|nakagraduate|nipasa|nahagbong|nagtuon" | |
| r"|gipauli|gi-deport" | |
| # Sports / weather | |
| r"|midaog|napildi|nagwagi|nagtala|mitama)\b", | |
| # ββ Broad morphological verb patterns (catch translated verbs) ββ | |
| # gi-/nag-/naka-/mi- prefix verbs (Cebuano focus/aspect markers) | |
| r"\b(gi|nag|naka|mi)(hatag|seguro|protekta|panalipod" | |
| r"|tustos|tukod|hatod|dala|butang|lansad|abli|sirado" | |
| r"|pahibalo|pasidaan|pahayag|patuman|sumbong|hain|saka" | |
| r"|ubos|lakaw|dagan|gawas|sulod|balik|uli|biya" | |
| r"|pirma|apruba|deklara|proklama|negosyo|kampanya)\b", | |
| # Diplomatic/political nouns (common in CEB translations) | |
| r"\b(kasabutan|kasigurohan|kagawasan|kalinaw|seguridad" | |
| r"|diplomatiko|negosasyon|exemption|pag-exempt|tratado" | |
| r"|alyansa|kooperasyon|pakigtambayayong|misyon|delegasyon" | |
| r"|proteksyon|garantiya|pag-uyon|pag-apruba|paghusay)\b", | |
| ], | |
| "where": [ | |
| r"\bsa\s+[A-Z][a-zA-Z]", | |
| r"\b(barangay|dakbayan|lalawigan|munisipyo|probinsya|isla" | |
| r"|kalsada|distrito|rehiyon|sityo|purok|palasyo|kapitolyo" | |
| r"|embahada|kampo|dunggoanan|pantalan" | |
| r"|ospital|eskwelahan|simbahan|merkado|tindahan" | |
| r"|estasyon|terminal|airport|seaport|pier" | |
| r"|kadagatan|dagat|suba|bukid|kabukiran|baybayon)\b", | |
| # Philippine locations | |
| r"\b(Sugbo|Dabaw|Pilipinas|Mindanao|Visayas|Luzon" | |
| r"|Cebu City|Mandaue|Lapu-Lapu|Talisay|Minglanilla" | |
| r"|Davao City|Zamboanga|Iloilo|Bacolod|Cagayan de Oro" | |
| r"|General Santos|Tagbilaran|Dumaguete|Butuan" | |
| r"|NCR|Central Visayas|Western Visayas|Northern Mindanao)\b", | |
| ], | |
| "when": [ | |
| r"\b(niadtong|kagab-i|karong buntag|karon" | |
| r"|sa (Lunes|Martes|Miyerkoles|Huwebes|Biyernes|Sabado|Domingo)" | |
| r"|sa gabii|sa buntag|sa hapon|sa udto|sa kadlawon" | |
| r"|sa miaging (bulan|semana|tuig|gabii|buntag)" | |
| r"|bag-o lang|karong (bulan|semana|tuiga|adlaw|gabii)" | |
| r"|gahapon|ganina|kaganina" | |
| r"|mga alas \d+|mga dakong" | |
| r"|sa (Enero|Pebrero|Marso|Abril|Mayo|Hunyo" | |
| r"|Hulyo|Agosto|Septyembre|Oktubre|Nobyembre|Disyembre)" | |
| r"|tuig \d{4}|ika-\d+" | |
| # Translation-common temporal phrases | |
| r"|niining bulan|sa sulod sa|sa wala pa ang" | |
| r"|bag-ohay lang|sa miaging|sa sunod nga" | |
| r"|sukad sa|gikan sa|hangtod sa|sa panahon sa)\b", | |
| ], | |
| "why": [ | |
| r"\b(human|tungod (sa|kay)|human kini|ingon man|sumala sa|matud pa" | |
| r"|gisulti nga|sanglit|bunga sa|taliwala sa|aron" | |
| r"|subay sa|alang sa|isip bahin sa|tungod niini" | |
| r"|bisan pa|sa luyo sa|atubangan sa" | |
| r"|isip tubag|kalambigit sa|may kalabutan sa" | |
| r"|ilalom sa|base sa|sumala na usab sa" | |
| r"|batok sa|kontra sa|pabor sa|uyon sa)\b", | |
| ], | |
| "how": [ | |
| # Physical methods | |
| r"\b(gipusil|gidunggab|gisunog|nalunod|gibunal|gistab|gisaksak" | |
| r"|gibangga|gisagasaan|gibanlas|gisumbag|gisipa)\b", | |
| # Process / mechanism | |
| r"\b(pinaagi sa|gamit ang|pinaagi ng|sa pamamagitan" | |
| r"|sa pakig-alayon|sa pakigtambayayong|sa proseso nga" | |
| r"|sa bisa sa|sumala sa proseso|ilalom sa balaod" | |
| r"|pinaagi sa resolusyon|sa executive order" | |
| r"|sa tabang sa teknolohiya|gamit ang mga kagamitan" | |
| # Diplomatic / institutional process | |
| r"|sa pakig-negosasyon|sa pakig-alyansa" | |
| r"|sa diplomatikong paagi|pinaagi sa panag-istoryahanay" | |
| r"|sa pormal nga kasabutan|sa bilateral nga panag-istoryahanay" | |
| r"|sa tawag sa telepono|sa opisyal nga sulat" | |
| r"|sa bisa sa mando|sa atas sa|sa panawagan" | |
| r"|sa direktiba|sa mando|sa resolusyon" | |
| r"|sa high.level.+(panag-istoryahanay|tigom|miting))\b", | |
| ], | |
| }, | |
| } | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # ATTRIBUTION MARKERS | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _ATTRIBUTION = { | |
| "english": [ | |
| r"\baccording to\b", r"\bsaid\b", r"\bconfirmed\b", r"\bannounced\b", | |
| r"\bstated\b", r"\bdeclared\b", r"\breported by\b", r"\bquoted\b", | |
| r"\bcited\b", r"\bdisclosed\b", r"\brevealed\b", r"\bemphasized\b", | |
| r"\bclarified\b", r"\bnoted\b", r"\bdismissed\b", r"\bdirected\b", | |
| r"\burged\b", r"\bwarned\b", r"\bpledged\b", r"\bassured\b", | |
| r"\bdenied\b", r"\backnowledged\b", r"\bconceded\b", | |
| # Philippine English news commonly uses Filipino attribution | |
| r"\bayon (kay|sa)\b", r"\bsinabi (ni|ng)\b", r"\baniya\b", | |
| ], | |
| "tagalog": [ | |
| # Core attribution phrases | |
| r"\bayon (kay|sa)\b", r"\baniya\b", r"\bsinabi (ni|ng)\b", | |
| r"\bulat ni\b", r"\bkinumpirma\b", r"\bidineklara\b", | |
| r"\binihayag (ni|ng)\b", r"\bipinagpaliwanag\b", r"\bnagpahayag\b", | |
| r"\bpahayag (ni|ng)\b", r"\bsabi (ni|ng)\b", r"\bnagsalita si\b", | |
| r"\beksklusibo\b", | |
| # Formal attribution verbs (flexible preposition) | |
| r"\bgiit (ni|ng)\b", r"\bbinanggit (ni|ng)\b", r"\bdiniin (ni|ng)\b", | |
| r"\bpaglilinaw (ni|ng)\b", r"\bayon din\b", | |
| # Broader attribution patterns (match translated text) | |
| r"\bbinigyang-diin (ni|ng|si)\b", r"\bkumpirmasyon (ni|ng)\b", | |
| r"\bnagbabala (si|ang)\b", r"\bnagsulong (si|ang)\b", | |
| r"\binutos (ni|ng)\b", r"\bipinag-utos (ni|ng)\b", | |
| r"\bnag-utos (si|ang|ni)\b", r"\bnilinaw (ni|ng)\b", | |
| r"\bpaalala (ni|ng)\b", r"\bipinahayag (ni|ng)\b", | |
| r"\bnag-ulat (si|ang)\b", r"\bnagbigay-diin (si|ang)\b", | |
| r"\bayon naman\b", r"\bsinabi pa\b", r"\bdagdag pa niya\b", | |
| r"\bpag-uulat (ni|ng)\b", r"\bkuwento (ni|ng)\b", | |
| # Translation-common attribution (no specific preposition needed) | |
| r"\bnangako (si|ang)\b", r"\bkategorikong (itinanggi|isinusulong)\b", | |
| r"\bnag-atas (si|ang)\b", r"\bdinirehe (ni|ng)\b", | |
| r"\bnag-direct(a)? (si|ang)\b", | |
| ], | |
| "cebuano": [ | |
| # Core attribution phrases | |
| r"\bsumala (pa ni|kang|sa)\b", r"\bgikumpirmar\b", | |
| r"\bniingon\b", r"\bnidugang sa pag-ingon\b", r"\bingnon\b", | |
| r"\bmatud pa (ni|sa)\b", r"\bgisulti (ni|sa)\b", r"\bgipakita (ni|sa)\b", | |
| r"\bekslusibo\b", | |
| # Formal attribution verbs (flexible preposition) | |
| r"\bgibutyag (ni|sa)\b", r"\bgipahibalo (ni|sa)\b", | |
| r"\bgiklarohan (ni|sa)\b", r"\bgiduso (ni|sa)\b", r"\bpasabot (ni|sa)\b", | |
| # Broader attribution patterns (match translated text) | |
| r"\bgibug-aton (ni|sa|ng)\b", r"\bkumpirmasyon (ni|sa)\b", | |
| r"\bnagpasidaan (si|ang)\b", r"\bnagduso (si|ang)\b", | |
| r"\bgimandoan (ni|sa)\b", r"\bgitin-aw (ni|sa)\b", | |
| r"\bpahinumdom (ni|sa)\b", r"\bgipahayag (ni|sa)\b", | |
| r"\bnag-report (si|ang)\b", r"\bnag-utos (si|ang|ni)\b", | |
| r"\bsumala sab\b", r"\bniingon pa\b", r"\bdugang pa niya\b", | |
| r"\bpag-report (ni|sa)\b", r"\basoy (ni|sa)\b", | |
| # Translation-common attribution | |
| r"\bmisaad (si|ang)\b", r"\bgisalikway (sa|ni|usab)\b", | |
| r"\bnag-atas (si|ang)\b", | |
| ], | |
| } | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # FORMAL TRANSITION MARKERS | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _TRANSITIONS = { | |
| "english": [ | |
| r"\bmeanwhile\b", r"\badditionally\b", r"\bfurthermore\b", | |
| r"\bhowever\b", r"\bin addition\b", r"\bin related news\b", | |
| r"\bseparately\b", r"\bas of (this writing|press time)\b", | |
| r"\bthe investigation is (ongoing|continuing)\b", | |
| r"\bno charges have been filed\b", r"\bprior to\b", | |
| r"\bunder the\b", r"\balso\b", r"\bwhile\b", | |
| r"\bon the other hand\b", r"\bnevertheless\b", r"\bnonetheless\b", | |
| r"\bin contrast\b", r"\bas a result\b", r"\bconsequently\b", | |
| r"\bmoreover\b", | |
| ], | |
| "tagalog": [ | |
| # Core transitions | |
| r"\bsamantala\b", r"\bdagdag pa (rito|nito)\b", r"\bkaugnay nito\b", | |
| r"\bsa kabilang banda\b", r"\bkaragdagan\b", r"\bbukod dito\b", | |
| r"\bnais ipaalala\b", r"\bpatuloy ang imbestigasyon\b", | |
| r"\bwala pang nahatulan\b", r"\bkasunod (nito|ng)\b", | |
| r"\bsa kabila nito\b", r"\bbilang karagdagan\b", | |
| r"\bgayunpaman\b", r"\bsubalit\b", r"\bdatapwat\b", | |
| # Additional transitions | |
| r"\bnang dahil dito\b", r"\bbilang resulta\b", | |
| r"\bsa ganang\b", r"\bsa kaugnay\b", r"\bsa madaling salita\b", | |
| r"\bsa kabilang dako\b", r"\bsamakatuwid\b", r"\bkung kaya\b", | |
| r"\bpansamantala\b", r"\bunang\b", r"\bpangalawa\b", | |
| r"\bbago ang lahat\b", r"\bsa unang tingin\b", | |
| r"\bnararapat banggitin\b", r"\bkapansin-pansin\b", | |
| r"\bsa bahagi ng\b", r"\bayon naman\b", | |
| # Translation-common transitions (paragraph-starting phrases) | |
| r"\bSa ilalim ng\b", r"\bBago ang\b", r"\bKategorikong\b", | |
| r"\bBagaman\b", r"\bBagama.?t\b", r"\bAng matagumpay\b", | |
| r"\bBukod (sa|dito)\b", r"\bMaliban (sa|dito)\b", | |
| r"\bSa kabila ng\b", r"\bSa harap ng\b", | |
| ], | |
| "cebuano": [ | |
| # Core transitions | |
| r"\bsamtang\b", r"\bhinuon\b", r"\bdugang pa\b", | |
| r"\bsa laing bahin\b", r"\bpinasayran usab\b", | |
| r"\bpadayon ang imbestigasyon\b", r"\bwala pay nasentensiyahan\b", | |
| r"\bnidugang\b", | |
| r"\bapan\b", r"\blakip niini\b", r"\bbisan pa\b", | |
| # Additional transitions | |
| r"\btungod niini\b", r"\bisip resulta\b", | |
| r"\bsa ato pa\b", r"\bsa kalambigitan\b", r"\bsa laktod nga pulong\b", | |
| r"\bsa pikas bahin\b", r"\bbusa\b", r"\bmao nga\b", | |
| r"\bsa pagkakaron\b", r"\buna\b", r"\bikaduha\b", | |
| r"\bsa wala pa ang tanan\b", r"\bsa unang tan-aw\b", | |
| r"\bangay hisgutan\b", r"\bmamatikdan\b", | |
| r"\bsa bahin sa\b", r"\bsumala sab\b", | |
| # Translation-common transitions (paragraph-starting phrases) | |
| r"\bIlalom sa\b", r"\bSa wala pa ang\b", r"\bGisalikway\b", | |
| r"\bBisan og\b", r"\bBisan pa\b", r"\bAng malampusong\b", | |
| r"\bGawas (sa|niini)\b", r"\bLuwas (sa|niini)\b", | |
| r"\bSa luyo sa\b", r"\bAtubangan sa\b", | |
| ], | |
| } | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # FORMAL JOURNALISM MARKERS (bylines, exclusive tags) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _FORMAL_MARKERS = { | |
| "english": [ | |
| r"\breported by\b", r"\bstaff reporter\b", r"\bcorrespondent\b", | |
| r"\bexclusive\b", r"\bbreaking\b", r"\bspecial report\b", | |
| r"\beditor\b", r"\bnewsdesk\b", r"\bPress Release\b", | |
| ], | |
| "tagalog": [ | |
| r"\bulat ni\b", r"\bespesyal na ulat\b", r"\beksklusibo\b", | |
| r"\bmula sa\s+[A-Z][a-z]+\b", r"\bpang-editoryal\b", | |
| r"\biulat ni\b", r"\b-PNA\b", r"\b-PTV\b", r"\b-GMA\b", | |
| r"\b-ABS-CBN\b", r"\b-Rappler\b", r"\b-Inquirer\b", | |
| r"\bBulletin ng\b", r"\bmga larawan mula sa\b", | |
| ], | |
| "cebuano": [ | |
| r"\bulat ni\b", r"\bekslusibo\b", r"\bespesyal nga taho\b", | |
| r"\bbalita gikan sa\b", | |
| r"\bitaho ni\b", r"\b-SunStar\b", r"\b-CDN\b", | |
| r"\b-Freeman\b", r"\b-SuperBalita\b", | |
| r"\bmga hulagway gikan sa\b", | |
| ], | |
| } | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # HELPERS | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _match_any(patterns: list, text: str) -> bool: | |
| return any(re.search(p, text, re.IGNORECASE) for p in patterns) | |
| def _count_matches(patterns: list, text: str) -> int: | |
| return sum(1 for p in patterns if re.search(p, text, re.IGNORECASE)) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # MAIN FUNCTION | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def analyze_structure(text: str) -> dict: | |
| """Analyze a news article BODY for inverted pyramid journalism structure. | |
| The text parameter should be the article BODY only (no headline). | |
| The lead is extracted from the first two paragraphs of the body. | |
| Returns: | |
| dict with keys: | |
| language: detected language ('english'/'tagalog'/'cebuano') | |
| lead_5w1h: dict β which of Who/What/Where/When/Why/How found in lead | |
| lead_completeness: int (0β6) β how many 5W+1H signals found in lead | |
| has_attribution: bool β named source attribution found in body | |
| attribution_count: int β number of attribution signals found | |
| has_transitions: bool β formal transition markers found | |
| transition_count: int β number of transition signals | |
| has_formal_markers: bool β bylines / exclusive tags found | |
| formalism_score: int (0β100) β overall journalism formalism score | |
| assessment: str β human-readable verdict | |
| signals_found: list[str] β matched signals for display | |
| recommendations: list[dict] β actionable improvement tips | |
| """ | |
| if not text or len(text.strip()) < 30: | |
| return { | |
| "language": "unknown", | |
| "lead_5w1h": {}, | |
| "lead_completeness": 0, | |
| "has_attribution": False, | |
| "attribution_count": 0, | |
| "has_transitions": False, | |
| "transition_count": 0, | |
| "has_formal_markers": False, | |
| "formalism_score": 0, | |
| "assessment": "Text too short for structure analysis.", | |
| "signals_found": [], | |
| "recommendations": [], | |
| } | |
| lang = _detect_language(text) | |
| # Extract lead: first two paragraphs (or first 500 chars if no blank line) | |
| # Modern digital news often splits the 5W1H across the hook and context paragraphs. | |
| paragraphs = [p.strip() for p in re.split(r"\n\s*\n", text) if p.strip()] | |
| lead = "\n".join(paragraphs[:2]) if len(paragraphs) >= 2 else text[:500] | |
| lead_lower = lead.lower() | |
| full_lower = text.lower() | |
| # Select patterns for the detected language, fall back to English | |
| patterns_5w1h = _5W1H.get(lang, _5W1H["english"]) | |
| patterns_attr = _ATTRIBUTION.get(lang, _ATTRIBUTION["english"]) | |
| patterns_trans = _TRANSITIONS.get(lang, _TRANSITIONS["english"]) | |
| patterns_formal = _FORMAL_MARKERS.get(lang, _FORMAL_MARKERS["english"]) | |
| # ββ Lead: 5W+1H check ββ | |
| dimensions = ["who", "what", "where", "when", "why", "how"] | |
| lead_5w1h = {} | |
| signals_found = [] | |
| for dim in dimensions: | |
| dim_patterns = patterns_5w1h.get(dim, []) | |
| found = _match_any(dim_patterns, lead_lower) | |
| # Multi-language fallback: if detected as Tagalog/Cebuano but 5W+1H | |
| # patterns don't match (common in code-switching), also try English patterns | |
| if not found and lang in ("tagalog", "cebuano"): | |
| en_patterns = _5W1H["english"].get(dim, []) | |
| found = _match_any(en_patterns, lead_lower) | |
| # For English text with Philippine context, also try Filipino patterns | |
| if not found and lang == "english": | |
| for alt_lang in ("tagalog", "cebuano"): | |
| alt_patterns = _5W1H[alt_lang].get(dim, []) | |
| if _match_any(alt_patterns, lead_lower): | |
| found = True | |
| break | |
| lead_5w1h[dim] = found | |
| if found: | |
| signals_found.append(f"Lead has '{dim.upper()}'") | |
| lead_completeness = sum(lead_5w1h.values()) | |
| # ββ Body: attribution ββ | |
| attribution_count = _count_matches(patterns_attr, full_lower) | |
| # Multi-language fallback for attribution too | |
| if lang in ("tagalog", "cebuano"): | |
| en_attr = _count_matches(_ATTRIBUTION["english"], full_lower) | |
| attribution_count = max(attribution_count, en_attr) | |
| elif lang == "english": | |
| # English articles may use Filipino attributions | |
| tl_attr = _count_matches(_ATTRIBUTION["tagalog"], full_lower) | |
| attribution_count = max(attribution_count, tl_attr) | |
| has_attribution = attribution_count > 0 | |
| if has_attribution: | |
| signals_found.append(f"Attribution found ({attribution_count} signal(s))") | |
| # ββ Body: transitions ββ | |
| transition_count = _count_matches(patterns_trans, full_lower) | |
| # Multi-language fallback | |
| if lang in ("tagalog", "cebuano"): | |
| en_trans = _count_matches(_TRANSITIONS["english"], full_lower) | |
| transition_count = max(transition_count, en_trans) | |
| has_transitions = transition_count > 0 | |
| if has_transitions: | |
| signals_found.append(f"Formal transitions found ({transition_count})") | |
| # ββ Formal markers (bylines, exclusive) ββ | |
| formal_count = _count_matches(patterns_formal, full_lower) | |
| has_formal_markers = formal_count > 0 | |
| if has_formal_markers: | |
| signals_found.append("Formal journalism markers found (byline/exclusive)") | |
| # ββ Formalism score (0β100) ββ | |
| # Breakdown: | |
| # 5W+1H completeness: up to 48 pts (8 pts per dimension) | |
| # Attribution: up to 24 pts | |
| # Transitions: up to 16 pts | |
| # Formal markers: up to 12 pts | |
| score = 0 | |
| score += lead_completeness * 8 # max 48 | |
| score += min(24, attribution_count * 8) # max 24 | |
| score += min(16, transition_count * 8) # max 16 | |
| score += min(12, formal_count * 6) # max 12 | |
| # ββ Cebuano orthographic formalism bonus (max +10) ββ | |
| ortho_note = None | |
| if lang == "cebuano": | |
| _FORMAL_VISAYAN = [ | |
| r"\bdakbayan\b", r"\blalawigan\b", r"\bnabsan\b", | |
| r"\bniadtong\b", r"\bkagab-i\b", r"\bsumala pa ni\b", | |
| r"\bgikumpirmar\b", r"\bmatud pa\b|matud pa ni\b", | |
| ] | |
| _INFORMAL_VISAYAN = [ | |
| r"'s\s", | |
| r"\bsabi ng\b|\bayon kay\b|\bpatay\b|\bpulis\b", | |
| ] | |
| ortho_bonus = sum( | |
| 2 for p in _FORMAL_VISAYAN | |
| if re.search(p, full_lower, re.IGNORECASE) | |
| ) | |
| ortho_penalty = sum( | |
| 2 for p in _INFORMAL_VISAYAN | |
| if re.search(p, text, re.IGNORECASE) | |
| ) | |
| net_ortho = min(10, max(-6, ortho_bonus - ortho_penalty)) | |
| score += net_ortho | |
| if net_ortho > 0: | |
| ortho_note = f"Formal Visayan orthography (+{net_ortho} pts)" | |
| signals_found.append(ortho_note) | |
| elif net_ortho < 0: | |
| ortho_note = f"Informal mixing / Taglish detected ({net_ortho} pts)" | |
| signals_found.append(ortho_note) | |
| formalism_score = min(100, max(0, score)) | |
| # ββ Assessment ββ | |
| if formalism_score >= 70: | |
| assessment = "Well-structured β follows inverted pyramid journalism format." | |
| elif formalism_score >= 45: | |
| assessment = ( | |
| "Partially structured β some journalism conventions present, " | |
| "but missing key elements." | |
| ) | |
| elif formalism_score >= 20: | |
| assessment = ( | |
| "Weakly structured β few journalism markers detected. " | |
| "May be opinion, social media post, or informal writing." | |
| ) | |
| else: | |
| assessment = ( | |
| "No journalism structure detected β text lacks 5W+1H lead, " | |
| "named attribution, and formal transitions. " | |
| "Consistent with social media posts or fake news." | |
| ) | |
| # ββ Actionable recommendations ββ | |
| recommendations = _build_recommendations( | |
| lead_5w1h, dimensions, has_attribution, attribution_count, | |
| has_transitions, has_formal_markers, formalism_score | |
| ) | |
| return { | |
| "language": lang, | |
| "lead_5w1h": lead_5w1h, | |
| "lead_completeness": lead_completeness, | |
| "has_attribution": has_attribution, | |
| "attribution_count": attribution_count, | |
| "has_transitions": has_transitions, | |
| "transition_count": transition_count, | |
| "has_formal_markers": has_formal_markers, | |
| "formalism_score": formalism_score, | |
| "assessment": assessment, | |
| "signals_found": signals_found, | |
| "recommendations": recommendations, | |
| } | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # RECOMMENDATIONS BUILDER | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _build_recommendations(lead_5w1h, dimensions, has_attribution, | |
| attribution_count, has_transitions, | |
| has_formal_markers, formalism_score): | |
| """Build actionable, trilingual recommendations.""" | |
| recommendations = [] | |
| _5w1h_tips = { | |
| "who": { | |
| "en": "Add a clear subject β mention WHO is involved (e.g., a named person, official, or organization).", | |
| "fil": "Magdagdag ng malinaw na paksa β banggitin SINO ang sangkot (hal., isang pinangalanang tao, opisyal, o organisasyon).", | |
| "ceb": "Idugang og tin-aw nga hilisgutan β hisgote KINSA ang nalambigit (pananglitan, usa ka ginganlan nga tawo, opisyal, o organisasyon).", | |
| }, | |
| "what": { | |
| "en": "Specify WHAT happened β use a clear action verb describing the event (e.g., 'arrested', 'announced', 'signed').", | |
| "fil": "Tukuyin KUNG ANO ang nangyari β gumamit ng malinaw na pandiwa na naglalarawan ng pangyayari (hal., 'inaresto', 'inihayag', 'nilagdaan').", | |
| "ceb": "Itakda UNSA ang nahitabo β gamita ang tin-aw nga berbo sa paglitok sa panghitabo (pananglitan, 'gidakop', 'gipahibalo', 'gipirmahan').", | |
| }, | |
| "where": { | |
| "en": "Include WHERE it happened β add a location such as a city, province, or specific place.", | |
| "fil": "Isama KUNG SAAN nangyari β magdagdag ng lokasyon tulad ng lungsod, lalawigan, o tiyak na lugar.", | |
| "ceb": "Ilakip DIIN nahitabo β idugang og lokasyon sama sa dakbayan, lalawigan, o piho nga dapit.", | |
| }, | |
| "when": { | |
| "en": "Add WHEN it happened β include a time reference (e.g., 'yesterday', 'on Monday', 'at 3 PM').", | |
| "fil": "Idagdag KAILAN nangyari β magdagdag ng sanggunian sa oras (hal., 'kahapon', 'noong Lunes', 'nang 3 PM').", | |
| "ceb": "Idugang KANUS-A nahitabo β iapil ang reference sa oras (pananglitan, 'kagahapon', 'niadtong Lunes', 'alas 3 sa hapon').", | |
| }, | |
| "why": { | |
| "en": "Explain WHY it happened β add context or a causal phrase (e.g., 'due to', 'after', 'following').", | |
| "fil": "Ipaliwanag BAKIT nangyari β magdagdag ng konteksto o pariralang nagsasaad ng dahilan (hal., 'dahil sa', 'matapos', 'kasunod ng').", | |
| "ceb": "Ipasabot NGANO nahitabo β idugang og konteksto o hinungdan nga hugpong (pananglitan, 'tungod sa', 'human sa', 'sunod sa').", | |
| }, | |
| "how": { | |
| "en": "Describe HOW it happened β add detail about the method or manner (e.g., 'shot', 'through a resolution', 'via executive order').", | |
| "fil": "Ilarawan PAANO nangyari β magdagdag ng detalye tungkol sa paraan (hal., 'binaril', 'sa pamamagitan ng resolusyon', 'sa executive order').", | |
| "ceb": "Ihulagway GIUNSA nahitabo β idugang og detalye bahin sa pamaagi (pananglitan, 'gipusil', 'pinaagi sa resolusyon', 'pinaagi sa executive order').", | |
| }, | |
| } | |
| missing_5w1h = [dim for dim in dimensions if not lead_5w1h.get(dim)] | |
| for dim in missing_5w1h: | |
| tip = _5w1h_tips.get(dim, {}) | |
| recommendations.append({ | |
| "type": "5w1h", | |
| "dimension": dim.upper(), | |
| "priority": "high" if dim in ("who", "what") else "medium", | |
| "tip_en": tip.get("en", f"Add {dim.upper()} to the lead."), | |
| "tip_fil": tip.get("fil", f"Idagdag ang {dim.upper()} sa lead."), | |
| "tip_ceb": tip.get("ceb", f"Idugang ang {dim.upper()} sa lead."), | |
| }) | |
| # Attribution recommendation | |
| if not has_attribution: | |
| recommendations.append({ | |
| "type": "attribution", | |
| "priority": "high", | |
| "tip_en": "Add source attribution β quote or cite a named person, official, or organization (e.g., 'according to Mayor Cruz', 'ayon kay Sen. Reyes').", | |
| "tip_fil": "Magdagdag ng source attribution β mag-quote o mag-cite ng pinangalanang tao, opisyal, o organisasyon (hal., 'ayon kay Mayor Cruz', 'sinabi ni Sen. Reyes').", | |
| "tip_ceb": "Pagdugang og source attribution β pag-quote o pag-cite og ginganlan nga tawo, opisyal, o organisasyon (pananglitan, 'sumala kang Mayor Cruz', 'matud pa ni Sen. Reyes').", | |
| }) | |
| elif attribution_count < 3: | |
| recommendations.append({ | |
| "type": "attribution", | |
| "priority": "low", | |
| "tip_en": f"Consider adding more source attributions β only {attribution_count} found. Strong journalism cites multiple sources.", | |
| "tip_fil": f"Pag-isipang magdagdag ng higit pang source attributions β {attribution_count} lang ang natagpuan. Malakas na pamamahayag ay nag-cite ng maraming pinagmulan.", | |
| "tip_ceb": f"Hunahunaa ang pagdugang og dugang nga source attributions β {attribution_count} ra ang nakaplagan. Lig-on nga pamamahayag nag-cite og daghang tinubdan.", | |
| }) | |
| # Transition recommendation | |
| if not has_transitions: | |
| recommendations.append({ | |
| "type": "transitions", | |
| "priority": "medium", | |
| "tip_en": "Add formal transitions between paragraphs β use words like 'Meanwhile', 'Additionally', 'However' to guide readers through the story.", | |
| "tip_fil": "Magdagdag ng pormal na transitions sa pagitan ng mga talata β gumamit ng mga salitang tulad ng 'Samantala', 'Dagdag pa rito', 'Sa kabilang banda'.", | |
| "tip_ceb": "Pagdugang og pormal nga transitions tali sa mga parapo β gamita ang mga pulong sama sa 'Samtang', 'Dugang pa', 'Hinuon'.", | |
| }) | |
| # Formal markers recommendation | |
| if not has_formal_markers: | |
| recommendations.append({ | |
| "type": "formal_markers", | |
| "priority": "low", | |
| "tip_en": "Consider adding a byline or reporter attribution to strengthen journalistic credibility.", | |
| "tip_fil": "Pag-isipang magdagdag ng byline o attribution ng reporter upang palakasin ang kredibilidad ng pamamahayag.", | |
| "tip_ceb": "Hunahunaa ang pagdugang og byline o attribution sa reporter aron lig-onon ang kredibilidad sa pamamahayag.", | |
| }) | |
| # Positive reinforcement if well-structured | |
| if formalism_score >= 70 and not recommendations: | |
| recommendations.append({ | |
| "type": "positive", | |
| "priority": "info", | |
| "tip_en": "Excellent! This article follows standard journalism conventions well.", | |
| "tip_fil": "Mahusay! Sinusunod ng artikulong ito ang mga standard na kombensyon ng pamamahayag.", | |
| "tip_ceb": "Maayo kaayo! Kini nga artikulo nagsunod sa mga standard nga kombensyon sa pamamahayag.", | |
| }) | |
| return recommendations | |