Fihrist
Place a book on a lattice that stays even-handed when you change the model underneath it.
pip install fihrist
Fihrist ships a faceted classification artifact — 3,660 leaves across 10 axes — built so that no tradition sits at the centre as the unmarked default. Give it a book's contents and it tells you where that book belongs. The placement is earned from what the work is about, never from the author's nationality or the country it was published in.
Named for the Kitāb al-Fihrist (Baghdad, 987 CE), a bookseller's attempt to index every book then known, across every language he could reach.
What you need before you start
Fihrist takes an enriched book record, not a title.
A title and an author are not enough, and that is the whole point rather than a missing feature. The lattice slots on what a book contains. Feed it a bare title and the only thing a model can classify on is the author's name and origin — which is precisely the bias the artifact exists to remove.
So the input is a record carrying idea units: the handful of things the book is actually about. Step 2 below shows how to get them.
What Fihrist does and does not do
Fihrist makes no network calls and never sees an API key. It builds prompts and checks answers. The model call is yours, on your account, with your choice of provider.
| Fihrist owns | You own |
|---|---|
| the artifact, verified against its sha256 on load | the API key |
| leaf and parent lookup, the 9 embedded parity audits | the model call |
| building the research prompt | running it |
| building the slot prompt | running it |
| validating the reply — invented leaves are dropped |
Reads as a limitation for about ten seconds, and as a feature forever: no key, no telemetry, no surprise bill, and it works on a plane.
1 — Inspect the artifact
Offline, immediate, free.
import fihrist
lat = fihrist.load() # hash-verified against the shipped sha256
len(lat) # 3660
lat.axes # ['Discipline', 'Place', 'Time', 'Form', 'Genre', ...]
len(lat.leaves("Discipline")) # 1246
lat.find("Meiji") # [Leaf('Modern Japan (Meiji–Shōwa)', axis='Discipline', ...)]
lat.parents_of("Modern Japan (Meiji–Shōwa)", "Discipline")
# ['Japanese history']
lat.audits # the 9 parity audit reports, carried inside the file
The artifact carries its own audits. You do not have to take the parity claim on trust — open
lat.audits and read what was checked.
From the shell:
fihrist verify
fihrist axes
fihrist leaves --axis Discipline --grep literature
2 — Enrich a book
Fihrist writes the research prompt. You run it against any model that can search the web.
prompt = fihrist.enrich_prompt("I Am a Cat", "Natsume Sōseki")
record = fihrist.parse_enrichment(your_model(prompt))
record["ideaUnits"]
# [{'id': 'the-outsider-narrator-sees-clearest-1',
# 'title': 'The Outsider Narrator Sees Clearest',
# 'tags': ['narration', 'satire', 'detachment']}, ...]
The instruction body is frozen and byte-identical for every book — only the assignment block at the end changes. That constancy is deliberate. A prompt that said more about a Japanese novel than a French one would manufacture the very difference the artifact exists to measure, then hand it to the slotter as though the lattice had caused it.
3 — Slot it
prompt = fihrist.slot_prompt([record])
results = fihrist.validate(your_model(prompt), [record])
results[0].attached[0]
{"axis": "Discipline", "parent": "Japanese history", "leaf": "Modern Japan (Meiji–Shōwa)",
"confidence": "stretch", "culture": "Japanese", "status": "valid"}
Batch as many books into one slot_prompt call as your context window allows. The lattice index is
rendered first and identically every time, so a prompt-caching backend caches it across a whole run.
Validation is not a formality
fihrist.summarise(results)
# {'books': 1, 'attachments': 7, 'rejectedAttachments': 1,
# 'booksWithNoAttachment': 0, 'leavesPerBook': 7.0, 'byAxis': {...}}
Three things worth knowing:
- Invented leaves are dropped. Models produce plausible leaves in the artifact's own house style.
An invented one that survives into your results is worse than a missing one, because it looks like
evidence. Anything not on the lattice goes to
rejectedwith a reason, never intoattached. - A wrong parent is repaired, and the repair is recorded in
parentRepaired. Polyhierarchy means a leaf can hang from several parents; any true one is accepted. - Check
booksWithNoAttachmentfirst. A batch that comes back structurally valid and substantively empty is the quietest way a run fails — the JSON parses, nothing raises, and the numbers are simply thin. That counter is where it shows.
From the shell
fihrist verify # hash check
fihrist axes # the 10 axes
fihrist leaves --axis Genre --grep thriller
fihrist prompt enrich --title "I Am a Cat" --author "Natsume Sōseki"
fihrist prompt slot books.json > prompt.txt
fihrist validate response.json --books books.json --out slotted.json
The claim, and its limits
Seven engines — Claude, GPT, DeepSeek, and local Qwen models from 30B to 235B — slotted the same 1,232 books onto this lattice. All of them top-ranked India, China and Japan on the Place axis. A biased engine buries those. None did. The correction lives in the artifact rather than in a prompt or a model, so swapping the model does not bring the bias back.
What is not established: a later 425-book corpus run, pre-registered and sealed, did not confirm this at corpus scale — its three gates all counted Discipline leaves, and the registered engine produces 0.20 Discipline leaves per book on fiction, so the probes largely measured the instrument rather than the artifact. That run, its failure, and the reasoning are published alongside the evidence rather than quietly dropped. See the corpus dataset.
Seven of the ten axes carry a published parity audit. Three — Agent/Entity, Action/Process,
Thing/Matter — are provisional and marked as such (fihrist.PROVISIONAL_AXES).
The guard rules
fihrist.GUARDS is the anti-bias correction, carried verbatim from the run that produced the
published results. Rewording it invalidates every parity claim attached to this artifact. If you
need different behaviour, write your own prompt rather than editing those lines, and do not report
the result as a Fihrist placement.
Cost
Fihrist itself is free and offline. Steps 2 and 3 are your own model calls — roughly one search-enabled call and one slot call per book.
Just want to look?
The evidence is being published as datasets — 425 books slotted independently by three engines, the artifact with its audits, and a browsable view of the result. Until those land, everything the package needs is in the wheel, and the source repo is bk2022-ui/lattice.
Licence
Code: Apache-2.0. The artifact (world-lattice.json) and the corpus: CC-BY-4.0.
Attribution is the point. If the lattice is doing work for you, cite it.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fihrist-0.1.0.tar.gz.
File metadata
- Download URL: fihrist-0.1.0.tar.gz
- Upload date:
- Size: 249.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d6770c842bbd792452c22cbcc3d553a819343f431688b3a2feafc5a06e733e63
|
|
| MD5 |
4c49a674cb05245e7d1ae0b9a0699f96
|
|
| BLAKE2b-256 |
6ed5947b914c789911c0011b97571d418387be8b739e981e37fbed1586705944
|
File details
Details for the file fihrist-0.1.0-py3-none-any.whl.
File metadata
- Download URL: fihrist-0.1.0-py3-none-any.whl
- Upload date:
- Size: 254.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0eac8e4d7ac5641aca44f823a8a96c84153a49ba299bd7326b00c097147e34b9
|
|
| MD5 |
bd1fdb822cb11ed0738ccf1bf60c23f9
|
|
| BLAKE2b-256 |
e5837fc106975bf8aabb49f2a66da50c5f4436da1e8691bc716a18434f8623f1
|