5.5 KiB
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_idis set → Story 2.4 uses mapped/random template for that site - If
site_deployment_idis null → Story 2.4 uses random template (no config persistence)
Acceptance Criteria
- The job configuration file supports an optional
deployment_targetsarray 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_idbased on its index:- If
deployment_targetshas N sites, articles 0 through N-1 get assigned round-robin - Articles N and beyond get
site_deployment_id = null - If
deployment_targetsis not specified, all articles getsite_deployment_id = null
- If
- The
site_deployment_idis stored in theGeneratedContentrecord at creation time - Invalid hostnames in
deployment_targetscause 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
- Add
deployment_targetsfield (optional array of strings) to job config schema - Update job config validation to check deployment_targets is an array of strings
- Update example job file in
jobs/directory with the new field
2. Implement Target Resolution
Effort: 2 story points
- 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
- Add
validate_and_resolve_targets(hostnames: List[str]) -> dictfunction- 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
- Add
assign_site_for_article(article_index: int, resolved_targets: dict) -> Optional[int]function - If resolved_targets is empty: return None
- If article_index < len(resolved_targets): return targets[article_index]
- If article_index >= len(resolved_targets): return None
4. Integration with Content Generation Service
Effort: 2 story points
- Update
src/generation/batch_processor.pyto parsedeployment_targetsfrom job config - Call validation function at job start (before generating any content)
- For each article in batch:
- Call assignment function to get site_deployment_id
- Pass site_deployment_id to repository when creating GeneratedContent
- Log assignment decisions at INFO level
5. Unit Tests
Effort: 2 story points
- Test hostname resolution with valid hostnames
- Test hostname resolution with invalid hostnames
- Test round-robin assignment with 3 targets, 10 articles
- Test assignment with no deployment_targets (all null)
- Test validation errors for non-existent hostnames
- Achieve >80% code coverage (100% achieved with 13 unit tests)
6. Integration Tests
Effort: 2 story points
- Test full generation flow with deployment_targets specified
- Test 10 articles with 3 targets: verify first 3 assigned, remaining 7 are null
- Test with deployment_targets = null (all articles get null site_deployment_id)
- Test error handling for invalid deployment targets
- Verify site_deployment_id persisted correctly in database (9 integration tests)
Dev Notes
Example Job Config
{
"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
- Tier restriction: Only tier1 articles can be assigned to deployment targets; tier2/tier3 always get null
- Target identifier: Only support custom_hostname (not numeric IDs)
- Validation timing: Validate all targets at job start (fail fast)
- Overflow handling: Simple - just assign null after targets exhausted
- 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