Skip to content

Results and Artifacts

ExtractionResult[T] is a frozen envelope for usable output, diagnostics, failed source blocks, status, and local artifact references. The generic output type can be SurveySVIS or another application model.

Create a result

from survey_scribe.results import (
    Diagnostic,
    DiagnosticCode,
    DiagnosticSeverity,
    ExtractionResult,
    FailedBlock,
)

result = ExtractionResult(
    output=survey,
    diagnostics=(
        Diagnostic(
            code=DiagnosticCode.quality_low_confidence,
            message="One variable needs human review.",
            severity=DiagnosticSeverity.warning,
            details={"raw_name": "q_age"},
        ),
    ),
    failed_blocks=(
        FailedBlock(
            block_id="block-000042",
            message="The answer table was incomplete.",
            source_order=41,
        ),
    ),
)

When survey_id is omitted and output.survey_id is a string, the envelope copies it automatically.

Result fields

Field Type Default
output T \| None Required
survey_id str \| None None
run_id str Random UUID4 hexadecimal value
diagnostics tuple[Diagnostic, ...] ()
failed_blocks tuple[FailedBlock, ...] ()
artifacts tuple[ArtifactReference, ...] ()
status ResultStatus Computed

The envelope is frozen. Caller-owned output can remain mutable.

Status rules

Status Condition
failed output is None
partial A failed block, error diagnostic, or operational failure code exists
success Usable output exists without an operational failure

A low-confidence quality warning alone remains success. It reports a review condition, not a failed extraction operation.

Detached snapshots

snapshot = result.serialization_snapshot()

The method returns a deep-copied, JSON-compatible dict[str, Any]. Later changes to mutable caller-owned output do not change the returned snapshot.

Write artifacts

from pathlib import Path

written = result.write(
    Path("output"),
    sidecar=True,
    overwrite=False,
)

write() returns a new ExtractionResult[T] with ArtifactReference values. Each reference contains its role, path, generation ID, and SHA-256 digest.

The output layout is:

output/
|-- <survey_id>_svis.json
`-- .survey-scribe/
    |-- aliases/
    `-- surveys/
        `-- <exact_identity_key>/
            |-- active.json
            `-- generations/
                `-- <generation_id>/
                    |-- <survey_id>_svis.json
                    |-- <survey_id>_sidecar.json
                    `-- manifest.json

An exact SurveySVIS uses the legacy-compatible _svis.json projection. Other output types use _result.json by default. Supply a typed serializer to select a different safe filename and artifact plan:

from survey_scribe.serialization import JsonArtifactSerializer

serializer = JsonArtifactSerializer(MyResult, filename_suffix="_analysis.json")
written = result.write(Path("output"), serializer=serializer)

Generation directories preserve immutable historical output, and active.json identifies the current generation.

Routed output

An exact RoutedSurveySVIS uses manifest version 2 and publishes both the routed main and the ordered legacy projection:

output/
|-- <survey_id>_svis.json
`-- .survey-scribe/surveys/<exact_identity_key>/generations/<generation_id>/
    |-- <survey_id>_routed_svis.json
    |-- <survey_id>_svis.json
    |-- <survey_id>_sidecar.json
    `-- manifest.json

The routed main contains routing_schema_version, routing_graph, and the append-only audit. The stable <survey_id>_svis.json file is reconstructed as an exact v1 SurveySVIS; routed-only fields are not added to the legacy model. Manifest v2 records the equal routed and graph schema versions, both output digests, prompt versions, and source/model response digests without raw prompt or response bodies.

The exact audit path is routing_graph.routing_audit. Routing-specific status uses the standard result envelope:

Routing outcome Result status
Usable graph with unresolved review warnings success
Usable graph with an unlinked variable or failed source region partial
No usable graph after source, provider, or invariant failure failed

Collision and overwrite behavior

A second write for the same survey raises ArtifactCollisionError by default. To publish a new generation while preserving the old one:

updated = result.write(Path("output"), overwrite=True)

The writer restricts survey_id to portable, non-reserved filename characters. It rejects case and trailing-dot aliases. A process-owned OS lock covers both recovery and publication and is released by the operating system after a crash.

Internal symlinks and Windows reparse points are rejected. Generation files are written and flushed in a staging directory before one durable rename. A typed journal makes the new generation authoritative before stable projection writes. After an unclean shutdown, the next writer repairs the projection and pointer idempotently while it holds the same process lock. Required file and directory flush errors abort publication instead of being ignored.

Sensitive content

Sidecar diagnostics pass through package redaction. The main SVIS or routed SVIS artifact is the intended data product and can contain questionnaire content, filenames, notes, and bounded source citations. Logs, diagnostics, sidecars, manifests, evaluator reports, and persistent caches must not contain source or model prose. Write artifacts only to an approved, access-controlled location.

Legacy serialization

Use legacy_payload() for JSON-compatible Python values or legacy_json_bytes() for stable UTF-8 bytes:

from survey_scribe.serialization import legacy_json_bytes, legacy_payload

values = legacy_payload(survey)
payload = legacy_json_bytes(survey)

The byte serializer preserves model field order, uses two-space indentation, and rejects non-finite JSON numbers.