Source code for diddesign.errors

"""DIDdesign structured error and warning system.

Provides error codes, warning codes, and structured exceptions that parallel
the Stata DIDdesign package's E001-E018 / W001-W010 diagnostic system.  Each
error carries a machine-readable code and a context dictionary with the
diagnostic quantities a user needs to identify the root cause.
"""

from __future__ import annotations

import warnings
from enum import IntEnum
from typing import Any, Dict, Optional


[docs] class ErrorCode(IntEnum): """Standard error codes matching Stata's E001-E018 system.""" # Parameter / option errors E001 = 1 # Missing required parameter or variable E002 = 2 # Invalid parameter value # Data errors E003 = 3 # Data constraint violation (no obs, non-binary, unbalanced panel, etc.) # Computation errors E004 = 4 # Point estimate failed E005 = 5 # Covariance matrix computation failed E006 = 6 # GMM weight computation failed # Design constraints E007 = 7 # SA design-specific constraint violation E008 = 8 # Panel structure violation E009 = 9 # Bootstrap failure E010 = 10 # kmax out of range E011 = 11 # Insufficient moment conditions # Validation errors E012 = 12 # Treatment not binary (not 0/1) E013 = 13 # Treatment not absorbing (irreversible) E014 = 14 # Standard DID requires common treatment adoption time E015 = 15 # SA design requires >= 3 time periods E016 = 16 # RCS post indicator inconsistent with time E017 = 17 # Time column contains non-finite values E018 = 18 # RCS requires cluster variable E019 = 19 # Ambiguous string time ordering E020 = 20 # Invalid design/data_type parameter
[docs] class WarningCode(IntEnum): """Standard warning codes matching Stata's W001-W010 system.""" W001 = 1 # Automatic variable type conversion W002 = 2 # Factor variable expansion W003 = 3 # Duplicate covariate removal W004 = 4 # Parallel option not implemented W005 = 5 # Bootstrap partial iteration failure W006 = 6 # Covariance matrix singular (rank reduction) W007 = 7 # Condition number too large W008 = 8 # GMM weights do not sum to 1 W009 = 9 # GMM weights outside [0, 1] W010 = 10 # Bootstrap effective sample insufficient W011 = 11 # J-test disabled: insufficient moment conditions (kmax <= 2) W012 = 12 # Bootstrap iterations below recommended threshold
# --------------------------------------------------------------------------- # Fix Suggestions for Common Errors # --------------------------------------------------------------------------- ERROR_SUGGESTIONS: dict[str, str] = { "E001": "Check that all required parameters (unit_id for panel, post for RCS) are provided.", "E002": "When using the formula interface, do not pass outcome, treatment, post, or covariates as separate parameters. For non-formula calls, verify that lead values are non-negative integers and kmax is 1-8.", "E003": "Ensure the treatment column contains only 0 and 1 values (binary indicator).", "E007": "SA design requires absorbing treatment: once treated, a unit stays treated.", "E008": "K-DID (kmax>2) requires panel data. Set data_type='panel' and provide unit_id.", "E009": "Bootstrap needs at least 2 clusters. Check id_cluster or unit_id has >=2 unique values.", "E014": "SA design is not supported for RCS data. Use design='did' or switch to panel data.", "E016": "Cannot specify both id() and post(). Use id/unit_id for panel, post for RCS.", "E018": "RCS design requires cluster(). Specify id_cluster for bootstrap inference.", "E020": "design must be 'did' or 'sa'; data_type must be 'panel' or 'rcs'.", } # --------------------------------------------------------------------------- # Structured Exceptions # ---------------------------------------------------------------------------
[docs] class DidDataError(ValueError): """Raised when input data cannot satisfy the DID data requirements. This is the backward-compatible base class. ``DataContractError`` is an alias for this class. """
[docs] class DidError(Exception): """Base DIDdesign structured error with code and context.""" def __init__( self, code: ErrorCode, message: str, context: Optional[Dict[str, Any]] = None, ): self.code = code self.message = message self.context = context or {} # Build detailed multi-line message code_str = f"E{int(code):03d}" detail_parts = [f"[{code_str}] {message}"] suggestion = ERROR_SUGGESTIONS.get(code_str, "") if suggestion: detail_parts.append(f" Suggestion: {suggestion}") if self.context: detail_parts.append(" Context:") for k, v in self.context.items(): detail_parts.append(f" {k} = {v}") self.detailed_message = "\n".join(detail_parts) super().__init__(self.detailed_message)
[docs] class DidValueError(DidError, DidDataError): """Structured DIDdesign error for parameter / data validation failures. Inherits from both :class:`DidError` and :class:`DidDataError` so that existing ``except ValueError`` and ``except DataContractError`` handlers continue to catch these errors. """
[docs] class DidRuntimeError(DidError, RuntimeError): """Structured DIDdesign error for computation failures. Inherits from both :class:`DidError` and :class:`RuntimeError` so that existing ``except RuntimeError`` handlers continue to catch these errors. """
# --------------------------------------------------------------------------- # Structured Warnings # ---------------------------------------------------------------------------
[docs] class DidWarning(UserWarning): """DIDdesign structured warning with code and context.""" def __init__( self, code: WarningCode, message: str, context: Optional[Dict[str, Any]] = None, ): self.code = code self.context = context or {} detail = f"[W{int(code):03d}] {message}" if self.context: ctx_str = ", ".join(f"{k}={v}" for k, v in self.context.items()) detail += f" ({ctx_str})" super().__init__(detail)
# --------------------------------------------------------------------------- # Convenience emitter # ---------------------------------------------------------------------------
[docs] def did_warn( code: WarningCode, message: str, context: Optional[Dict[str, Any]] = None, stacklevel: int = 2, ) -> None: """Emit a structured DIDdesign warning.""" warnings.warn( DidWarning(code, message, context), stacklevel=stacklevel, )