# Story 1.5: Health Check Endpoint

Status: done

## Story

As an **API consumer**,
I want **a health check endpoint**,
So that **I can verify the service is running and dependencies are healthy**.

## Acceptance Criteria

1. **AC1:** GET /health returns 200 response with HealthResponse schema:
   - `status`: "healthy" | "degraded" | "unhealthy"
   - `version`: Application version (e.g., "1.0.0")
   - `dependencies`: Object with health status of each dependency

2. **AC2:** Each dependency check returns DependencyHealth schema:
   - `status`: "healthy" | "unhealthy"
   - `latency_ms`: Integer response time in milliseconds
   - `error`: Optional error message when unhealthy

3. **AC3:** PostgreSQL health check:
   - Executes `SELECT 1` query
   - Returns healthy if query succeeds
   - Returns unhealthy with error message on failure

4. **AC4:** Redis health check:
   - Executes `PING` command
   - Returns healthy if response is "PONG"
   - Returns unhealthy with error message on failure

5. **AC5:** Milvus health check:
   - Calls `list_collections()` API
   - Returns healthy if call succeeds
   - Returns unhealthy with error message on failure

6. **AC6:** Status aggregation logic:
   - All dependencies healthy → status: "healthy"
   - Any dependency unhealthy → status: "degraded"
   - Database (critical) unhealthy → return HTTP 503

7. **AC7:** Performance requirements:
   - Total response time < 500ms
   - Individual dependency timeout: 300ms each
   - Concurrent dependency checks for efficiency

8. **AC8:** No authentication required for health endpoint

## Tasks / Subtasks

- [x] **Task 1: Create Pydantic response models** (AC: 1, 2)
  - [x] Create `src/api/schemas/health.py`
  - [x] Define `DependencyHealth` model with status, latency_ms, error fields
  - [x] Define `HealthResponse` model with status, version, dependencies fields
  - [x] Add Literal types for status values

- [x] **Task 2: Create health check service module** (AC: 3, 4, 5, 7)
  - [x] Create `src/services/health.py` module
  - [x] Implement `check_postgres()` async function (SELECT 1)
  - [x] Implement `check_redis()` async function (PING)
  - [x] Implement `check_milvus()` async function (list_collections)
  - [x] Add 300ms timeout to each check
  - [x] Return DependencyHealth for each check

- [x] **Task 3: Create Redis and Milvus client dependencies** (AC: 4, 5)
  - [x] Add Redis async client setup in `src/api/dependencies.py` or dedicated module
  - [x] Add Milvus client setup using milvus_client library
  - [x] Ensure clients are reusable (connection pooling)

- [x] **Task 4: Update health endpoint implementation** (AC: 1, 6, 7, 8)
  - [x] Update `src/api/routes/health.py` with full implementation
  - [x] Run all dependency checks concurrently with `asyncio.gather()`
  - [x] Aggregate results into HealthResponse
  - [x] Return 503 if database is unhealthy
  - [x] Return 200 with status "degraded" if non-critical dependencies fail

- [x] **Task 5: Verify health check endpoint** (AC: 1-8)
  - [x] Start all dependencies (PostgreSQL, Redis, Milvus)
  - [x] Test GET /health returns healthy status with all latencies
  - [x] Stop Redis, verify response shows degraded status
  - [x] Stop PostgreSQL, verify 503 response
  - [x] Verify response time < 500ms

## Dev Notes

### Architecture Patterns and Constraints

- **Response Models:** Use Pydantic models with Literal types for status enum
- **Concurrency:** Use `asyncio.gather()` to run checks in parallel
- **Timeouts:** 300ms per check, 500ms total max
- **Critical vs Non-Critical:** Database is critical (503 on failure), Redis/Milvus are non-critical (degraded)

### Project Structure Notes

From previous stories, the following exist:
- `src/api/routes/health.py` - Health endpoint shell (needs full implementation)
- `src/db/session.py` - Async SQLAlchemy engine and session
- `src/core/config.py` - Settings with redis_url, milvus_url

Files to create/modify in this story:
- `src/api/schemas/health.py` - NEW: Pydantic models for health response
- `src/services/health.py` - NEW: Health check service functions
- `src/api/routes/health.py` - MODIFY: Full implementation
- `src/api/dependencies.py` - MODIFY: Add Redis/Milvus client dependencies

### Learnings from Previous Story

**From Story 1-4-structured-logging-setup (Status: done)**

- **structlog Configured:** Application uses structlog with JSON format in production, pretty console in debug
- **Request Tracing:** request_id available via `request.state.request_id` and bound to structlog context
- **Logging Pattern:** Use `logger.info("event_name", key=value)` format for all logs
- **Package Manager:** Project uses `uv` - use `uv add redis milvus-client` if needed

[Source: docs/sprint-artifacts/1-4-structured-logging-setup.md#Dev-Agent-Record]

### References

- [Source: docs/sprint-artifacts/tech-spec-epic-1.md#APIs-and-Interfaces] - HealthResponse model specification
- [Source: docs/sprint-artifacts/tech-spec-epic-1.md#Workflows-and-Sequencing] - Health check sequence diagram
- [Source: docs/sprint-artifacts/tech-spec-epic-1.md#Non-Functional-Requirements] - 500ms response time target
- [Source: docs/epics.md#Story-1.5] - Original story definition

## Dev Agent Record

### Context Reference

N/A - Story context will be generated by story-context workflow

### Agent Model Used

Claude Opus 4.5 (claude-opus-4-5-20251101)

### Debug Log References

- Healthy response (all deps up): `{"status":"healthy","version":"1.0.0","dependencies":{"database":{"status":"healthy","latency_ms":5},"redis":{"status":"healthy","latency_ms":1},"milvus":{"status":"healthy","latency_ms":4}}}`
- Response time: 292ms (< 500ms target)
- X-Request-ID header verified: `x-request-id: 6c998c26-f30a-407d-8c99-335bf6488778`
- Degraded response (Redis down): `{"status":"degraded",...,"redis":{"status":"unhealthy","error":"Connection refused"}}`
- Unhealthy response (PostgreSQL down): HTTP 503 with `{"status":"unhealthy",...,"database":{"status":"unhealthy","error":"Connection refused"}}`

### Completion Notes List

- Created `src/api/schemas/health.py` with `DependencyHealth` and `HealthResponse` Pydantic models using Literal types
- Created `src/services/health.py` with `check_postgres()`, `check_redis()`, `check_milvus()` async functions (300ms timeout each)
- Added `aggregate_health_status()` function for status aggregation logic
- Added `RedisDep` and `MilvusDep` dependencies in `src/api/dependencies.py` with singleton pattern for connection reuse
- Updated `src/api/routes/health.py` with full implementation using `asyncio.gather()` for concurrent checks
- All 8 acceptance criteria verified:
  - AC1: HealthResponse schema with status/version/dependencies ✓
  - AC2: DependencyHealth schema with status/latency_ms/error ✓
  - AC3: PostgreSQL SELECT 1 health check ✓
  - AC4: Redis PING health check ✓
  - AC5: Milvus get_collections health check ✓
  - AC6: Status aggregation (healthy/degraded/503) ✓
  - AC7: Performance < 500ms with concurrent checks ✓
  - AC8: No authentication required ✓

### File List

| Status | File Path |
|--------|-----------|
| NEW | src/api/schemas/__init__.py |
| NEW | src/api/schemas/health.py |
| NEW | src/services/__init__.py |
| NEW | src/services/health.py |
| MODIFIED | src/api/routes/health.py |
| MODIFIED | src/api/dependencies.py |
