fix(po-email): recognise a site named with an instance number

MEGCPO9566's subject reads "MEGCREQ11976 (Equestria 215)". A site name with
a bare instance number was not recognised as a site reference, so the PO
fell through the whole name chain and came back as "no MEGA site name found"
rather than naming the site it wanted.

A bracketed name with an instance number is now a site reference, and a
text whose bracketed names all resolve is not flagged at all.
This commit is contained in:
DEls 2026-11-10 10:12:19 +02:00
parent a3841106ae
commit 41ff08742c
3 changed files with 41 additions and 9 deletions

View file

@ -234,6 +234,15 @@ bracket that is a code or a spec — `(C26/27)`, `(110mm PVC)` — is not a site
trigger it. **Willow Park Ext 98** (`C25/40`) was added to the app on 2026-11-09 with its
commercial details still blank, and MEGCPO9564 was moved onto it.
A site name may carry an instance number instead of an "EXT" qualifier — MEGCPO9566's subject
reads `MEGCREQ11976 (Equestria 215)`, and MEGA's own plant register calls that site *"Equestria
215"* too (buildsite lists it as *Equestria*, code *Ext 215*). **Equestria 215** (`C25/31`) was
added to the app on 2026-11-10 with its commercial details still blank.
> **A message flagged for review has already been marked as seen.** Once the site it names has
> been added, re-run the script with `--force` so the message is checked again — a plain re-run
> skips it as already seen and the PO is never filed.
**The project code on the PO's lines is deliberately not used.** It is a cost allocation, not a
statement of where the work is going. MEGCPO9550 is a **Cedar Gate** delivery — its subject says
`(Cedar Gate)` and its email subject says *"RE: Cedar Gate Paving & Kerbs PO"* — yet four of its

View file

@ -330,8 +330,9 @@ def site_vocabulary(sites: list[dict]) -> set[str]:
def names_a_site(candidate: str, vocabulary: set[str]) -> bool:
"""Is this bracketed phrase MEGA writing the site, rather than a code?
"EXT98" is MEGA's own way of marking an instance of a site, and a phrase
whose words are all ours is a site name. "(DN150)", "(T16)" and
"EXT98" is MEGA's own way of marking an instance of a site, a name with an
instance number -- "(Equestria 215)" -- is one too, and a phrase whose
words are all ours is a site name. "(DN150)", "(T16)" and
"(Union fitting)" are none of those, so they are not site names.
"""
tokens = [t for t in re.split(r"[^A-Za-z0-9]+", candidate.lower()) if t]
@ -340,6 +341,8 @@ def names_a_site(candidate: str, vocabulary: set[str]) -> bool:
if any(re.fullmatch(r"ext\d+", t) for t in tokens):
return True
words = [t for t in tokens if len(t) >= 4 and t.isalpha()]
if any(re.fullmatch(r"\d{2,}", t) for t in tokens):
return bool(words)
return bool(words) and all(word in vocabulary for word in words)
@ -353,13 +356,16 @@ def unknown_site_name(text: str, sites: list[dict]) -> str | None:
now left for a person instead.
"""
vocabulary = site_vocabulary(sites)
for match in BRACKETED.finditer(text or ""):
candidate = match.group(1).strip()
if not names_a_site(candidate, vocabulary):
continue
if resolve_site_by_name(candidate, sites) is None:
return candidate
return None
candidates = [
match.group(1).strip()
for match in BRACKETED.finditer(text or "")
if names_a_site(match.group(1).strip(), vocabulary)
]
if not candidates:
return None
if any(resolve_site_by_name(candidate, sites) for candidate in candidates):
return None
return candidates[0]
def site_for_po(

View file

@ -250,6 +250,23 @@ class UnknownSiteName(unittest.TestCase):
def test_a_number_in_brackets_is_not_a_site(self):
self.assertIsNone(unknown_site_name("Call (012) 803 4456", SITES))
def test_a_site_named_with_an_instance_number_is_named(self):
# MEGCPO9566: "MEGCREQ11976 (Equestria 215)".
self.assertEqual(
unknown_site_name("MEGCREQ11976 (Equestria 215)", SITES),
"Equestria 215",
)
def test_a_site_named_with_an_instance_number_resolves_once_it_exists(self):
sites = SITES + [{"id": "equestria", "name": "Equestria 215", "code": "C25/31"}]
self.assertIsNone(unknown_site_name("MEGCREQ11976 (Equestria 215)", sites))
def test_a_known_site_in_the_same_text_settles_it(self):
# One bracketed name we know means the text names a site we have.
self.assertIsNone(
unknown_site_name("Tools (Nellmapius EXT25) (Grade 40)", SITES)
)
if __name__ == "__main__":
unittest.main()