# Task Recon: Implement Continue-on-Error Logic, Default Location Fallback, and Results File Output

**Task ID:** 27.3
**Status:** pending
**Generated:** 2026-01-29T12:00:00Z
**Agent:** claude-opus-4-5-20251101

---

## Task Summary

Implement error handling with continue-on-error semantics, default location resolution from tenant profile, and results file output with timestamp naming. This completes the bulk-assign command by adding:
1. Continue-on-error logic to handle individual row failures
2. Default location fallback from tenant profile configuration
3. Results file output generation with timestamp-based filenames
4. Final summary display with JSON output support

### Parent Task Context

**Parent Task:** Implement Numbers Bulk-Assign Command (ID: 27)
**Parent Goal:** Add bulk-assign command to numbers CLI with progress bar, continue-on-error, and results file output
**This Subtask's Role:** Completes the bulk-assign command by implementing error handling, default location resolution from tenant config, and results file output. This subtask builds on:
- Subtask 27.1: CSV parsing and validation phase (done)
- Subtask 27.2: Progress bar with real-time counters (done)

### Dependencies
- Depends on: 27.1 (CSV Parsing and Validation), 27.2 (Progress Bar Implementation)

### Acceptance Criteria
From task details:
1. Continue-on-error: If `--continue-on-error` flag set, log error and continue; otherwise stop and output partial results
2. Default location: Check `--default-location` option first, then tenant profile's `default_location`
3. Results file: Default `bulk-assign-results-{timestamp}.csv` using `write_results_csv()` utility
4. Final summary: Show totals (processed, succeeded, failed, skipped) and results file path
5. JSON output: Return structured object with summary and results array
6. Exit codes: 0 if all succeeded, 1 if any failures
7. Edge cases: Empty CSV, all validation failures, write permission errors

---

## Insights from Previous Tasks

### Dependency Task Notes

| Task ID | Title | Key Insight |
|---------|-------|-------------|
| 27.1 | CSV Parsing and Validation Phase | Default location applied before validation via `row.location = default_location` pattern; Exit codes: dry-run 0/1, validation failure without --continue-on-error raises ValidationError (exit 5) |
| 27.2 | Progress Bar with Real-Time Success/Fail Counts | Progress context manager access via `formatter.progress(total, description)`; task_id via `list(progress.task_ids)[0]`; BulkAssignResult status values: 'success', 'failed', 'skipped' |
| 26.3 | Results File Generation | `write_results_csv()` function exists in `bulk_operations.py`; output columns: row, user, phone_number, status, error |
| 23.1 | Assign Command | ConfigManager API: `get_default_tenant()` + `get_tenant(name)` to access tenant profile; `TenantProfile.default_location` is the field to check |

### Reusable Patterns from Dependencies

- **Default location application pattern**: See `numbers.py:1009-1012` - iterate rows and set `row.location = default_location` when `not row.location`
- **ConfigManager tenant access**: See `numbers.py:616-622` - `config_manager.get_default_tenant()` then `config_manager.get_tenant(name)` to get `TenantProfile`
- **Results file writing**: See `bulk_operations.py:179-236` - `write_results_csv(results, output_path)` utility function

### Warnings and Gotchas

- **ANSI codes in test assertions**: Even with `--no-color`, Rich adds ANSI codes around numbers. Use substring assertions like `"Successful:" in result.stdout` instead of exact matches.
- **Enum comparison**: Use `operation.status == OperationStatus.SUCCEEDED` (not string comparison)
- **Error already collected in results**: Subtask 27.2 implementation already catches `TeamsPhoneError` and records failures; need to handle continue-on-error stop logic

### Open Items Inherited

- [x] Subtask 27.2: `--output` option is accepted but results file is not yet written (current task)
- [x] TODO at line 1249: `# TODO (subtask 27.3): Write results to output file using write_results_csv()`

---

## Architecture Context

### Relevant Design Decisions

- **Layered Architecture**: CLI layer (`numbers.py`) handles user interaction and output formatting; services layer (`bulk_operations.py`) handles business logic and file I/O
- **Exit codes**: ValidationError = 5, general failure = 1, success = 0 (see `exceptions.py`)
- **Results file location**: Caller determines output path, `write_results_csv()` creates parent directories automatically

### Module Structure

```
src/teams_phone/cli/numbers.py           # bulk_assign command (lines 932-1259)
src/teams_phone/services/bulk_operations.py  # BulkAssignResult, write_results_csv
src/teams_phone/infrastructure/config_manager.py  # TenantProfile access
src/teams_phone/models/tenant.py         # TenantProfile.default_location field
```

### Conventions to Follow

- Timestamp format for filename: `%Y%m%d-%H%M%S` (e.g., `20260129-143052`)
- Error joining: Multiple errors joined with `"; "` delimiter
- Results include all rows: validation failures, processing failures, successes
- ConfigurationError on write failures (non-blocking if assignments succeeded)

---

## Related Code References

### Primary References (Must Read)

| File | Lines | Purpose |
|------|-------|---------|
| `src/teams_phone/cli/numbers.py` | 1107-1259 | Current bulk_assign execution phase - modify for continue-on-error and results file |
| `src/teams_phone/cli/numbers.py` | 1009-1012 | Existing default_location application pattern from CLI option |
| `src/teams_phone/services/bulk_operations.py` | 179-236 | `write_results_csv()` function to use for output |
| `src/teams_phone/infrastructure/config_manager.py` | 183-190 | `get_default_tenant()` method for tenant profile access |

### Secondary References (Helpful Context)

| File | Lines | Purpose |
|------|-------|---------|
| `src/teams_phone/cli/numbers.py` | 614-623 | Pattern for accessing tenant default_location via ConfigManager in assign command |
| `src/teams_phone/models/tenant.py` | 46 | `TenantProfile.default_location` field definition |
| `src/teams_phone/services/bulk_operations.py` | 61-78 | `BulkAssignResult` dataclass structure |
| `src/teams_phone/exceptions.py` | 112-121 | `ConfigurationError` for file write failures |
| `tests/unit/test_cli_numbers.py` | 1736-2579 | Existing bulk-assign tests for patterns |

### Code Patterns to Follow

```python
# Pattern 1: Accessing tenant default_location (from numbers.py:616-622)
default_tenant_name = services.config_manager.get_default_tenant()
if default_tenant_name:
    tenant_profile = services.config_manager.get_tenant(default_tenant_name)
    if tenant_profile.default_location:
        # Use tenant_profile.default_location
```

```python
# Pattern 2: Writing results file (from bulk_operations.py:179)
from teams_phone.services.bulk_operations import write_results_csv
write_results_csv(results, output_path)
# Raises ConfigurationError on permission failure
```

```python
# Pattern 3: Timestamp filename generation (standard Python)
from datetime import datetime
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
default_output = Path(f"bulk-assign-results-{timestamp}.csv")
```

---

## Dependencies

### Internal Modules

- `teams_phone.services.bulk_operations` - `write_results_csv`, `BulkAssignResult`
- `teams_phone.infrastructure.config_manager` - `ConfigManager.get_default_tenant()`, `ConfigManager.get_tenant()`
- `teams_phone.models.tenant` - `TenantProfile.default_location`
- `teams_phone.exceptions` - `ConfigurationError`

### External Packages

- `datetime` - Timestamp generation for default filename
- `pathlib.Path` - File path construction (already imported)

### New Dependencies Needed

None required - all dependencies are already imported or available in the codebase.

---

## Testing Guidance

### Test File Location

`tests/unit/test_cli_numbers.py` - Add tests to `TestNumbersBulkAssignCommand` class

### Test Patterns in Use

```python
# Example test pattern from test_cli_numbers.py:1990-2041
@pytest.mark.unit
def test_bulk_assign_default_location_applied(self, tmp_path: "Path") -> None:
    """bulk-assign --default-location applies to rows without location."""
    csv_content = "user,phone_number\njohn@contoso.com,+14255551234\n"
    csv_file = tmp_path / "test.csv"
    csv_file.write_text(csv_content)

    captured_rows: list[Any] = []

    async def capture_validation(rows: list[Any]) -> MagicMock:
        captured_rows.extend(rows)
        # ... mock setup
        return mock_result

    with patch(...):
        result = runner.invoke(app, [...])
        assert captured_rows[0].location == "Seattle-HQ"
```

### Fixtures Available

- `tmp_path` - pytest built-in for temporary directory
- CSV content constants can be defined inline (see existing tests)

### Test Utilities

- `tests/conftest.py` - Contains shared fixtures
- `runner = CliRunner()` - Typer CLI test runner (already defined in test file)
- Mock patterns for `BulkOperations`, `GraphClient`, service classes

---

## Risks and Blockers

### Technical Risks

- [ ] **Continue-on-error behavior**: Current implementation in 27.2 already catches `TeamsPhoneError` per row and continues. Need to verify the "stop on first error" path for when `--continue-on-error` is NOT set. The exception catch may need to re-raise or break the loop.
- [ ] **Results file write timing**: Should write results after processing completes, even if interrupted by Ctrl+C. Current KeyboardInterrupt handler adds "Cancelled" results but doesn't write file.
- [ ] **Partial success exit code**: Task spec says exit 1 if any failures. Current implementation at line 1252-1254 does this, but JSON output mode may need different handling.

### Dependency Risks

- None - All dependency subtasks (27.1, 27.2) are marked done

### Open Questions

- [ ] Should default location from tenant profile be validated via `LocationService.validate_location()` before applying to rows? (Yes, per task details: "Validate default location exists using LocationService")
- [ ] When both `--default-location` CLI option AND tenant profile `default_location` exist, which takes precedence? (CLI option, per task details: "Check `--default-location` option first")
- [ ] For JSON output with results, should it include the results file path? (Yes, per task details: "For JSON output: return structured object with summary and results array")

---

## Implementation Recommendations

### Suggested Approach

1. **Add import for `datetime`** and `write_results_csv` if not already imported
2. **Implement default location fallback from tenant profile**:
   - After applying `--default-location` CLI option (line 1009-1012)
   - If default_location option not provided, check tenant profile
   - Validate the resolved default location via `LocationService.validate_location()`
   - Show info message when using tenant default location
3. **Modify continue-on-error behavior**:
   - Currently errors are caught and recorded (line 1203-1213)
   - Add check: if `not continue_on_error` and error occurred, break the loop
   - Output partial results before exiting
4. **Add results file output**:
   - Generate timestamp-based default filename if `--output` not provided
   - Write results using `write_results_csv()` after processing
   - Handle `ConfigurationError` gracefully (warn but don't fail if assignments succeeded)
   - Show results file path in summary
5. **Update JSON output**:
   - Include results array with all row outcomes
   - Include summary counts and results file path

### Files to Create/Modify

| Action | File | Reason |
|--------|------|--------|
| Modify | `src/teams_phone/cli/numbers.py` | Add default location fallback, continue-on-error stop logic, results file output, JSON summary |
| Modify | `tests/unit/test_cli_numbers.py` | Add tests for new functionality |

### Quick Wins

1. **Results file output**: Add `write_results_csv()` call right after the execution summary (line 1249 has the TODO comment)
2. **Default filename generation**: Simple datetime-based string format before execution phase starts

---

## Cheat Sheet

### Key Imports

```python
from datetime import datetime
from teams_phone.services.bulk_operations import write_results_csv
# ConfigManager, BulkAssignResult already imported
```

### Key Functions to Use

- `config_manager.get_default_tenant()` - Returns tenant name or None
- `config_manager.get_tenant(name)` - Returns TenantProfile
- `TenantProfile.default_location` - Optional[str] field
- `write_results_csv(results, output_path)` - Writes CSV, raises ConfigurationError on permission error
- `location_service.validate_location(identifier)` - Validates location, raises NotFoundError if invalid

### Common Patterns

- Timestamp filename: `datetime.now().strftime("%Y%m%d-%H%M%S")`
- Default output path: `Path(f"bulk-assign-results-{timestamp}.csv")`
- Exit code pattern: `raise typer.Exit(1 if fail_count > 0 else 0)`

### Key Lines to Modify

- Line 1009-1012: After CLI default location application, add tenant profile fallback
- Line 1203-1213: Error handling - add break if not continue_on_error
- Line 1249: TODO comment - replace with write_results_csv() call
- Line 1241-1248: Summary display - add results file path

---

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