Employment services are NACE-coded companies, and only they are offered as the company a listing came through #671
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#671
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The maintainer's list says: “Employment services are companies with correct NACE code associated. Are the only kind on companies that can be a reference a listing from another company.” Postulo already has two things called an employment service or an agency, and neither is defined by a NACE code, so this issue says which is which, reads the sentence from the code, and makes the one place where a company stands between a listing and its employer offer only the companies that qualify.
What exists today
CompanyKind.EMPLOYMENT_SERVICEis “the public office a job seeker is registered with -- France Travail, IEFP, the Bundesagentur für Arbeit, Jobcentre Plus” (src/postulo/jobs/models.py:209-221). The docstring adds: “A recruitment agency is not a third kind”. It is picked from a registry of 37 offices by country (src/postulo/jobs/employment_services.py:50-90,src/postulo/jobs/forms.py:243-250,:376-390). What the kind changes: the report is addressed to it and the first one's adviser is named (src/postulo/applications/reports.py:511-520), it is left out of the industry tallies (:522-529), and the companies table can filter on it (src/postulo/jobs/tables.py:94-99).CompanyQuerySet.employers()(jobs/models.py:111-118) is the set without it, and nothing insrc/calls it; one test does (tests/test_employment_services.py:109).Channel.EMPLOYMENT_SERVICEis “the listings of the public employment service the person is registered with” (src/postulo/applications/models.py:153-164), counted separately in the report (reports.py:508-510).Application.through_agency. An optional foreign key tojobs.Company, “the recruitment agency it went through”, with the comment that “the posting's company stays the employer: an agency is deliberately not a kind of company” (applications/models.py:388-400, #239). The picker offers every company of the owner except the employer (src/postulo/applications/forms.py:401-435, the agency half at:423-435); the API accepts any company the caller owns (src/postulo/api/routers/common.py:62-68); the importer matches the agency by name (src/postulo/core/importer.py:766,:899).Application.referred_byis aContact, a person, and is not a company at all (applications/models.py:380-387).JobPosting.companyis the employer; “found via” is free text (jobs/models.py:874-897).Industrycarries one:Industry.codeis “the NACE division this name is”, set bysave()from the name, so renaming an industry to a division's name gives it the code (jobs/models.py:55-81,jobs/industries.pycode_for). A company has industries through a many-to-many (jobs/models.py:288-290).src/postulo/jobs/data/nace-2.1.jsonholds 22 sections and 87 divisions in 24 languages and no groups or classes (data/LICENCE.md: “Groups (three digits) and classes (four) were not taken”). Division 78 is “Employment activities” (section O in the file); the starter list of familiar industry names has nothing for recruitment or staffing (industries.py:64-97).How the sentence reads against this. The code gives “employment service” the public-office meaning, and the maintainer's sentence gives it a NACE meaning. The only place one company stands between a listing and its employer is
through_agency, so “the only kind of companies that can be a reference a listing from another company” is read here as: the picker for the company an application went through offers the companies whose NACE code is employment activities (division 78), and no others. The public office is also something that refers a person to another company's listing, which is the case the maintainer's wording fits best; it is kept in the qualifying set below. Checked againstmainatf09b6010b.Proposal
src/postulo/jobsbesideindustries.py: a role has a key, a label and a set of division codes. This issue ships one role, the intermediary (78). The place of learning (#685: division 85), the awarding body (#686: 85 and 94) and the membership organisation (#693: 94) add theirs without a second mechanism; the organisation of an experience (#683) gets no role, since any company may be an employer.Company.industrieswith a division in the role's set), plus the companies whose kind is the public employment service.ApplicationForm._scope_referrer_and_agencyoffers the qualifying companies first and the rest behind an explicit “Show every company” control that works with scripts off. The employer stays excluded, and a company the row already names stays on the list, as the comment atapplications/forms.py:425-427already requires.jobs/models.py:209-218,applications/models.py:388-392).Decisions
employment_serviceis in the archive, the API (api/schemas.py:90,:193) and the CSV aliases (core/csv_import.py:395-409) and does not change) and let “employment service” in the interface mean the NACE role; (c) fold them, so the kind is derived from NACE. Recommended: (b) for the label only, and nothing in code or stored data renamed. The maintainer's sentence defines the term by NACE, the public office is still its own relation (the person is registered with it, the report is addressed to it), and (c) would make the report's addressee depend on an industry nobody has set. The channel's label is changed to match, so one word does not name two sets of companies.through_agencyis stored, exported and merged,jobs/merging.py:342,:442) must not become invalid. Revisit strictness once companies carry a code from somewhere other than typing; this keeps the maintainer's intent as the default and does not lock anyone out.Company.nacefield at class level; (c) from an identifier register. Recommended: (a).Industry.codeis already the NACE division (#140), the whole of division 78 is what agencies are, and (a) changes no archive, API or candidate format. Classes are needed by #686 and are not in the vendored file; that issue decides whether to harvest them from the same Publications Office dataset (the licence of the classes is not checked here).through_agencyalready travels with “apply” (tests/test_referrals.py:294). If the maintainer reads “a listing from another company” as the listing carrying it, (b) is a separate issue onJobPosting(archive bump, capture, API).industries.pycode_formatches division names only). Recommended: no alias table in this issue; the division name is offered in every language Eurostat publishes, and the help says so.Prior art
No new dependency. NACE Rev. 2.1 is already vendored (Eurostat, CC BY 4.0,
src/postulo/jobs/data/LICENCE.md) and #140 chose it over a hand-made list; the rule that a classification is taken, not invented, is the one the maintainer's list states. Not verified: the Rev. 2.1 titles of the groups of division 78 (Rev. 2 has 78.1, 78.2 and 78.3).Builds on
Issues #202 (the kind), #239 (the agency on the application), #140 and #266 (a classification is taken, not written), and #329 (companies get addresses and phones; the office is a company there too).
Needed by
#685, #686 and #693 add their roles to the registry; #695 reuses g3's. #683 shares the company link with them and uses no role.
Done when
tests/test_nace.pycovers the registry: the intermediary role is division 78, an unknown role is refused, a company with an industry named “Employment activities” (and its translations) qualifies, a company with none does not, and a public employment service always does.tests/test_referrals.pychanges with the picker:test_the_form_offers_only_the_owners_own(:121) andtest_the_employer_is_not_offered_as_its_own_agency(:158) assert the qualifying-first order, and a new test asserts “show every company” lists the rest;test_an_agency_that_is_the_employer_already_stays_on_its_list(:239) still passes unchanged.tests/security/test_isolation_sweep.pyif the picker gains a URL, otherwise the form test above).src/postulo/core/export.pyFORMAT_VERSION, 29 atf09b6010b) and the candidate format (4) stay as they are, because nothing new is stored. If the maintainer chooses (b) under Where the NACE code lives, both bump (by one each, from whatever they are when the commit lands) and the standing checklist applies.From the list of improvements of 2026-10-02.