diff --git a/Channel-Maparr/matching_core.py b/Channel-Maparr/matching_core.py index d7e9147..bfa7c98 100644 --- a/Channel-Maparr/matching_core.py +++ b/Channel-Maparr/matching_core.py @@ -702,9 +702,21 @@ def _trailing_number(name): 'KIND', 'KING', 'KINGS', 'KISS', 'KITE', 'KNEE', 'KNEW', 'KNOW', 'KNOWN', }) + # OTA branding context that immediately follows a station callsign: a channel + # number (optionally prefixed by a broadcast suffix), e.g. "KING 5", "WAVE 3", + # "WOOD TV8", "WHO 13". Used to rescue a denylisted common-word callsign in a + # loose position WITHOUT also rescuing bare program words ("King of the Hill", + # "Doctor Who"), which never carry this trailing number. bug-098. + _OTA_NUMBER_CONTEXT = re.compile(r'^\s+(?:TV|DT|CD|LP|LD)?\s*\d{1,3}\b', re.IGNORECASE) + def _is_callsign_allowed(self, callsign): """A candidate callsign is allowed if it is not denylisted, OR the plugin - supplied a known-real callsign set that contains it (DB rescue).""" + supplied a known-real callsign set that contains it (DB rescue). + + NOTE: this full rescue is used only at the PARENTHESIZED priorities (1/1b), + where the parentheses are an unambiguous OTA signal. The end-of-name and + loose priorities deliberately do NOT use it for denylisted words -- see + bug-098 hardening in _compute_callsign_with_confidence.""" return (callsign not in self._CALLSIGN_DENYLIST or (self._known_callsigns is not None and callsign in self._known_callsigns)) @@ -746,18 +758,28 @@ def _compute_callsign_with_confidence(self, channel_name): if paren_suffix_match: return paren_suffix_match.group(1).upper(), True - # Priority 3: Callsigns at the end + # Priority 3: Callsigns at the end. A denylisted common word at the end + # ("WOLF KING", "Doctor Who") is NOT rescued here -- end position alone is + # too weak a signal for a word that is also a real callsign. Non-denylisted + # callsigns (and suffixed forms like "KING-TV") still match. bug-098. end_match = re.search(r'\b([KW][A-Z]{2,4}(?:-(?:TV|CD|LP|DT|LD))?)\s*(?:\.[a-z]+)?\s*$', channel_name, re.IGNORECASE) if end_match: callsign = end_match.group(1).upper() - if self._is_callsign_allowed(callsign): + if callsign not in self._CALLSIGN_DENYLIST: return callsign, True - # Priority 4: Any word matching callsign pattern (low confidence) + # Priority 4: Any word matching callsign pattern (low confidence). A + # denylisted common word is rescued here ONLY in OTA branding context -- + # immediately followed by a channel number ("KING 5", "WAVE 3", "WOOD + # TV8", "WHO 13") -- never as a bare program word ("King of the Hill", + # "Doctor Who", "Will Ferrell"). bug-098. word_match = re.search(r'\b([KW][A-Z]{2,4}(?:-(?:TV|CD|LP|DT|LD))?)\b', channel_name, re.IGNORECASE) if word_match: callsign = word_match.group(1).upper() - if self._is_callsign_allowed(callsign): + if callsign not in self._CALLSIGN_DENYLIST: + return callsign, False + if (self._known_callsigns is not None and callsign in self._known_callsigns + and self._OTA_NUMBER_CONTEXT.match(channel_name[word_match.end():])): return callsign, False return None, False diff --git a/Channel-Maparr/plugin.json b/Channel-Maparr/plugin.json index 7b2aa49..f2a927e 100644 --- a/Channel-Maparr/plugin.json +++ b/Channel-Maparr/plugin.json @@ -1,6 +1,6 @@ { "name": "Channel Mapparr", - "version": "1.26.1791324", + "version": "1.26.1801833", "description": "Standardizes broadcast (OTA) and premium/cable channel names using network data and channel lists. Supports M3U stream import, category organization, and fuzzy matching across 42K+ channels in 11 countries.", "author": "PiratesIRC", "license": "MIT", diff --git a/Channel-Maparr/plugin.py b/Channel-Maparr/plugin.py index cfd4749..18edefc 100644 --- a/Channel-Maparr/plugin.py +++ b/Channel-Maparr/plugin.py @@ -44,7 +44,7 @@ class PluginConfig: """Configuration constants for Channel Maparr.""" - PLUGIN_VERSION = "1.26.1791324" + PLUGIN_VERSION = "1.26.1801833" # Channel Database Settings DEFAULT_CHANNEL_DATABASES = "US" diff --git a/scripts/core_manifest.json b/scripts/core_manifest.json index 7d8ff5b..02a880a 100644 --- a/scripts/core_manifest.json +++ b/scripts/core_manifest.json @@ -1,3 +1,3 @@ { - "matching_core.py": "2ac7ac4cb283306152701bfd977ff2f13700d63d8023fdfa280b1480f5a969dc" + "matching_core.py": "d9a2d065584fb797789f3afe3d51dbd94d3d28a1666361b1f22c8eec6b03943b" }