cadrumo.application.live.filed_data_capture module

Filed-declaration capture services for live AEAT workflows.

The listing helpers read AEAT declaration-register rows without downloading artefacts. The capture helpers download the selected filed-declaration artefacts through the authenticated Sede adapter, persist encrypted FiledObservationProtocol payloads and artefacts, promote extracted casillas into registry-grounded calculation observations, and attempt to stamp matching current ModeloRecord filings with live ExternalEvidence.

Source capture resolves a law-determined ModeloRevision through a generation-pinned registry operation before asking the Sede adapter which prior declarations a target filing needs, so cross-period inputs remain registry-authored rather than adapter-inferred. The module never creates a remote submission or mutates AEAT state; filing-record stamping is local evidence enrollment against an existing current record.

See also

FiledDataCapturePort

Supplies the authenticated register and source-capture capabilities.

cadrumo.application.live.filed_capture_finalizer.finalize_filed_capture()

Persists the latest captured filed observations as calculation-history evidence, and is the function this module actually calls. Each registry-enrollment refusal becomes a typed FiledDataCaptureFailureRow, raised under FAIL_FAST and reported under BEST_EFFORT.

cadrumo.application.live.filed_observation_persistence.enroll_filed_justificante_evidence()

Persists matching justificante metadata and stamps current filing records when the receipt matches.

filed_data_capture_failure_row(*, modelo, year, error, declaration=None)[source]

Map one failed capture into a FiledDataCaptureFailureRow.

Return type:

FiledDataCaptureFailureRow

Parameters:
submitted_file_extraction_notices(observation)[source]

Project a recorded submitted-file layout refusal onto the notice channel.

The Sede adapter is the authority for both parsing and the persisted error text. It records a refusal under submitted_file_extraction_error and preserves its declaration-PDF fallback without trying to reinterpret either one here. This projection only makes that already-recorded fact visible to the operator, retaining the parser’s own reason and the filed record that needs follow-up. A clean capture has no metadata key and therefore no advisory.

Return type:

tuple[Notice, ...]

Parameters:

observation (FiledObservationProtocol)

class FiledCaptureAccumulator(operation=None, observation_paths=<factory>, artefact_refs=<factory>, justificante_csvs=<factory>, filing_record_ids=<factory>, conflicting_filing_record_ids=<factory>, reconciliation_results=<factory>, observations_for_calculation=<factory>, evidence_notices=<factory>, recapture_notices=<factory>, justificante_csvs_by_observation=<factory>, casilla_count=0, absorbed_count=0)[source]

Bases: object

Mutable accumulator for one filed-declaration capture run.

Holds the persisted-artefact ledgers shared by the single-shot, bulk, and source capture paths. absorb() folds one captured observation into the run (persist manifest, collect artefact refs, enrol justificante evidence, reconcile the filing chain); capture_report_fields() projects the deduped report fields every capture report shares. The dict.fromkeys dedup ordering is preserved verbatim so report values stay byte-identical.

Parameters:
operation: PinnedAuthorityOperation | None
observation_paths: list[str]
artefact_refs: list[str]
justificante_csvs: list[str]
filing_record_ids: list[str]
conflicting_filing_record_ids: list[str]
reconciliation_results: list[FilingReconciliationResult]
observations_for_calculation: list[FiledObservationProtocol]
evidence_notices: list[Notice]
recapture_notices: list[Notice]

Recapture-divergence advisories, one per re-captured filing whose casilla values this sweep changed. Read before each upsert, never after.

justificante_csvs_by_observation: dict[tuple[str, int, str, str], tuple[str, ...]]
casilla_count: int
absorbed_count: int

Observations folded in, counted in every mode. observation_paths cannot serve as the tally because a preview persists nothing and would leave it empty, which would silently uncap a limited sweep.

property reached_count: int

Units this run REACHED, answering SyncRunCoverageSource.

The surface-neutral name the sync-run store asks for, mapped onto this sweep’s own tally. Both coverage counts are read off this one object so they cannot be drawn from different populations.

property divergences: Sequence[Notice]

Divergences found among the reached units, one entry each.

Bounded by reached_count by construction: absorb() appends at most one advisory per observation and increments the tally on the same pass, so the sync-run coverage bound cannot be violated from here.

absorb(observation, *, ports, bucket_id, output_root, dry_run=False)[source]

Persist one captured observation and fold its artefacts into the run.

With dry_run the divergence read still happens and every write is skipped: no observation is persisted, no justificante evidence is enrolled, and nothing is queued for the calculation-observation write downstream. The preview therefore runs the real funnel minus its writes, rather than a parallel implementation that could drift from it.

Return type:

None

Parameters:
capture_report_fields()[source]

Return the deduped report fields shared by every filed-capture report.

Return type:

_CaptureReportFields

async list_filed_data(*, filed_data_port, modelo, year_from, year_to)[source]

List declarations via AEAT and return a FiledDataListingReport.

Return type:

FiledDataListingReport

Parameters:
async list_filed_data_bulk(*, filed_data_port, year_from, year_to, modelos=None, operation=None)[source]

List filed declarations across modelos with one authenticated register session.

Parameters:
  • year_from (int) – First filing year to query.

  • year_to (int) – Last filing year to query.

  • modelos (tuple[str, ...] | None) – Modelo codes to walk; every registry modelo when omitted.

  • operation (PinnedAuthorityOperation | None) – Caller-held generation-pinned authority operation; opened from the indexed bundled authority when omitted.

  • filed_data_port (FiledDataCapturePort) – Composed register acquisition capability. Its outer implementation owns session and browser lifecycles.

Return type:

BulkFiledDataListingReport

Returns:

A BulkFiledDataListingReport of the per-modelo rows and failures.

async capture_filed_data(*, filed_data_port, modelo, year, output_root, ports, period=None, expediente_id=None, limit=None, operation=None)[source]

Capture filed-declaration artefacts and return a FiledDataCaptureReport.

The report accounts for persisted observation manifests, encrypted artefact references, saved justificante CSVs, stamped ModeloRecord ids, conflicts, and calculation observation keys produced from the captured AEAT rows.

Return type:

FiledDataCaptureReport

Parameters:
async capture_filed_data_bulk(*, filed_data_port, year_from, year_to, output_root, ports, modelos=None, limit=None, dry_run=False, sync_run_repository=None, events=None, operation=None)[source]

Capture filed declarations across a year range and return a BulkFiledDataCaptureReport.

Unsupported modelo/year pairs are recorded as failures before live contact. Supported pairs share one authenticated register session and then follow the same persistence, justificante enrolment, and calculation-observation path as capture_filed_data().

Parameters:
  • year_from (int) – First filing year to query.

  • year_to (int) – Last filing year to query.

  • output_root (Path) – Root the captured observations and artefacts persist under.

  • ports (FiledObservationPersistencePorts) – Composed filed-observation persistence and transformation ports.

  • modelos (tuple[str, ...] | None) – Modelo codes to walk; every registry modelo when omitted.

  • limit (int | None) – Cap on captured observations; unbounded when omitted.

  • filed_data_port (FiledDataCapturePort) – Composed register acquisition capability. Its outer implementation owns session and browser lifecycles.

  • dry_run (bool) – Preview the sweep. AEAT is still read and the divergence set the upsert would introduce is still computed, but nothing is written: no observation persisted, no justificante evidence enrolled, no calculation observation finalized. The report carries dry_run=True and the divergences as its primary result.

  • sync_run_repository (SyncRunRecordRepositoryProtocol | None) – Persistence port for the completed-run provenance record. Required for a non-preview capture that reaches a supported query pair; the outer entrypoint composes the concrete adapter.

  • operation (PinnedAuthorityOperation | None) – Caller-held generation-pinned authority operation; opened from the indexed bundled authority when omitted.

  • events (OperationEventEmitter | None) – Optional operation event emitter. The composed filed-history pull supplies it to publish phase, safe unit-count, and refusal-scope facts at the canonical workflow boundaries.

Return type:

BulkFiledDataCaptureReport

async capture_source_filed_data(*, filed_data_port, modelo, year, period, output_root, ports, operation=None)[source]

Capture source observations and return a SourceFiledDataCaptureReport.

The source relationships are selected from the immutable bundled authority. Caller-controlled registry and source roots are deliberately not accepted: live evidence capture must use the same validated legal snapshot as filing.

Return type:

SourceFiledDataCaptureReport

Parameters:
async discover_filed_history(*, filed_data_port, profile=None, today=None)[source]

Discover what history to walk, unioning both signals into one grid.

Reads the register’s offered option lists through the SAME verified-session bring-up every filed-capture path uses, so a missing or unverified auth session refuses here exactly as it does on the capture path rather than with a discovery-specific error nobody has seen before.

Nothing is persisted and no pair is queried: this is a read of the register’s own controls plus a pure derivation over already-persisted profile data.

profile is optional so the register-options read can be exercised on its own, but omitting it means the report carries NO taxpayer-specific denominator — see FiledHistoryDiscoveryReport.carries_a_taxpayer_specific_denominator, which is the flag a caller must check before making any coverage claim.

Parameters:
  • filed_data_port (FiledDataCapturePort) – Filed-data capability used to discover register availability.

  • profile (TaxpayerProfile | None) – The taxpayer’s declared TaxpayerProfile, supplying the load-bearing PROFILE_APPLICABILITY signal. None yields a register-options-only report.

  • today (date | None) – Reference date for applicability and the year span’s upper bound. Defaults to the Madrid civil date the rest of the CLI resolves filing dates against.

Return type:

FiledHistoryDiscoveryReport

Returns:

The union FiledHistoryDiscoveryReport.

Raises:

SedeNavigationError – When the session carries no persisted browser state, propagated unchanged from the shared register bring-up.

class ExpectedFiledDeclarationGrid(**data)[source]

Bases: BaseModel

The (modelo, ejercicio) pairs the taxpayer’s OWN declared facts expect.

Tagged PROFILE_APPLICABILITY. This is the load-bearing denominator: every value in it comes from data the taxpayer declared during setup, walked through the same applicability machinery the overview calendar already reconciles obligations with, so it is taxpayer-specific by construction and needs no authenticated session.

Variables:
  • modelos – Registry modelos this profile’s declared facts do not rule out, in sorted order. A modelo the applicability engine positively answers “no” for is absent, and so is one the registry does not model at all — the latter because no declared fact feeds a verdict for it, so nominating it would manufacture an expectation the profile never made.

  • ejercicios – The filing years spanned by the declared activity dates, newest first.

  • activity_start_declared – Whether the profile declared an activity_start_date. When False, ejercicios is EMPTY and this grid makes no claim: it is “cannot say”, never “nothing expected”. A consumer must surface that distinction rather than reporting a clean zero, because a silently empty profile signal leaves only the signal whose informativeness is unconfirmed.

  • activity_end_declared – Whether the profile declared an activity_end_date, which caps the span. A taxpayer who ceased activity is not expected to have filed afterwards, and flagging those years as expected-but-not-found would be a false anomaly.

Parameters:

data (Any)

modelos: tuple[str, ...]
ejercicios: tuple[int, ...]
activity_start_declared: bool
activity_end_declared: bool
property pairs: tuple[tuple[str, int], ...]

Return every expected (modelo, ejercicio) pair, newest year first.

class FiledHistoryDiscoveryPair(**data)[source]

Bases: BaseModel

One (modelo, ejercicio) pair to walk, carrying which signal nominated it.

The signal set is what makes a zero-row outcome readable, so it travels with the pair rather than being discarded once the union is built. The two predicates below exist so no consumer re-derives the asymmetry: a caller asks the pair whether a zero-row result is an anomaly instead of re-checking tags and possibly getting the rule wrong in one of several places.

Variables:
  • modelo – Modelo code.

  • ejercicio – Filing year.

  • signals – Every signal that nominated this pair, in the canonical enum declaration order, deduplicated. Never empty — a pair nominated by nothing is not walked.

Parameters:

data (Any)

modelo: str
ejercicio: FilingYear
signals: tuple[FiledHistoryDiscoverySignal, ...]
property expected_by_profile: bool

Whether the taxpayer’s own declared facts expected a filing for this pair.

property zero_rows_is_an_anomaly: bool

Whether finding no declaración for this pair is worth an advisory.

True only when the profile signal nominated the pair: the taxpayer’s own declared facts expected a filing that was not found. A pair nominated ONLY by the register’s option list is a plain negative however empty it comes back, because whether that list is scoped to this NIF at all is unconfirmed — treating its emptiness as a finding would raise an alert from a signal that may carry no information about this taxpayer.

class FiledHistoryDiscoveryReport(**data)[source]

Bases: BaseModel

The union walk grid, with every pair tagged by the signal(s) behind it.

The union is deliberately additive: the register’s offered option set can only ever WIDEN the grid the profile expects, never narrow it and never substitute for it. That is why a pair present in only one signal is still walked, while only the profile-nominated ones can produce an anomaly.

Variables:
  • pairs – Every pair to walk, sorted by modelo then descending ejercicio so recent filings are reached first.

  • profile_year_span_determined – Whether the profile declared the activity start date the year axis needs. When False the profile signal contributed nothing and the report says so, rather than presenting a register-options-only grid as though both signals had agreed.

  • register_options_read – Whether the register’s option lists were read at all. False on the profile-only path (no live session), which is not a failure — the design ships fully functional without it.

Parameters:

data (Any)

pairs: tuple[FiledHistoryDiscoveryPair, ...]
profile_year_span_determined: bool
register_options_read: bool
scoping_signal: RegisterScopingSignal
property walk_pairs: tuple[tuple[str, int], ...]

Return the plain (modelo, ejercicio) pairs to query, in walk order.

property profile_expected_pairs: tuple[FiledHistoryDiscoveryPair, ...]

Return only the pairs the taxpayer’s declared facts expected a filing for.

property register_options_only_pairs: tuple[FiledHistoryDiscoveryPair, ...]

Return the pairs nominated ONLY by the unconfirmed register option list.

property carries_a_taxpayer_specific_denominator: bool

Whether any coverage claim this report supports rests on taxpayer facts.

False means every walked pair came from the register’s offered option list, whose scoping is unconfirmed, so the report supports NO completeness claim at all — only a record of what was queried.

expected_filed_declaration_grid(profile, *, today)[source]

Derive the taxpayer-specific candidate grid from the profile’s declared facts.

The modelo axis reuses build_obligation_coverage(), which already partitions the whole AEAT obligation universe against a TaxpayerProfile into surfaced / confidently excluded / advised / out-of-scope. Nothing is re-derived here: a modelo is a candidate when that partition does NOT place it in a confident negative or out of scope.

Two exclusions are deliberate. A modelo the registry does not model at all is dropped, because no declared fact produced its verdict — nominating it would invent an expectation the taxpayer never made and then report the inevitable zero rows as an anomaly. And the year axis is capped by a declared activity_end_date, because a taxpayer who ceased activity is not expected to have filed afterwards.

surfaced_modelos is passed empty on purpose. The partition is total, so with nothing surfaced every non-negative verdict lands in advised; the union taken below covers both tuples anyway, so the result does not depend on which side a candidate falls out of.

Parameters:
  • profile (TaxpayerProfile) – The taxpayer’s declared three-axis TaxpayerProfile.

  • today (date) – Reference date for applicability evaluation and the year span’s upper bound.

Return type:

ExpectedFiledDeclarationGrid

Returns:

The ExpectedFiledDeclarationGrid. When the profile declared no activity start date the grid carries no ejercicios and says so through activity_start_declared.

casillas_a_recapture_would_change(fresh, stored, *, tolerance=Decimal('0'))[source]

Return every casilla whose freshly captured value disagrees with the stored one.

Derived from the observed casilla set rather than from a hand-listed field list, for the same reason the invoice reconfirm diff is: the failure this exists to catch is a comparison that OMITS a casilla, and a hand-listed set is precisely how that omission arrives. A newly-extracted casilla is compared the moment it is captured.

Only casillas present on BOTH sides are compared. A casilla the fresh capture read and the stored revision never held is not a changed value – it is a wider extraction – and reporting it as a divergence would fire the advisory on every extraction improvement.

NOT substitutable with the tree’s other per-casilla comparators, and the intersection rule above is why. detect_casilla_divergences REPORTS absence, as missing-on-one-side rows, and the revision-vs-revision delta in application/modelo/projection.py treats an absent casilla as zero. Both of those contracts would fire this advisory on an extraction improvement, which is the one thing it must never do. The absence contract is the discriminator, not the tolerance.

Parameters:
Return type:

tuple[TypeAliasType, ...]

Returns:

The changed casilla ids, sorted, so the notice text is deterministic.

classify_register_scoping_signal(profile, availability, *, today)[source]

Say what the offered modelo set SUGGESTS about its own scoping, for free.

The question this addresses – is the declaraciones register’s option list scoped to the authenticated NIF, or a static universal catalogue – cannot be settled without an authorised live probe against an account with real filing history. Nobody has authorised one, and the design never depended on it resolving. What it does have is a cheap, offline, taxpayer-specific discriminator nobody was reading: if the register offers a modelo the taxpayer’s own declared facts positively EXCLUDE, the list is offering something this taxpayer cannot have filed, which is what a universal catalogue looks like.

The result is advisory only and changes nothing about what is walked. The offered set is unioned in additively either way, so a reading here can neither widen nor narrow the grid, and it MUST NOT be rendered as a settled answer – see RegisterScopingSignal, whose members are all hedges precisely so that it cannot be.

The evidence is asymmetric, and so is the confidence. LIKELY_UNIVERSAL is a positive observation: an excluded modelo was offered. Its counterpart is only ever the ABSENCE of that observation, which a universal catalogue also produces for a taxpayer whose profile excludes nothing the register lists – so it stays LIKELY_NIF_SCOPED, never confirmation.

Parameters:
Return type:

RegisterScopingSignal

Returns:

The RegisterScopingSignal reading. INCONCLUSIVE when either side of the comparison is empty, because then the comparison discriminates nothing.

filed_history_discovery_report(*, expected, availability=None, scoping_signal=RegisterScopingSignal.INCONCLUSIVE)[source]

Union the two discovery signals into one provenance-tagged walk grid.

A pair nominated by both signals carries both tags; a pair nominated by one carries only that one. The union never drops a pair either signal offered, which is what makes the register’s contribution purely coverage-widening: it cannot remove anything the profile expected, and it cannot lend its own pairs the profile signal’s standing.

Parameters:
Return type:

FiledHistoryDiscoveryReport

Returns:

The FiledHistoryDiscoveryReport walk grid.

class FiledHistoryPairOutcome(**data)[source]

Bases: BaseModel

What one walked pair produced, keeping a refusal distinct from a zero.

refused is not derivable from row_count. The register walker refuses a page whose grid declares more records than it rendered, and that refusal is absorbed into a failure row upstream — so a refused pair also reports zero rows. Reading the zero as “nothing filed” is precisely the silent under-report this feature exists to remove, which is why the refusal is its own field and why the notices below branch on it.

Parameters:

data (Any)

modelo: str
ejercicio: FilingYear
signals: tuple[FiledHistoryDiscoverySignal, ...]
row_count: int
captured_count: int
refused: bool
failure_type: str | None
failure_message: str | None
property expected_by_profile: bool

Whether the taxpayer’s own declared facts expected a filing here.

property is_a_genuine_empty: bool

Whether this pair answered, and the answer was no filings.

False for a refused pair however few rows it reported: a refusal is not an answer, so it is not an empty one either.

class FiledHistoryOnboardingRun(**data)[source]

Bases: BaseModel

One history-onboarding sweep: what was walked, captured and reconciled.

Carries no completeness ratio, deliberately. Part of the walked grid comes from AEAT’s offered option list, whose scoping to this NIF is unconfirmed, so any fraction over the grid would look like coverage while resting on a denominator that may have nothing to do with this taxpayer. denominator_note states in prose what the denominator was — the honest form of the same information.

Parameters:

data (Any)

pairs: tuple[FiledHistoryPairOutcome, ...]
dry_run: bool
captured_count: int
reached_count: int

Units this sweep REACHED, from the accumulator tally counted in every mode. Carried separately from captured_count because that one is len(observation_paths), which a preview leaves empty – so it cannot answer “was this sweep truncated” on the very path where the question matters most.

scoping_signal: RegisterScopingSignal
carries_a_taxpayer_specific_denominator: bool
iva_wallet_status: str
iva_wallet_divergence: str | None
iva_wallet_blocked: bool
notificaciones_status: str
notificaciones_row_count: int
stage_failures: tuple[str, ...]
sync_run_ref: SyncRunRecordReference | None
evidence_notices: tuple[Notice, ...]
recapture_notices: tuple[Notice, ...]

Per-artefact evidence advisories raised during capture, each keeping its own reason.

property refused_pairs: tuple[FiledHistoryPairOutcome, ...]

Return pairs that produced a failure row instead of an answer.

property genuinely_empty_pairs: tuple[FiledHistoryPairOutcome, ...]

Return pairs that answered with no filings.

property denominator_note: str

State what the coverage denominator was, and what it does not establish.

expected_but_not_found_notice(run)[source]

Warn for every pair the profile expected that produced no declaración.

Fires ONLY for pairs carrying PROFILE_APPLICABILITY. A pair nominated only by the register’s option list is never named here however empty it came back, because that list’s informativeness for this taxpayer is unconfirmed — an alert raised from it could be pure noise, and an advisory only earns trust if every firing is a real finding.

A REFUSED pair is also never named. It did not answer, so “the profile expected a filing that was not found” is not what happened; the refusal travels as its own failure row and its own reporting.

Returns None when nothing qualifies, so a clean run stays quiet.

Return type:

Notice | None

Parameters:

run (FiledHistoryOnboardingRun)

recapture_divergence_notices(captured, *, repository, operation=None)[source]

Warn for every re-captured filing whose casilla values changed.

A re-capture is an unconditional upsert, so without this a corrected filing silently overwrites the previously observed values and the operator never learns their history changed. Refusing the write outright would be wrong — AEAT legitimately permits a complementaria — so this mirrors the shipped censo-divergence shape: a standing advisory, never a silent auto-resolve.

Read BEFORE the capture is persisted; afterwards the prior values are gone.

Return type:

tuple[Notice, ...]

Parameters:
class FiledHistoryDiscoveryPort(*args, **kwargs)[source]

Bases: Protocol

The discovery step pull_filed_history() sequences, as a port.

Exists so the COMPOSITION can be exercised. discover_filed_history() brings up a verified authenticated session, and it is the composition’s first stage, so reaching for it directly made the sequencing, the failure propagation and the notice plumbing unreachable without a certificate. None of those need AEAT — only the discovery step does — and a test whose safety depends on the machine having no certificate configured is not a test: on a box that HAS one it stops refusing and makes a real authenticated call.

Narrow on purpose. One boundary, shaped exactly like the function it defaults to, rather than a general indirection layer over every stage.

async pull_filed_history(*, certificate_secret_backend_factory, browser_session_factory, operator_scope_ports, filed_data_port, iva_remote_state_port, notifications_ports, ports, output_root, profile=None, today=None, limit=None, dry_run=False, discover=<function discover_filed_history>, sync_run_repository=None, events=None)[source]

Sequence discovery, bulk filed capture, IVA wallet and notificaciones.

Composes existing primitives and adds no capture mechanism of its own. In particular it does NOT wrap the register walk in its own error handling: the bulk sweep already absorbs any walk failure — including the truncated-page refusal — into a typed failure row and continues to the next pair. Wrapping it again would duplicate that authority and could swallow the very failure row the taxonomy exists to produce.

Each later stage is guarded separately so a partial run reports which stage failed rather than collapsing into one error. That matters because these stages are independent: a notificaciones timeout says nothing about whether the filed capture succeeded, and losing the capture report to an unrelated failure would waste a long authenticated sweep.

Parameters:
  • certificate_secret_backend_factory (CertificateSecretBackendFactory) – Creates the secure certificate-secret backend.

  • browser_session_factory (BrowserSessionFactoryPort) – Creates the authenticated browser session.

  • operator_scope_ports (OperatorScopePorts) – Operator-scope capabilities used by authenticated stages.

  • iva_remote_state_port (IvaRemoteStatePort) – Composed IVA wallet acquisition and persistence port.

  • notifications_ports (NotificationsPorts) – Composed notifications query and snapshot persistence bundle.

  • output_root (Path) – Root the capture writes its encrypted stores under.

  • ports (FiledObservationPersistencePorts) – Composed filed-observation persistence and transformation ports.

  • profile (TaxpayerProfile | None) – The taxpayer’s declared TaxpayerProfile, supplying the load-bearing discovery signal. None yields a run with no taxpayer-specific denominator, reported as such.

  • today (date | None) – Reference date for applicability and the year span.

  • limit (int | None) – Optional cap on captured declaraciones, forwarded unchanged.

  • dry_run (bool) – Read the discovered filed-declaration scope without persisting observations, evidence, calculation observations, or a sync-run provenance record. IVA-wallet and notification captures are omitted because they are separate persisted remote-state stages.

  • discover (FiledHistoryDiscoveryPort) – The discovery step to sequence, defaulting to discover_filed_history(). Injected so the composition itself is reachable without an authenticated session; production never passes it.

  • filed_data_port (FiledDataCapturePort) – Composed filed-data acquisition capability. The same bundle is used for discovery, register walks, and source capture.

  • sync_run_repository (SyncRunRecordRepositoryProtocol | None) – Completed-run persistence port forwarded to the bulk capture after discovery finds a supported pair.

  • events (OperationEventEmitter | None) – Optional operation event emitter that receives only stable stage identifiers, safe unit counters, and stable refusal scopes.

Return type:

FiledHistoryOnboardingRun

Returns:

The composed FiledHistoryOnboardingRun.

capture_report_path(path, *, output_root)[source]

Return a stable report path relative to the configured output root when possible.

Return type:

str

Parameters: