indexing¶
Roll the json output mode's records up into
embedding-sized units for a search index or vector store. See the
Search indexing guide for the record contract and
worked ChromaDB / Elasticsearch recipes.
opm.indexing ¶
Roll the JSON output mode's records up into embedding-sized index units.
opm transform -t json records every decision the processing model made,
which is the right shape for debugging an ODD and the wrong shape for a search
index: one record per table cell is not something you embed, and a nested tree
has no stable identity to upsert against.
This module bridges the two. It walks the record tree, groups it at section boundaries, and emits one JSONL line per retrievable unit with a flat metadata map: scalars for labels, arrays of strings for extracted names and the like.
Indexing the processing model rather than the source is the whole point: the
ODD has already decided what the reader sees. omit drops the apparatus,
alternate picks the displayed reading, templates expand abbreviations. An
XPath scrape of the same TEI would index
<choice><abbr>Mr</abbr><expan>Mister</expan></choice> as MrMister.
FieldSpec
dataclass
¶
FieldSpec(
name: str,
behaviours: frozenset[str] = frozenset(),
elements: frozenset[str] = frozenset(),
models: frozenset[str] = frozenset(),
fragment: str | None = None,
metadata: bool = True,
inline: bool | None = None,
)
Material to pull out of a passage: a facet, a passage of its own, or page chrome.
A note and a person name are the same operation — recognise a record, take
its text — differing only in where the text goes. metadata=True copies
it onto the passage as a list of distinct strings, for filtering;
metadata=False emits it as its own retrievable record tagged kind
and linked to its parent, and whether a search engine indexes those is
then a filter at load time rather than a decision baked into the file.
inline is the one choice that cannot be deferred: it decides whether the
text stays in the containing passage's embedded document string. It
defaults to metadata, which is what each case usually wants — a name reads
as part of the sentence, an extracted note does not — and can be set
explicitly to keep a fragment in both places.
fragment is a different source: the name of a [[chunking.fragments]]
entry. That HTML is transformed once per page (or once per document when
the fragment is global), stripped to a scalar, and copied onto every
record from that page. It cannot be mixed with a JSON selector, and it is
always metadata — never a hit of its own.
fragment
class-attribute
instance-attribute
¶
[[chunking.fragments]] name; when set, the other selectors stay empty.
UnitSpec
dataclass
¶
UnitSpec(
name: str,
behaviours: frozenset[str] = frozenset(),
elements: frozenset[str] = frozenset(),
models: frozenset[str] = frozenset(),
emit: bool = True,
min_chars: int | None = None,
)
A JSON record that opens a retrievable passage.
When [[index.units]] is present it replaces the default titled-division
walk: only matching records become units, and unmatched structure is walked
through so nested paragraphs (or whatever you selected) can still be found.
name is stored as metadata.kind. emit=False uses the match only
as context — typically a heading that labels the following paragraph — and
writes no JSONL line of its own. min_chars overrides the global floor
for records this spec emits.
IndexOptions
dataclass
¶
IndexOptions(
max_chars: int = 1500,
min_chars: int = 40,
overlap: int = 1,
fields: tuple[FieldSpec, ...] = (),
units: tuple[UnitSpec, ...] = (),
)
Tuning for the rollup. Defaults suit prose in a general-purpose embedder.
document_title ¶
Best-effort document title across the vocabularies opm ships ODDs for.
Source code in src/opm/indexing.py
build_records ¶
build_records(
document: list,
*,
doc_stem: str,
source: str,
title: str | None = None,
anchors: dict[str, str] | None = None,
chunk_file: str | None = None,
page_metadata: dict[str, str] | None = None,
options: IndexOptions | None = None,
) -> list[dict]
Turn one document's (or one chunk's) JSON-mode records into index records.
page_metadata is copied onto every record — values already stripped to
scalars, typically from fragment fields evaluated once for the page.
Source code in src/opm/indexing.py
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 | |
index_document ¶
index_document(
xml_path: Path,
*,
cfg: ProjectConfig,
odd: Path | None = None,
project_root: Path | None = None,
options: IndexOptions | None = None,
base_css: str | None = None,
) -> list[dict]
Transform xml_path in json mode and roll the records up for indexing.
opm.project.Project.index runs this over a corpus. odd replaces
[transform.json] odd, options the [index] settings, and
base_css is passed on to the compiler.
Where the project chunks its output, each chunk is transformed on its own
and tagged with the file it will be published as. That is what makes a
retrieval hit citable: a chunk-based href needs no xml:id at all, which
matters because chunk selectors may rebuild the region as a detached tree
whose ids never existed in the source document.
Source code in src/opm/indexing.py
699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 | |
write_jsonl ¶
Write records to path, one JSON object per line (UTF-8).