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:
parent
a3841106ae
commit
41ff08742c
3 changed files with 41 additions and 9 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in a new issue