Praneshrajan15's picture
Deploy DataForge playground API
eed1cab verified
Raw
History Blame Contribute Delete
5.59 kB
"""In-episode hypothesis and issue tracker for the DataForge RL agent.
The scratchpad is a mutable, episode-scoped data structure that the agent
uses to record hypotheses, confirmed issues, and dead ends. The environment
exposes a compact summary of the scratchpad in each observation, enabling
the agent to reason about its investigation history without direct access
to the underlying data structure.
Example::
>>> from dataforge.agent.scratchpad import Scratchpad
>>> pad = Scratchpad()
>>> pad.add_hypothesis("Rating column has decimal shift", [5], ["rating"], "decimal_shift")
>>> pad.confirm_issue(5, "rating", "decimal_shift")
>>> pad.summary()
'Hypotheses: 1 (0 pending). Confirmed: 1. Dead ends: 0.'
"""
from __future__ import annotations
from dataclasses import dataclass, field
__all__ = [
"ConfirmedIssue",
"DeadEnd",
"HypothesisRecord",
"Scratchpad",
]
@dataclass(frozen=True)
class HypothesisRecord:
"""A recorded hypothesis about a data-quality root cause.
Args:
claim: Textual description of the hypothesis.
affected_rows: Row indices the hypothesis covers.
affected_columns: Column names the hypothesis covers.
root_cause_type: Detector-vocabulary root cause type.
confirmed: Whether the hypothesis was confirmed by ground truth.
"""
claim: str
affected_rows: tuple[int, ...]
affected_columns: tuple[str, ...]
root_cause_type: str
confirmed: bool = False
@dataclass(frozen=True)
class ConfirmedIssue:
"""A confirmed data-quality issue at a specific location.
Args:
row: Zero-indexed row number.
column: Column name.
issue_type: Issue type classification.
"""
row: int
column: str
issue_type: str
@dataclass(frozen=True)
class DeadEnd:
"""A recorded dead end — an investigation path that yielded nothing.
Args:
description: What was tried and why it failed.
step_number: Step at which the dead end was recorded.
"""
description: str
step_number: int
@dataclass
class Scratchpad:
"""Mutable in-episode tracker for hypotheses, confirmed issues, and dead ends.
Reset at the start of each episode. The ``summary()`` method produces a
compact string for inclusion in agent observations.
Example::
>>> pad = Scratchpad()
>>> pad.add_hypothesis("Decimal shift in rating", [5], ["rating"], "decimal_shift")
>>> len(pad.hypotheses)
1
"""
hypotheses: list[HypothesisRecord] = field(default_factory=list)
confirmed_issues: list[ConfirmedIssue] = field(default_factory=list)
dead_ends: list[DeadEnd] = field(default_factory=list)
def add_hypothesis(
self,
claim: str,
affected_rows: list[int],
affected_columns: list[str],
root_cause_type: str,
) -> HypothesisRecord:
"""Record a new hypothesis.
Args:
claim: Textual description of the hypothesis.
affected_rows: Row indices the hypothesis covers.
affected_columns: Column names the hypothesis covers.
root_cause_type: Detector-vocabulary root cause type.
Returns:
The recorded hypothesis.
"""
record = HypothesisRecord(
claim=claim,
affected_rows=tuple(affected_rows),
affected_columns=tuple(affected_columns),
root_cause_type=root_cause_type,
)
self.hypotheses.append(record)
return record
def confirm_hypothesis(self, index: int) -> None:
"""Mark a hypothesis as confirmed.
Args:
index: Index into the ``hypotheses`` list.
Raises:
IndexError: If the index is out of range.
"""
old = self.hypotheses[index]
self.hypotheses[index] = HypothesisRecord(
claim=old.claim,
affected_rows=old.affected_rows,
affected_columns=old.affected_columns,
root_cause_type=old.root_cause_type,
confirmed=True,
)
def confirm_issue(self, row: int, column: str, issue_type: str) -> None:
"""Record a confirmed issue.
Args:
row: Zero-indexed row number.
column: Column name.
issue_type: Issue type classification.
"""
self.confirmed_issues.append(ConfirmedIssue(row=row, column=column, issue_type=issue_type))
def add_dead_end(self, description: str, step_number: int) -> None:
"""Record a dead end.
Args:
description: What was tried and why it failed.
step_number: Step at which the dead end was recorded.
"""
self.dead_ends.append(DeadEnd(description=description, step_number=step_number))
def reset(self) -> None:
"""Clear all tracked state for a new episode."""
self.hypotheses.clear()
self.confirmed_issues.clear()
self.dead_ends.clear()
def summary(self) -> str:
"""Produce a compact summary string for observation embedding.
Returns:
A one-line summary of scratchpad state.
Example::
>>> Scratchpad().summary()
'Hypotheses: 0 (0 pending). Confirmed: 0. Dead ends: 0.'
"""
pending = sum(1 for h in self.hypotheses if not h.confirmed)
return (
f"Hypotheses: {len(self.hypotheses)} ({pending} pending). "
f"Confirmed: {len(self.confirmed_issues)}. "
f"Dead ends: {len(self.dead_ends)}."
)