Skip to content

Sources API

Source content remains untrusted data throughout normalization. The default registry accepts local files only and uses typed exceptions for format, security, resource, dependency, conversion, and timeout failures.

See Local Sources for supported formats, default limits, optional dependencies, and operational controls.

Registry methods

Method Return Behavior
convert(source, limits=...) SourceDocument Validate, snapshot, hash, and normalize one local source
convert_for_svis(source, extraction_date=..., limits=...) SourceSvisConversionResult Add native XLSForm SVIS and routing semantics when available
convert_with_native(source, svis, limits=...) SourceConversionResult Add an exact routing binding and optional native semantics

All methods accept a local path or confined SourceBundle. They raise typed SourceError subclasses for input, format, security, dependency, conversion, timeout, and resource-limit failures. They do not accept bytes, streams, URLs, or network paths.

Core source types

base

Typed source port, normalized models, limits, and local-path confinement.

LocalSource module-attribute

LocalSource: TypeAlias = str | os.PathLike[str]

DEFAULT_SOURCE_LIMITS module-attribute

DEFAULT_SOURCE_LIMITS = SourceLimits()

SourceLimits dataclass

SourceLimits(
    max_source_bytes: int = 250 * 1024 * 1024,
    max_pages: int = 2000,
    max_archive_expanded_bytes: int = 1024 * 1024 * 1024,
    max_archive_ratio: float = 100.0,
    max_archive_entries: int = 10000,
    max_archive_filename_chars: int = 512,
    max_archive_path_depth: int = 20,
    max_xml_part_bytes: int = 64 * 1024 * 1024,
    max_xml_elements: int = 2000000,
    max_xml_depth: int = 256,
    max_cells: int = 2000000,
    max_companions: int = 100,
    deadline_seconds: float = 30 * 60.0,
)

Resource ceilings applied before or during local source conversion.

max_source_bytes class-attribute instance-attribute

max_source_bytes: int = 250 * 1024 * 1024

max_pages class-attribute instance-attribute

max_pages: int = 2000

max_archive_expanded_bytes class-attribute instance-attribute

max_archive_expanded_bytes: int = 1024 * 1024 * 1024

max_archive_ratio class-attribute instance-attribute

max_archive_ratio: float = 100.0

max_archive_entries class-attribute instance-attribute

max_archive_entries: int = 10000

max_archive_filename_chars class-attribute instance-attribute

max_archive_filename_chars: int = 512

max_archive_path_depth class-attribute instance-attribute

max_archive_path_depth: int = 20

max_xml_part_bytes class-attribute instance-attribute

max_xml_part_bytes: int = 64 * 1024 * 1024

max_xml_elements class-attribute instance-attribute

max_xml_elements: int = 2000000

max_xml_depth class-attribute instance-attribute

max_xml_depth: int = 256

max_cells class-attribute instance-attribute

max_cells: int = 2000000

max_companions class-attribute instance-attribute

max_companions: int = 100

deadline_seconds class-attribute instance-attribute

deadline_seconds: float = 30 * 60.0

SourceBundle dataclass

SourceBundle(
    root: Path,
    primary: Path,
    companions: tuple[Path, ...] = (),
)

One primary local source and explicitly confined companion files.

root instance-attribute

root: Path

primary instance-attribute

primary: Path

companions class-attribute instance-attribute

companions: tuple[Path, ...] = ()

ResolvedSource dataclass

ResolvedSource(
    root: Path,
    primary: Path,
    companions: tuple[Path, ...] = (),
    primary_sha256: str | None = None,
    companion_sha256: tuple[str, ...] = (),
)

Validated absolute paths passed from the source port to adapters.

root instance-attribute

root: Path

primary instance-attribute

primary: Path

companions class-attribute instance-attribute

companions: tuple[Path, ...] = ()

primary_sha256 class-attribute instance-attribute

primary_sha256: str | None = None

companion_sha256 class-attribute instance-attribute

companion_sha256: tuple[str, ...] = ()

SourceCoverage

Bases: BaseModel

Complete immutable accounting for physical source conversion units.

model_config class-attribute instance-attribute

model_config = ConfigDict(extra='forbid', frozen=True)

unit class-attribute instance-attribute

unit: Literal['document', 'page', 'sheet'] = 'document'

total_units class-attribute instance-attribute

total_units: int = Field(default=1, ge=1)

converted_units class-attribute instance-attribute

converted_units: tuple[int, ...] = (1,)

failed_units class-attribute instance-attribute

failed_units: tuple[int, ...] = ()

complete property

complete: bool

Return whether every physical unit converted successfully.

reject_boolean_units classmethod

reject_boolean_units(value: object) -> object

Reject Boolean unit values before Pydantic converts them to integers.

validate_complete_accounting

validate_complete_accounting() -> SourceCoverage

SourceProvenance

Bases: BaseModel

Available physical origin for normalized source content.

model_config class-attribute instance-attribute

model_config = ConfigDict(extra='forbid', frozen=True)

source_name instance-attribute

source_name: str

page class-attribute instance-attribute

page: int | None = Field(default=None, ge=1)

pages class-attribute instance-attribute

pages: tuple[int, ...] = ()

sheet class-attribute instance-attribute

sheet: str | None = None

section_path class-attribute instance-attribute

section_path: tuple[str, ...] = ()

row_start class-attribute instance-attribute

row_start: int | None = Field(default=None, ge=1)

row_end class-attribute instance-attribute

row_end: int | None = Field(default=None, ge=1)

reject_boolean_pages classmethod

reject_boolean_pages(value: object) -> object

Reject Boolean page values before Pydantic converts them to integers.

validate_section_path classmethod

validate_section_path(
    value: tuple[str, ...],
) -> tuple[str, ...]

Normalize and reject empty source section labels.

validate_row_range

validate_row_range() -> SourceProvenance

SourceTable

Bases: BaseModel

A table preserved as ordered text cells without formula execution.

model_config class-attribute instance-attribute

model_config = ConfigDict(extra='forbid', frozen=True)

id instance-attribute

id: str

rows instance-attribute

rows: tuple[tuple[str, ...], ...]

provenance instance-attribute

provenance: SourceProvenance

SourceBlock

Bases: BaseModel

One ordered text or complete-table unit from an untrusted source.

model_config class-attribute instance-attribute

model_config = ConfigDict(extra='forbid', frozen=True)

id instance-attribute

id: str

order class-attribute instance-attribute

order: int = Field(ge=0)

kind instance-attribute

kind: Literal['text', 'table']

text instance-attribute

text: str

provenance instance-attribute

provenance: SourceProvenance

table class-attribute instance-attribute

table: SourceTable | None = None

validate_table_kind

validate_table_kind() -> SourceBlock

SourceDocument

Bases: BaseModel

Deterministic normalized content; all source text remains untrusted data.

model_config class-attribute instance-attribute

model_config = ConfigDict(extra='forbid', frozen=True)

source_name instance-attribute

source_name: str

media_type instance-attribute

media_type: str

blocks instance-attribute

blocks: tuple[SourceBlock, ...]

coverage class-attribute instance-attribute

diagnostics class-attribute instance-attribute

diagnostics: tuple[SourceDiagnostic, ...] = ()

snapshot_sha256 class-attribute instance-attribute

snapshot_sha256: str | None = Field(
    default=None, pattern="^[0-9a-f]{64}$"
)

trust class-attribute instance-attribute

trust: Literal['untrusted'] = 'untrusted'

tables property

tables: tuple[SourceTable, ...]

Return complete tables in stable source order.

validate_stable_order

validate_stable_order() -> SourceDocument

SourceDiagnostic

Bases: BaseModel

Stable immutable diagnostic for one source or physical source unit.

model_config class-attribute instance-attribute

model_config = ConfigDict(extra='forbid', frozen=True)

code instance-attribute

code: str

message instance-attribute

message: str

severity class-attribute instance-attribute

severity: Literal['warning', 'error'] = 'error'

unit class-attribute instance-attribute

unit: Literal['document', 'page', 'sheet'] | None = None

unit_index class-attribute instance-attribute

unit_index: int | None = Field(default=None, ge=1)

validate_unit_reference

validate_unit_reference() -> SourceDiagnostic

SourceError

SourceError(message: str)

Bases: SurveyScribeError

Base class for source-port errors with stable machine-readable codes.

code class-attribute instance-attribute

code = 'SOURCE_ERROR'

diagnostic instance-attribute

diagnostic = SourceDiagnostic(
    code=self.code, message=message
)

SourceInputError

SourceInputError(message: str)

Bases: SourceError, ValueError

The caller supplied a non-local, missing, or unconfined source.

code class-attribute instance-attribute

code = 'SOURCE_INPUT_INVALID'

SourceFormatError

SourceFormatError(message: str)

Bases: SourceError, ValueError

The local file format is unsupported, corrupt, or ambiguous.

code class-attribute instance-attribute

code = 'SOURCE_FORMAT_UNSUPPORTED'

SourceSecurityError

SourceSecurityError(message: str)

Bases: SourceError

Untrusted content contains a prohibited execution or escape vector.

code class-attribute instance-attribute

code = 'SOURCE_SECURITY_REJECTED'

SourceDependencyError

SourceDependencyError(message: str)

Bases: SourceError, ImportError

A lazy optional source dependency is unavailable or unconfigured.

code class-attribute instance-attribute

code = 'SOURCE_DEPENDENCY_MISSING'

SourceConversionError

SourceConversionError(message: str)

Bases: SourceError

An adapter could not normalize the source safely.

code class-attribute instance-attribute

code = 'SOURCE_CONVERSION_FAILED'

SourceTimeoutError

SourceTimeoutError(message: str)

Bases: SourceError, TimeoutError

Killable source conversion exceeded its configured deadline.

code class-attribute instance-attribute

code = 'SOURCE_TIMEOUT'

SourceLimitError

SourceLimitError(limit: str, message: str)

Bases: SourceError

A configured source resource ceiling was exceeded.

code class-attribute instance-attribute

code = 'SOURCE_LIMIT_EXCEEDED'

limit instance-attribute

limit = limit

SourceAdapter

Bases: Protocol

Minimal adapter port used by the source registry.

convert

convert(
    source: ResolvedSource, *, limits: SourceLimits
) -> SourceDocument

Normalize one validated local source without external side effects.

resolve_local_source

resolve_local_source(
    source: LocalSource | SourceBundle,
    *,
    limits: SourceLimits = DEFAULT_SOURCE_LIMITS,
) -> ResolvedSource

Validate local-only input and confine bundle paths after resolution.

Parameters:

Name Type Description Default
source LocalSource | SourceBundle

Existing local file or a bundle rooted in one local directory.

required
limits SourceLimits

Resource ceilings applied to the primary and companion files.

DEFAULT_SOURCE_LIMITS

Returns:

Type Description
ResolvedSource

Absolute validated paths for the selected source.

Raises:

Type Description
SourceInputError

A path is remote, missing, not a file, or escapes its bundle root.

SourceLimitError

File size or companion count exceeds a configured limit.

snapshot_resolved_source

snapshot_resolved_source(
    source: ResolvedSource,
    *,
    limits: SourceLimits = DEFAULT_SOURCE_LIMITS,
) -> Iterator[ResolvedSource]

Yield a bounded private copy whose digest matches converted bytes.

validate_source_argument

validate_source_argument(
    source: object,
) -> LocalSource | SourceBundle

Validate the public local-source argument without accessing the filesystem.

Registry

registry

Deterministic Tier 1 source adapter registry.

SourceRegistry

SourceRegistry(adapters: Mapping[str, SourceAdapter])

Map verified file suffixes to minimal source adapters.

Create a suffix-to-adapter registry.

Parameters:

Name Type Description Default
adapters Mapping[str, SourceAdapter]

Adapter mapping keyed by file suffix. Suffix matching is case-insensitive.

required

default classmethod

default() -> SourceRegistry

Create the verified Tier 1 registry without importing optional SDKs.

Returns:

Type Description
SourceRegistry

A registry for PDF, DOCX, XLSX, CSV, HTML, Markdown, and text files.

convert

Resolve one local source, verify its format, and normalize it.

Parameters:

Name Type Description Default
source LocalSource | SourceBundle

Existing local file or confined source bundle.

required
limits SourceLimits

Resource ceilings for validation and conversion.

DEFAULT_SOURCE_LIMITS

Returns:

Type Description
SourceDocument

Deterministic ordered blocks with physical source provenance.

Raises:

Type Description
SourceInputError

The local source path is invalid.

SourceFormatError

The suffix is unsupported or conflicts with the file signature.

SourceSecurityError

Untrusted content contains a prohibited vector.

SourceDependencyError

An optional adapter dependency is unavailable.

SourceLimitError

A configured resource ceiling is exceeded.

SourceConversionError

The selected adapter cannot normalize the file.

SourceTimeoutError

Killable conversion exceeds its deadline.

convert_with_native

convert_with_native(
    source: LocalSource | SourceBundle,
    svis: SurveySVIS,
    *,
    limits: SourceLimits = DEFAULT_SOURCE_LIMITS,
) -> SourceConversionResult

Normalize one source and add its exact binding and native semantics.

convert_for_svis

convert_for_svis(
    source: LocalSource | SourceBundle,
    *,
    extraction_date: date,
    limits: SourceLimits = DEFAULT_SOURCE_LIMITS,
) -> SourceSvisConversionResult

Normalize a source and parse package-native SVIS when available.

SourceConversionResult dataclass

SourceConversionResult(
    document: SourceDocument,
    source_binding: RoutingSourceBinding,
    native: NativeRoutingSemantics | None,
)

Additive normalized source result with binding and optional native routing.

document instance-attribute

document: SourceDocument

source_binding instance-attribute

source_binding: RoutingSourceBinding

native instance-attribute

native: NativeRoutingSemantics | None

SourceSvisConversionResult dataclass

SourceSvisConversionResult(
    document: SourceDocument,
    svis: SurveySVIS | None,
    native: NativeRoutingSemantics | None,
)

Normalized source plus optional package-native SVIS and routing semantics.

document instance-attribute

document: SourceDocument

svis instance-attribute

svis: SurveySVIS | None

native instance-attribute

native: NativeRoutingSemantics | None

Document adapters

docling

Safe document adapters, including killable local Docling PDF conversion.

DoclingPdfAdapter

DoclingPdfAdapter(
    *,
    converter: PdfConverter | None = None,
    artifacts_path: str | PathLike[str] | None = None,
)

Normalize a local PDF in a fresh killable, network-disabled process.

convert

convert(
    source: ResolvedSource, *, limits: SourceLimits
) -> SourceDocument

DocxAdapter

Parse DOCX XML as inert text after strict archive inspection.

convert

convert(
    source: ResolvedSource, *, limits: SourceLimits
) -> SourceDocument

HtmlAdapter

Normalize visible HTML text and tables without loading active resources.

convert

convert(
    source: ResolvedSource, *, limits: SourceLimits
) -> SourceDocument

MarkdownAdapter

Normalize UTF-8 Markdown while preserving preamble and complete tables.

convert

convert(
    source: ResolvedSource, *, limits: SourceLimits
) -> SourceDocument

TextAdapter

Normalize local UTF-8 text without interpreting its instructions.

convert

convert(
    source: ResolvedSource, *, limits: SourceLimits
) -> SourceDocument

Tabular adapters

tabular

Deterministic CSV and lazy-openpyxl XLSX source adapters.

CsvAdapter

Normalize UTF-8 CSV rows with physical one-based row provenance.

convert

convert(
    source: ResolvedSource, *, limits: SourceLimits
) -> SourceDocument

convert_until

convert_until(
    source: ResolvedSource,
    *,
    limits: SourceLimits,
    deadline: float,
) -> SourceDocument

Normalize CSV content under a caller-owned end-to-end deadline.

XlsxAdapter

XlsxAdapter(
    *, workbook_loader: WorkbookLoader | None = None
)

Normalize XLSX cells without formulas, macros, links, or eager workbook loading.

convert

convert(
    source: ResolvedSource, *, limits: SourceLimits
) -> SourceDocument

convert_until

convert_until(
    source: ResolvedSource,
    *,
    limits: SourceLimits,
    deadline: float,
) -> SourceDocument

Normalize workbook content under a caller-owned end-to-end deadline.

XLSForm adapter

The default registry uses XlsFormAdapter for .xlsx. It keeps ordinary XLSX normalization and adds native SVIS, choices, relevance, group, repeat, and source binding data when the workbook has an XLSForm survey sheet.

xlsform

Bounded core XLSForm adapter with versioned native-routing support.

XLSFORM_SUPPORT_MATRIX module-attribute

XLSFORM_SUPPORT_MATRIX: Mapping[str, str] = (
    MappingProxyType(
        {
            "tested_profile": "XLSForm 1.3 core; ODK-compatible workbook structure",
            "question_types": "acknowledge,audio,barcode,calculate,date,dateTime,decimal,end,file,geopoint,geoshape,geotrace,hidden,image,integer,note,range,select_multiple,select_multiple_from_file,select_one,select_one_from_file,start,text,time,today,username,video",
            "diagnostic_codes": "METADATA_INCOMPLETE,XLSFORM_CHOICE_LIST_MISSING,XLSFORM_FEATURE_UNSUPPORTED,XLSFORM_FUNCTION_UNSUPPORTED,XLSFORM_EXPRESSION_UNSUPPORTED,XLSFORM_REFERENCE_UNRESOLVED,XLSFORM_TYPE_UNSUPPORTED",
            "survey_choices_settings": "preserved",
            "multilingual_labels": "preserved",
            "groups": "logical_containment",
            "repeats": "logical_template",
            "reference_comparisons": "exact",
            "selected": "exact",
            "boolean_and_or_not": "exact",
            "functions_other_than_selected": "opaque",
            "arithmetic": "opaque",
            "constraints": "preserved_not_flow",
            "calculations": "preserved_not_flow",
            "choice_filters": "preserved_not_flow",
            "excel_formulas_macros_external_links": "rejected",
            "external_choices": "confined_bundle_only",
            "limitations": "no formula, macro, external-link, dynamic-instance, rank, or XML-external evaluation; non-projected functions remain exact source text",
        }
    )
)

XLSFORM_SUPPORT_MATRIX_VERSION module-attribute

XLSFORM_SUPPORT_MATRIX_VERSION = '1.0'

XlsFormAdapter

XlsFormAdapter(*, xlsx_adapter: XlsxAdapter | None = None)

Preserve XLSX normalization and add native semantics for XLSForm workbooks.

convert

convert(
    source: ResolvedSource, *, limits: SourceLimits
) -> SourceDocument

Return the unchanged normalized XLSX document contract.

convert_until

convert_until(
    source: ResolvedSource,
    *,
    limits: SourceLimits,
    deadline: float,
) -> SourceDocument

Normalize the workbook under one source-owned deadline.

convert_native

convert_native(
    source: ResolvedSource,
    document: SourceDocument,
    *,
    limits: SourceLimits,
) -> NativeRoutingSemantics | None

Parse an XLSForm when a case-insensitive survey sheet is present.

convert_native_until

convert_native_until(
    source: ResolvedSource,
    document: SourceDocument,
    *,
    limits: SourceLimits,
    deadline: float,
) -> NativeRoutingSemantics | None

Parse native XLSForm semantics under the active source deadline.

convert_svis_until

convert_svis_until(
    source: ResolvedSource,
    document: SourceDocument,
    *,
    limits: SourceLimits,
    deadline: float,
    extraction_date: date,
) -> tuple[SurveySVIS, NativeRoutingSemantics] | None

Build authoritative SVIS and routing semantics from one XLSForm parse.

Chunking

chunking

Deterministic token-aware chunking with table and overlap provenance.

TokenEstimator

Bases: Protocol

Minimal tokenizer-independent estimation port.

estimate

estimate(text: str) -> int

Return a deterministic non-negative token estimate.

ConservativeTokenEstimator

Dependency-free fallback that budgets one token per UTF-8 byte.

estimate

estimate(text: str) -> int

RepeatedRowOrigin

Bases: BaseModel

Physical table and one-based source row for repeated content.

model_config class-attribute instance-attribute

model_config = ConfigDict(extra='forbid', frozen=True)

table_id instance-attribute

table_id: str

row class-attribute instance-attribute

row: int = Field(ge=1)

provenance instance-attribute

provenance: SourceProvenance

RepeatedRow

Bases: BaseModel

Inventory entry for an exact normalized row occurring more than once.

model_config class-attribute instance-attribute

model_config = ConfigDict(extra='forbid', frozen=True)

row instance-attribute

row: tuple[str, ...]

count class-attribute instance-attribute

count: int = Field(ge=2)

origins instance-attribute

origins: tuple[RepeatedRowOrigin, ...]

SourceChunk

Bases: BaseModel

One stable source-order chunk with explicit overlap ownership.

model_config class-attribute instance-attribute

model_config = ConfigDict(extra='forbid', frozen=True)

id instance-attribute

id: str

order class-attribute instance-attribute

order: int = Field(ge=0)

text instance-attribute

text: str

block_ids instance-attribute

block_ids: tuple[str, ...]

new_block_ids instance-attribute

new_block_ids: tuple[str, ...]

overlap_block_ids class-attribute instance-attribute

overlap_block_ids: tuple[str, ...] = ()

provenance instance-attribute

provenance: tuple[SourceProvenance, ...]

parts instance-attribute

parts: tuple[SourceChunkPart, ...]

new_part_ids instance-attribute

new_part_ids: tuple[str, ...]

overlap_part_ids class-attribute instance-attribute

overlap_part_ids: tuple[str, ...] = ()

token_count class-attribute instance-attribute

token_count: int = Field(ge=0)

ChunkedDocument

Bases: BaseModel

Deterministic chunks and completeness inventory for one source.

model_config class-attribute instance-attribute

model_config = ConfigDict(extra='forbid', frozen=True)

source_name instance-attribute

source_name: str

chunks instance-attribute

chunks: tuple[SourceChunk, ...]

repeated_rows instance-attribute

repeated_rows: tuple[RepeatedRow, ...]

chunk_document

chunk_document(
    document: SourceDocument,
    *,
    max_tokens: int,
    overlap_tokens: int = 0,
    estimator: TokenEstimator | None = None,
) -> ChunkedDocument

Chunk without dropping preamble, complete tables, or source order.

Parameters:

Name Type Description Default
document SourceDocument

Normalized source document to chunk.

required
max_tokens int

Hard maximum for every final chunk, including overlap.

required
overlap_tokens int

Target budget for complete prior text blocks copied into the next chunk.

0
estimator TokenEstimator | None

Token estimator. The conservative dependency-free estimator is used when omitted.

None

Returns:

Type Description
ChunkedDocument

Stable chunks plus an inventory of exact repeated table rows.

Raises:

Type Description
ValueError

max_tokens is below one, or overlap_tokens is negative or not smaller than max_tokens.

OCR cache validation

ocr

Offline checksum validation for approved local EasyOCR artifacts.

OcrArtifact dataclass

OcrArtifact(filename: str, size: int, sha256: str)

One approved local OCR archive and its immutable digest metadata.

filename instance-attribute

filename: str

size instance-attribute

size: int

sha256 instance-attribute

sha256: str

OcrArtifactValidation dataclass

OcrArtifactValidation(
    artifact: OcrArtifact,
    status: OcrValidationStatus,
    checksum_checked: bool,
)

Accurate local validation state for one approved OCR archive.

artifact instance-attribute

artifact: OcrArtifact

status instance-attribute

status: OcrValidationStatus

checksum_checked instance-attribute

checksum_checked: bool

validate_ocr_cache

validate_ocr_cache(
    cache: Path,
    *,
    manifest: Sequence[
        OcrArtifact
    ] = APPROVED_OCR_ARTIFACTS,
) -> tuple[OcrArtifactValidation, ...]

Validate local files without fetching or redistributing artifacts.

Parameters:

Name Type Description Default
cache Path

Local directory that contains OCR archives.

required
manifest Sequence[OcrArtifact]

Expected artifact names, byte sizes, and SHA-256 digests.

APPROVED_OCR_ARTIFACTS

Returns:

Type Description
tuple[OcrArtifactValidation, ...]

One validation result per manifest item in manifest order.

Note

Validation is caller-enforced. PDF conversion does not call this function automatically.

main

main(
    *,
    environ: Mapping[str, str] | None = None,
    stdout: TextIO | None = None,
    stderr: TextIO | None = None,
) -> int

Validate the configured local OCR cache and return a deterministic exit code.

Parameters:

Name Type Description Default
environ Mapping[str, str] | None

Environment mapping used to locate the cache.

None
stdout TextIO | None

Stream for per-artifact status and successful summary output.

None
stderr TextIO | None

Stream for configuration and validation failures.

None

Returns:

Type Description
int

0 when every artifact is valid, 1 after validation failures, or

int

2 when no existing cache directory is configured.