Skip to content

design: group evaluation provenance in result objects #251

Description

@lipikaramaswamy

Context

AnonymizerResult and PreviewResult currently carry both output data and individual pieces of run configuration needed by a later Anonymizer.evaluate(result) call:

PR #243 highlights the scaling concern: every new detection or evaluation-scoping option must be copied onto both result classes and threaded through their public constructors. These values form a coherent set of evaluation provenance, but they currently appear as unrelated top-level result fields.

This is not a blocker for #243. That PR can preserve compatibility by appending its new optional field after existing fields. This issue captures a separately planned public-API design.

Goals

  • Keep results self-contained so they can be evaluated after the original AnonymizerConfig is no longer available.
  • Group related evaluation provenance instead of continually expanding both result constructors.
  • Preserve or deliberately migrate the public contracts of AnonymizerResult and PreviewResult.
  • Make persisted/hand-constructed results and compatibility behavior explicit.

Option A: Keep flat fields

Continue adding optional metadata fields directly to both result classes.

Advantages

  • Smallest implementation change.
  • Existing attribute access remains direct.
  • No new public type.

Tradeoffs

  • Duplicates fields across both result types.
  • Public constructor signatures keep growing.
  • Evaluation requirements remain coupled to result layout.
  • Related configuration can drift or be only partially preserved.

Option B: Add an EvaluationContext

Introduce a focused context containing only information needed by evaluate():

@dataclass(frozen=True, kw_only=True)
class EvaluationContext:
    detect: Detect
    replace_method: ReplaceMethod | None
    rewrite: Rewrite | None
    data_summary: str | None

Results would carry one context:

@dataclass
class AnonymizerResult:
    dataframe: pd.DataFrame
    trace_dataframe: pd.DataFrame
    resolved_text_column: str
    failed_records: list[FailedRecord]
    evaluation_context: EvaluationContext | None = field(default=None, kw_only=True)

Advantages

  • Groups exactly the metadata consumed by evaluation.
  • Preserves the complete detection/rewrite settings rather than selected fields.
  • Limits future growth of result constructors.
  • Narrow name and responsibility.

Tradeoffs

  • Introduces a new public type and access path.
  • Requires a compatibility story for existing attributes and hand-constructed results.
  • Must snapshot mutable Pydantic models rather than retain mutable references.

Option C: Add a broader RunContext

Preserve broader provenance, potentially including:

  • mode (replace or rewrite)
  • detection configuration
  • replacement or rewrite configuration
  • input semantic context such as data_summary and text-column identity
  • result/context schema version
  • other reproducibility metadata

Advantages

  • Makes results more self-describing and supports future reproducibility/persistence work.
  • Can serve consumers beyond evaluate().

Tradeoffs

  • Larger API and design surface.
  • Risks collecting configuration that is unnecessary, mutable, sensitive, or difficult to serialize.
  • Less justified if evaluation remains the only consumer.

Option D: Preserve the original configuration directly

Store a deep snapshot of the original AnonymizerConfig, plus the minimal input context needed for evaluation.

Advantages

  • Avoids defining a parallel configuration model.
  • Automatically preserves future configuration fields.

Tradeoffs

  • Couples persisted results tightly to configuration schema evolution.
  • Retains settings unrelated to evaluation.
  • Requires decisions about mutation, serialization, and potentially sensitive input metadata.

Suggested migration

If Option B or C is selected:

  1. Introduce the context as a new keyword-only field and populate it on all run and preview paths.
  2. Have evaluate() prefer the context while falling back to legacy fields for old or hand-constructed results.
  3. Preserve existing attributes as deprecated read-only forwarding properties where practical.
  4. Document serialization and deep-copy semantics.
  5. Remove legacy constructor parameters only in a major release.
  6. Update the bundled anonymizer skill alongside the public API.

Fields that should remain outside the context

These describe the result itself rather than evaluation provenance:

  • dataframe
  • trace_dataframe
  • failed_records
  • resolved_text_column
  • preview_num_records
  • private display state such as _display_cycle_index

Design questions

  • Is evaluation the only intended consumer, making EvaluationContext preferable to RunContext?
  • Should the context be public, or an internal implementation detail with stable forwarding properties?
  • Should it contain full Detect / Rewrite models or an immutable serialized snapshot?
  • What compatibility is promised for positional result construction?
  • How should old pickled or otherwise persisted result objects be handled?
  • Should the context carry an explicit schema version?

Acceptance criteria

  • A preferred option and public compatibility policy are documented.
  • Existing released constructor parameter order is preserved until an intentional breaking release.
  • run(), preview(), and evaluate() use one canonical source for evaluation provenance.
  • Replace and rewrite evaluation are covered by compatibility tests.
  • Hand-constructed and legacy-result fallback behavior is tested.
  • Serialization/mutation semantics are documented.
  • Public documentation and skills/anonymizer/SKILL.md are updated with the chosen API.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions