Big-Link-Man/docs/stories/story-2.5-deployment-target...

132 lines
5.5 KiB
Markdown

# Story 2.5: Deployment Target Assignment
## Status
Completed - All acceptance criteria met, 33/33 tests passing (includes tier1-only constraint)
## Story
**As a developer**, I want to assign deployment targets (site_deployment_id) to generated content during the content generation process based on job configuration, so that specific articles are assigned to specific sites while others remain unassigned for random template selection.
## Context
This story only assigns `site_deployment_id` to GeneratedContent records. Template selection is handled entirely by Story 2.4's existing logic:
- If `site_deployment_id` is set → Story 2.4 uses mapped/random template for that site
- If `site_deployment_id` is null → Story 2.4 uses random template (no config persistence)
## Acceptance Criteria
- The job configuration file supports an optional `deployment_targets` array containing site custom_hostnames
- **Only tier1 articles are assigned to deployment targets** - tier2, tier3, etc. always get `site_deployment_id = null`
- During tier1 content generation, each article is assigned a `site_deployment_id` based on its index:
- If `deployment_targets` has N sites, articles 0 through N-1 get assigned round-robin
- Articles N and beyond get `site_deployment_id = null`
- If `deployment_targets` is not specified, all articles get `site_deployment_id = null`
- The `site_deployment_id` is stored in the `GeneratedContent` record at creation time
- Invalid hostnames in `deployment_targets` cause graceful errors with clear messages
- Valid hostnames that don't exist in the database cause graceful errors
## Tasks / Subtasks
### 1. Update Job Configuration Schema
**Effort:** 1 story point
- [x] Add `deployment_targets` field (optional array of strings) to job config schema
- [x] Update job config validation to check deployment_targets is an array of strings
- [x] Update example job file in `jobs/` directory with the new field
### 2. Implement Target Resolution
**Effort:** 2 story points
- [x] Add `resolve_hostname_to_id(hostname: str) -> Optional[int]` helper function
- Query SiteDeployment table by custom_hostname
- Return site_deployment_id if found, None if not found
- [x] Add `validate_and_resolve_targets(hostnames: List[str]) -> dict` function
- Pre-validate all hostnames at job start (fail fast)
- Return dict mapping hostname → site_deployment_id
- Raise clear error if any hostname is invalid/not found
### 3. Implement Round-Robin Assignment
**Effort:** 2 story points
- [x] Add `assign_site_for_article(article_index: int, resolved_targets: dict) -> Optional[int]` function
- [x] If resolved_targets is empty: return None
- [x] If article_index < len(resolved_targets): return targets[article_index]
- [x] If article_index >= len(resolved_targets): return None
### 4. Integration with Content Generation Service
**Effort:** 2 story points
- [x] Update `src/generation/batch_processor.py` to parse `deployment_targets` from job config
- [x] Call validation function at job start (before generating any content)
- [x] For each article in batch:
- Call assignment function to get site_deployment_id
- Pass site_deployment_id to repository when creating GeneratedContent
- [x] Log assignment decisions at INFO level
### 5. Unit Tests
**Effort:** 2 story points
- [x] Test hostname resolution with valid hostnames
- [x] Test hostname resolution with invalid hostnames
- [x] Test round-robin assignment with 3 targets, 10 articles
- [x] Test assignment with no deployment_targets (all null)
- [x] Test validation errors for non-existent hostnames
- [x] Achieve >80% code coverage (100% achieved with 13 unit tests)
### 6. Integration Tests
**Effort:** 2 story points
- [x] Test full generation flow with deployment_targets specified
- [x] Test 10 articles with 3 targets: verify first 3 assigned, remaining 7 are null
- [x] Test with deployment_targets = null (all articles get null site_deployment_id)
- [x] Test error handling for invalid deployment targets
- [x] Verify site_deployment_id persisted correctly in database (9 integration tests)
## Dev Notes
### Example Job Config
```json
{
"job_name": "Multi-Site Launch",
"project_id": 2,
"deployment_targets": [
"www.domain1.com",
"www.domain2.com",
"www.domain3.com"
],
"tiers": [
{
"tier": 1,
"article_count": 10
}
]
}
```
### Assignment Example
Job with tier1 (10 articles) and tier2 (100 articles), 3 deployment targets:
**Tier1 articles:**
- Article 0 → www.domain1.com (site_deployment_id = 5)
- Article 1 → www.domain2.com (site_deployment_id = 8)
- Article 2 → www.domain3.com (site_deployment_id = 12)
- Articles 3-9 → null
**Tier2 articles:**
- All 100 articles → null (tier2+ never get deployment targets)
### Technical Decisions
1. **Tier restriction:** Only tier1 articles can be assigned to deployment targets; tier2/tier3 always get null
2. **Target identifier:** Only support custom_hostname (not numeric IDs)
3. **Validation timing:** Validate all targets at job start (fail fast)
4. **Overflow handling:** Simple - just assign null after targets exhausted
5. **Null handling:** No deployment_targets = all articles get null
### Dependencies
- **Story 1.6:** SiteDeployment table must exist
- **Story 2.3:** Content generation service must be functional
- **Story 2.4:** Template selection logic already handles null site_deployment_id
### Database Changes Required
None - `site_deployment_id` field already exists in GeneratedContent model (added in Story 2.4)
### Total Effort
11 story points