Classify companies by sector above industry, with the ESCO–NACE correspondence #897
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#897
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?
Why
A company's classification is one flat vocabulary called Industry (
jobs/models.py:57): the 87 NACE divisions as a seed, plus whatever the person types. The word is wrong for a third of the economy. Public administration, education, health, defence and the arts are not industries in anybody's mouth, and NACE itself never uses the word: it classifies economic activities into 22 sections and 87 divisions. The translators already noticed: French, Portuguese and Spanish render Industry as Secteur, Setor and Sector, which is the name of the level above the one the field holds.Two levels make each word accurate where it is used:
jobs/industries.pyargues today.What exists today
nace-2.1.jsonalready ships the 22 sections with names in 24 languages (sections), andindustries.section_of(code)(industries.py:152) maps a division to its section.look_of(:190) already draws badges by section (#856). The hierarchy is in the data; only the word and the UI are missing.Industry.code(models.py:74) follows the name (code_for,industries.py:204): a name that matches a division keeps its code, a made-up word has none.Company.industriesis a many-to-many (models.py:358);roles.pyanswers intermediary, place of learning, awarding body and membership organisation from the divisions of a company's industries (#671, #685, #686, #693).jobs/esco.py, #266);fetch_escoharvests them from the ESCO API withfull=false, so the stored entry is{"isco": "2513", "names": {...}}and nothing about sectors.The ESCO companion correspondence
ESCO published an ESCO–NACE crosswalk in December 2025 and re-issued it against NACE Rev. 2.1 in February 2026 (
ESCO-NACE rev. 2.1 crosswalk.xlsxon the ESCO other crosswalks page, with a technical report). The same correspondence is in the API from v1.2.1: every occupation resource carries_links.hasNACECode, a list of NACE Rev. 2.1 concepts at group or class level. Checked against the live API on 2026-10-12:The NACE concepts are their own scheme (
http://data.europa.eu/ux2/nace2.1/…) withbroaderConceptup to the division and the section, so the group62.2resolves to division62and sectionJwithout any table of our own. The crosswalk maps each of the 3 039 occupations to about 1.5 NACE categories on average, most at class level; truncating to the division is one string operation.That gives Postulo a bridge it has never had between the two classifications it already carries: an occupation implies the sectors and divisions it is usually employed in, and a company's sector implies the occupations usually found there.
What would change
Model.
Industrygainssection(CharField, one letter, blank), filled fromsection_of(code)whenever the name matches a division, and pickable from the 22 sections when the name is the person's own. A custom industry with no sector stays allowed and keeps the plain look.Companygains nothing: its sectors are the sections of its industries, the same way its roles are. A migration fillssectionfromcodefor every existing row.Reference data.
fetch_escoaddsnaceto each occupation entry: the division codes truncated fromhasNACECode(["62"]for the example above), in the same harvest, with no extra consent step. The ESCO file's loader guard accepts the new key;esco.pygainsdivisions_for(unit_group)(the union over the group's occupations) andindustries.pygainssections_for(divisions). Both are reference data under the existing EUPL 1.2 / Eurostat terms;data/ESCO-LICENCE.mdrecords the crosswalk's provenance.Interface.
templates/jobs/company_form.html:107): a Sector chooser above the industries, which narrows the datalist of suggestions to that section's divisions and the starter names that belong to it; picking an industry with a code sets the sector back, so the two never disagree.jobs/tables.py:79): a Sector column and filter, grouped; the industry badge keeps its section tone.views.py:856): grouped by sector; the industry form (forms.py:568) offers the sector for words without a code.applications/reports.py:532), analytics (analytics.py:582) and the By industry widget (widgets.py:278): roll up to sectors, with the industry breakdown one level down.esco.suggestions,esco.py:261). Conversely, a company whose industries are empty is offered, from the titles of the postings already recorded for it, the divisions ESCO attaches to those occupations. Offered and never required, the wayroles.pyoffers.Archive, API, CSV.
industriesstays a list of names in the archive and the API (export.py:1168,api/schemas.py:125), withsectorbeside each name only where the person set one by hand (a code's sector is derivable and is not stored twice).FORMAT_VERSIONbumps; the importer reads both shapes.Words. English keeps Industry for the fine level and adds Sector for the coarse one. The glossary in Weblate must settle the pairs before the strings land (#706): Secteur moves up and the fine level needs a word in each language, for instance Secteur / Activité or Branche (fr), Setor / Atividade or Ramo (pt), Sector / Actividad or Rama (es), Sektor / Branche (de). The current translations of Industry as Secteur, Setor, Sector become wrong on the day the sector level appears and must be retranslated in the same release.
Decisions
Industry, hand-picked, nullable. The alternative, a sector directly onCompany, makes two sources of truth for the coded case.Checked against
mainatcffdb2b36. The ESCO API'shasNACECodeand the Rev. 2.1 crosswalk file were checked on 2026-10-12.Fixed in
a17d60c23. The recommended answer was taken for each decision: Industry stays the fine-level word, a custom industry's sector lives on the industry (chosen by hand, optional), and the ESCO bridge orders suggestions both ways. Weblate: the fr/pt/es translations of “Industry” (Secteur, Setor, Sector) now name the level above and need retranslating.