Merge PR #11: clasificador con negaciones en español
ci / lint-test-leaks (push) Canceled after 0s
ci / lint-test-leaks (push) Canceled after 0s
This commit is contained in:
@@ -19,10 +19,17 @@ from cayceops.models import MailClassification
|
||||
|
||||
_APPROVAL_WORDS = re.compile(r"\b(approv|accept|welcome)", re.I)
|
||||
|
||||
# any of these tokens anywhere in the mail vetoes an `approval` result
|
||||
# any of these tokens anywhere in the mail vetoes an `approval` result.
|
||||
# English and Spanish (merchants write in both; mixed mails are common).
|
||||
_NEGATIONS = re.compile(
|
||||
r"\b(not|no|never|cannot|can'?t|won'?t|hasn'?t|isn'?t|"
|
||||
r"no\s+longer|unable|decline\w*|regret\w*|unfortunately)\b",
|
||||
r"no\s+longer|unable|decline\w*|regret\w*|unfortunately|"
|
||||
# spanish: standalone negation words
|
||||
r"nunca|jam[aá]s|tampoco|ning[uú]n(?:a|o)?|nadie|nada|"
|
||||
# spanish: multi-word negations and rejection stems
|
||||
r"no\s+podemos|no\s+es\s+posible|no\s+acept(?:amos|ar[aá])|"
|
||||
r"rechaz\w*|lamentablemente|descartad\w*|"
|
||||
r"no\s+ha\s+sido\s+aprobad\w*|no\s+procede)\b",
|
||||
re.I,
|
||||
)
|
||||
|
||||
@@ -34,11 +41,24 @@ _RULES: list[tuple[MailClassification, re.Pattern[str], float]] = [
|
||||
re.compile(r"\b(declin|reject|not able to|unable to|we regret|won.t be able)\w*", re.I), # noqa: E501
|
||||
0.9,
|
||||
),
|
||||
(
|
||||
MailClassification.REJECTION,
|
||||
# spanish rejections (strong stems, not just negation words)
|
||||
re.compile(r"\b(rechaz\w*|no\s+vamos\s+a\s+pod(?:er|emos)|su\s+solicitud\s+ha\s+sido\s+denegada|no\s+cumple\s+los\s+requisitos)\b", re.I), # noqa: E501
|
||||
0.9,
|
||||
),
|
||||
(
|
||||
MailClassification.APPROVAL,
|
||||
re.compile(r"\b(approv|accept|welcome to the|you.re (in|approved)|live|activated|glad to)\w*", re.I), # noqa: E501
|
||||
0.85,
|
||||
),
|
||||
(
|
||||
MailClassification.APPROVAL,
|
||||
# spanish approvals (standalone positive stems; the veto still
|
||||
# downgrades if ANY negation appears elsewhere in the mail)
|
||||
re.compile(r"\b(aprob\w*|aceptad\w*|bienvenid\w*|ha\s+sido\s+aceptada|encantados?\s+de\s+(trabajar|colaborar))\b", re.I), # noqa: E501
|
||||
0.85,
|
||||
),
|
||||
(
|
||||
MailClassification.QUESTION,
|
||||
re.compile(r"\b(could you|can you|please (provide|clarify|confirm)|what (is|are)|how (do|does|can)|need more (info|details))\b", re.I), # noqa: E501
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
"""Spanish negation tests (round-3 point 4): merchants write in Spanish;
|
||||
negations like 'nunca', 'lamentablemente' or 'no podemos' must veto
|
||||
`approval` exactly like their English counterparts. Veto rule unchanged:
|
||||
when in doubt -> needs_review."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from cayceops.components.maild import classify as clf
|
||||
from cayceops.models import MailClassification
|
||||
|
||||
APPROVAL_ONLY = MailClassification.APPROVAL
|
||||
|
||||
|
||||
def _never_approval(cfg_factory, text: str) -> None:
|
||||
kind, _ = clf.classify(text, cfg_factory())
|
||||
assert kind is not APPROVAL_ONLY, f"{text!r} classified as APPROVAL"
|
||||
|
||||
|
||||
def test_english_baseline_still_works(cfg):
|
||||
config = cfg()
|
||||
kind, _ = clf.classify("Your application was approved, welcome to the program", config)
|
||||
assert kind is APPROVAL_ONLY
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"text",
|
||||
[
|
||||
# 1. rejection stem
|
||||
"Lamentamos informarle que su solicitud fue rechazada",
|
||||
# 2. nunca
|
||||
"Nunca aceptamos negocios de criptomonedas",
|
||||
# 3. jamás
|
||||
"Jamás hemos trabajado con affiliates externos",
|
||||
# 4. tampoco
|
||||
"No podemos aceptar su sitio, tampoco podemos ofrecerle otro acuerdo",
|
||||
# 5. ningún/ninguna
|
||||
"En este momento no tenemos ningún hueco para nuevos partners",
|
||||
# 6. no podemos (multi-word)
|
||||
"Su propuesta es interesante pero no podemos proceder",
|
||||
# 7. no es posible (multi-word)
|
||||
"Gracias por escribir, pero no es posible colaborar este trimestre",
|
||||
# 8. lamentablemente
|
||||
"Lamentablemente su aplicación no ha sido aprobada",
|
||||
# 9. no aceptamos
|
||||
"Por política interna no aceptamos sitios de viajes",
|
||||
# 10. mixed spanish/english: negation in spanish, approval word in english
|
||||
"Thanks for reaching out! Lamentablemente we cannot approve your site",
|
||||
# 11. mixed: approval in spanish, negation in english
|
||||
"Apreciamos su interés, but we are not accepting new partners",
|
||||
# 12. nadie / nada
|
||||
"Aquí nadie decidiría eso, así que no hay nada que aprobar",
|
||||
],
|
||||
)
|
||||
def test_spanish_and_mixed_negations_veto_approval(cfg, text):
|
||||
_never_approval(cfg, text)
|
||||
|
||||
|
||||
def test_spanish_pure_approval_still_approves(cfg):
|
||||
"""A clean Spanish approval WITHOUT any negation must still classify
|
||||
approval — the veto must not over-fire."""
|
||||
config = cfg()
|
||||
kind, confidence = clf.classify(
|
||||
"Su solicitud fue aprobada. Bienvenido al programa, estamos encantados "
|
||||
"de trabajar con ustedes.",
|
||||
config,
|
||||
)
|
||||
assert kind is APPROVAL_ONLY
|
||||
assert confidence >= 0.5
|
||||
|
||||
|
||||
def test_spanish_rejection_classifies_rejection(cfg):
|
||||
config = cfg()
|
||||
kind, conf = clf.classify(
|
||||
"Lamentamos informarle que su solicitud fue rechazada por el comité",
|
||||
config,
|
||||
)
|
||||
assert kind is MailClassification.REJECTION
|
||||
assert conf >= 0.9
|
||||
Reference in New Issue
Block a user