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:
- Introduce the context as a new keyword-only field and populate it on all run and preview paths.
- Have
evaluate() prefer the context while falling back to legacy fields for old or hand-constructed results.
- Preserve existing attributes as deprecated read-only forwarding properties where practical.
- Document serialization and deep-copy semantics.
- Remove legacy constructor parameters only in a major release.
- 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
Context
AnonymizerResultandPreviewResultcurrently carry both output data and individual pieces of run configuration needed by a laterAnonymizer.evaluate(result)call:replace_methodrewrite_configentity_labelsdata_summaryentity_label_denylist/ the eventual public name for excluded labels (proposed in feat: add excluded labels for entities in Anonymizer #243)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
AnonymizerConfigis no longer available.AnonymizerResultandPreviewResult.Option A: Keep flat fields
Continue adding optional metadata fields directly to both result classes.
Advantages
Tradeoffs
Option B: Add an
EvaluationContextIntroduce a focused context containing only information needed by
evaluate():Results would carry one context:
Advantages
Tradeoffs
Option C: Add a broader
RunContextPreserve broader provenance, potentially including:
replaceorrewrite)data_summaryand text-column identityAdvantages
evaluate().Tradeoffs
Option D: Preserve the original configuration directly
Store a deep snapshot of the original
AnonymizerConfig, plus the minimal input context needed for evaluation.Advantages
Tradeoffs
Suggested migration
If Option B or C is selected:
evaluate()prefer the context while falling back to legacy fields for old or hand-constructed results.Fields that should remain outside the context
These describe the result itself rather than evaluation provenance:
dataframetrace_dataframefailed_recordsresolved_text_columnpreview_num_records_display_cycle_indexDesign questions
EvaluationContextpreferable toRunContext?Detect/Rewritemodels or an immutable serialized snapshot?Acceptance criteria
run(),preview(), andevaluate()use one canonical source for evaluation provenance.skills/anonymizer/SKILL.mdare updated with the chosen API.