Compare financial explanations and plan evidence

The finance API compares economic explanations across explicit accounting mappings. It answers two questions:

  1. Which explanations fit the supplied disclosures under each mapping?

  2. Which hypothetical evidence outcomes would exclude a selected explanation across the retained mappings?

Available in updatesupport-finance>=0.1.6 with updatesupport>=0.1.7.

This helps an analyst decide what to investigate next. A disappearing customer liability might represent a transfer, refund or credit. The same balance-sheet and indirect cash-flow totals can support several explanations. Testing them together shows which additional information would change that conclusion.

Keep the shared model and disputed mappings separate

Start with a core NamedLinearFeasibilityProblem: source facts, variable domains and relationships common to all alternatives. Keep incomplete bridges open with signed residuals. A base tier must not contain a disputed mapping that a later alternative is intended to replace. The comparison is additive; it never removes or substitutes source facts or shared relationships.

Supply DisclosureAlternative objects in two catalogs:

  • Mappings declare how captions or measurement scopes relate. For example, whether an indirect cash-flow line covers deferred revenue alone or also includes a separate customer-liability caption.

  • Explanations restrict economic movements. For example, a refund-only explanation can exclude transfers and residual movements. Those exclusions are assumptions, not reported zeros.

The API labels each alternative’s constraints as analyst policies and rejects reported or derived fact constraints in alternatives. Put source measurements in the base problem and pass their DisclosureFact evidence separately. Existing measurement normalization, precision and evidence-link checks still apply.

import updatesupport_finance as f

comparison = f.compare_disclosure_explanations(
    problem,
    base_tier="reported",
    mappings=mappings,
    explanations=explanations,
    evidence=facts,
)
cell = comparison.case("joint", "refund_only")
print(cell.status, cell.intervals, cell.witness, cell.conflict)
print(comparison.to_markdown())

Each cell includes financial target bounds and a checked feasible assignment, or an irreducible conflict when infeasible. Numerical failures remain undetermined. An auxiliary fixed-zero probe provides a feasibility witness even if all financial targets are unbounded. It has no financial meaning. Use targets=() for compatibility alone, or pass existing target names to limit the bounds calculated. include_conflicts=False skips conflict diagnosis.

The summary reports possible_in_all_feasible_mappings, mapping_dependent, excluded_in_all_feasible_mappings or undetermined. These describe compatibility across source-compatible mappings. They do not report probability or identify the company’s actual explanation.

Search a finite catalog of hypothetical evidence outcomes

DisclosureEvidenceRequest declares an outcome that might be learned, rather than a source that has already supplied it. Its constraints remain hypothetical policies. mapping_names represents a scope-review outcome selecting retained mappings. Such selection does not label discarded mappings infeasible. requires names companion outcomes needed for a valid bundle.

plan = f.plan_disclosure_evidence(
    comparison,
    requests,
    eliminate=["refund_only"],
    max_bundles=64,
)
for bundle in plan.minimal_bundles:
    print(bundle.requests, bundle.retained_mappings)

In the synthetic example, an approximately 150 million customer-caption exit, 220 million deferred-revenue increase and 80 million cash-flow adjustment fit both transfer-only and refund-only explanations under two mappings. A hypothetical cap of 20 million on other net deferred-revenue additions excludes the refund-only explanation under the dr_only mapping. It does not exclude it under the joint mapping. The planner therefore finds two inclusion-minimal sufficient bundles:

  • A gross-refund cap of 20 million by itself.

  • An other-deferred-revenue cap of 20 million together with a review confirming the dr_only mapping.

Neither part of the second bundle suffices alone. This is a conditional research plan: the actual future outcomes may differ from the supplied hypothetical caps.

Every sufficient bundle must leave at least one feasible catalog explanation in every source-compatible retained mapping. Contradictory outcomes, empty mapping selections, missing dependencies, uncovered mappings and unresolved solver results cannot certify separation. The empty bundle is considered too, so an already excluded explanation needs no additional evidence.

The search enumerates 2 ** len(requests) bundles, with a default limit of 64. Minimal means no sufficient strict subset exists in the supplied catalog. It does not mean cheapest, likeliest or highest expected information gain. A sole surviving catalog entry is conditional on the catalog and does not establish an actual cause. Include other or mixed explanations where relevant.

Evaluate different possible answers directly

evaluate_disclosure_evidence evaluates selected hypothetical outcomes without an elimination goal. Use it when drafting an analyst brief: a low amount might exclude one account, a high amount another, and an intermediate amount both. Keep mixed or unclassified explanations open where appropriate.

outcome = f.evaluate_disclosure_evidence(
    comparison,
    requests,
    selected=["refund_cap"],
    targets=None,  # Financial bounds as well as compatibility.
)
print(outcome.status)
print(outcome.comparison.summary())

selected=None selects the entire supplied catalog; selected=() adds no evidence. Names must belong to the catalog. Dependencies are checked against the selected set. The default targets=() solves compatibility only; targets=None bounds all financial targets. include_conflicts=True adds conflict diagnosis. The result supports JSON and Markdown exports with its solved comparison.

compatible means every source-compatible retained mapping has a surviving catalog explanation. uncovered_mapping means a mapping fits the observations but none of the supplied accounts does: revisit the catalog. Contradictory answers remain inconsistent_with_all_mappings; missing dependencies, empty mapping selections and unresolved solver results retain distinct statuses.

Write a competing-accounts brief

Lead with the economic question, why the distinction matters and two accounts that fit the same reporting. Define material amounts or shares explicitly: exact zero exclusions can make trivial movements defeat a useful narrative. Keep other movements open and check alternative screening definitions.

For each evidence question, declare:

  • The measurement, period, caption/cohort and gross/net basis required.

  • Current source coverage and a candidate source for the missing answer.

  • Hypothetical answers in different directions and their tested consequences.

  • Whether an answer excludes an account, requires another movement, or calls for a different framing.

Financial stakes and story labels are analyst-authored. Compatibility and necessary amounts are solver-derived. A sole survivor does not establish an actual cause. Missing or unmatched scope is not a numerical answer. Examples of future outcomes need not cover every possible answer or predict its likelihood.

Export and replay

Comparisons and plans support to_json(), to_markdown(), to_tables() and to_dataframes(). JSON retains source evidence, conditions, full solver reports and witnesses for both the basis comparison and evaluated bundles. bundle.comparison gives access to an outcome’s solved matrix. Planning uses compatibility-only solves; re-run the comparison on that outcome’s source_problem, base_tier, mappings and explanations for financial bounds.

Use the existing snapshot API to save the complete comparison matrix with a selected financial target and tier. Carry source normalization and scope records into context together with comparison.snapshot_context():

snapshot = f.capture_disclosure_snapshot(
    comparison.report.problem,
    facts=facts,
    as_of=review_cutoff,
    target="refund",
    tier=cell.tier,
    context={**source_context, **comparison.snapshot_context()},
)
replayed = f.DisclosureSnapshot.from_json(snapshot.to_json()).replay()

Source retrieval, accounting interpretation and candidate evidence selection remain explicit analyst inputs. This workflow does not infer customer identities, cash-flow scope or undisclosed movements from caption names.