feat: curvit-content-creator service — scheduled AI editorial assistant (issue #331)#336
Merged
Merged
Conversation
- New Docker service: services/content-creator/ - 10-stage workflow: content inventory, research, opportunity analysis, brief generation, draft generation, link audit, conversion validation, quality review, CMS draft creation, admin support data - Two operating modes: NewContent and Refresh/Expansion (Mixed default) - Separate CONTENT_CREATOR_AI_API_KEY from cv-analysis service - AI provenance tracking: PromptVersion, ModelUsed, GeneratedAt, GenerationMode - Ad-hoc generation endpoint for admins (POST /admin/generate) - Weekly Monday 01:00 UTC asyncio scheduler - Safety: never auto-publishes, no fabrication, draft-only CMS writes - Conversion rules: Curvit mention after 25%, max 2 refs, no hard sell - DB models: GenerationRun, ResearchDigest, ContentOpportunity, ContentBrief, ContentQualityReview, ContentLinkAudit - 78 tests: unit, route, workflow, scheduling, safety - docker-compose.yml: content-creator service entry (no Traefik) - .github/workflows/ci-python.yml: content-creator path triggers - docs/content-creator.md: full architecture, workflow, operations docs Co-authored-by: NickLetts2 <90337962+NickLetts2@users.noreply.github.com>
- Fix prompt version constant names (drop redundant -v1 suffix) - Fix semaphore lazy init to avoid race condition - Fix link auditor HEAD→GET fallback (check 405 status, not exception) - Fix SQLAlchemy boolean comparison to use .is_(False) - Propagate KeyboardInterrupt/SystemExit in workflow exception handler - Fix CodeQL URL substring check in test (use startswith) Co-authored-by: NickLetts2 <90337962+NickLetts2@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Add AI Content Creator service for draft generation
feat: curvit-content-creator service — scheduled AI editorial assistant (issue #331)
May 30, 2026
…ring sanitization' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Introduces a new internal content-creator FastAPI service for scheduled AI-assisted draft generation, with CMS integration, link auditing, workflow persistence, admin endpoints, Docker wiring, CI triggers, documentation, and tests.
Changes:
- Adds the content-creator service with scheduler, workflow engine, Anthropic client, CMS client, SQLAlchemy models, and admin API.
- Adds Docker Compose integration and Python CI path/matrix coverage for the new service.
- Adds service documentation and tests for routes, workflow behavior, scheduling, and safety invariants.
Reviewed changes
Copilot reviewed 22 out of 27 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
.github/workflows/ci-python.yml |
Adds content-creator to Python CI triggers and service matrix. |
docker-compose.yml |
Adds internal content-creator service container. |
docs/content-creator.md |
Documents architecture, workflow, safety controls, operations, and testing. |
services/content-creator/Dockerfile |
Defines build/runtime image for the new service. |
services/content-creator/internal_auth.py |
Re-exports shared internal auth helpers. |
services/content-creator/requirements.in |
Adds direct Python dependencies. |
services/content-creator/requirements.txt |
Adds pinned dependency lock file. |
services/content-creator/app/__init__.py |
Initializes the app package. |
services/content-creator/app/config.py |
Adds service-specific settings and startup validation. |
services/content-creator/app/db.py |
Re-exports shared DB session helpers. |
services/content-creator/app/main.py |
Adds FastAPI app, middleware, metrics, scheduler lifespan, and health endpoints. |
services/content-creator/app/models/__init__.py |
Initializes models package. |
services/content-creator/app/models/db_models.py |
Adds workflow/run/research/opportunity/quality/link audit ORM models. |
services/content-creator/app/models/schemas.py |
Adds API request/response schemas. |
services/content-creator/app/routers/__init__.py |
Initializes routers package. |
services/content-creator/app/routers/admin.py |
Adds admin generation, run history, queue, quality, link, research, opportunity, and recommendation endpoints. |
services/content-creator/app/scheduler.py |
Adds weekly asyncio scheduler. |
services/content-creator/app/services/__init__.py |
Initializes services package. |
services/content-creator/app/services/ai_client.py |
Adds Anthropic-based brief, draft, quality, and refresh candidate functions. |
services/content-creator/app/services/cms_client.py |
Adds CMS inventory and draft creation client. |
services/content-creator/app/services/link_auditor.py |
Adds markdown link extraction and HTTP link auditing. |
services/content-creator/app/services/workflow.py |
Adds end-to-end content generation workflow orchestration. |
services/content-creator/tests/__init__.py |
Initializes tests package. |
services/content-creator/tests/conftest.py |
Adds test DB and HTTP client fixtures. |
services/content-creator/tests/test_routes.py |
Adds route integration tests. |
services/content-creator/tests/test_unit.py |
Adds scheduler, helper, conversion, and link auditor tests. |
services/content-creator/tests/test_workflow.py |
Adds workflow and draft safety tests. |
Comment on lines
+85
to
+98
| run_id = str(uuid.uuid4()) | ||
| parameters = { | ||
| "target_count": request.target_count, | ||
| "content_type": request.content_type, | ||
| "topic_hint": request.topic_hint, | ||
| "notes": request.notes, | ||
| } | ||
|
|
||
| background_tasks.add_task( | ||
| _run_adhoc_in_background, | ||
| run_id=run_id, | ||
| generation_mode=request.generation_mode, | ||
| parameters=parameters, | ||
| ) |
Comment on lines
+40
to
+44
| response = await client.get( | ||
| f"{settings.cms_service_url}/admin/content", | ||
| headers=headers, | ||
| params=params, | ||
| ) |
Comment on lines
+594
to
+600
| rejected = composite_quality < _MIN_COMPOSITE_QUALITY | ||
| rejection_reason = None | ||
| if rejected: | ||
| rejection_reason = f"Composite quality score {composite_quality} below threshold {_MIN_COMPOSITE_QUALITY}" | ||
| if not conversion_rule_pass: | ||
| rejection_reason += "; conversion rules violated" | ||
|
|
Comment on lines
+291
to
+294
| if generation_mode in ("Refresh", "Mixed") and existing_summaries: | ||
| refresh_candidates = await ai_client.identify_refresh_candidates(existing_summaries) | ||
| refresh_count = _DEFAULT_REFRESH_COUNT if generation_mode == "Mixed" else target_count | ||
| for candidate in refresh_candidates[:refresh_count]: |
Comment on lines
+69
to
+72
| resp = await client.head(url) | ||
| # Some servers reject HEAD with 405; fall back to GET | ||
| if resp.status_code == 405: | ||
| resp = await client.get(url) |
Comment on lines
+11
to
+13
| class GenerationRun(Base): | ||
| """Tracks each execution of the content generation workflow (scheduled or ad-hoc).""" | ||
| __tablename__ = "ContentCreatorRuns" |
Co-authored-by: NickLetts2 <90337962+NickLetts2@users.noreply.github.com>
Comment on lines
+4
to
+22
| import asyncio | ||
| import logging | ||
|
|
||
| from fastapi import FastAPI, Response | ||
| from prometheus_fastapi_instrumentator import Instrumentator | ||
|
|
||
| from app.config import get_settings | ||
| from app.routers.admin import router as admin_router | ||
| from app.scheduler import run_scheduler | ||
| from internal_auth import CorrelationIdMiddleware, InternalApiKeyMiddleware | ||
| from shared_api_key_scheme import setup_api_key_scheme | ||
|
|
||
| logging.basicConfig(level=logging.INFO) | ||
| logger = logging.getLogger(__name__) | ||
|
|
||
| _scheduler_task: asyncio.Task | None = None | ||
|
|
||
|
|
||
| async def _lifespan(app: FastAPI): # type: ignore[type-arg] |
Comment on lines
+132
to
+134
| protected override void Down(MigrationBuilder migrationBuilder) | ||
| { | ||
| } |
Comment on lines
+604
to
+610
| rejected = composite_quality < _MIN_COMPOSITE_QUALITY or not conversion_rule_pass | ||
| rejection_reason = None | ||
| if rejected: | ||
| rejection_reason = f"Composite quality score {composite_quality} below threshold {_MIN_COMPOSITE_QUALITY}" | ||
| if not conversion_rule_pass: | ||
| rejection_reason += "; conversion rules violated" | ||
|
|
|
|
||
| # ── Stage 6: Link intelligence ────────────────────────────────────────── | ||
| logger.info("[%s] Stage 6: Link audit for '%s'", run_id, title) | ||
| links = link_auditor.extract_links_from_markdown(content_markdown) |
Comment on lines
+69
to
+72
| async with httpx.AsyncClient( | ||
| timeout=timeout, | ||
| follow_redirects=True, | ||
| ) as client: |
Comment on lines
+106
to
+107
| except ValueError: | ||
| return True |
NickLetts2
added a commit
that referenced
this pull request
Jun 1, 2026
Co-authored-by: NickLetts2 <90337962+NickLetts2@users.noreply.github.com>
NickLetts2
added a commit
that referenced
this pull request
Jun 1, 2026
…ator feat: curvit-content-creator service — scheduled AI editorial assistant (issue #331)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Introduces
curvit-content-creator, a dedicated internal Docker service that generates draft content on a weekly schedule. The service acts as an editorial assistant — it never auto-publishes, never invents statistics or testimonials, and always writes to CMS asDraft.Service architecture
/health,/ready,/metrics, and/admin/*endpointsCONTENT_CREATOR_AI_API_KEY(distinct from cv-analysis)10-stage editorial workflow
status="Draft",ai_generated=True— no publish path existsGeneration modes
NewContent|Refresh|Expansion|Mixed(weekly default: 2 new evergreen + 1 refresh/expansion + 1 news blog)AI provenance tracking
Every generated item stores
PromptVersion,ModelUsed,GeneratedAt,GenerationModefor future performance comparison against analytics signals from issue #332.New DB entities
GenerationRun·ResearchDigest·ContentOpportunity·ContentBrief·ContentQualityReview·ContentLinkAuditAdmin endpoints
Other changes
docker-compose.yml—content-creatorservice entry (no ingress, depends on postgres + cms-service).github/workflows/ci-python.yml—services/content-creator/**path triggers addeddocs/content-creator.md— architecture, workflow, AI prompts, safety controls, operations, monitoring