# Task Recon: Implement search_numbers with wildcard pattern matching

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

---

## Task Summary

Implement the `search_numbers` method in `NumberService` that supports wildcard pattern matching (206*, *1234, *555*) using client-side filtering with `utils.match_pattern`.

### Parent Task Context

**Parent Task:** Implement NumberService Core Operations (ID: 19)
**Parent Goal:** Create the NumberService for listing, searching, and retrieving phone number inventory from Graph API with support for filtering, pagination, and pattern matching.
**This Subtask's Role:** This is the final subtask (19.3) that completes the NumberService by adding search functionality. The list_numbers (19.1) and get_number (19.2) methods are already implemented. This subtask builds on list_numbers to provide pattern-based searching.

### Dependencies
- Depends on: 19.1 (list_numbers - completed)

### Acceptance Criteria
From task details:
1. Accept pattern parameter (e.g., '206*', '*1234', '555')
2. Use list_numbers() to fetch all numbers (no server-side pattern filtering available in Graph API)
3. Apply client-side filtering using utils.match_pattern() for each number's telephone_number field
4. Handle pattern types:
   - Prefix match: '206*' matches numbers containing 206 at any position (after normalization)
   - Suffix match: '*1234' matches numbers ending with 1234
   - Contains: '*555*' matches numbers containing 555 anywhere
   - Exact substring: '1234' matches numbers containing that sequence
5. Return list[NumberAssignment] of matching numbers
6. Consider adding max_results parameter (default 100) to limit returned results

---

## Insights from Previous Tasks

### Dependency Task Notes

| Task ID | Title | Key Insight |
|---------|-------|-------------|
| 19.1 | Implement list_numbers | City filter is client-side; method signature follows UserService.list_users pattern |
| 19.2 | Implement get_number | format_for_update used for E.164 normalization; async iterator early return pattern |

### Reusable Patterns from Dependencies
- **OData filter construction**: See `number_service.py:81-90` for filter building pattern
- **Client-side filtering**: See `number_service.py:98-100` for city filter pattern - same approach needed for pattern matching
- **Async iterator pattern**: See `number_service.py:92-101` for iterating GraphClient.get_paginated results

### Warnings and Gotchas
- **Performance concern**: search_numbers fetches ALL numbers via list_numbers(), then filters client-side. For large inventories this may be slow. Consider documenting this behavior.
- **max_items interaction**: If caller passes max_items to list_numbers, pattern filtering happens AFTER pagination limits, potentially returning fewer results than expected.
- **Pattern edge cases**: Empty pattern should return False per utils.match_pattern; "*" alone matches everything.

### Open Items Inherited
- [x] 19.2: Implement `get_number` method (COMPLETED)
- [ ] 19.3: Implement `search_numbers` method with wildcard pattern support (THIS TASK)

---

## Architecture Context

### Relevant Design Decisions
- **Client-side pattern matching**: Graph API does not support wildcard/regex filtering on telephoneNumber field. All pattern matching must be done client-side.
- **Repository pattern**: Services abstract Graph API access; they do not expose HTTP details to callers.
- **Pydantic strict mode**: All API responses validated against strict models to detect contract changes early.

### Module Structure
```
src/teams_phone/
├── services/
│   └── number_service.py  # NumberService with list_numbers, get_number, search_numbers
├── utils.py               # match_pattern function for wildcard matching
└── models/
    └── number.py          # NumberAssignment model
```

### Conventions to Follow
- Use keyword-only arguments after positional args (see `list_numbers` signature with `*,`)
- Return `list[Model]` not generators (consistent with existing methods)
- Include comprehensive docstring with Args, Returns, Raises sections
- Use `from __future__ import annotations` for type hints

---

## Related Code References

### Primary References (Must Read)

| File | Lines | Purpose |
|------|-------|---------|
| `C:\Users\frank\Projects\teams-phone\src\teams_phone\services\number_service.py` | 37-103 | list_numbers implementation - will be called by search_numbers |
| `C:\Users\frank\Projects\teams-phone\src\teams_phone\utils.py` | 126-172 | match_pattern function to use for filtering |
| `C:\Users\frank\Projects\teams-phone\src\teams_phone\services\user_service.py` | 122-173 | search_users implementation pattern (similar purpose) |

### Secondary References (Helpful Context)

| File | Lines | Purpose |
|------|-------|---------|
| `C:\Users\frank\Projects\teams-phone\tests\unit\test_number_service.py` | 1-539 | Existing test patterns for NumberService |
| `C:\Users\frank\Projects\teams-phone\tests\unit\test_utils.py` | 185-260 | Tests for match_pattern function - shows expected behavior |
| `C:\Users\frank\Projects\teams-phone\src\teams_phone\models\number.py` | 132-185 | NumberAssignment model definition |

### Code Patterns to Follow

```python
# Example: client-side filtering pattern from list_numbers (number_service.py:98-100)
if city is not None and number.city != city:
    continue
numbers.append(number)
```

```python
# Example: match_pattern usage from utils.py:126-172
def match_pattern(number: str, pattern: str) -> bool:
    """Check if a phone number matches a search pattern.

    Supports wildcards for flexible searching:
    - `*` matches any sequence of characters
    - Exact digit sequences match those digits anywhere in the number
    - Patterns are matched against the normalized (digits-only) number
    """
    # Normalizes number, converts pattern wildcards to regex
    ...
```

```python
# Example: search method signature from UserService (user_service.py:122-127)
async def search_users(
    self,
    query: str,
    *,
    max_results: int = 25,
) -> list[UserConfiguration]:
```

---

## Dependencies

### Internal Modules
- `teams_phone.utils.match_pattern` - Pattern matching utility (ALREADY IMPLEMENTED)
- `teams_phone.models.NumberAssignment` - Return type model
- `teams_phone.services.number_service.NumberService.list_numbers` - Data source method

### External Packages
- No new external dependencies needed

### New Dependencies Needed
- None required

---

## Testing Guidance

### Test File Location
`C:\Users\frank\Projects\teams-phone\tests\unit\test_number_service.py`

### Test Patterns in Use
```python
# Example test structure from test_number_service.py:356-379
@pytest.mark.unit
@pytest.mark.asyncio
async def test_get_number_returns_number_assignment(
    self, mock_graph_client: MagicMock
) -> None:
    """get_number returns NumberAssignment object."""
    number_data = make_number_dict("num-1", "+12065551234")

    async def mock_paginated(
        *args: Any, **kwargs: Any
    ) -> AsyncIterator[dict[str, Any]]:
        yield number_data

    mock_graph_client.get_paginated = mock_paginated

    service = NumberService(mock_graph_client)
    result = await service.get_number("+12065551234")

    assert isinstance(result, NumberAssignment)
```

### Fixtures Available
- `mock_graph_client` - MagicMock for GraphClient (test_number_service.py:54-57)
- `make_number_dict()` - Helper to create test data dicts (test_number_service.py:16-51)

### Test Utilities
- `tests/conftest.py` - Custom pytest markers (unit, integration, contract)

### Test Cases to Implement
From task test strategy:
1. Test prefix patterns (206*)
2. Test suffix patterns (*1234)
3. Test contains patterns (*555*)
4. Test exact substring patterns
5. Test empty pattern handling
6. Test no matches case
7. Verify match_pattern utility integration
8. Test max_results parameter limiting

---

## Risks and Blockers

### Technical Risks
- [x] **Performance for large inventories**: search_numbers fetches all numbers then filters. For tenants with 10,000+ numbers this could be slow. Mitigation: Document this behavior; consider adding early-exit when max_results reached.

### Dependency Risks
- [x] **list_numbers dependency**: Already implemented and tested in 19.1 - no blockers.

### Open Questions
- [ ] Should search_numbers support combining pattern with other filters (status, type, city)? The CLI spec shows `--status` option for `numbers search`. Consider adding filter parameters or note as future enhancement.
- [ ] Should empty pattern return all numbers or empty list? Current match_pattern returns False for empty patterns, so empty pattern would return no results.

---

## Implementation Recommendations

### Suggested Approach
1. Add `search_numbers` method to NumberService class after `get_number`
2. Accept `pattern: str` and optional `max_results: int = 100` parameters
3. Import `match_pattern` from utils
4. Call `await self.list_numbers()` to get all numbers
5. Filter using list comprehension with `match_pattern(n.telephone_number, pattern)`
6. Return filtered list, limited to `max_results`
7. Add comprehensive unit tests following existing patterns

### Files to Create/Modify
| Action | File | Reason |
|--------|------|--------|
| Modify | `C:\Users\frank\Projects\teams-phone\src\teams_phone\services\number_service.py` | Add search_numbers method |
| Modify | `C:\Users\frank\Projects\teams-phone\tests\unit\test_number_service.py` | Add tests for search_numbers |

### Quick Wins
- Method is straightforward: call list_numbers, filter with match_pattern, return limited results
- match_pattern already handles all pattern complexity (wildcards, normalization)
- Existing test infrastructure makes test implementation fast

---

## Cheat Sheet

### Key Imports
```python
from teams_phone.utils import match_pattern
from teams_phone.models import NumberAssignment
```

### Key Functions to Use
- `match_pattern(number: str, pattern: str) -> bool` - Check if number matches pattern
- `await self.list_numbers()` - Fetch all numbers from API

### Common Patterns
- Client-side filtering: See `number_service.py:98-100`
- Search method signature: See `user_service.py:122-127`
- Async test with mock: See `test_number_service.py:356-379`

### Method Signature Template
```python
async def search_numbers(
    self,
    pattern: str,
    *,
    max_results: int = 100,
) -> list[NumberAssignment]:
    """Search phone numbers by wildcard pattern.

    Fetches all numbers and applies client-side pattern matching.
    Supports wildcards: '206*' (prefix), '*1234' (suffix), '*555*' (contains).

    Args:
        pattern: Search pattern with optional wildcards (*).
        max_results: Maximum number of results to return (default 100).

    Returns:
        List of NumberAssignment objects matching the pattern.

    Raises:
        ContractError: If API response doesn't match expected schema.
        AuthenticationError: If authentication fails.
    """
```

---

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