# Task Recon: Implement CSV Parsing with BulkAssignRow Dataclass

**Task ID:** 26.1
**Status:** pending
**Generated:** 2026-01-29
**Agent:** claude-opus-4-5-20251101

---

## Task Summary

Create the BulkAssignRow and BulkValidationResult dataclasses and implement the `parse_assign_csv` method for reading bulk assignment CSV files. This method will parse CSV files containing user/phone number/location data, validate required columns, track row numbers for error reporting, and collect parsing errors and warnings per row.

### 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 parent task encompasses CSV parsing, async validation against services (user existence, number availability, location validity), and results file generation.
**This Subtask's Role:** Foundation for bulk operations - provides the data structures (`BulkAssignRow`, `BulkValidationResult`) and parsing logic that subsequent subtasks (26.2, 26.3) will use for validation and results writing.

### Dependencies
- Depends on: Task 10 (Phone Number Utilities), Task 15 (LocationService), Task 17 (UserService), Task 41 (Validation Checkpoint 3)
- Note: These are parent task dependencies. Subtask 26.1 has no direct subtask dependencies.

### Acceptance Criteria
From task details:
1. Create `BulkAssignRow` dataclass with fields: row_number (int), user (str), phone_number (str), location (str | None), errors (list[str]), warnings (list[str])
2. Create `BulkValidationResult` dataclass with fields: rows (list[BulkAssignRow]), valid_count (int), error_count (int), warning_count (int)
3. Implement `parse_assign_csv(file_path: Path) -> list[BulkAssignRow]` that:
   - Uses Python csv.DictReader with utf-8-sig encoding (matching CacheManager pattern)
   - Validates required columns exist: 'user', 'phone_number'
   - Treats 'location' column as optional
   - Creates BulkAssignRow for each row with 1-indexed row_number for user-facing display
   - Collects parsing errors per row in errors list
   - Generates warnings for empty optional fields
   - Handles csv.Error exceptions gracefully with row context
4. Raise ValidationError for missing required columns in header

---

## Insights from Previous Tasks

### Dependency Task Notes
| Task ID | Title | Key Insight |
|---------|-------|-------------|
| 10 | Implement Phone Number Utilities | ValidationError accepts `remediation` kwarg; use `from teams_phone.exceptions import ValidationError` |
| 15 | Implement LocationService | CSV boolean conversion pattern: CSV returns strings, convert to bool before Pydantic. Use `dict[str, Any]` for modified rows. CacheManager CSV methods return empty list when file doesn't exist (graceful degradation) |
| 17 | Implement UserService | TYPE_CHECKING import pattern for avoiding circular imports. `_strip_odata_metadata()` helper pattern for cleaning API responses |
| 41 | Validation Checkpoint 3 | Write operations tested successfully. Unicode encoding fallback implemented for Windows console |

### Reusable Patterns from Dependencies
- CSV reading pattern with `utf-8-sig` encoding: `CacheManager._read_csv()` at `src/teams_phone/infrastructure/cache_manager.py:150-174`
- ValidationError with remediation: `src/teams_phone/exceptions.py:85-95`
- Service class structure with CacheManager injection: `src/teams_phone/services/location_service.py:20-36`

### Warnings and Gotchas
- **From Task 15**: CSV fields come as strings from DictReader - any type conversion must be explicit
- **From Task 10**: ValidationError should include `remediation` for user guidance
- **From Task 17**: Import order matters to avoid circular imports

### Open Items Inherited
- [ ] Consider adding `phonenumbers` library for sophisticated international formatting (from Task 10) - not relevant to this subtask

---

## Architecture Context

### Relevant Design Decisions
- Services are the business logic layer, abstracting data access
- CSV reading uses utf-8-sig encoding to handle PowerShell BOM exports
- Pydantic strict mode used for models - but BulkAssignRow/BulkValidationResult should be dataclasses per task spec
- Graceful degradation pattern: return empty list if CSV missing/malformed

### Module Structure
New file `src/teams_phone/services/bulk_operations.py` will be created:
- Sits in services layer alongside `location_service.py`, `user_service.py`, `number_service.py`
- Following pattern: imports from exceptions, models, and infrastructure
- Should NOT be added to `services/__init__.py` exports if there are circular import concerns (follow UserService pattern)

### Conventions to Follow
- Google-style docstrings with Args, Returns, Raises sections
- Type hints with PEP 604 union syntax (`str | None` not `Optional[str]`)
- `@pytest.mark.unit` decorator on all unit tests
- Test class organization: `TestClassName` for each method/feature
- Use `from __future__ import annotations` for forward references

---

## Related Code References

### Primary References (Must Read)
| File | Lines | Purpose |
|------|-------|---------|
| `src/teams_phone/infrastructure/cache_manager.py` | 150-174 | CSV reading pattern with utf-8-sig encoding, graceful error handling |
| `src/teams_phone/exceptions.py` | 85-95 | ValidationError definition with remediation |
| `src/teams_phone/services/location_service.py` | 1-55 | Service class structure, CSV row processing pattern |

### Secondary References (Helpful Context)
| File | Lines | Purpose |
|------|-------|---------|
| `src/teams_phone/utils.py` | 1-42 | ValidationError usage pattern with remediation |
| `tests/unit/test_location_service.py` | 1-100 | Test patterns: tmp_path fixture, CSV file creation, pytest.mark.unit |
| `tests/unit/test_cache_manager.py` | 1-85 | Test patterns: CSV testing with tmp_path |

### Code Patterns to Follow

```python
# CSV reading pattern from cache_manager.py:150-174
def _read_csv(self, filename: str) -> list[dict[str, str]]:
    csv_path = self._cache_dir / filename

    if not csv_path.exists():
        return []

    try:
        with csv_path.open(encoding="utf-8-sig") as f:
            reader = csv.DictReader(f)
            return list(reader)
    except csv.Error:
        return []
```

```python
# ValidationError usage pattern from utils.py:36-39
raise ValidationError(
    f"Invalid phone number: '{number}' has fewer than 10 digits",
    remediation="Provide a valid phone number with at least 10 digits.",
)
```

```python
# Service class pattern from location_service.py:20-36
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from teams_phone.exceptions import NotFoundError
# ... class LocationService with __init__ accepting dependencies
```

---

## Dependencies

### Internal Modules
- `teams_phone.exceptions.ValidationError` - for raising header validation errors
- `pathlib.Path` - for file path handling (already used across codebase)
- `csv` - standard library CSV module (already used in cache_manager.py)
- `dataclasses.dataclass` - for BulkAssignRow and BulkValidationResult

### External Packages
- No new external packages required - all dependencies already in pyproject.toml

### New Dependencies Needed
- None required

---

## Testing Guidance

### Test File Location
`tests/unit/test_bulk_operations.py` (new file)

### Test Patterns in Use
```python
# Test structure from tests/unit/test_location_service.py:19-28
class TestLocationServiceInit:
    """Tests for LocationService initialization."""

    @pytest.mark.unit
    def test_init_stores_cache_manager(self, tmp_path: Path) -> None:
        """Constructor stores the cache_manager instance."""
        cache_manager = CacheManager(cache_dir=str(tmp_path))
        service = LocationService(cache_manager)
        assert service.cache_manager is cache_manager
```

```python
# CSV test data pattern from tests/unit/test_location_service.py:14-16
LOCATIONS_CSV = """locationId,civicAddressId,description,...
93cb8a70-b4af-41df-9928-d07607e21776,a1b2c3d4-...,Main Office,..."""
```

### Fixtures Available
- `tmp_path` - pytest built-in fixture for temporary directories (used extensively)
- No project-specific fixtures in conftest.py for services

### Test Utilities
- `tests/conftest.py` - defines markers: unit, integration, contract
- Write CSV files using `(tmp_path / "filename.csv").write_text(content, encoding="utf-8")`

### Test Cases to Implement (from task details)
1. Valid CSV parsing - all fields present
2. Missing required columns detection (raise ValidationError)
3. UTF-8 BOM handling (utf-8-sig encoding)
4. Empty file handling
5. Malformed rows handling
6. Optional location column handling (present vs absent)
7. Row number tracking verification (1-indexed)
8. Empty required field values collected as errors

---

## Risks and Blockers

### Technical Risks
- [ ] **Dataclass vs Pydantic decision**: Task spec says dataclass, but project uses Pydantic extensively. Recommend using dataclass as specified since these are internal data structures, not API contracts.
- [ ] **Mutable default arguments**: `errors: list[str]` and `warnings: list[str]` in dataclass need `field(default_factory=list)` to avoid shared mutable default bug.

### Dependency Risks
- [ ] None - all dependencies (Tasks 10, 15, 17, 41) are marked as done

### Open Questions
- [ ] Should `parse_assign_csv` be a standalone function or method on a class? Task spec shows it as a method on `BulkOperations` class, but subtask 26.2 defines the class. Recommend: Create as standalone function or static method for subtask 26.1, then integrate into BulkOperations class in subtask 26.2.
- [ ] Should empty string values in required columns (user, phone_number) be treated as errors? Recommend: Yes, add to row.errors list.

---

## Implementation Recommendations

### Suggested Approach
1. Create `src/teams_phone/services/bulk_operations.py` with dataclass definitions
2. Define `BulkAssignRow` dataclass with `field(default_factory=list)` for list fields
3. Define `BulkValidationResult` dataclass
4. Implement `parse_assign_csv()` function following cache_manager CSV pattern
5. Add header validation to raise ValidationError if required columns missing
6. Add row-level error collection for empty required fields
7. Add warning generation for empty optional location field
8. Create test file with comprehensive coverage

### Files to Create/Modify
| Action | File | Reason |
|--------|------|--------|
| Create | `src/teams_phone/services/bulk_operations.py` | New module for bulk operations |
| Create | `tests/unit/test_bulk_operations.py` | Unit tests for parse_assign_csv |

### Quick Wins
- Start with dataclass definitions - they're simple and testable
- Implement basic parsing without validation first, then add validation logic
- Use existing CSV test patterns from test_location_service.py

---

## Cheat Sheet

### Key Imports
```python
from __future__ import annotations

import csv
from dataclasses import dataclass, field
from pathlib import Path

from teams_phone.exceptions import ValidationError
```

### Key Functions to Use
- `csv.DictReader(f)` - returns dict[str, str] per row, with keys from header
- `csv_path.open(encoding="utf-8-sig")` - handles UTF-8 with BOM
- `dataclass(field(default_factory=list))` - safe mutable defaults

### Common Patterns
- CSV reading: See `cache_manager.py:150-174`
- ValidationError with remediation: See `utils.py:36-39`
- Service test structure: See `test_location_service.py:19-28`
- CSV test data: See `test_location_service.py:14-16`

### Dataclass Definition Pattern
```python
from dataclasses import dataclass, field

@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)
```

---

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