diff --git a/app.py b/app.py index 77d21cb..f99ac2c 100644 --- a/app.py +++ b/app.py @@ -8,7 +8,7 @@ Run it: uv run uvicorn app:app --reload """ import re -from collections import defaultdict +from collections import Counter, defaultdict from contextlib import asynccontextmanager from functools import lru_cache from pathlib import Path @@ -491,6 +491,19 @@ def analyze(draft: Draft): if k: for s in {t["sid"] for t in g["toks"]}: out.setdefault((s, k), gi) + elif kind == "multi2": + # vowel-only founding keys can't carry a coda, but if 2+ + # members agree on one (orange + pourage both AO-R-schwa), + # it's part of the group's sound and phrases may join on it + counts: Counter = Counter() + for t in g["toks"]: + for mk in set(multi_keys(t["word"])): + if mk.startswith("m2:"): + counts[mk] += 1 + for mk, c in counts.items(): + if c >= 2: + for s in {t["sid"] for t in g["toks"]}: + out.setdefault((s, mk), gi) return out def attach_or_collect(t, key, bucket, gmap): diff --git a/tests/test_rhymes.py b/tests/test_rhymes.py index 05d0cda..ac73e30 100644 --- a/tests/test_rhymes.py +++ b/tests/test_rhymes.py @@ -347,3 +347,12 @@ def test_phrase_with_one_new_half_is_not_suppressed(): "Only thing that sold out is the seats though") group_with(text, "beast mode", "beast though", "sleep though", "seats though") + + +def test_phrase_joins_slant_group_via_coda_consensus(): + # orange + pourage found their group on vowels alone (end slant); + # door hinge joins because 2+ members agree on the AO-R coda + text = ("Pick up an orange\n" + "It's by the door hinge\n" + "eating pourage") + group_with(text, "orange", "door hinge", "pourage")