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

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_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

  • Add deployment_targets field (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]) -> 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

  • 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.py to parse deployment_targets from 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

  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