diff --git a/.env.example b/.env.example index 8cebba3..f13a58e 100644 --- a/.env.example +++ b/.env.example @@ -27,13 +27,13 @@ WEBHOOK_TIMEOUT=10 # ===== ALERT THRESHOLDS ===== # Temperature thresholds in Celsius (set to empty to disable) -# Default: 15°C (59°F) min, 27°C (80.6°F) max +# Default: 15°C (59°F) min, 32°C (90°F) max ALERT_TEMP_MIN_C=15.0 -ALERT_TEMP_MAX_C=27.0 +ALERT_TEMP_MAX_C=32.0 # Humidity thresholds in percentage (set to empty to disable) -# Default: 30% min, 70% max -ALERT_HUMIDITY_MIN=30.0 +# Default: 20% min, 70% max +ALERT_HUMIDITY_MIN=20.0 ALERT_HUMIDITY_MAX=70.0 # ===== PERIODIC STATUS UPDATES ===== diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5da9b08..890ae62 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,6 +76,12 @@ jobs: exit 1 fi + # Validate CLOUDFLARED_TOKEN (required since we deploy with cloudflare profile) + if [ -z "${CLOUDFLARED_TOKEN}" ]; then + echo "Missing required secret: CLOUDFLARED_TOKEN" + exit 1 + fi + # Create .env file with required configuration echo "BEARER_TOKEN=${BEARER_TOKEN}" > .env @@ -93,8 +99,8 @@ jobs: # Production monitoring defaults (intentionally enabled for production, # differs from local development defaults in .env.example) echo "ALERT_TEMP_MIN_C=15.0" >> .env - echo "ALERT_TEMP_MAX_C=27.0" >> .env - echo "ALERT_HUMIDITY_MIN=30.0" >> .env + echo "ALERT_TEMP_MAX_C=32.0" >> .env + echo "ALERT_HUMIDITY_MIN=20.0" >> .env echo "ALERT_HUMIDITY_MAX=70.0" >> .env echo "STATUS_UPDATE_ENABLED=true" >> .env echo "STATUS_UPDATE_INTERVAL=3600" >> .env @@ -110,4 +116,4 @@ jobs: run: mkdir -p logs - name: Deploy with Docker Compose - run: docker compose -p temp-monitor up -d --build --remove-orphans + run: docker compose -p temp-monitor --profile cloudflare up -d --build --remove-orphans diff --git a/README.md b/README.md index e50c165..260cea6 100644 --- a/README.md +++ b/README.md @@ -99,8 +99,8 @@ All configuration is done via environment variables in `.env`. Copy `.env.exampl | Variable | Default | Description | |----------|---------|-------------| | `ALERT_TEMP_MIN_C` | `15.0` | Low temperature alert (Celsius) | -| `ALERT_TEMP_MAX_C` | `27.0` | High temperature alert (Celsius) | -| `ALERT_HUMIDITY_MIN` | `30.0` | Low humidity alert (%) | +| `ALERT_TEMP_MAX_C` | `32.0` | High temperature alert (Celsius) | +| `ALERT_HUMIDITY_MIN` | `20.0` | Low humidity alert (%) | | `ALERT_HUMIDITY_MAX` | `70.0` | High humidity alert (%) | ### Periodic Status Updates diff --git a/temp_monitor.py b/temp_monitor.py index 12d7b1b..c6a2d66 100644 --- a/temp_monitor.py +++ b/temp_monitor.py @@ -117,8 +117,8 @@ alert_thresholds = AlertThresholds( temp_min_c=float(os.getenv('ALERT_TEMP_MIN_C', '15.0')) if os.getenv('ALERT_TEMP_MIN_C') else None, - temp_max_c=float(os.getenv('ALERT_TEMP_MAX_C', '27.0')) if os.getenv('ALERT_TEMP_MAX_C') else None, - humidity_min=float(os.getenv('ALERT_HUMIDITY_MIN', '30.0')) if os.getenv('ALERT_HUMIDITY_MIN') else None, + temp_max_c=float(os.getenv('ALERT_TEMP_MAX_C', '32.0')) if os.getenv('ALERT_TEMP_MAX_C') else None, + humidity_min=float(os.getenv('ALERT_HUMIDITY_MIN', '20.0')) if os.getenv('ALERT_HUMIDITY_MIN') else None, humidity_max=float(os.getenv('ALERT_HUMIDITY_MAX', '70.0')) if os.getenv('ALERT_HUMIDITY_MAX') else None ) diff --git a/thoughts/tasks/issue-24-pydantic-validation/2025-12-31-research.md b/thoughts/tasks/issue-24-pydantic-validation/2025-12-31-research.md deleted file mode 100644 index c9bdbc0..0000000 --- a/thoughts/tasks/issue-24-pydantic-validation/2025-12-31-research.md +++ /dev/null @@ -1,643 +0,0 @@ ---- -date: 2025-12-31T23:09:13Z -researcher: Claude Code -git_commit: aaec873487e0beff2c7b850a7c204112331b013f -branch: refactor/webhooks-tokens -repository: temp_monitor -topic: "Issue #24 - Pydantic for Data Validation" -tags: [research, codebase, validation, pydantic, dataclasses, webhook, flask] -status: complete -last_updated: 2025-12-31 -last_updated_by: Claude Code ---- - -# Research: Issue #24 - Pydantic for Data Validation - -**Date**: 2025-12-31T23:09:13Z -**Researcher**: Claude Code -**Git Commit**: aaec873487e0beff2c7b850a7c204112331b013f -**Branch**: refactor/webhooks-tokens -**Repository**: temp_monitor - -## Research Question - -Review GitHub Issue #24 which recommends using Pydantic for data validation. Document the current state of data validation in the codebase, the dataclass structures that would be affected, and the existing validation patterns. - -## Summary - -The codebase currently uses Python `@dataclass` decorators for structured data (`WebhookConfig`, `AlertThresholds`) with no runtime validation. API endpoints accept JSON data via `request.get_json()` with minimal existence checks. The issue proposes migrating to Pydantic to address three critical validation gaps: -- Issue #9: Missing numeric input validation in webhook config endpoint -- Issue #11: Unvalidated WebhookConfig construction -- Issue #12: Unvalidated AlertThresholds (no min < max check) - -Additionally, a Flask-RESTX skill is available in the project that provides an alternative approach using Flask-RESTX models for request validation and OpenAPI documentation. - ---- - -## Detailed Findings - -### 1. Current Dataclass Definitions - -#### WebhookConfig (`webhook_service.py:17-24`) - -```python -@dataclass -class WebhookConfig: - """Configuration for a webhook endpoint""" - url: str - enabled: bool = True - retry_count: int = 3 - retry_delay: int = 5 # seconds - timeout: int = 10 # seconds -``` - -**Current State:** -- No `__post_init__` method exists -- No URL format validation -- No range validation for numeric fields -- `url` can be an empty string -- `retry_count` can be negative -- `timeout` can be zero or negative - -#### AlertThresholds (`webhook_service.py:27-33`) - -```python -@dataclass -class AlertThresholds: - """Temperature and humidity thresholds for alerts""" - temp_min_c: Optional[float] = 15.0 # 59°F - temp_max_c: Optional[float] = 27.0 # 80.6°F - humidity_min: Optional[float] = 30.0 - humidity_max: Optional[float] = 70.0 -``` - -**Current State:** -- No `__post_init__` method exists -- No validation that `temp_min_c < temp_max_c` -- No validation that `humidity_min < humidity_max` -- Temperature values can be any float (including impossible values like -1000 C) -- Humidity can be negative or exceed 100% - ---- - -### 2. API Endpoint Validation Patterns - -#### PUT /api/webhook/config (`temp_monitor.py:416-483`) - -**Current validation:** -```python -data = request.get_json() -if not data: - return jsonify({'error': 'No data provided'}), 400 - -if 'webhook' in data: - webhook_data = data['webhook'] - if not webhook_service: - if 'url' not in webhook_data: - return jsonify({'error': 'URL required to create webhook config'}), 400 -``` - -**What is NOT validated:** -- URL format (empty string accepted) -- `enabled` type (any truthy/falsy value accepted) -- `retry_count` range (negative numbers accepted) -- `retry_delay` range (negative numbers accepted) -- `timeout` range (zero or negative accepted) -- Threshold value ranges -- `temp_min_c < temp_max_c` constraint - -#### Dataclass Instantiation (`temp_monitor.py:438-455`) - -```python -config = WebhookConfig( - url=webhook_data.get('url', webhook_service.webhook_config.url if webhook_service.webhook_config else ''), - enabled=webhook_data.get('enabled', True), - retry_count=webhook_data.get('retry_count', 3), - retry_delay=webhook_data.get('retry_delay', 5), - timeout=webhook_data.get('timeout', 10) -) - -thresholds = AlertThresholds( - temp_min_c=threshold_data.get('temp_min_c'), - temp_max_c=threshold_data.get('temp_max_c'), - humidity_min=threshold_data.get('humidity_min'), - humidity_max=threshold_data.get('humidity_max') -) -``` - -**Pattern:** Uses `dict.get()` with defaults, no type checking or range validation. - ---- - -### 3. Existing Validation Patterns in Codebase - -#### Pattern A: None/Existence Checks -- `temp_monitor.py:423-424`: Check if JSON data exists -- `temp_monitor.py:432-434`: Check if required key exists -- `webhook_service.py:69-71`: Check if webhook_config exists and is enabled - -#### Pattern B: Type Conversion from Environment Variables -- `temp_monitor.py:71-73`: `int(os.getenv('WEBHOOK_RETRY_COUNT', '3'))` -- `temp_monitor.py:77-80`: Conditional float conversion with None fallback - -#### Pattern C: Single Business Rule Validation -- `temp_monitor.py:56-62`: Validates `status_update_interval >= sampling_interval` - - Only validation that enforces a business rule - - Auto-corrects invalid values with logging - -#### Pattern D: Value Capping -- `temp_monitor.py:193-194`: Caps humidity at 100% - - Silent adjustment without warning - -#### Pattern E: Try/Except Error Handling -- `temp_monitor.py:478-483`: Catches all exceptions, returns 500 with details - ---- - -### 4. Type Annotations Usage - -**webhook_service.py:** -- Imports `Optional, Dict, Any, List` from `typing` module -- All function signatures have type hints -- Used for documentation only, no runtime enforcement - -**temp_monitor.py:** -- No type annotations on functions -- No type hints on variables - ---- - -### 5. GitHub Issue #24 Summary - -**Issue Title:** RECOMMENDATION: Use Pydantic for Data Validation - -**Labels:** bug, documentation, enhancement - -**Author:** fakebizprez - -**Created:** 2025-12-31T23:00:59Z - -**State:** OPEN - -**Key Points from Issue:** - -1. **Three Critical Validation Issues Identified:** - - Issue #9: Missing numeric input validation (`temp_monitor.py:438-455`) - - Issue #11: Unvalidated WebhookConfig (`webhook_service.py:18-24`) - - Issue #12: Unvalidated AlertThresholds (`webhook_service.py:28-33`) - -2. **Proposed Pydantic Models:** - -```python -# WebhookConfig replacement -class WebhookConfigRequest(BaseModel): - url: str - enabled: bool = True - retry_count: int = Field(ge=1, le=10, description="1-10 retries") - retry_delay: int = Field(ge=1, le=60, description="1-60 seconds") - timeout: int = Field(ge=5, le=120, description="5-120 seconds") - -# AlertThresholds replacement -class AlertThresholds(BaseModel): - temp_min_c: Optional[float] = None - temp_max_c: Optional[float] = None - humidity_min: Optional[float] = None - humidity_max: Optional[float] = None - - @validator('temp_max_c') - def min_less_than_max(cls, v, values): - if v and 'temp_min_c' in values and values['temp_min_c']: - if values['temp_min_c'] >= v: - raise ValueError('temp_min must be < temp_max') - return v -``` - -3. **Benefits Listed:** - - Automatic request validation - - Type-safe configuration objects - - Clear error messages - - Zero boilerplate code - - Built-in bounds checking - - Reusable validation models - -4. **Raspberry Pi Compatibility:** - - Pydantic has ARM wheels - - Pure Python, no C compilation - - ~5MB added, ~200KB runtime overhead - -5. **Implementation Options:** - - Option A: Add Pydantic in current PR (recommended) - - Option B: Add Pydantic in follow-up PR - ---- - -### 6. Alternative: Flask-RESTX Approach - -The project has a Flask-RESTX skill (`.claude/skills/flask-restx-webhooks`) that provides an alternative validation approach: - -**Flask-RESTX Model Definition:** -```python -from flask_restx import Namespace, fields - -webhooks_ns = Namespace('webhooks', description='Webhook operations') - -webhook_config_model = webhooks_ns.model('WebhookConfig', { - 'url': fields.String(required=True, description='Webhook URL'), - 'enabled': fields.Boolean(default=True, description='Enable webhook'), - 'retry_count': fields.Integer(min=1, max=10, description='Retry attempts'), - 'retry_delay': fields.Integer(min=1, max=60, description='Retry delay seconds'), - 'timeout': fields.Integer(min=5, max=120, description='Request timeout') -}) -``` - -**Benefits of Flask-RESTX:** -- Automatic Swagger/OpenAPI documentation generation -- Request validation through `@expect` decorator -- Response marshalling with field definitions -- Already integrated with Flask ecosystem -- No additional dependency for validation (uses existing Flask-RESTX) - -**Trade-offs vs Pydantic:** - -| Aspect | Pydantic | Flask-RESTX | -|--------|----------|-------------| -| Validation | Built-in with Field() | Via model fields | -| Swagger/OpenAPI | Requires integration | Built-in | -| Dataclass replacement | Direct replacement | Separate models | -| Cross-validator | @validator decorator | Custom implementation | -| Type safety | Strong with Python typing | Less strict | -| Internal use | Can use same models | Need separate models | - ---- - -### 7. Dataclass Instantiation Locations - -**WebhookConfig created at:** -- `temp_monitor.py:68-74`: From environment variables at startup -- `temp_monitor.py:438-444`: From API request JSON - -**AlertThresholds created at:** -- `temp_monitor.py:76-81`: From environment variables at startup -- `temp_monitor.py:450-455`: From API request JSON -- `webhook_service.py:42`: Default instantiation in `__init__` - ---- - -## Code References - -- `webhook_service.py:17-24` - WebhookConfig dataclass definition -- `webhook_service.py:27-33` - AlertThresholds dataclass definition -- `webhook_service.py:39-42` - WebhookService.__init__ with default AlertThresholds -- `webhook_service.py:47-51` - set_webhook_config setter method -- `webhook_service.py:53-57` - set_alert_thresholds setter method -- `temp_monitor.py:68-81` - Dataclass instantiation from environment variables -- `temp_monitor.py:416-483` - PUT /api/webhook/config endpoint -- `temp_monitor.py:438-444` - WebhookConfig instantiation from JSON -- `temp_monitor.py:450-455` - AlertThresholds instantiation from JSON -- `temp_monitor.py:56-62` - Only business rule validation in codebase - -## Architecture Documentation - -### Current Validation Architecture - -``` -Request JSON - | - v -request.get_json() - | - v -if not data: return 400 <-- Only existence check - | - v -dict.get() with defaults <-- No type/range validation - | - v -@dataclass instantiation <-- No __post_init__ validation - | - v -WebhookService methods -``` - -### Proposed Pydantic Architecture (from Issue #24) - -``` -Request JSON - | - v -request.get_json() - | - v -PydanticModel(**data) <-- Automatic validation - | - Type checking - +--> ValidationError - Range checking (Field()) - | return 400 - Cross-field validation (@validator) - | - v -Validated model instance - | - v -WebhookService methods -``` - -## Related Research - -- GitHub Issue #23 (referenced in Issue #24) - Related webhook PR -- GitHub Issues #9, #11, #12 - Original validation issues - -## Open Questions - -1. **Flask-RESTX vs Pydantic:** Should both be used together (Flask-RESTX for API docs, Pydantic for internal validation) or pick one approach? - -2. **Migration Strategy:** If adopting Pydantic, should existing dataclasses be replaced entirely or wrapped? - -3. **Error Response Format:** What error response format should validation failures return? Pydantic's default or custom? - -4. **Environment Variable Validation:** Should Pydantic also validate environment variable parsing at startup? - -5. **Backwards Compatibility:** Will API consumers need to handle new validation error responses? - ---- - -## Follow-up Research: Flask-RESTX Implementation Plan - -**Added:** 2025-12-31T23:15:00Z - -Based on the decision to use Flask-RESTX instead of Pydantic, here is the detailed implementation plan. - -### Why Flask-RESTX Over Pydantic - -1. **Built-in OpenAPI/Swagger** - Automatic API documentation at `/docs` -2. **No additional dependency** - Just add `flask-restx` to requirements.txt -3. **Flask ecosystem integration** - Works naturally with existing Flask patterns -4. **Request validation via decorators** - Clean `@expect` pattern -5. **Response marshalling** - Consistent API response formatting - -### Required Changes - -#### 1. Add Dependency - -**File:** `requirements.txt` -``` -flask-restx>=1.3.0 -``` - -#### 2. Create API Models Module - -**New file:** `api_models.py` - -```python -from flask_restx import Namespace, fields - -# Create namespace for webhook endpoints -webhooks_ns = Namespace('webhooks', description='Webhook configuration and management') - -# Webhook configuration model with validation -webhook_config_model = webhooks_ns.model('WebhookConfig', { - 'url': fields.String( - required=True, - description='Slack webhook URL', - example='https://hooks.slack.com/services/...' - ), - 'enabled': fields.Boolean( - default=True, - description='Enable/disable webhook notifications' - ), - 'retry_count': fields.Integer( - default=3, - min=1, - max=10, - description='Number of retry attempts (1-10)' - ), - 'retry_delay': fields.Integer( - default=5, - min=1, - max=60, - description='Initial retry delay in seconds (1-60)' - ), - 'timeout': fields.Integer( - default=10, - min=5, - max=120, - description='Request timeout in seconds (5-120)' - ) -}) - -# Alert thresholds model -alert_thresholds_model = webhooks_ns.model('AlertThresholds', { - 'temp_min_c': fields.Float( - description='Minimum temperature threshold in Celsius', - min=-50, - max=100, - example=15.0 - ), - 'temp_max_c': fields.Float( - description='Maximum temperature threshold in Celsius', - min=-50, - max=100, - example=27.0 - ), - 'humidity_min': fields.Float( - description='Minimum humidity threshold percentage', - min=0, - max=100, - example=30.0 - ), - 'humidity_max': fields.Float( - description='Maximum humidity threshold percentage', - min=0, - max=100, - example=70.0 - ) -}) - -# Combined config update request model -webhook_config_update_model = webhooks_ns.model('WebhookConfigUpdate', { - 'webhook': fields.Nested(webhook_config_model, description='Webhook settings'), - 'thresholds': fields.Nested(alert_thresholds_model, description='Alert thresholds') -}) - -# Response models -webhook_config_response = webhooks_ns.model('WebhookConfigResponse', { - 'webhook': fields.Nested(webhook_config_model), - 'thresholds': fields.Nested(alert_thresholds_model) -}) - -error_response = webhooks_ns.model('ErrorResponse', { - 'error': fields.String(description='Error message'), - 'details': fields.String(description='Additional error details') -}) - -success_response = webhooks_ns.model('SuccessResponse', { - 'message': fields.String(description='Success message'), - 'config': fields.Nested(webhook_config_response, description='Updated configuration') -}) -``` - -#### 3. Initialize Flask-RESTX API - -**File:** `temp_monitor.py` - Add after Flask app initialization - -```python -from flask_restx import Api - -api = Api( - app, - version='1.0', - title='Temperature Monitor API', - description='Server room environmental monitoring API', - doc='/docs', # Swagger UI endpoint - authorizations={ - 'bearer': { - 'type': 'apiKey', - 'in': 'header', - 'name': 'Authorization', - 'description': 'Bearer token authentication. Format: "Bearer "' - } - }, - security='bearer' -) -``` - -#### 4. Migrate PUT /api/webhook/config Endpoint - -**Current location:** `temp_monitor.py:416-483` - -**New implementation using Flask-RESTX:** - -```python -from flask_restx import Resource -from api_models import webhooks_ns, webhook_config_update_model, success_response, error_response - -api.add_namespace(webhooks_ns, path='/api/webhook') - -@webhooks_ns.route('/config') -class WebhookConfigResource(Resource): - @webhooks_ns.doc(security='bearer') - @webhooks_ns.marshal_with(webhook_config_response) - @require_token - def get(self): - """Get current webhook configuration""" - if not webhook_service or not webhook_service.webhook_config: - return {'enabled': False, 'message': 'Webhook not configured'}, 200 - - config = webhook_service.webhook_config - thresholds = webhook_service.alert_thresholds - - return { - 'webhook': { - 'url': config.url, - 'enabled': config.enabled, - 'retry_count': config.retry_count, - 'retry_delay': config.retry_delay, - 'timeout': config.timeout - }, - 'thresholds': { - 'temp_min_c': thresholds.temp_min_c, - 'temp_max_c': thresholds.temp_max_c, - 'humidity_min': thresholds.humidity_min, - 'humidity_max': thresholds.humidity_max - } - } - - @webhooks_ns.doc(security='bearer') - @webhooks_ns.expect(webhook_config_update_model, validate=True) - @webhooks_ns.marshal_with(success_response) - @webhooks_ns.response(400, 'Validation Error', error_response) - @webhooks_ns.response(500, 'Server Error', error_response) - @require_token - def put(self): - """Update webhook configuration""" - global webhook_service - data = webhooks_ns.payload # Already validated by @expect - - # Custom cross-field validation for thresholds - if 'thresholds' in data: - thresholds = data['thresholds'] - if (thresholds.get('temp_min_c') is not None and - thresholds.get('temp_max_c') is not None and - thresholds['temp_min_c'] >= thresholds['temp_max_c']): - return {'error': 'temp_min_c must be less than temp_max_c'}, 400 - - if (thresholds.get('humidity_min') is not None and - thresholds.get('humidity_max') is not None and - thresholds['humidity_min'] >= thresholds['humidity_max']): - return {'error': 'humidity_min must be less than humidity_max'}, 400 - - # Process validated data (existing logic) - # ... -``` - -### Validation Coverage - -| Issue | Validation Required | Flask-RESTX Solution | -|-------|--------------------|-----------------------| -| #9 | Numeric bounds for retry_count, timeout | `fields.Integer(min=1, max=10)` | -| #11 | URL required, valid config values | `required=True`, field constraints | -| #12 | temp_min < temp_max | Custom validator in endpoint | - -### Cross-Field Validation - -Flask-RESTX doesn't have built-in cross-field validators like Pydantic's `@validator`. Implement in endpoint: - -```python -def validate_thresholds(thresholds: dict) -> tuple[bool, str]: - """Validate threshold relationships""" - if thresholds.get('temp_min_c') and thresholds.get('temp_max_c'): - if thresholds['temp_min_c'] >= thresholds['temp_max_c']: - return False, 'temp_min_c must be less than temp_max_c' - - if thresholds.get('humidity_min') and thresholds.get('humidity_max'): - if thresholds['humidity_min'] >= thresholds['humidity_max']: - return False, 'humidity_min must be less than humidity_max' - - return True, '' -``` - -### Migration Steps - -1. **Add flask-restx to requirements.txt** -2. **Create api_models.py with model definitions** -3. **Initialize Api in temp_monitor.py** -4. **Migrate webhook endpoints to Resource classes** -5. **Keep existing dataclasses for internal use** (WebhookConfig, AlertThresholds) -6. **Add validation logic in endpoints** -7. **Test Swagger UI at /docs** - -### Endpoints After Migration - -| Endpoint | Method | Flask-RESTX Resource | -|----------|--------|----------------------| -| `/api/webhook/config` | GET | WebhookConfigResource.get() | -| `/api/webhook/config` | PUT | WebhookConfigResource.put() | -| `/api/webhook/test` | POST | WebhookTestResource.post() | -| `/api/webhook/enable` | POST | WebhookEnableResource.post() | -| `/api/webhook/disable` | POST | WebhookDisableResource.post() | - -### Swagger Documentation Access - -After implementation, Swagger UI will be available at: -- **Swagger UI:** `http://localhost:8080/docs` -- **OpenAPI JSON:** `http://localhost:8080/swagger.json` - -### Keeping Existing Dataclasses - -The `WebhookConfig` and `AlertThresholds` dataclasses in `webhook_service.py` should remain unchanged. They serve as internal data structures. Flask-RESTX models handle API request/response validation, while dataclasses handle internal state. - -``` -API Request --> Flask-RESTX Model (validation) --> Dataclass (internal storage) -``` - -### Error Response Format - -Flask-RESTX validation errors return: -```json -{ - "message": "Input payload validation failed", - "errors": { - "webhook.retry_count": "Value must be between 1 and 10" - } -} -``` - -This is more structured than the current generic error responses. diff --git a/webhook_service.py b/webhook_service.py index 844aee4..39f4318 100644 --- a/webhook_service.py +++ b/webhook_service.py @@ -29,8 +29,8 @@ class WebhookConfig: class AlertThresholds: """Temperature and humidity thresholds for alerts""" temp_min_c: Optional[float] = 15.0 # 59°F - temp_max_c: Optional[float] = 27.0 # 80.6°F - humidity_min: Optional[float] = 30.0 + temp_max_c: Optional[float] = 32.0 # 90°F + humidity_min: Optional[float] = 20.0 humidity_max: Optional[float] = 70.0