ThesisProject / checker /internal /structure_analyzer.py
JeyBii's picture
Fix TL/CEB attribution+transitions: flexible prepositions, morphological verbs, translation-aware patterns
6e88390 verified
Raw
History Blame Contribute Delete
55.6 kB
"""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