""" Rewrite the "Violence Used" line of every reviewed summary in summary-overrides.json so that it names DETENTION explicitly where the victim was held, and says "No detention" where the summary already states the attack was foiled, a snatch, or a hit-and-run. Rule: README-START-HERE.md, "Definitions". Nothing is added that the summary does not already say: a line gets the "Detention —" label only when that same summary (any category) already describes the victim being held, tied, handcuffed, locked in, taken away or abducted; the supporting phrase is checked before the edit. Idempotent. Supersedes the Violence Used wording in build_overrides_batch1..7.py; summary-overrides.json is the authoritative store. Run build_cases.py --all and verify_all.py afterwards. """ import json import re import sys from pathlib import Path BASE = Path(__file__).resolve().parent STORE = BASE / "summary-overrides.json" HELD = re.compile(r"(held (him|her|them|the|at|for|in|with|captive|hostage|against|under)|tied|bound|taped|handcuff|" r"zip.?tie|cable tie|captiv|hostage|locked|restrain|confine|shackle|abduct|kidnapp|" r"seized (him|her|them)|forced (him|her|them|the \w+) (into|inside|back)|drove (him|her|them)|" r"took (him|her|them)|taken (away|to|from)|driven|put a sack|bag over|hooded)", re.I) FOILED = re.compile(r"(none|no one was harmed|foiled|snatched|not a violent|no physical violence|" r"unoccupied|no confrontation)", re.I) # Per-case hand rewrites where the mechanical label would read badly. Each value is the # full new Violence Used line (without the bold label). "support" must occur in the summary. HAND = { "218": ("Detention — she was held at gunpoint in her home and suffered a scalp laceration that needed stitches; she said she shot one of the intruders during the confrontation.", "held at gunpoint"), "219": ("No detention and no assault reported — the bag was snatched from behind. (The database description's \"beaten with a stick\" is not supported by the linked article.)", "snatched from behind"), "221": ("Detention — held for five days tied to a bed in a makeshift prison; he was kept drugged with sleeping pills, restrained and harassed throughout.", "tied to a bed"), "222": ("No detention — a street attack from behind. He was slashed, suffering a 10-centimetre knife wound to the forehead (with no damage to his eyes or vital organs), and fell to the ground.", "from behind"), "224": ("Detention — abducted and held by an armed group that accused him of illegal Hundi/Hawala dealing and threatened him.", "abducted"), "229": ("Possible detention — by his own account he was drugged and became docile in the car; as he was made to get out in a residential area near Golders Green the car drove off and struck his leg, leaving him unable to walk.", "drugged"), "230": ("No detention — the attempt to force her into the van was fought off. The child's father intervened and was struck by the attackers as the victim cried out and many passers-by looked on.", "force the mother into"), "242": ("Detention — the two forced entry and held her in her own home while beating her: she was punched in the face roughly ten times in front of her family and taken to hospital; the attackers fled when the husband appeared.", "punched"), "253": ("Detention — forced into a van and driven off; inside the van he was physically assaulted and threatened with a firearm, and he was later taken to hospital with serious but non-life-threatening injuries.", "forced into"), "256": ("No detention — the intruders were armed (one with a switchblade, another with a fake firearm) but the attempt was foiled at the fence and no one was harmed.", "foiled"), "273": ("Detention — confronted inside his own home by the two men, who stabbed and robbed him; he described the scene afterwards as looking \"like a massacre\".", "stabbed"), "292": ("Detention — threatened with a gun-like object and forced back inside his home.", "forced back"), "298": ("He was assaulted during the robbery; the source on file does not say whether anyone was restrained.", "assaulted"), "299": ("No detention beyond the moment of the mugging — physical force and coercion: forced phone handovers, being held against a wall and forced biometric unlocks.", "held against a wall"), "322": ("Detention — abducted and held for two days; one of his fingers was severed.", "held for two days"), "349": ("None — no detention and no confrontation: an unoccupied-premises break-in (the wider wave included violent, armed raids elsewhere, but not here).", "unoccupied"), "364": ("No detention — a daylight snatch: he was hit across the head and face as he waited in his car.", "waited in his car"), "301": ("Detention — the on-duty security personnel were tied up to carry out the theft; no injuries were reported.", "tied up"), "372": ("Detention — taken and tied to a chair; she was stripped naked, struck in the legs with a hammer, and burned.", "tied to a chair"), } # Lead clause + original sentence (first letter lowercased). "support" must occur in the summary. LEAD = { "228": ("Detention — bound and held for about 17 days at the townhouse;", "held for about 17 days"), "237": ("Detention — forced into a stolen vehicle and held until the next day;", "released the next day"), "238": ("Detention — the man was held and later released in Créteil;", "released in Créteil"), "240": ("Detention — the gang locked the victims in the shop before fleeing (they escaped through the shutters);", "locked them in the shop"), "244": ("Detention — the pair were driven to a police station and held until about 8:30 p.m.;", "released around 8:30"), "245": ("Detention — forced into a car and moved between locations through the night;", "moved between locations"), "247": ("Detention — forcibly taken to the area of the Arc de Triomphe;", "forcibly took him"), "272": ("Detention — a sack over his head, forced into a car and driven out of the city;", "sack over his head"), "279": ("Detention — the family was held in the attackers' cars over a two-hour drive;", "two-hour drive"), "317": ("Detention — seized outside his home and held until the ransom was paid;", "released after the ransom"), "334": ("Detention — subdued at gunpoint inside the car;", "subdued him at gunpoint"), "340": ("Detention — forced into a car, taken to a house in Jimbaran and released several hours later;", "released several hours later"), "369": ("Detention — trapped in the bedsit;", "trap"), "370": ("Detention — all three family members were bound with zip ties for about 13 hours;", "13 hours"), "371": ("Detention — hooded, handcuffed and held blindfolded for six days;", "six days"), "285": ("No detention, no violence —", "sleight-of-hand"), } def relabel(cid, summary): m = re.search(r"(\*\*Violence Used:\*\*\s*)(.+)", summary) if not m: return summary, "no Violence Used line" label, old = m.group(1), m.group(2) if cid in HAND: new, support = HAND[cid] if support.lower() not in summary.lower(): raise SystemExit(f"ABORT case {cid}: support phrase {support!r} not in summary") elif cid in LEAD: lead, support = LEAD[cid] if support.lower() not in summary.lower(): raise SystemExit(f"ABORT case {cid}: support phrase {support!r} not in summary") body = old while body.lower().startswith(lead.lower()): # idempotent: strip any lead already applied body = body[len(lead):].lstrip() body = body[0].lower() + body[1:] if cid == "285": body = body[len("none — "):] if body.startswith("none — ") else body new = lead + " " + body if new == old: return summary, "already labelled" elif re.match(r"(Detention|No detention|Possible detention)", old): fixed = re.sub(r"^No detention — none — ", "No detention — ", old) if fixed != old: return summary.replace(label + old, label + fixed, 1), fixed[:90] return summary, "already labelled" elif HELD.search(old): new = "Detention — " + old[0].lower() + old[1:] elif FOILED.search(old): body = re.sub(r"^None\s*[—-]\s*", "", old) new = "No detention — " + body[0].lower() + body[1:] elif HELD.search(summary): new = "Detention (see Attack Method). " + old else: return summary, "UNLABELLED (no holding cue and not foiled)" return summary.replace(label + old, label + new, 1), new[:90] def main(): dry = "--dry-run" in sys.argv store = json.loads(STORE.read_text(encoding="utf-8")) changed = 0 for cid in sorted(store, key=int): new, note = relabel(cid, store[cid]) print(f"{cid}: {note}") if new != store[cid]: store[cid] = new changed += 1 if not dry: STORE.write_text(json.dumps(store, ensure_ascii=False, indent=2), encoding="utf-8") print(f"\n{'would change' if dry else 'changed'} {changed} of {len(store)} summaries") if __name__ == "__main__": main()