schema.org source: on a page with several postings, an identifier matches by the employer's name and picks the wrong one #590
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#590
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?
Found on 2026-09-29 in a read-only review of postulo at
95348908d, reading how the schema.org source chooses among several postings and running it on a page that embeds three.Checked again on 2026-10-01 against
mainat0a5cad587: still present._matches_urlreads a posting'sidentifierwith_text, which prefers a dict'snameover itsvalue. schema.org and Google's documented JobPosting example write the identifier as{"@type": "PropertyValue", "name": "<employer>", "value": "<id>"}, so what is compared with the page's address is the employer's name. The comparison is a substring test, and_best_postingreturns the first posting that matches by any rule, so an earlier posting whose employer's name appears in the host wins over a later one whoseurlis exactly the page.Related to #176, which introduced choosing the posting that names the page.
plugins/builtin/__init__.py:251-253(identifier = _text(posting.get("identifier")), thenidentifier.lower() in here),:110-117(_text:value.get("name") or value.get("value")) and:267-275(_best_postingreturns the first posting matching by any rule). The extension does the same:postulo-chromiumsrc/lib/parse.js:403-411(text),:556-570(matchesUrl) and:581-587(bestPosting).https://careers.acme.com/jobs/3. The page embeds JSON-LD for jobs 1, 2 and 3 (the advert and two "similar jobs"), each with its ownurlandidentifier: {"@type": "PropertyValue", "name": "Acme", "value": "n000N"}for job N.SchemaOrgSource().parsereturns "Job 1".1000and10001on/jobs/10001choose the posting with1000.The project's own fixture
tests/fixtures/postings/weworkremotely.html:42-46has exactly this identifier shape, with the employer's name inname.url,@idorsameAsequals the page; only if none does, fall back to identifiers. Read an identifier'svalue(or the plain string), never itsname, and match it as a whole path segment or query value rather than as a substring. Mirror the change inpostulo-chromium'sparse.js.Done when
tests/fixtures/postings/with three postings that share an employer and carryPropertyValueidentifiers, read bytests/test_source_corpus.pyas the posting whoseurlis the page; today it reads as the first.tests/test_capture.pythat identifiers1000and10001on/jobs/10001choose10001.postulo-chromium'stest/corpus.test.mjs.