call-number falls through to extra instead of populating Zotero's callNumber
Summary
The pipeline does not map the standard CSL-JSON field call-number to Zotero's
callNumber field. There is no map_call_number in csl_field_mappers.py and no
catch-through in csl_to_zotero, so call-number (or any synonym like callNumber)
in the CSL-JSON input ends up in Zotero's free-text extra blob — not in the proper
callNumber field. As a result, every book, bookSection, etc. ingested to date
has gone in with a blank callNumber.
This blocks the physical-book cataloguing workflow (sticker / shelf-card generation
keyed off callNumber) and silently degrades records that do arrive with a call
number supplied.
Environment
- Pipeline: v2
- File:
v2/csl_mapper.py, v2/csl_field_mappers.py
- Affected item types: every type that lists
callNumber in zotero_allowed_fields.py
(book, bookSection, manuscript, map, thesis, audioRecording, videoRecording, etc.
— basically everything physical)
Evidence
grep -n 'call' v2/csl_mapper.py v2/csl_field_mappers.py returns no map_call_number
function and no copy-through.
csl_to_zotero (csl_mapper.py:146) only invokes the explicit map_* helpers; nothing
copies arbitrary recognised CSL keys into zotero_item.
map_extra_fields (csl_field_mappers.py:373) treats unknown CSL keys as text and
appends them to zotero_item['extra'] — call-number is not in the standard_keys
set, so it lands there.
clean_unexpected_fields (csl_mapper.py:92) cannot recover this either, because it
only acts on keys already present in zotero_item, and call-number was never added
to that dict in the first place.
Expected vs Actual
- Expected: CSL-JSON input containing
"call-number": "PR1868.A1 1985" results
in a Zotero item with callNumber: "PR1868.A1 1985", and that value flows through
to the Obsidian note's callnumber YAML field (already wired in
obsidian_writer.py:113).
- Actual: The value is appended to
extra as text (call-number: PR1868.A1 1985).
Zotero's callNumber field stays empty. The Obsidian YAML callnumber field stays
empty. Anything keyed off callNumber (sorting, filtering, label printing) is broken.
Likely Root Causes
- Missing mapper: No
map_call_number helper exists.
- No catch-through for recognised-but-unmapped CSL fields:
csl_to_zotero requires
an explicit map_* for every field that should land in Zotero.
- Skill-side gap: The
bibnow SKILL.md does not currently instruct Claude to
include call-number for physical items, so even once the mapper is fixed, entries
will continue to arrive without the field unless the skill is updated in parallel.
Proposed Fixes
-
Add map_call_number to csl_field_mappers.py:
- Read
call-number from the CSL item.
- Write to
zotero_item['callNumber'] if the item type allows it
(callNumber in ZOTERO_ALLOWED_FIELDS[item_type]); otherwise no-op (or fall
through to extra with an explicit log line).
- Accept both
call-number (CSL standard, kebab) and callNumber (Zotero camelCase)
as input keys, since LLM-generated CSL sometimes drifts.
-
Wire it into csl_to_zotero alongside the existing map_isbn, map_doi, etc.
-
Add call-number to standard_keys in map_extra_fields so it isn't
double-handled (mapped to the proper field and re-appended to extra).
-
Update bibnow/SKILL.md (separate change in the skill repo) to instruct Claude
to look up the LC class number (catalog.loc.gov, MARC 050) by ISBN for any
physical-item CSL entry and emit it as call-number.
-
Test cases:
- Book with
call-number set → Zotero callNumber populated, extra does not
contain call-number: line.
- Book with
callNumber (camelCase) input → same result.
- Item type that doesn't allow
callNumber (e.g. webpage) → field gracefully
skipped or routed to extra, no crash.
- Backfill: a one-off script to walk existing Zotero items and migrate any
call-number: line out of extra into the proper callNumber field.
Out of Scope (separate issues)
- The skill-side change (instructing Claude to generate call numbers).
- Backfill of existing records that went in with blank
callNumber.
call-numberfalls through toextrainstead of populating Zotero'scallNumberSummary
The pipeline does not map the standard CSL-JSON field
call-numberto Zotero'scallNumberfield. There is nomap_call_numberincsl_field_mappers.pyand nocatch-through in
csl_to_zotero, socall-number(or any synonym likecallNumber)in the CSL-JSON input ends up in Zotero's free-text
extrablob — not in the propercallNumberfield. As a result, everybook,bookSection, etc. ingested to datehas gone in with a blank
callNumber.This blocks the physical-book cataloguing workflow (sticker / shelf-card generation
keyed off
callNumber) and silently degrades records that do arrive with a callnumber supplied.
Environment
v2/csl_mapper.py,v2/csl_field_mappers.pycallNumberinzotero_allowed_fields.py(book, bookSection, manuscript, map, thesis, audioRecording, videoRecording, etc.
— basically everything physical)
Evidence
grep -n 'call' v2/csl_mapper.py v2/csl_field_mappers.pyreturns nomap_call_numberfunction and no copy-through.
csl_to_zotero(csl_mapper.py:146) only invokes the explicitmap_*helpers; nothingcopies arbitrary recognised CSL keys into
zotero_item.map_extra_fields(csl_field_mappers.py:373) treats unknown CSL keys as text andappends them to
zotero_item['extra']—call-numberis not in thestandard_keysset, so it lands there.
clean_unexpected_fields(csl_mapper.py:92) cannot recover this either, because itonly acts on keys already present in
zotero_item, andcall-numberwas never addedto that dict in the first place.
Expected vs Actual
"call-number": "PR1868.A1 1985"resultsin a Zotero item with
callNumber: "PR1868.A1 1985", and that value flows throughto the Obsidian note's
callnumberYAML field (already wired inobsidian_writer.py:113).extraas text (call-number: PR1868.A1 1985).Zotero's
callNumberfield stays empty. The Obsidian YAMLcallnumberfield staysempty. Anything keyed off
callNumber(sorting, filtering, label printing) is broken.Likely Root Causes
map_call_numberhelper exists.csl_to_zoterorequiresan explicit
map_*for every field that should land in Zotero.bibnowSKILL.md does not currently instruct Claude toinclude
call-numberfor physical items, so even once the mapper is fixed, entrieswill continue to arrive without the field unless the skill is updated in parallel.
Proposed Fixes
Add
map_call_numbertocsl_field_mappers.py:call-numberfrom the CSL item.zotero_item['callNumber']if the item type allows it(
callNumber in ZOTERO_ALLOWED_FIELDS[item_type]); otherwise no-op (or fallthrough to
extrawith an explicit log line).call-number(CSL standard, kebab) andcallNumber(Zotero camelCase)as input keys, since LLM-generated CSL sometimes drifts.
Wire it into
csl_to_zoteroalongside the existingmap_isbn,map_doi, etc.Add
call-numbertostandard_keysinmap_extra_fieldsso it isn'tdouble-handled (mapped to the proper field and re-appended to extra).
Update
bibnow/SKILL.md(separate change in the skill repo) to instruct Claudeto look up the LC class number (catalog.loc.gov, MARC 050) by ISBN for any
physical-item CSL entry and emit it as
call-number.Test cases:
call-numberset → ZoterocallNumberpopulated,extradoes notcontain
call-number:line.callNumber(camelCase) input → same result.callNumber(e.g.webpage) → field gracefullyskipped or routed to
extra, no crash.call-number:line out ofextrainto the propercallNumberfield.Out of Scope (separate issues)
callNumber.