Sequence 10: E-Filing Status Tracking - Implementation Plan¶
Created: 2025-12-24 Status: Ready for Approval Depends On: Sequence 9 (complete)
Overview¶
Sequence 10 implements e-filing status tracking. UltraTax CS handles actual IRS/state transmission (confirmed ASM-001). Our system tracks status via SurePrep CS Connect webhooks and notifies clients/staff of acceptance or rejection.
Stories¶
| Story | Title | Priority | Effort |
|---|---|---|---|
| S10-001 | SurePrep E-File Status Integration | P0 | Medium |
| S10-002 | E-File Workflow Trigger | P0 | Small |
| S10-003 | Acceptance Notification | P0 | Small |
| S10-004 | Rejection Handling | P0 | Medium |
Existing Infrastructure (Reuse)¶
Already implemented:
- src/services/sureprep_service.py - SurePrep API (needs e-file status methods)
- src/workflows/communication/notification_workflow.py - Email/SMS notifications (S8)
- src/domain/workflow.py - TaxReturn entity with state machine
- src/api/routes/webhooks.py - Webhook handlers
- src/repositories/workflow_repository.py - Tax return state management
- config.yaml - SurePrep configuration section
Key patterns to follow:
- Service pattern with dry_run mode for development
- Repository pattern with async methods and _to_entity() conversion
- Result dataclasses with ok/fail factory methods
- Webhook handlers update DB then trigger workflows
- structlog logging with bound context
Parallel Execution Strategy¶
To maximize throughput, organize phases into parallel tracks:
TRACK A (Domain/Repository) TRACK B (Service/Webhook) TRACK C (Tests)
───────────────────────────── ─────────────────────────── ─────────────────
Phase 1: Domain Model Phase 2: SurePrep Methods (wait)
↓ ↓
Phase 3: Repository Phase 4: Webhook Handler Phase 6: Unit Tests
↓ ↓ (background)
Phase 5: EFilingWorkflow (merge) ↓
↓ Phase 7: Integration
Phase 8: API Routes ←─────────────────────────────────────────── Tests
↓
Phase 9: Integration conftest
↓
Phase 10: Code Audit
Parallel opportunities: - Phase 1 + Phase 2 can run in parallel (no dependencies) - Phase 6 (unit tests) can run in background after Phase 5 - Track C tests can start once domain is defined
Implementation Order¶
Phase 1: Domain Model (Track A)¶
New File: src/domain/efiling.py
"""
E-filing status domain entities for Tax Practice AI.
Per USER_STORIES.md S10-001 through S10-004.
Note: UltraTax handles transmission; we track status via SurePrep.
"""
from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
from typing import List, Optional
from uuid import UUID
class EFileStatus(str, Enum):
"""E-file submission status."""
READY = "ready" # Ready for transmission (8879 signed)
PENDING = "pending" # Submitted to UltraTax, awaiting acknowledgement
TRANSMITTED = "transmitted" # Transmitted to IRS/state
ACCEPTED = "accepted" # Accepted by IRS/state
REJECTED = "rejected" # Rejected - needs correction
class EFileJurisdiction(str, Enum):
"""Filing jurisdiction."""
FEDERAL = "federal"
STATE = "state"
class RejectionCategory(str, Enum):
"""E-file rejection categories."""
DATA_MISMATCH = "data_mismatch" # SSN/name mismatch, AGI mismatch
MISSING_INFO = "missing_info" # Missing schedule, incomplete data
DUPLICATE = "duplicate" # Already filed
TECHNICAL = "technical" # Transmission error, try again
IDENTITY = "identity" # Identity verification required
@dataclass
class EFileSubmission:
"""
E-file submission record.
Tracks status for each jurisdiction (federal + each state).
"""
id: UUID
tax_return_id: UUID
client_id: UUID
jurisdiction: EFileJurisdiction
state_code: Optional[str] # e.g., "FL", "GA" (None for federal)
status: EFileStatus
sureprep_tracking_id: Optional[str] # SurePrep's tracking reference
ultratax_confirmation: Optional[str] # UltraTax confirmation number
irs_confirmation: Optional[str] # IRS/state confirmation number
submitted_at: Optional[datetime]
transmitted_at: Optional[datetime]
accepted_at: Optional[datetime]
rejected_at: Optional[datetime]
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
def is_complete(self) -> bool:
"""Check if submission has final status."""
return self.status in (EFileStatus.ACCEPTED, EFileStatus.REJECTED)
def is_accepted(self) -> bool:
"""Check if accepted."""
return self.status == EFileStatus.ACCEPTED
def is_rejected(self) -> bool:
"""Check if rejected."""
return self.status == EFileStatus.REJECTED
def can_resubmit(self) -> bool:
"""Check if can be resubmitted after rejection."""
return self.status == EFileStatus.REJECTED
@dataclass
class EFileRejection:
"""
E-file rejection details.
Stores error codes and categorization for resolution tracking.
"""
id: UUID
submission_id: UUID
tax_return_id: UUID
error_code: str # IRS/state error code
error_message: str # Human-readable message
category: RejectionCategory
auto_correctable: bool # Can system auto-fix?
resolution_notes: Optional[str]
resolved_at: Optional[datetime]
resolved_by: Optional[UUID] # Staff user ID
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
def is_resolved(self) -> bool:
"""Check if rejection has been resolved."""
return self.resolved_at is not None
# =============================================================================
# CONSTANTS
# =============================================================================
# Common IRS rejection codes and their categories
REJECTION_CODE_CATEGORIES = {
# Data mismatch
"IND-031": RejectionCategory.DATA_MISMATCH, # SSN/name mismatch
"IND-032": RejectionCategory.DATA_MISMATCH, # Spouse SSN/name mismatch
"IND-181": RejectionCategory.DATA_MISMATCH, # AGI doesn't match
"IND-182": RejectionCategory.DATA_MISMATCH, # PIN doesn't match
# Duplicate filing
"IND-510": RejectionCategory.DUPLICATE, # Already filed
"IND-511": RejectionCategory.DUPLICATE, # Duplicate SSN
# Missing info
"IND-901": RejectionCategory.MISSING_INFO, # Missing schedule
"IND-902": RejectionCategory.MISSING_INFO, # Incomplete form
# Identity
"IND-516": RejectionCategory.IDENTITY, # ID verification needed
"IND-517": RejectionCategory.IDENTITY, # IP PIN required
# Technical (default)
"R0000": RejectionCategory.TECHNICAL, # Transmission error
}
# Rejection codes that can be auto-corrected
AUTO_CORRECTABLE_CODES = {
"R0000", # Retry transmission
}
def categorize_rejection(error_code: str) -> RejectionCategory:
"""Categorize rejection by error code."""
return REJECTION_CODE_CATEGORIES.get(error_code, RejectionCategory.TECHNICAL)
def is_auto_correctable(error_code: str) -> bool:
"""Check if rejection can be auto-corrected."""
return error_code in AUTO_CORRECTABLE_CODES
Update: src/domain/__init__.py - Export efiling entities
Phase 2: SurePrep E-File Status Methods (Track B - PARALLEL with Phase 1)¶
Update: src/services/sureprep_service.py
Add e-file status methods to existing service:
# =============================================================================
# E-FILE STATUS (S10)
# =============================================================================
class EFileStatusEvent(str, Enum):
"""E-file status events from SurePrep."""
PENDING = "pending"
TRANSMITTED = "transmitted"
ACCEPTED = "accepted"
REJECTED = "rejected"
@dataclass
class EFileStatusUpdate:
"""E-file status update from SurePrep."""
tracking_id: str
jurisdiction: str # "federal" or state code
status: EFileStatusEvent
confirmation_number: Optional[str]
error_code: Optional[str]
error_message: Optional[str]
timestamp: datetime
class SurePrepService(BaseService):
# ... existing methods ...
# =========================================================================
# E-FILE STATUS (S10)
# =========================================================================
async def get_efile_status(
self,
binder_id: str,
) -> List[EFileStatusUpdate]:
"""
Get e-file status for all jurisdictions in binder.
Args:
binder_id: SurePrep binder ID
Returns:
List of status updates for each jurisdiction
"""
log = self._log.bind(
operation="get_efile_status",
binder_id=binder_id,
)
log.info("getting_efile_status")
if self._config.dry_run:
log.info("efile_status_dry_run")
return self._simulate_efile_status(binder_id)
# TODO: Implement actual API call to SurePrep
log.warning("efile_status_not_implemented")
return []
async def poll_efile_status(
self,
binder_ids: List[str],
) -> Dict[str, List[EFileStatusUpdate]]:
"""
Poll e-file status for multiple binders.
Used by scheduled job to batch status checks.
Args:
binder_ids: List of SurePrep binder IDs
Returns:
Dict mapping binder_id to status updates
"""
results = {}
for binder_id in binder_ids:
results[binder_id] = await self.get_efile_status(binder_id)
return results
def _simulate_efile_status(self, binder_id: str) -> List[EFileStatusUpdate]:
"""Simulate e-file status for dry_run mode."""
return [
EFileStatusUpdate(
tracking_id=f"track_{binder_id}",
jurisdiction="federal",
status=EFileStatusEvent.ACCEPTED,
confirmation_number="IRS-2024-123456789",
error_code=None,
error_message=None,
timestamp=datetime.now(timezone.utc),
)
]
Phase 3: Repository Layer (Track A)¶
New File: src/repositories/efiling_repository.py
"""
E-filing repository for Tax Practice AI.
Data access for e-file submissions and rejections.
Per USER_STORIES.md S10-001 through S10-004.
"""
class EFileSubmissionRepository:
"""Repository for e-file submissions."""
async def create(
self,
tax_return_id: UUID,
client_id: UUID,
jurisdiction: EFileJurisdiction,
state_code: Optional[str] = None,
) -> EFileSubmission
async def find_by_id(self, submission_id: UUID) -> Optional[EFileSubmission]
async def find_by_return(self, tax_return_id: UUID) -> List[EFileSubmission]
async def find_pending(self) -> List[EFileSubmission]
async def update_status(
self,
submission_id: UUID,
status: EFileStatus,
confirmation: Optional[str] = None,
) -> EFileSubmission
async def mark_transmitted(
self,
submission_id: UUID,
sureprep_tracking_id: str,
) -> EFileSubmission
async def mark_accepted(
self,
submission_id: UUID,
irs_confirmation: str,
) -> EFileSubmission
async def mark_rejected(
self,
submission_id: UUID,
) -> EFileSubmission
class EFileRejectionRepository:
"""Repository for e-file rejections."""
async def create(
self,
submission_id: UUID,
tax_return_id: UUID,
error_code: str,
error_message: str,
category: RejectionCategory,
auto_correctable: bool,
) -> EFileRejection
async def find_by_submission(self, submission_id: UUID) -> Optional[EFileRejection]
async def find_unresolved(self) -> List[EFileRejection]
async def find_by_category(self, category: RejectionCategory) -> List[EFileRejection]
async def mark_resolved(
self,
rejection_id: UUID,
resolved_by: UUID,
notes: Optional[str] = None,
) -> EFileRejection
Update: src/repositories/__init__.py - Export efiling repositories
Phase 4: Webhook Handler (Track B)¶
Update: src/api/routes/webhooks.py
Add SurePrep e-file status webhook handler:
# =============================================================================
# SUREPREP E-FILE WEBHOOKS (S10)
# =============================================================================
@router.post("/sureprep/efile-status")
async def sureprep_efile_status_webhook(
request: Request,
efiling_workflow: EFilingWorkflow = Depends(get_efiling_workflow),
) -> dict:
"""
Handle SurePrep e-file status webhook.
Events:
- efile.pending: Submitted to UltraTax
- efile.transmitted: Sent to IRS/state
- efile.accepted: Accepted by IRS/state
- efile.rejected: Rejected with error codes
"""
body = await request.json()
# Verify webhook signature
signature = request.headers.get("X-SurePrep-Signature")
if not verify_sureprep_signature(body, signature):
raise HTTPException(status_code=401, detail="Invalid signature")
event_type = body.get("event_type")
binder_id = body.get("binder_id")
if event_type == "efile.accepted":
result = await efiling_workflow.process_acceptance(
binder_id=binder_id,
jurisdiction=body.get("jurisdiction"),
confirmation_number=body.get("confirmation_number"),
)
elif event_type == "efile.rejected":
result = await efiling_workflow.process_rejection(
binder_id=binder_id,
jurisdiction=body.get("jurisdiction"),
error_code=body.get("error_code"),
error_message=body.get("error_message"),
)
else:
result = await efiling_workflow.process_status_update(
binder_id=binder_id,
event_type=event_type,
payload=body,
)
return {"received": True, "processed": result.success}
Phase 5: E-Filing Workflow (Track A - after Phase 3)¶
New File: src/workflows/filing/__init__.py
New File: src/workflows/filing/efiling_workflow.py
"""
E-filing workflow for Tax Practice AI.
Orchestrates e-file status tracking and notifications.
Per USER_STORIES.md S10-001 through S10-004.
Note: UltraTax handles actual IRS/state transmission.
We track status via SurePrep CS Connect.
"""
from dataclasses import dataclass
from typing import List, Optional
from uuid import UUID
import structlog
from src.domain.efiling import (
EFileSubmission,
EFileRejection,
EFileStatus,
EFileJurisdiction,
RejectionCategory,
categorize_rejection,
is_auto_correctable,
)
from src.repositories.efiling_repository import (
EFileSubmissionRepository,
EFileRejectionRepository,
)
from src.repositories.workflow_repository import TaxReturnRepository
log = structlog.get_logger()
# =============================================================================
# RESULT TYPES
# =============================================================================
@dataclass
class EFileResult:
"""Result of e-filing operations."""
success: bool
submission: Optional[EFileSubmission] = None
submissions: Optional[List[EFileSubmission]] = None
error: Optional[str] = None
@classmethod
def ok(cls, submission: EFileSubmission = None, submissions: List[EFileSubmission] = None):
return cls(success=True, submission=submission, submissions=submissions)
@classmethod
def fail(cls, error: str):
return cls(success=False, error=error)
@dataclass
class RejectionResult:
"""Result of rejection processing."""
success: bool
rejection: Optional[EFileRejection] = None
auto_corrected: bool = False
error: Optional[str] = None
@classmethod
def ok(cls, rejection: EFileRejection, auto_corrected: bool = False):
return cls(success=True, rejection=rejection, auto_corrected=auto_corrected)
@classmethod
def fail(cls, error: str):
return cls(success=False, error=error)
# =============================================================================
# E-FILING WORKFLOW (S10)
# =============================================================================
class EFilingWorkflow:
"""
Orchestrates e-file status tracking.
Handles:
- Marking returns ready for e-filing (S10-002)
- Processing status updates from SurePrep (S10-001)
- Sending acceptance notifications (S10-003)
- Processing rejections (S10-004)
"""
def __init__(
self,
submission_repository: EFileSubmissionRepository,
rejection_repository: EFileRejectionRepository,
return_repository: TaxReturnRepository,
sureprep_service=None,
notification_workflow=None,
audit_service=None,
):
self._submission_repo = submission_repository
self._rejection_repo = rejection_repository
self._return_repo = return_repository
self._sureprep = sureprep_service
self._notification = notification_workflow
self._audit = audit_service
self._log = structlog.get_logger().bind(workflow="EFilingWorkflow")
# =========================================================================
# S10-002: E-FILE WORKFLOW TRIGGER
# =========================================================================
async def mark_ready_for_filing(
self,
tax_return_id: UUID,
jurisdictions: List[str], # ["federal", "FL", "GA"]
) -> EFileResult:
"""
Mark return as ready for e-filing after 8879 signature.
Creates submission records for each jurisdiction.
"""
self._log.info(
"marking_ready_for_filing",
tax_return_id=str(tax_return_id),
jurisdictions=jurisdictions,
)
try:
# Get tax return
tax_return = await self._return_repo.find_by_id(tax_return_id)
if not tax_return:
return EFileResult.fail("Tax return not found")
submissions = []
for jurisdiction in jurisdictions:
if jurisdiction == "federal":
sub = await self._submission_repo.create(
tax_return_id=tax_return_id,
client_id=tax_return.client_id,
jurisdiction=EFileJurisdiction.FEDERAL,
)
else:
sub = await self._submission_repo.create(
tax_return_id=tax_return_id,
client_id=tax_return.client_id,
jurisdiction=EFileJurisdiction.STATE,
state_code=jurisdiction,
)
submissions.append(sub)
# Update return status
await self._return_repo.update_status(
tax_return_id,
"ready_to_file",
)
if self._audit:
await self._audit.log_efile_ready(
tax_return_id=tax_return_id,
jurisdictions=jurisdictions,
)
return EFileResult.ok(submissions=submissions)
except Exception as e:
self._log.error("mark_ready_failed", error=str(e))
return EFileResult.fail(str(e))
# =========================================================================
# S10-001: STATUS PROCESSING
# =========================================================================
async def process_status_update(
self,
binder_id: str,
event_type: str,
payload: dict,
) -> EFileResult:
"""Process generic status update from SurePrep."""
self._log.info(
"processing_status_update",
binder_id=binder_id,
event_type=event_type,
)
try:
# Find submission by binder/tracking ID
# Update status based on event type
# Map: pending, transmitted
return EFileResult.ok()
except Exception as e:
self._log.error("status_update_failed", error=str(e))
return EFileResult.fail(str(e))
# =========================================================================
# S10-003: ACCEPTANCE NOTIFICATION
# =========================================================================
async def process_acceptance(
self,
binder_id: str,
jurisdiction: str,
confirmation_number: str,
) -> EFileResult:
"""
Process e-file acceptance from SurePrep.
Updates status and sends client notification.
"""
self._log.info(
"processing_acceptance",
binder_id=binder_id,
jurisdiction=jurisdiction,
)
try:
# Find submission
submission = await self._find_submission_by_binder(
binder_id, jurisdiction
)
if not submission:
return EFileResult.fail("Submission not found")
# Update status
submission = await self._submission_repo.mark_accepted(
submission_id=submission.id,
irs_confirmation=confirmation_number,
)
# Check if all jurisdictions accepted
all_submissions = await self._submission_repo.find_by_return(
submission.tax_return_id
)
all_accepted = all(s.is_accepted() for s in all_submissions)
if all_accepted:
await self._return_repo.update_status(
submission.tax_return_id,
"filed",
)
# Send client notification
if self._notification:
await self._send_acceptance_notification(
submission=submission,
confirmation_number=confirmation_number,
)
if self._audit:
await self._audit.log_efile_accepted(
submission_id=submission.id,
confirmation=confirmation_number,
)
return EFileResult.ok(submission=submission)
except Exception as e:
self._log.error("acceptance_processing_failed", error=str(e))
return EFileResult.fail(str(e))
# =========================================================================
# S10-004: REJECTION HANDLING
# =========================================================================
async def process_rejection(
self,
binder_id: str,
jurisdiction: str,
error_code: str,
error_message: str,
) -> RejectionResult:
"""
Process e-file rejection from SurePrep.
Categorizes error, creates rejection record, notifies preparer.
"""
self._log.info(
"processing_rejection",
binder_id=binder_id,
jurisdiction=jurisdiction,
error_code=error_code,
)
try:
# Find submission
submission = await self._find_submission_by_binder(
binder_id, jurisdiction
)
if not submission:
return RejectionResult.fail("Submission not found")
# Update submission status
submission = await self._submission_repo.mark_rejected(
submission_id=submission.id,
)
# Categorize rejection
category = categorize_rejection(error_code)
auto_correctable = is_auto_correctable(error_code)
# Create rejection record
rejection = await self._rejection_repo.create(
submission_id=submission.id,
tax_return_id=submission.tax_return_id,
error_code=error_code,
error_message=error_message,
category=category,
auto_correctable=auto_correctable,
)
# Update return workflow
await self._return_repo.update_status(
submission.tax_return_id,
"rejected",
)
# Handle based on category
if auto_correctable:
# Attempt auto-correction (retry transmission)
await self._attempt_auto_correction(submission, rejection)
else:
# Notify preparer
if self._notification:
await self._send_rejection_notification(
submission=submission,
rejection=rejection,
)
# Notify client if info needed
if category in (RejectionCategory.DATA_MISMATCH, RejectionCategory.IDENTITY):
if self._notification:
await self._send_client_rejection_notification(
submission=submission,
rejection=rejection,
)
if self._audit:
await self._audit.log_efile_rejected(
submission_id=submission.id,
error_code=error_code,
category=category.value,
)
return RejectionResult.ok(
rejection=rejection,
auto_corrected=auto_correctable,
)
except Exception as e:
self._log.error("rejection_processing_failed", error=str(e))
return RejectionResult.fail(str(e))
# =========================================================================
# POLLING (Fallback if webhooks unavailable)
# =========================================================================
async def poll_pending_submissions(self) -> int:
"""
Poll SurePrep for status of pending submissions.
Called by scheduled job as fallback to webhooks.
Returns count of submissions updated.
"""
if not self._sureprep:
return 0
pending = await self._submission_repo.find_pending()
updated = 0
for submission in pending:
# Get binder ID from tax return
# Poll SurePrep for status
# Process any updates
pass
return updated
# =========================================================================
# INTERNAL METHODS
# =========================================================================
async def _find_submission_by_binder(
self,
binder_id: str,
jurisdiction: str,
) -> Optional[EFileSubmission]:
"""Find submission by SurePrep binder and jurisdiction."""
# Implementation: lookup by sureprep_tracking_id or via return->binder mapping
pass
async def _send_acceptance_notification(
self,
submission: EFileSubmission,
confirmation_number: str,
) -> None:
"""Send acceptance email to client."""
if not self._notification:
return
# Get client info
# Send email with confirmation number
self._log.info(
"sending_acceptance_notification",
submission_id=str(submission.id),
)
async def _send_rejection_notification(
self,
submission: EFileSubmission,
rejection: EFileRejection,
) -> None:
"""Send rejection notification to preparer."""
if not self._notification:
return
self._log.info(
"sending_preparer_rejection_notification",
submission_id=str(submission.id),
)
async def _send_client_rejection_notification(
self,
submission: EFileSubmission,
rejection: EFileRejection,
) -> None:
"""Send rejection notification to client when info needed."""
if not self._notification:
return
self._log.info(
"sending_client_rejection_notification",
submission_id=str(submission.id),
)
async def _attempt_auto_correction(
self,
submission: EFileSubmission,
rejection: EFileRejection,
) -> bool:
"""Attempt auto-correction for technical errors."""
# For now, just log - would retry transmission
self._log.info(
"attempting_auto_correction",
submission_id=str(submission.id),
error_code=rejection.error_code,
)
return False
Phase 6: Unit Tests (Track C - BACKGROUND after Phase 5)¶
Can run in parallel with Phase 7-8
New Files:
- tests/unit/domain/test_efiling.py (~30 tests)
- tests/unit/workflows/filing/__init__.py
- tests/unit/workflows/filing/test_efiling_workflow.py (~40 tests)
Test coverage: - Domain entity creation and methods - Rejection categorization - Workflow status transitions - Notification triggers - Auto-correction logic
Phase 7: API Routes and Schemas (after Phase 5)¶
New File: src/api/schemas/efiling_schemas.py
"""
E-filing API schemas for Tax Practice AI.
Request/response schemas for e-file status tracking.
Per USER_STORIES.md S10-001 through S10-004.
"""
class EFileSubmissionResponse(BaseModel):
id: UUID
tax_return_id: UUID
jurisdiction: str
state_code: Optional[str]
status: str
confirmation_number: Optional[str]
submitted_at: Optional[datetime]
accepted_at: Optional[datetime]
rejected_at: Optional[datetime]
class EFileStatusResponse(BaseModel):
tax_return_id: UUID
submissions: List[EFileSubmissionResponse]
all_accepted: bool
has_rejections: bool
class EFileRejectionResponse(BaseModel):
id: UUID
error_code: str
error_message: str
category: str
auto_correctable: bool
is_resolved: bool
class MarkReadyRequest(BaseModel):
jurisdictions: List[str] = Field(
...,
description="Jurisdictions to file: 'federal' and/or state codes",
example=["federal", "FL"],
)
New File: src/api/routes/efiling.py
router = APIRouter(prefix="/v1/efiling", tags=["efiling"])
@router.post("/returns/{return_id}/ready")
async def mark_ready_for_filing(return_id: UUID, body: MarkReadyRequest)
@router.get("/returns/{return_id}/status")
async def get_efile_status(return_id: UUID)
@router.get("/returns/{return_id}/rejections")
async def get_rejections(return_id: UUID)
@router.post("/rejections/{rejection_id}/resolve")
async def resolve_rejection(rejection_id: UUID, body: ResolveRequest)
@router.get("/dashboard/pending")
async def get_pending_filings()
@router.get("/dashboard/rejections")
async def get_unresolved_rejections()
Phase 8: Update Main App and Routes¶
Update: src/api/main.py - Register efiling router
Update: src/api/routes/__init__.py - Export efiling module
Phase 9: Integration Test Schema¶
Update: tests/integration/conftest.py
-- E-file status enum (S10-001)
CREATE TYPE efile_status_enum AS ENUM (
'ready', 'pending', 'transmitted', 'accepted', 'rejected'
);
-- E-file jurisdiction enum
CREATE TYPE efile_jurisdiction_enum AS ENUM ('federal', 'state');
-- Rejection category enum (S10-004)
CREATE TYPE rejection_category_enum AS ENUM (
'data_mismatch', 'missing_info', 'duplicate', 'technical', 'identity'
);
-- E-file submissions (S10-001)
CREATE TABLE efile_submission (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tax_return_id UUID NOT NULL REFERENCES tax_return(id) ON DELETE CASCADE,
client_id UUID NOT NULL REFERENCES client(id) ON DELETE CASCADE,
jurisdiction efile_jurisdiction_enum NOT NULL,
state_code VARCHAR(2),
status efile_status_enum NOT NULL DEFAULT 'ready',
sureprep_tracking_id VARCHAR(100),
ultratax_confirmation VARCHAR(100),
irs_confirmation VARCHAR(100),
submitted_at TIMESTAMP WITH TIME ZONE,
transmitted_at TIMESTAMP WITH TIME ZONE,
accepted_at TIMESTAMP WITH TIME ZONE,
rejected_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(tax_return_id, jurisdiction, state_code)
);
CREATE INDEX idx_efile_submission_return ON efile_submission(tax_return_id);
CREATE INDEX idx_efile_submission_status ON efile_submission(status);
CREATE INDEX idx_efile_submission_pending ON efile_submission(status)
WHERE status IN ('ready', 'pending', 'transmitted');
-- E-file rejections (S10-004)
CREATE TABLE efile_rejection (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
submission_id UUID NOT NULL REFERENCES efile_submission(id) ON DELETE CASCADE,
tax_return_id UUID NOT NULL REFERENCES tax_return(id) ON DELETE CASCADE,
error_code VARCHAR(20) NOT NULL,
error_message TEXT NOT NULL,
category rejection_category_enum NOT NULL,
auto_correctable BOOLEAN NOT NULL DEFAULT FALSE,
resolution_notes TEXT,
resolved_at TIMESTAMP WITH TIME ZONE,
resolved_by UUID REFERENCES staff_user(id),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_efile_rejection_submission ON efile_rejection(submission_id);
CREATE INDEX idx_efile_rejection_unresolved ON efile_rejection(resolved_at)
WHERE resolved_at IS NULL;
Phase 10: Code Audit Checklist¶
After completing S10 implementation, verify:
Pattern Compliance¶
- All files have docstrings with story references (S10-00X)
- Result dataclasses use ok/fail factory methods
- Constructor dependency injection for all workflows
- structlog logging with bound context
- Section headers (=====) separating major code sections
- Optional AuditService with conditional
if self._audit: - Error handling: try/except with log.error()
Integration Points¶
- SurePrepService e-file status methods added
- Webhook handler for SurePrep e-file events
- NotificationWorkflow for acceptance/rejection emails
- TaxReturnRepository for status updates
- Audit logging for all status changes
Domain Completeness¶
- EFileStatus enum covers all states
- RejectionCategory covers IRS error types
- REJECTION_CODE_CATEGORIES mapping is comprehensive
- State code validation for state filings
Test Coverage¶
- Domain entity tests (enums, methods, categorization)
- Workflow tests (status transitions, notifications)
- Webhook handler tests (signature verification, event routing)
- Repository tests (CRUD, status queries)
- All tests passing before commit
Documentation Sync¶
- ARCHITECTURE.md updated with S10 files
- backlog.md S10 marked complete
- Presentation updated (55% progress, 44 stories)
- USER_STORIES.md unchanged (already updated)
Test Coverage Summary¶
| Component | Unit | Integration | E2E |
|---|---|---|---|
| EFilingDomain | 30+ | - | - |
| EFilingRepository | - | 15+ | - |
| EFilingWorkflow | 40+ | 10 | 3 |
| EFilingAPI | 15+ | - | 5 |
| Webhooks | 10+ | 5 | 2 |
Estimated new tests: ~100
Ready to Implement¶
Approval requested to proceed with parallel execution: - Track A: Phase 1 (Domain) → Phase 3 (Repository) → Phase 5 (Workflow) - Track B: Phase 2 (SurePrep methods) → Phase 4 (Webhooks) - Track C: Phase 6 (Unit tests in background)
Estimated implementation: 4 stories, ~100 tests