# Task ID: 15

**Title:** Implement LocationService

**Status:** pending

**Dependencies:** 3, 5, 9

**Priority:** medium

**Description:** Create the LocationService for reading emergency locations from CSV cache with search, validation, and staleness detection.

**Details:**

Create `src/teams_phone/services/location_service.py`:
```python
class LocationService:
    def __init__(self, cache_manager: CacheManager): ...
    
    def list_locations(self) -> list[EmergencyLocation]: ...
    def get_location(self, location_id: str) -> EmergencyLocation: ...
    def search_locations(self, query: str) -> list[EmergencyLocation]:
        """Search by name, city, or address (case-insensitive)."""
        ...
    def validate_location(self, identifier: str) -> EmergencyLocation:
        """Validate location exists by ID or name. Raise NotFoundError if not."""
        ...
    def get_cache_age(self) -> timedelta | None: ...
    def is_cache_stale(self) -> bool: ...
    def get_staleness_warning(self) -> str | None:
        """Return warning message if cache > 30 days old."""
        ...
```

Graceful degradation: return empty list if CSV missing, log warning.
Search matches description, city, address fields.

**Test Strategy:**

Unit tests with CSV fixtures: list/search/get operations, staleness detection, missing file handling, validation errors for unknown locations.

---

## Implementation Notes

### Session: 2026-01-29 18:50 - Agent: claude-opus-4-5-20251101

**Status:** Implemented

#### What Was Done
- Created `src/teams_phone/services/location_service.py` with LocationService class
- Implemented all required methods:
  - `list_locations()` - reads CSV, converts string booleans, returns typed EmergencyLocation list
  - `get_location(location_id)` - returns location by ID or None
  - `search_locations(query)` - case-insensitive search on description, city, address
  - `validate_location(identifier)` - lookup by ID or description, raises NotFoundError
  - `get_cache_age()` - returns timedelta from CacheManager
  - `is_cache_stale()` - delegates to CacheManager
  - `get_staleness_warning()` - returns formatted warning message or None
- Updated `src/teams_phone/services/__init__.py` to export LocationService
- Created `tests/unit/test_location_service.py` with 25 comprehensive unit tests

#### Key Decisions Made
- **Boolean conversion pattern**: Used `dict[str, Any]` copy of row to convert `isDefault` from string to bool before model validation. CacheManager returns `dict[str, str]`, but Pydantic needs actual bool for `isDefault` field.
- **Validation by description**: `validate_location()` matches description case-insensitively after trying exact ID match.
- **Search returns first match per field**: Search checks description first, then city, then address. A location matching multiple fields is only returned once.

#### Patterns Discovered
- `EmergencyLocation.model_validate(dict)` works with field aliases (`locationId` → `location_id`) via `populate_by_name=True` config
- CacheManager CSV methods return empty list when file doesn't exist (graceful degradation)
- Service exports that might cause circular imports should have comments - followed existing pattern from UserService

#### Open Items for Next Agent
- None - task fully complete

#### Gotchas and Warnings
- CSV `isDefault` field is a string ("True"/"False"), must convert to bool before Pydantic validation
- Use `dict[str, Any]` when modifying CSV row dict to avoid mypy errors (original is `dict[str, str]`)
- One unrelated test failing (`test_output_formatter.py::TestSuccessMessage::test_success_includes_checkmark`) - pre-existing issue with unicode checkmark, not related to this task

---
