# Story 2.5: Deployment Target Assignment ## Status Draft ## 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 each article knows which site/bucket it will be deployed to. **Note:** This story ONLY assigns site_deployment_id. Template selection logic is handled entirely by Story 2.4. ## Acceptance Criteria - The job configuration file supports an optional `deployment_targets` array containing site custom_hostnames or site_deployment_ids. - The job configuration file supports an optional `deployment_overflow` strategy ("round_robin", "random_available", or "none"). - During content generation, each article is assigned a `site_deployment_id` based on its index in the batch: - If `deployment_targets` is specified, cycle through the list (round-robin by default). - If the batch size exceeds the target list, apply the overflow strategy. - If no `deployment_targets` specified, `site_deployment_id` remains null (random template in Story 2.4). - The `site_deployment_id` is stored in the `GeneratedContent` record at creation time. - Invalid site references in `deployment_targets` cause graceful errors with clear messages. ## Tasks / Subtasks ### 1. Update Job Configuration Schema **Effort:** 2 story points - [ ] Add `deployment_targets` field (optional array of strings) to job config schema - [ ] Add `deployment_overflow` field (optional string: "round_robin", "random_available", "none") - [ ] Default `deployment_overflow` to "round_robin" if not specified - [ ] Update job config validation to check deployment_targets format - [ ] Update example job files in `jobs/` directory with new fields ### 2. Implement Target Resolution Service **Effort:** 3 story points - [ ] Create `DeploymentTargetResolver` class in `src/deployment/` or appropriate module - [ ] Implement `resolve_target(identifier: str) -> Optional[int]` method - Accept custom_hostname or site_deployment_id (as string) - Query SiteDeployment table to get site_deployment_id - Return None if not found - [ ] Implement `validate_targets(targets: List[str])` method - Pre-validate all targets in deployment_targets array - Return list of invalid targets if any - Fail fast with clear error message ### 3. Implement Assignment Strategy Logic **Effort:** 4 story points - [ ] Implement `assign_site_for_article(article_index: int, job_config: dict, total_articles: int) -> Optional[int]` - [ ] **Round-robin strategy:** - Cycle through deployment_targets using modulo operation - Example: 10 articles, 5 targets → article_index % len(targets) - [ ] **Random available strategy:** - When article_index exceeds len(targets), query for SiteDeployments not in targets list - Randomly select from available sites - Handle case where no other sites exist (error) - [ ] **None strategy:** - Raise error if article_index exceeds len(targets) - Strict mode: only deploy exact number of articles as targets - [ ] Handle case where deployment_targets is None/empty (return None for all) ### 4. Database Integration **Effort:** 2 story points - [ ] Verify `site_deployment_id` field exists in `GeneratedContent` model (added in Story 2.4) - [ ] Update `GeneratedContentRepository.create()` to accept `site_deployment_id` parameter - [ ] Ensure proper foreign key relationship to SiteDeployment table - [ ] Add database index on `site_deployment_id` for query performance ### 5. Integration with Content Generation Service **Effort:** 3 story points - [ ] Update `src/generation/service.py` to parse deployment config from job - [ ] Call target resolver to validate deployment_targets at job start - [ ] For each article in batch: - Call assignment strategy to get site_deployment_id - Pass site_deployment_id to repository when creating GeneratedContent - [ ] Log assignment decisions (INFO level: "Article X assigned to site Y") - [ ] Handle assignment errors gracefully without breaking batch ### 6. Unit Tests **Effort:** 3 story points - [ ] Test target resolution with valid hostnames - [ ] Test target resolution with valid site_deployment_ids - [ ] Test target resolution with invalid identifiers - [ ] Test round-robin strategy with various batch sizes - [ ] Test random_available strategy - [ ] Test none strategy with overflow scenarios - [ ] Test validation of deployment_targets array - [ ] Achieve >80% code coverage ### 7. Integration Tests **Effort:** 2 story points - [ ] Test full generation flow with deployment_targets specified - [ ] Test round-robin assignment across 10 articles with 5 targets - [ ] 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 ## Dev Notes ### Example Job Config ```json { "job_name": "Multi-Site T1 Launch", "project_id": 2, "deployment_targets": [ "www.domain1.com", "www.domain2.com", "www.domain3.com" ], "deployment_overflow": "round_robin", "tiers": [ { "tier": 1, "article_count": 10 } ] } ``` ### Assignment Example (Round-Robin) 10 articles, 3 targets: - Article 0 → domain1.com - Article 1 → domain2.com - Article 2 → domain3.com - Article 3 → domain1.com - Article 4 → domain2.com - ... and so on ### Assignment Example (Random Available) 10 articles, 3 targets, 5 total sites in database: - Article 0-2 → Round-robin through specified targets - Article 3+ → Random selection from domain4.com, domain5.com ### Technical Decisions 1. **Target identifier:** Support both hostname and numeric ID for flexibility 2. **Validation timing:** Validate all targets at job start (fail fast) 3. **Overflow default:** Round-robin is the safest default 4. **Null handling:** No deployment_targets = all articles get null site_deployment_id ### Dependencies - **Story 1.6:** SiteDeployment table must exist - **Story 2.3:** Content generation service must be functional ### Related Stories - **Story 2.4:** Consumes site_deployment_id for template selection (but that's 2.4's concern, not this story's) ### Database Changes Required None - `site_deployment_id` field added in Story 2.4 task #5 ### Testing Strategy - Unit tests: Test assignment algorithms in isolation - Integration tests: Test full job execution with various configs - Edge cases: Empty targets, oversized batches, invalid hostnames