Classifying: file order outranks kind order, so a rejection in one language is read by another's interview rule #18

Closed
opened 2026-10-01 10:47:59 +00:00 by tiagoagueda · 1 comment
Owner

Found on 2026-09-29 in a read-only review of postulo-imap at cc09c6381, running classify on sentences that say two things.

classify loops over the rule files in alphabetical order and, inside each file, over the kinds. A weaker kind in an earlier file therefore beats a stronger kind in the message's own language. The module and the README both promise the opposite ("most consequential first, and the first that matches wins"; "The most consequential reading wins"), and several patterns are valid in more than one language, so a rejection or an offer that mentions an earlier interview or assessment is filed as that interview or assessment.

  • Where:

    • src/postulo_imap/classifying.py:119-125, classify: for data in _loaded(): outside, for kind, patterns in data["compiled"]: inside; _loaded sorts the files by name (src/postulo_imap/classifying.py:71): de, en, es, fr, it, nl, pl, pt.
    • src/postulo_imap/classifying.py:8-10 and README.md:58-59: the promise. src/postulo_imap/classifying.py:21-24 and README.md:82-84 do say the files are tried in turn, but only as a reason the wrong language may be named, not that the kind may be wrong.
    • src/postulo_imap/rules/es.json:36 and src/postulo_imap/rules/pt.json:36: the same pattern, entrevista (telef[óo]nica|presencial|por v[íi]deo); es is consulted first.
    • src/postulo_imap/rules/de.json:46 and src/postulo_imap/rules/de.json:52: online-?(test|assessment) and assessment[-\s]?center, both plain English as well; de is consulted before en. src/postulo_imap/rules/nl.json:47 repeats the second.
    • src/postulo_imap/classifying.py:112-117 and src/postulo_imap/classifying.py:88-90: the only parameter that would narrow the files, and languages() "for the connection form". No caller passes only and the form has no such field (src/postulo_imap/sync.py:42-117).
    • tests/test_imap.py:324-328, test_the_most_consequential_reading_wins: two kinds, both English.
  • What happens: run on the snapshot:

    • "Após a entrevista presencial, lamentamos informar que a sua candidatura não foi selecionada." → interview (es), matched entrevista presencial. The Portuguese rejection rule is never reached.
    • "Depois da entrevista telefónica, não vamos avançar com a sua candidatura." → interview (es).
    • "Após a entrevista por vídeo, temos o prazer de lhe oferecer a posição." → interview (es): an offer read as an invitation.
    • "Unfortunately, after the assessment center, we will not be proceeding with your application." → assessment (de).
    • "Following the online-test we are delighted to offer you the role." → assessment (de).

    The same sentences with the weaker term in the message's own file are read correctly ("After your interview we regret to inform you" → rejected), which is what shows the order of the files is the cause.

  • Why it matters: no mixed-language mailbox is needed: every Portuguese message is read by the Spanish rules first and every English one by the German rules first. The suggestion says would move it to Interviewing or Assessment for a rejection or an offer, names the wrong language under Recognised, and for an interview collects dates with the wrong language's patterns (dates_in(text, data["language"]), src/postulo_imap/classifying.py:126), so Portuguese month names are not found. The person accepts a wrong status, or declines and loses the entry.

  • Fix: loop over the kinds first and the files second: for each kind in the fixed order, try every language, and report the language of the pattern that matched. Offer and rejected in any language are then tried before interview in any language. The fixed order already exists as MOST_CONSEQUENTIAL_FIRST in tests/test_imap.py:168 and is asserted for every file; move it into classifying.py and index each file's patterns by kind. Optionally add a languages field to the connection form and pass it as only; otherwise remove only and correct the languages() docstring.

Done when

  • tests/test_imap.py has a cross-language test: each language's rejection sentence and each language's offer sentence, combined with another language's interview and assessment terms, stays rejected or offer. With the sentences above it fails today.
  • The docstring of classifying.py and README.md describe the order the code uses.
Found on 2026-09-29 in a read-only review of postulo-imap at cc09c6381, running `classify` on sentences that say two things. `classify` loops over the rule files in alphabetical order and, inside each file, over the kinds. A weaker kind in an earlier file therefore beats a stronger kind in the message's own language. The module and the README both promise the opposite ("most consequential first, and the first that matches wins"; "The most consequential reading wins"), and several patterns are valid in more than one language, so a rejection or an offer that mentions an earlier interview or assessment is filed as that interview or assessment. - **Where:** - `src/postulo_imap/classifying.py:119-125`, `classify`: `for data in _loaded():` outside, `for kind, patterns in data["compiled"]:` inside; `_loaded` sorts the files by name (`src/postulo_imap/classifying.py:71`): de, en, es, fr, it, nl, pl, pt. - `src/postulo_imap/classifying.py:8-10` and `README.md:58-59`: the promise. `src/postulo_imap/classifying.py:21-24` and `README.md:82-84` do say the files are tried in turn, but only as a reason the wrong language may be named, not that the kind may be wrong. - `src/postulo_imap/rules/es.json:36` and `src/postulo_imap/rules/pt.json:36`: the same pattern, `entrevista (telef[óo]nica|presencial|por v[íi]deo)`; es is consulted first. - `src/postulo_imap/rules/de.json:46` and `src/postulo_imap/rules/de.json:52`: `online-?(test|assessment)` and `assessment[-\s]?center`, both plain English as well; de is consulted before en. `src/postulo_imap/rules/nl.json:47` repeats the second. - `src/postulo_imap/classifying.py:112-117` and `src/postulo_imap/classifying.py:88-90`: the `only` parameter that would narrow the files, and `languages()` "for the connection form". No caller passes `only` and the form has no such field (`src/postulo_imap/sync.py:42-117`). - `tests/test_imap.py:324-328`, `test_the_most_consequential_reading_wins`: two kinds, both English. - **What happens:** run on the snapshot: - "Após a entrevista presencial, lamentamos informar que a sua candidatura não foi selecionada." → `interview` (es), matched `entrevista presencial`. The Portuguese rejection rule is never reached. - "Depois da entrevista telefónica, não vamos avançar com a sua candidatura." → `interview` (es). - "Após a entrevista por vídeo, temos o prazer de lhe oferecer a posição." → `interview` (es): an offer read as an invitation. - "Unfortunately, after the assessment center, we will not be proceeding with your application." → `assessment` (de). - "Following the online-test we are delighted to offer you the role." → `assessment` (de). The same sentences with the weaker term in the message's own file are read correctly ("After your interview we regret to inform you" → `rejected`), which is what shows the order of the files is the cause. - **Why it matters:** no mixed-language mailbox is needed: every Portuguese message is read by the Spanish rules first and every English one by the German rules first. The suggestion says *would move it to Interviewing* or *Assessment* for a rejection or an offer, names the wrong language under *Recognised*, and for an interview collects dates with the wrong language's patterns (`dates_in(text, data["language"])`, `src/postulo_imap/classifying.py:126`), so Portuguese month names are not found. The person accepts a wrong status, or declines and loses the entry. - **Fix:** loop over the kinds first and the files second: for each kind in the fixed order, try every language, and report the language of the pattern that matched. Offer and rejected in any language are then tried before interview in any language. The fixed order already exists as `MOST_CONSEQUENTIAL_FIRST` in `tests/test_imap.py:168` and is asserted for every file; move it into `classifying.py` and index each file's patterns by kind. Optionally add a languages field to the connection form and pass it as `only`; otherwise remove `only` and correct the `languages()` docstring. ## Done when - `tests/test_imap.py` has a cross-language test: each language's rejection sentence and each language's offer sentence, combined with another language's interview and assessment terms, stays `rejected` or `offer`. With the sentences above it fails today. - The docstring of `classifying.py` and `README.md` describe the order the code uses.
tiagoagueda added this to the 0.5.0 milestone 2026-10-01 10:47:59 +00:00
Author
Owner

Fixed by pull request #25.

Fixed by pull request #25.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo-imap#18
No description provided.