# Task Recon: Implement Results File Generation

**Task ID:** 26.3
**Status:** pending
**Generated:** 2026-01-29T23:45:00Z
**Agent:** claude-opus-4-5-20251101

---

## Task Summary

Implement the `write_results_csv` method to output bulk operation results with success/failure status, error details, and original row data. This is the final subtask of the bulk CSV parsing and validation feature (Task 26).

### Parent Task Context

**Parent Task:** Implement Bulk CSV Parsing and Validation (ID: 26)
**Parent Goal:** Create utilities for parsing and validating bulk operation CSV files with dry-run support. The BulkOperations class provides the infrastructure for bulk phone number assignments from CSV input.
**This Subtask's Role:** Completes the bulk operations workflow by providing output file generation. After bulk operations execute, results need to be written to a CSV file for user review, showing which rows succeeded, failed, or were skipped with detailed error messages.

### Dependencies
- Depends on: 26.1 (CSV Parsing - DONE)

### Acceptance Criteria
From task details:
1. Define `BulkAssignResult` dataclass with fields: row_number (int), user (str), phone_number (str), status (str - 'success'|'failed'|'skipped'), error (str | None)
2. Implement `write_results_csv(results: list[BulkAssignResult], output_path: Path) -> None`
3. Opens file with utf-8 encoding (no BOM for output)
4. Uses csv.DictWriter with fieldnames: row, user, phone_number, status, error
5. Writes header row and all result rows
6. Handles Path creation if parent directory doesn't exist
7. Raises ConfigurationError if write fails due to permission issues
8. Ensure consistent field ordering in output CSV
9. Map result statuses to human-readable values ('success', 'failed', 'skipped')
10. Concatenate multiple errors with semicolon delimiter if needed

---

## Insights from Previous Tasks

### Dependency Task Notes
| Task ID | Title | Key Insight |
|---------|-------|-------------|
| 26.1 | CSV Parsing with BulkAssignRow | Used `field(default_factory=list)` for mutable defaults; UTF-8 with BOM support (`utf-8-sig`) for reading; 1-indexed row numbers starting at 2 (after header) |
| 26.2 | Async Validation Logic | Added `BulkOperations` class; use `TYPE_CHECKING` guard for circular import prevention |

### Reusable Patterns from Dependencies
- **CSV encoding pattern**: Input uses `utf-8-sig` for BOM handling, output uses plain `utf-8` (no BOM)
- **Dataclass with defaults**: See `BulkAssignRow` in `bulk_operations.py:24-41` for `field(default_factory=list)`
- **Error collection pattern**: Rows accumulate errors in lists; for output, join with semicolon

### Warnings and Gotchas
- **Circular import still exists**: Run tests via `pytest tests/unit/` not `pytest tests/unit/test_bulk_operations.py` alone
- **Row numbers start at 2**: Match the input CSV row numbering (header = row 1)
- **Output column name**: Task specifies `row` (not `row_number`) in output CSV

### Open Items Inherited
- None - this is the final subtask of Task 26

---

## Architecture Context

### Relevant Design Decisions
- **ConfigurationError for file system issues**: All permission/write errors should raise `ConfigurationError` with remediation guidance (exit code 7)
- **UTF-8 without BOM for output**: PowerShell exports include BOM, but CLI output should be clean UTF-8
- **Semicolon delimiter for multiple errors**: When a row has multiple errors, concatenate with `;`

### Module Structure
The `write_results_csv` method should be added to `bulk_operations.py` as a standalone function (like `parse_assign_csv`), not as a method of `BulkOperations` class. The task details show it as a method signature but the pattern from 26.1 uses standalone functions.

### Conventions to Follow
- Use dataclass for `BulkAssignResult` (matching `BulkAssignRow` and `BulkValidationResult`)
- Raise `ConfigurationError` with `remediation` parameter for user-facing errors
- Use `from e` for chained exceptions to preserve traceback
- Include comprehensive docstrings with Args, Returns, Raises sections

---

## Related Code References

### Primary References (Must Read)
| File | Lines | Purpose |
|------|-------|---------|
| `src/teams_phone/services/bulk_operations.py` | 1-269 | Current module - add BulkAssignResult and write_results_csv here |
| `src/teams_phone/infrastructure/cache_manager.py` | 150-174 | `_read_csv()` pattern for CSV handling (inverse operation) |
| `src/teams_phone/infrastructure/cache_manager.py` | 54-71 | `ensure_token_dir()` pattern for directory creation with PermissionError handling |
| `src/teams_phone/exceptions.py` | 112-121 | `ConfigurationError` definition and exit code |

### Secondary References (Helpful Context)
| File | Lines | Purpose |
|------|-------|---------|
| `tests/unit/test_bulk_operations.py` | 1-787 | Existing tests - add new test class here |
| `tests/unit/test_cache_manager.py` | 73-84 | Test pattern for PermissionError mocking |
| `src/teams_phone/services/bulk_operations.py` | 23-41 | `BulkAssignRow` dataclass pattern to follow |

### Code Patterns to Follow

```python
# Dataclass pattern from bulk_operations.py:23-41
@dataclass
class BulkAssignRow:
    """A single row from a bulk assignment CSV file."""
    row_number: int
    user: str
    phone_number: str
    location: str | None
    errors: list[str] = field(default_factory=list)
    warnings: list[str] = field(default_factory=list)
```

```python
# PermissionError handling pattern from cache_manager.py:54-71
def ensure_token_dir(self) -> None:
    try:
        self._token_dir.mkdir(parents=True, exist_ok=True)
    except PermissionError as e:
        raise ConfigurationError(
            f"Cannot create token directory {self._token_dir}: {e}",
            remediation=(
                f"Check that you have write permissions to {self._token_dir}.\n"
                "You may need to create the directory manually or run with elevated privileges."
            ),
        ) from e
```

```python
# Test pattern for PermissionError from test_cache_manager.py:73-84
@pytest.mark.unit
def test_permission_error_raises_configuration_error(self, tmp_path: Path) -> None:
    """ensure_token_dir raises ConfigurationError on permission failure."""
    token_path = tmp_path / "restricted" / "tokens"
    manager = CacheManager(token_dir=str(token_path))

    with patch.object(Path, "mkdir", side_effect=PermissionError("Access denied")):
        with pytest.raises(ConfigurationError) as exc_info:
            manager.ensure_token_dir()

    assert "Cannot create" in str(exc_info.value)
    assert exc_info.value.remediation is not None
```

---

## Dependencies

### Internal Modules
- `teams_phone.exceptions.ConfigurationError` - For permission error handling
- `dataclasses.dataclass, field` - For BulkAssignResult definition
- `pathlib.Path` - For file path handling
- `csv` - For DictWriter

### External Packages
- `csv` (stdlib) - Already used in module
- `pathlib` (stdlib) - Already used in module

### New Dependencies Needed
- None required - all dependencies already imported or in stdlib

---

## Testing Guidance

### Test File Location
`tests/unit/test_bulk_operations.py`

### Test Patterns in Use
```python
# Test pattern for CSV file operations from test_bulk_operations.py:141-159
@pytest.mark.unit
def test_parses_valid_csv_with_all_columns(self, tmp_path: Path) -> None:
    """parse_assign_csv parses CSV with all columns correctly."""
    csv_file = tmp_path / "bulk.csv"
    csv_file.write_text(VALID_CSV, encoding="utf-8")

    rows = parse_assign_csv(csv_file)

    assert len(rows) == 2
    assert rows[0].row_number == 2
```

### Fixtures Available
- `tmp_path` (pytest built-in) - Temporary directory fixture for file operations
- `mock_user_service`, `mock_number_service`, `mock_location_service` - Existing fixtures (not needed for this subtask)

### Test Utilities
- `tests/conftest.py` - Marker registration only
- No shared fixtures needed for file write tests

### Required Test Cases (from test strategy)
1. Valid results file generation
2. File encoding verification (UTF-8 without BOM)
3. Directory creation for output
4. Permission error handling
5. Empty results list
6. Multiple errors per row formatting (semicolon delimiter)

---

## Risks and Blockers

### Technical Risks
- [ ] **Windows path handling**: Use `Path` consistently; `output_path.parent.mkdir()` should work cross-platform
- [ ] **File handle cleanup**: Use context manager (`with open(...)`) to ensure file handles are released

### Dependency Risks
- None - all dependencies (26.1) are complete

### Open Questions
- [ ] **Should `write_results_csv` be a standalone function or method of BulkOperations?** Task details show method signature but 26.1 used standalone function pattern. Recommend: standalone function for consistency.
- [ ] **Column order**: Task specifies `row, user, phone_number, status, error` - confirm this matches CLI output expectations

---

## Implementation Recommendations

### Suggested Approach
1. Add `BulkAssignResult` dataclass after `BulkValidationResult` (around line 59)
2. Add `write_results_csv` standalone function after `parse_assign_csv` (after line 158)
3. Handle directory creation with `output_path.parent.mkdir(parents=True, exist_ok=True)`
4. Wrap file operations in try/except for PermissionError
5. Use `csv.DictWriter` with explicit `fieldnames` for consistent column ordering
6. Join multiple errors with `"; "` delimiter
7. Add test class `TestWriteResultsCsv` with 6 test cases

### Files to Create/Modify
| Action | File | Reason |
|--------|------|--------|
| Modify | `src/teams_phone/services/bulk_operations.py` | Add BulkAssignResult dataclass and write_results_csv function |
| Modify | `tests/unit/test_bulk_operations.py` | Add TestWriteResultsCsv test class with 6 tests |

### Quick Wins
- Start with the dataclass definition (simple, matches existing pattern)
- Implement basic write_results_csv with happy path first
- Add directory creation and error handling
- Write tests in parallel with implementation

---

## Cheat Sheet

### Key Imports
```python
# Already imported in bulk_operations.py
import csv
from dataclasses import dataclass, field
from pathlib import Path

# Need to add
from teams_phone.exceptions import ConfigurationError
```

### Key Functions to Use
- `output_path.parent.mkdir(parents=True, exist_ok=True)` - Create parent directories
- `csv.DictWriter(f, fieldnames=[...])` - Write CSV with ordered columns
- `writer.writeheader()` - Write column header row
- `writer.writerow({...})` - Write data row

### Common Patterns
- **PermissionError handling**: See `cache_manager.py:54-71`
- **Dataclass definition**: See `bulk_operations.py:24-41`
- **Test with tmp_path**: See `test_cache_manager.py:73-84`

### BulkAssignResult Structure
```python
@dataclass
class BulkAssignResult:
    row_number: int
    user: str
    phone_number: str
    status: str  # 'success' | 'failed' | 'skipped'
    error: str | None = None
```

### Expected Output CSV Format
```csv
row,user,phone_number,status,error
2,user1@contoso.com,+14255551234,success,
3,user2@contoso.com,+14255555678,failed,User not found; Number not in inventory
4,user3@contoso.com,+14255559999,skipped,Missing required field: user
```

---

*Generated by task-recon (claude-opus-4-5-20251101) | Ready for implementation*
