diff --git a/scripts/mccarthy-corpus/stoplist_mccarthy.json b/scripts/mccarthy-corpus/stoplist_mccarthy.json new file mode 100644 index 0000000..8ca4b43 --- /dev/null +++ b/scripts/mccarthy-corpus/stoplist_mccarthy.json @@ -0,0 +1,173 @@ +{ + "_why": [ + "Surfaces excluded from renaming because they are REAL-WORLD referents, not the author's", + "invented proper nouns. The R49 rule is that ambiguous cases are deliberately RENAMED --", + "renaming is the safe direction and leaving is the leaking one -- so this list is kept to", + "categories that are unambiguously real: geography, demonyms, brands, foreign common nouns,", + "and extraction fragments that are not words at all.", + "", + "⚠ SENSITIVITY FLOOR, stated because it is part of the result: the entity map holds 199", + "surfaces and the top 170 by count were classified, with every ambiguous one READ IN", + "CONTEXT before being placed. The bottom 29 were NOT individually read, so a rare", + "real-world referent may be renamed. That is the safe direction and an accepted cost.", + "", + "⚠ McCarthy is a hard case for the same reason Hemingway was and Bronte was not: the Border", + "Trilogy is half set in real Mexico, so real towns, real states and real Spanish sit beside", + "invented characters on every page. The 1,411 non-ASCII letters measured on this corpus are", + "the same fact from the other side." + ], + "_caught_by_reading_in_context": { + "Socorro": "NOT the New Mexico town. She is the COOK at Mac's ranch in Cities of the Plain -- 'Socorro came and took the plate of biscuits'. A character; left renameable.", + "Webster": "NOT the dictionary or the city. 'the outrider Webster', 'A Tennessean named Webster' -- one of Glanton's men. Character.", + "Jackson": "'two men named Jackson, one black, one white, both forenamed John'. Characters, not the city or the president.", + "Harlan": "'a man named Harlan from Texas'. Character, not the Kentucky county.", + "Franklin": "KEPT RENAMEABLE. 'I'm here to see Mr Franklin' -- a lawyer in All the Pretty Horses. ⚠ He was in the geography list on the first pass, because Franklin is also the old name for El Paso, and audit_stoplist.py caught the contradiction against this very note.", + "Lamar": "'called Lamar on the mobile ... he can have his unit back' -- a lawman in No Country. Character, not the street or the Texas county.", + "Griffin": "STOPLISTED. 'Fort Griffin', 'we pulled out from Griffin for a last hunt' -- the real Texas buffalo-hunting town.", + "Santa": "STOPLISTED. Never standalone: the fragment of 'Santa Rita del Cobre' and 'Santa Cruz'.", + "Varas": "STOPLISTED. The fragment of 'Las Varas', a real Chihuahua town on the Madera road.", + "Eagle": "STOPLISTED. 'Eagle Pass' (real Texas border town) and 'the Eagle Cafe'.", + "Avenue": "STOPLISTED. A street-name component -- 'Juárez Avenue'. A common noun, not a name.", + "Niño": "KEPT RENAMEABLE, deliberately. A Spanish common noun, but here it is the NAME OF A HORSE ('That Niño horse'). The author's invention even though the word is not -- exactly parallel to Hemingway's `Inglés`.", + "Keno": "KEPT RENAMEABLE for the same reason -- 'their horse Keno'. A gambling game's name used as a horse's.", + "Redbo": "KEPT RENAMEABLE -- John Grady's horse in All the Pretty Horses.", + "Mangas": "KEPT RENAMEABLE. Based on the real Apache chief Mangas Coloradas, but presented as a character who speaks ('The man's name was Mangas and he was cordial'). Ambiguous, so it takes the safe direction.", + "Travis": "KEPT RENAMEABLE. Dual use -- real 'Travis Park' in San Antonio AND a character who speaks ('said Travis'). Ambiguous, so it is renamed.", + "Venada": "KEPT RENAMEABLE. 'La Venada', a Juárez cantina. McCarthy's Juárez venues are largely invented and this cannot be settled from the text, so it takes the safe direction.", + "Jc": "KEPT RENAMEABLE. Not a fragment -- 'JC pulled up in front of Maud's' is a character in Cities of the Plain who is named by his initials.", + "Ed": "KEPT RENAMEABLE. Ed Tom Bell, the sheriff and first-person narrator of No Country for Old Men.", + "E / H / T / K": "STOPLISTED as fragments. E, H and T were the small-caps drop-cap defect repaired in D1; K is the fragment left by the weapon `H&K`. A bare initial is never a name -- the `G` class from the Hemingway build, where `G` was about to be renamed to a surname 248 times.", + "fragments": "Only the four letters MEASURED as entities on this corpus. A blanket A-Z was the first pass and it stoplisted `I` and `A`, which are ordinary English words -- `Sir I dont think I can do that` then tripped the honorific audit. Stoplist what the entity map actually produced, not the alphabet.", + "Ford / Hashknives": "STOPLISTED. 'a Model A Ford truck'; 'went to work for the Hashknives' -- the real Aztec Land & Cattle outfit. Brands and real companies.", + "Calle": "STOPLISTED. Spanish for 'street' -- 'the Calle de Noche Triste'. A common noun, like Avenue.", + "Yaqui / Gilenos": "STOPLISTED. Real indigenous peoples -- 'lay clear of the Yaqui country', 'all trace of the Gilenos faded'. Demonyms.", + "Terrell": "STOPLISTED. 'nineteen felony charges were filed in the Terrell County Court' -- a real Texas county." + }, + "geography": [ + "America", + "Animas", + "Antonio", + "Ardmore", + "Arizona", + "Arkansas", + "Avenue", + "Babícora", + "Baviacora", + "Bavispe", + "Bexar", + "Boquilla", + "Caborca", + "California", + "Calle", + "Casas", + "Chihuahua", + "Cloverdale", + "Coahuila", + "Diego", + "Durango", + "Eagle", + "El", + "Encantada", + "Francisco", + "Grandes", + "Griffin", + "Huisiachepic", + "Janos", + "Langtry", + "Madera", + "Mexico", + "Missouri", + "Nacozari", + "Namiquipa", + "Odessa", + "Oklahoma", + "Paso", + "Pilares", + "Purísima", + "Saltillo", + "San", + "Sanderson", + "Santa", + "Sierra", + "Sonora", + "States", + "Tennessee", + "Terrell", + "Texas", + "Torreón", + "Ures", + "Uvalde", + "Varas", + "Vietnam", + "Yuma" + ], + "demonyms_and_peoples": [ + "American", + "Americans", + "Apache", + "Apaches", + "Arabian", + "Chiricahuas", + "Comanche", + "Comanches", + "Delaware", + "Delawares", + "Gileno", + "Gilenos", + "Mennonite", + "Mexican", + "Mexicans", + "Mormon", + "Tarahumara", + "Vandiemenlander", + "Yaqui", + "Yaquis", + "Yumas" + ], + "brands_and_products": [ + "Blanca", + "Bronco", + "Carta", + "Colts", + "Ford", + "Hashknife", + "Hashknives", + "Ramcharger", + "Tecate", + "Winchester" + ], + "foreign_and_common_nouns": [ + "Adios", + "Bueno", + "Claro", + "Como", + "Cuates", + "Dios", + "Gracias", + "Hacendado", + "Mam", + "Nada", + "Nosir", + "Pues", + "Senor", + "Senora", + "Vamos", + "Yessir" + ], + "fragments": [ + "E", + "H", + "T", + "K" + ], + "_audit_allow": { + "_note": "Surfaces audit_entity_map.py flags as article-preceded that are nonetheless genuine names, READ and cleared. Pass as --allow when re-running the audit. They are article-preceded because English puts a surname attributively behind 'the': 'the Bailey horse', 'the Muñoz woman', 'the Borginnis woman'. That is the detector's known blind spot, not an error.", + "surfaces": [ + "Bailey", + "Muñoz", + "Borginnis", + "Venada", + "Moderno" + ] + } +} diff --git a/scripts/r49-corpus/audit_stoplist.py b/scripts/r49-corpus/audit_stoplist.py index bf3ef08..7ce8617 100644 --- a/scripts/r49-corpus/audit_stoplist.py +++ b/scripts/r49-corpus/audit_stoplist.py @@ -53,7 +53,14 @@ def main() -> int: text = load_text(Path(a.corpus)) blob = json.loads(Path(a.stoplist).read_text()) - surfaces = sorted({s for v in blob.values() if isinstance(v, list) for s in v}) + # ⚠ SKIP `_`-PREFIXED KEYS. By convention a stoplist carries its rationale in `_why` and + # `_caught_by_reading_in_context`, and `_why` is a LIST OF PROSE LINES. Scanning it as + # surfaces put every sentence of the rationale into the matcher, and its empty separator + # line matched the honorific pattern 139 times on McCarthy -- a flag with no surface name + # attached, at the top of the report, above the one real catch. A metadata key is not a + # surface set; found 2026-09-17 and latent for every corpus before it. + surfaces = sorted({s for k, v in blob.items() if not k.startswith("_") + and isinstance(v, list) for s in v if s}) cleared = {s.strip() for s in a.allow.split(",") if s.strip()} flagged = []