Derived results, caches, outputs and logs¶
Stages¶
A build runs stages. Each has a stable id and a plain name, and writes into
derived/<stage id>/:
id |
plain name |
reads |
|---|---|---|
|
gather the texts |
sources, |
|
find keyword candidates |
|
|
AI clean-up (opt-in) |
|
|
build the vocabulary |
|
|
place keywords in a common space |
|
|
group keywords into topics and themes |
|
|
apply your themes |
|
|
draw the map |
|
|
change over time |
|
|
place projected people |
|
The people’s keywords¶
keywords.build writes each person’s keywords twice:
file |
what it holds |
|---|---|
|
a model descriptor ( |
|
each person’s best keywords, at most 30 ( |
|
the candidates each keyword of the vocabulary gathers: |
Each person’s row holds every keyword of the vocabulary (the gated list, cut at
max_keywords) that their texts use, with its real counts; keywords_per_person
(advanced, empty by default) keeps only each person’s best ones, as versions before
1.0 did with 30. A run of an earlier version has no person_terms.json: the space
then reads the table, which held each person’s whole row.
The theme results¶
The theme stages write, at every depth of the theme tree (1 to 4), the files readers use:
file |
what it holds |
|---|---|
|
the grouping’s proposal: a |
|
with the comb: which keyword each text uses (texts × the vocabulary’s keywords, in its row order; |
|
the tree applied, as read: |
|
|
|
the same, each node with its place on the map ( |
|
one row per placed keyword: |
|
one row per mapped person and node with a weight: |
|
one row per organisation ( |
|
one row per person, time window and node: |
|
each projected person’s |
A person’s usage (their TF counts, by default) is divided by its sum over the
keywords the tree places. Their weight on a node is the part on the keywords
counting toward it: the keywords on the node and below it, each down to its
attribution (0: nowhere). Their share on a node is that weight over their
weights on the node’s level, so a person’s shares sum to 1 on each level where
they have usage. An organisation adds up its people. A node’s weight is the
sum of every person’s, and its share that sum over the people counted; a
keyword’s likewise. Colours: one hue per top-level node, a shade of it for
each node below.
At depth 2 the theme stages also write the engine’s two-level documents
(subfields_draft.json, curated.json, subfields.json,
subfield_weights.csv, lexicon_weights.csv, the subfields and concepts
of trajectory_windows.json, the themes and topics of positions.json).
They serve the numeric reference and the migration of older projects; nothing
new reads them, and at other depths they are absent (or empty).
Map versions¶
map.layout draws every built map version of decisions/maps.json (the pinned
one and those marked built), and the stages after it place on each. The pinned
version’s files are where they always are; every other built version’s are in
versions/<id>/ of each stage’s folder, under the same names, holding only what
is placed on that map:
file |
what it holds |
|---|---|
|
each mapped person (the people of the space, in its rows) and their place: |
|
each keyword’s place: |
|
each engine group ( |
|
the layout’s measures ( |
|
the applied tree, each node at the mean place of its people |
|
each time window’s point; in |
|
each person’s windows; in |
|
the projected people; in |
The space, the stored embeddings (models/embeddings.json), the clustered
keywords and the two-level documents are the pinned version’s alone. A map in
space adds a third coordinate (umap_z, z) to every place; a flat map has
none. run.json of map.layout lists the versions it built in
measures.versions: [{"id", "dimensions", "method", "seconds", "trustworthiness", "key", "reused", "pinned"}], the pinned one first (a kept version keeps the seconds it took to draw; the pinned one also keeps
the run’s counts).
A version unchanged is not drawn again. Each built version has a key, a
digest of everything its files are made from: the run’s inputs (code, stage
version, parameters such as neighbours and link_radius, the upstream runs,
the project parts) without decisions/maps.json, plus the version’s own
layout (method, seed, parameters, dimensions) and whether it is pinned. When
the generation a run replaces recorded the same key for a version, its files are
copied from it instead of computed (reused: true; for the pinned version, every
file of the stage’s folder but versions/, the amended embeddings, themes and
two-level documents included; for another, its versions/<id>/ folder), and the
progress says so. Marking another version built, unmarking one, adding a version
or editing a note thus redraws only what changed; a version no longer built is
simply left out. map.trajectories and overlays.position record a key per
version too (their own inputs without the layout’s run, the version’s layout key
and the pinned version’s): when every version’s key is the one their last
generation recorded, the whole stage is copied from it; otherwise it is computed
whole (its pass over the texts serves every version at once).
run.json¶
Every stage folder holds the record of the run that produced it:
{
"format": "cartolex-run/1",
"stage": "keywords.extract",
"run_id": "20260928T101200Z-7c1e",
"outcome": "succeeded",
"started_at": "2026-09-28T10:12:00Z",
"finished_at": "2026-09-28T10:12:41Z",
"code": {"version": "1.0.0", "fingerprint": "sha256:…", "stage_version": 1},
"parameters": {
"min_people": {"value": 3, "from": "default"},
"min_texts": {"value": 3, "from": "default"},
"counting_unit": {"value": "person", "from": "params.json"}
},
"inputs": [
{"kind": "stage", "stage": "corpus.assemble", "run_id": "20260928T101150Z-0a9d"},
{"kind": "decision", "path": "decisions/stopwords.json", "fingerprint": "sha256:…"}
],
"identity": {"language_models": {"en": "en_core_web_md@3.8.0"}},
"measures": {"seconds": 41.2, "peak_memory_mb": 812, "own_memory_mb": 406,
"counts": {"candidates_en": 5214}},
"warnings": []
}
parametersholds every effective value and where it came from:default,rule(computed from the project’s sizes; the rule is named), orparams.json.inputslists what the stage read: the runs of upstream stages by id, and source and decision files by fingerprint. Large tables are fingerprinted by their Parquet metadata and row-group statistics, so a check stays fast.codenames the cartolex version and a fingerprint of its sources, for the record, and the version of the stage: cartolex raises it when it deliberately changes what the stage produces, and a result made by another version needs an update. Other code changes leave results up to date.measuresfeed the cost estimates of later dry runs:peak_memory_mbis the most the stage’s process and its worker processes held together,own_memory_mbwhat its own process held (Linux only). A stage that sizes its workers to the job’s budget is estimated at most that budget, or at what its own process held, scaled, when that is more.
Is a result up to date? Six states¶
A stage is in exactly one state, computed from the records alone:
state |
when |
|---|---|
never built |
no |
up to date |
its last run succeeded, every input fingerprint matches the current file, every upstream run it read is still the current one, its parameters equal today’s effective ones, and it was made by the current version of the stage ( |
needs update |
its last run succeeded but something it read has changed; the record says what |
running |
a job holds the stage now |
failed |
its last attempt failed or was cancelled (recorded in |
skipped |
the stage does not apply: AI clean-up switched off, no overlay |
A stage whose upstream needs an update needs one too, and says which upstream changed. File dates never enter the decision.
Generations: a killed build loses nothing¶
A stage never writes into its own folder while it runs:
it writes into
derived/.staging/<stage id>.<run id>/;when it succeeds,
derived/<stage id>/moves toderived/.previous/<stage id>/and the staging folder takes its place; the move is recorded inderived/.journal.jsonfirst, so an interrupted swap is completed or undone when the project is next opened;when it fails or is cancelled, the staging folder is removed and nothing else changes, except the record of the attempt.
The last failed or cancelled attempt of a stage is recorded in
derived/.attempts/<stage id>.json: a run record like run.json, whose
outcome is failed or cancelled and whose error says why. It is removed
when a later run of the stage succeeds. derived/<stage id>/run.json is only
ever the record of a successful run.
Long stages also checkpoint by chunk inside their staging folder, so a killed run resumes from its last chunk instead of starting over. A cancel therefore ends in one of two plain states: « nothing changed », or « finished before the cancel ».
Caches¶
folder |
holds |
if deleted |
|---|---|---|
|
AI answers: per-term and per-batch triage caches, translations, label translations, token usage — terms only |
the answers are paid for again |
|
parsed texts, keyed by the text’s SHA-256 and the language model’s name and version |
texts are parsed again |
|
service responses, each with its lifetime; failures are never cached |
collection fetches again |
|
the app’s replies that take long to make (the texts and time windows of the map), and |
they are made again when the map is opened |
The AI cache keys are part of the format and never change within a major version, so a project, or an application migrating its own caches into one, re-bills nothing.
The files of cache/ai/, which an application migrating its own caches writes
as they are:
file |
holds |
key |
|---|---|---|
|
one AI verdict per term |
|
|
the answer to each batch of terms |
a hash of the batch’s terms, the domain title and the model |
|
the tokens the provider reported for every run of the AI clean-up by API, added up: |
— |
cache/parse/ holds parsed texts in immutable JSON-lines parts named by their
content’s digest; its layout is described with the extraction
(docs/dev/extraction.md), and a missing or foreign part is parsed again.
Outputs¶
outputs/ is the default root for what a project hands out: sites/,
figures/, tables/, bundles/. Every build goes into a new dated folder and
is never overwritten; a latest marker names the newest, and a build older
than the decisions it reflects is flagged. A host application can declare
other roots.
Logs¶
logs/jobs/<job id>.jsonl: one line per event of a job (start, phase, progress,
warning, end). Logs hold stage names, counts and times, never people’s names or
texts.