Task 002: add shared domain contracts
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime
|
||||
from uuid import NAMESPACE_URL, uuid5
|
||||
|
||||
from src.domain.contracts import (
|
||||
ArticleCreateRequest,
|
||||
ArticleCreateResponse,
|
||||
ArticleSummary,
|
||||
ArticleWorkflowStatus,
|
||||
PublishingStatus,
|
||||
)
|
||||
|
||||
|
||||
PLACEHOLDER_CREATED_AT = datetime(1970, 1, 1, tzinfo=UTC)
|
||||
|
||||
|
||||
def build_placeholder_article_response(
|
||||
request: ArticleCreateRequest,
|
||||
) -> ArticleCreateResponse:
|
||||
article_id = uuid5(
|
||||
NAMESPACE_URL,
|
||||
f"article:{request.target_site_id}:{request.brief_description}",
|
||||
)
|
||||
article = ArticleSummary(
|
||||
id=article_id,
|
||||
target_site_id=request.target_site_id,
|
||||
status=ArticleWorkflowStatus.ARTICLE_BRIEF_CREATED,
|
||||
publishing_status=PublishingStatus.PUBLISH_NOT_STARTED,
|
||||
brief_description=request.brief_description,
|
||||
working_title=request.working_title,
|
||||
language=request.language,
|
||||
content_type=request.content_type,
|
||||
primary_keyword=request.primary_keyword,
|
||||
assigned_editor_id=request.assigned_editor_id,
|
||||
created_at=PLACEHOLDER_CREATED_AT,
|
||||
updated_at=PLACEHOLDER_CREATED_AT,
|
||||
)
|
||||
return ArticleCreateResponse(article=article)
|
||||
@@ -0,0 +1,87 @@
|
||||
from .enums import (
|
||||
AgentJobErrorCategory,
|
||||
AgentJobStatus,
|
||||
AgentJobType,
|
||||
ArticleWorkflowStatus,
|
||||
AssetStatus,
|
||||
AssetType,
|
||||
ClaimRiskLevel,
|
||||
ClaimSupportStatus,
|
||||
PlanReviewStatus,
|
||||
PublishingStatus,
|
||||
ReviewStatus,
|
||||
ReviewType,
|
||||
Role,
|
||||
ScriptConfigVersionStatus,
|
||||
)
|
||||
from .models import (
|
||||
AgentJobOutput,
|
||||
AgentJobSummary,
|
||||
ArticleCreateRequest,
|
||||
ArticleCreateResponse,
|
||||
ArticleDetailResponse,
|
||||
ArticleListResponse,
|
||||
ArticleSummary,
|
||||
AssetSummary,
|
||||
ClaimSummary,
|
||||
DraftSummary,
|
||||
EvidenceSummary,
|
||||
PlanSectionSummary,
|
||||
PlanSummary,
|
||||
PublishCommitSummary,
|
||||
PublishingRules,
|
||||
ResearchArtifactManifestSummary,
|
||||
ResearchArtifactSummary,
|
||||
ReviewSummary,
|
||||
RunnerFileRef,
|
||||
ScriptConfigVersionSummary,
|
||||
TargetSiteConfig,
|
||||
UserSummary,
|
||||
)
|
||||
from .workflow import (
|
||||
ARTICLE_WORKFLOW_STATUSES,
|
||||
ARTICLE_WORKFLOW_TRANSITIONS,
|
||||
next_workflow_statuses,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"ARTICLE_WORKFLOW_STATUSES",
|
||||
"ARTICLE_WORKFLOW_TRANSITIONS",
|
||||
"AgentJobErrorCategory",
|
||||
"AgentJobOutput",
|
||||
"AgentJobStatus",
|
||||
"AgentJobSummary",
|
||||
"AgentJobType",
|
||||
"ArticleCreateRequest",
|
||||
"ArticleCreateResponse",
|
||||
"ArticleDetailResponse",
|
||||
"ArticleListResponse",
|
||||
"ArticleSummary",
|
||||
"ArticleWorkflowStatus",
|
||||
"AssetStatus",
|
||||
"AssetSummary",
|
||||
"AssetType",
|
||||
"ClaimRiskLevel",
|
||||
"ClaimSummary",
|
||||
"ClaimSupportStatus",
|
||||
"DraftSummary",
|
||||
"EvidenceSummary",
|
||||
"PlanReviewStatus",
|
||||
"PlanSectionSummary",
|
||||
"PlanSummary",
|
||||
"PublishCommitSummary",
|
||||
"PublishingRules",
|
||||
"PublishingStatus",
|
||||
"ResearchArtifactManifestSummary",
|
||||
"ResearchArtifactSummary",
|
||||
"ReviewStatus",
|
||||
"ReviewSummary",
|
||||
"ReviewType",
|
||||
"Role",
|
||||
"RunnerFileRef",
|
||||
"ScriptConfigVersionStatus",
|
||||
"ScriptConfigVersionSummary",
|
||||
"TargetSiteConfig",
|
||||
"UserSummary",
|
||||
"next_workflow_statuses",
|
||||
]
|
||||
@@ -0,0 +1,126 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Role(str, Enum):
|
||||
ADMIN = "ADMIN"
|
||||
EDITOR = "EDITOR"
|
||||
|
||||
|
||||
class ArticleWorkflowStatus(str, Enum):
|
||||
ARTICLE_BRIEF_CREATED = "ARTICLE_BRIEF_CREATED"
|
||||
BOUNDARY_QUESTIONS_GENERATED = "BOUNDARY_QUESTIONS_GENERATED"
|
||||
BOUNDARY_ANSWERS_SUBMITTED = "BOUNDARY_ANSWERS_SUBMITTED"
|
||||
PLAN_GENERATED = "PLAN_GENERATED"
|
||||
PLAN_REVIEW_REQUIRED = "PLAN_REVIEW_REQUIRED"
|
||||
PLAN_REVISION_REQUIRED = "PLAN_REVISION_REQUIRED"
|
||||
RESEARCH_RUNNING = "RESEARCH_RUNNING"
|
||||
EVIDENCE_MATRIX_READY = "EVIDENCE_MATRIX_READY"
|
||||
PARALLEL_PRODUCTION_RUNNING = "PARALLEL_PRODUCTION_RUNNING"
|
||||
DRAFT_ASSEMBLED = "DRAFT_ASSEMBLED"
|
||||
SEO_AND_LANGUAGE_REVIEW_READY = "SEO_AND_LANGUAGE_REVIEW_READY"
|
||||
FINAL_REVIEW_REQUIRED = "FINAL_REVIEW_REQUIRED"
|
||||
FINAL_REVISION_REQUIRED = "FINAL_REVISION_REQUIRED"
|
||||
PUBLISH_DRY_RUN_REQUIRED = "PUBLISH_DRY_RUN_REQUIRED"
|
||||
PUBLISH_COMMIT_READY = "PUBLISH_COMMIT_READY"
|
||||
PUBLISH_COMMIT_CREATED = "PUBLISH_COMMIT_CREATED"
|
||||
|
||||
|
||||
class AgentJobStatus(str, Enum):
|
||||
QUEUED = "QUEUED"
|
||||
RUNNING = "RUNNING"
|
||||
SUCCEEDED = "SUCCEEDED"
|
||||
FAILED = "FAILED"
|
||||
CANCELLED = "CANCELLED"
|
||||
|
||||
|
||||
class AgentJobType(str, Enum):
|
||||
BOUNDARY_QUESTIONS = "BOUNDARY_QUESTIONS"
|
||||
PLAN_GENERATION = "PLAN_GENERATION"
|
||||
RESEARCH = "RESEARCH"
|
||||
EVIDENCE_MATRIX = "EVIDENCE_MATRIX"
|
||||
SECTION_SCAFFOLD = "SECTION_SCAFFOLD"
|
||||
DRAFT_ASSEMBLY = "DRAFT_ASSEMBLY"
|
||||
SEO_REVIEW = "SEO_REVIEW"
|
||||
LANGUAGE_REVIEW = "LANGUAGE_REVIEW"
|
||||
PUBLISH_DRY_RUN = "PUBLISH_DRY_RUN"
|
||||
PUBLISH_COMMIT = "PUBLISH_COMMIT"
|
||||
TEST_CODEX = "TEST_CODEX"
|
||||
|
||||
|
||||
class AgentJobErrorCategory(str, Enum):
|
||||
FAILED_SCHEMA_VALIDATION = "FAILED_SCHEMA_VALIDATION"
|
||||
CLI_EXIT_CODE_FAILURE = "CLI_EXIT_CODE_FAILURE"
|
||||
TIMEOUT = "TIMEOUT"
|
||||
MISSING_OUTPUT_FILE = "MISSING_OUTPUT_FILE"
|
||||
UNSUPPORTED_CLAIMS_FOUND = "UNSUPPORTED_CLAIMS_FOUND"
|
||||
SOURCE_RETRIEVAL_FAILED = "SOURCE_RETRIEVAL_FAILED"
|
||||
RESEARCH_ARTIFACT_UPLOAD_FAILED = "RESEARCH_ARTIFACT_UPLOAD_FAILED"
|
||||
PUBLISH_DRY_RUN_FAILED = "PUBLISH_DRY_RUN_FAILED"
|
||||
GIT_CHECKOUT_FAILED = "GIT_CHECKOUT_FAILED"
|
||||
GIT_COMMIT_FAILED = "GIT_COMMIT_FAILED"
|
||||
GIT_PUSH_NON_FAST_FORWARD = "GIT_PUSH_NON_FAST_FORWARD"
|
||||
PUBLISH_VERIFICATION_FAILED = "PUBLISH_VERIFICATION_FAILED"
|
||||
|
||||
|
||||
class ClaimSupportStatus(str, Enum):
|
||||
SUPPORTED = "SUPPORTED"
|
||||
UNSUPPORTED = "UNSUPPORTED"
|
||||
NEEDS_REVIEW = "NEEDS_REVIEW"
|
||||
|
||||
|
||||
class ClaimRiskLevel(str, Enum):
|
||||
LOW = "low"
|
||||
MEDIUM = "medium"
|
||||
HIGH = "high"
|
||||
|
||||
|
||||
class PublishingStatus(str, Enum):
|
||||
PUBLISH_NOT_STARTED = "PUBLISH_NOT_STARTED"
|
||||
PUBLISH_DRY_RUN_REQUIRED = "PUBLISH_DRY_RUN_REQUIRED"
|
||||
PUBLISH_DRY_RUN_RUNNING = "PUBLISH_DRY_RUN_RUNNING"
|
||||
PUBLISH_DRY_RUN_FAILED = "PUBLISH_DRY_RUN_FAILED"
|
||||
PUBLISH_COMMIT_READY = "PUBLISH_COMMIT_READY"
|
||||
PUBLISH_COMMIT_CREATED = "PUBLISH_COMMIT_CREATED"
|
||||
PUBLISH_VERIFICATION_FAILED = "PUBLISH_VERIFICATION_FAILED"
|
||||
|
||||
|
||||
class AssetStatus(str, Enum):
|
||||
PENDING = "PENDING"
|
||||
GENERATED = "GENERATED"
|
||||
APPROVED = "APPROVED"
|
||||
REJECTED = "REJECTED"
|
||||
|
||||
|
||||
class AssetType(str, Enum):
|
||||
HERO_IMAGE = "hero_image"
|
||||
DIAGRAM = "diagram"
|
||||
TABLE = "table"
|
||||
INLINE_IMAGE = "inline_image"
|
||||
|
||||
|
||||
class PlanReviewStatus(str, Enum):
|
||||
PENDING_REVIEW = "PENDING_REVIEW"
|
||||
APPROVED = "APPROVED"
|
||||
REVISION_REQUESTED = "REVISION_REQUESTED"
|
||||
|
||||
|
||||
class ReviewStatus(str, Enum):
|
||||
PENDING = "PENDING"
|
||||
APPROVED = "APPROVED"
|
||||
CHANGES_REQUESTED = "CHANGES_REQUESTED"
|
||||
|
||||
|
||||
class ReviewType(str, Enum):
|
||||
PLAN = "PLAN"
|
||||
EVIDENCE = "EVIDENCE"
|
||||
ASSET = "ASSET"
|
||||
SEO_LANGUAGE = "SEO_LANGUAGE"
|
||||
FINAL = "FINAL"
|
||||
|
||||
|
||||
class ScriptConfigVersionStatus(str, Enum):
|
||||
DRAFT = "DRAFT"
|
||||
ACTIVE = "ACTIVE"
|
||||
DEPRECATED = "DEPRECATED"
|
||||
@@ -0,0 +1,266 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from .enums import (
|
||||
AgentJobErrorCategory,
|
||||
AgentJobStatus,
|
||||
AgentJobType,
|
||||
ArticleWorkflowStatus,
|
||||
AssetStatus,
|
||||
AssetType,
|
||||
ClaimRiskLevel,
|
||||
ClaimSupportStatus,
|
||||
PlanReviewStatus,
|
||||
PublishingStatus,
|
||||
ReviewStatus,
|
||||
ReviewType,
|
||||
Role,
|
||||
ScriptConfigVersionStatus,
|
||||
)
|
||||
|
||||
|
||||
JsonObject = dict[str, Any]
|
||||
|
||||
|
||||
class ContractModel(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class UserSummary(ContractModel):
|
||||
id: UUID
|
||||
display_name: str = Field(min_length=1)
|
||||
role: Role
|
||||
|
||||
|
||||
class PublishingRules(ContractModel):
|
||||
repository_url: str = Field(min_length=1)
|
||||
production_branch: str = Field(min_length=1)
|
||||
content_format: str = Field(default="mdx", min_length=1)
|
||||
content_path_template: str = Field(min_length=1)
|
||||
asset_path_template: str = Field(min_length=1)
|
||||
frontmatter_mapping: JsonObject = Field(default_factory=dict)
|
||||
transform_script_version_id: UUID | None = None
|
||||
dry_run_renderer: str | None = None
|
||||
|
||||
|
||||
class TargetSiteConfig(ContractModel):
|
||||
id: UUID
|
||||
name: str = Field(min_length=1)
|
||||
slug: str = Field(min_length=1)
|
||||
publishing_type: str = Field(default="git_next", min_length=1)
|
||||
default_language: str = Field(default="en", min_length=2)
|
||||
brand_voice: str = Field(min_length=1)
|
||||
audience: str = Field(min_length=1)
|
||||
seo_rules: JsonObject = Field(default_factory=dict)
|
||||
visual_rules: JsonObject = Field(default_factory=dict)
|
||||
source_rules: JsonObject = Field(default_factory=dict)
|
||||
publishing_rules: PublishingRules
|
||||
active_script_config_version_id: UUID | None = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class ScriptConfigVersionSummary(ContractModel):
|
||||
id: UUID
|
||||
target_site_id: UUID
|
||||
version: int = Field(ge=1)
|
||||
status: ScriptConfigVersionStatus
|
||||
publishing_yaml_hash: str = Field(min_length=1)
|
||||
transform_script_hash: str = Field(min_length=1)
|
||||
created_by: UUID
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class ArticleCreateRequest(ContractModel):
|
||||
target_site_id: UUID
|
||||
brief_description: str = Field(min_length=1)
|
||||
working_title: str | None = None
|
||||
language: str = Field(default="en", min_length=2)
|
||||
content_type: str = Field(default="longform_guide", min_length=1)
|
||||
primary_keyword: str | None = None
|
||||
assigned_editor_id: UUID | None = None
|
||||
|
||||
|
||||
class ArticleSummary(ContractModel):
|
||||
id: UUID
|
||||
target_site_id: UUID
|
||||
status: ArticleWorkflowStatus
|
||||
publishing_status: PublishingStatus
|
||||
brief_description: str
|
||||
working_title: str | None = None
|
||||
language: str
|
||||
content_type: str
|
||||
primary_keyword: str | None = None
|
||||
assigned_editor_id: UUID | None = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class ArticleCreateResponse(ContractModel):
|
||||
article: ArticleSummary
|
||||
|
||||
|
||||
class ArticleListResponse(ContractModel):
|
||||
articles: list[ArticleSummary]
|
||||
|
||||
|
||||
class PlanSectionSummary(ContractModel):
|
||||
id: UUID
|
||||
article_plan_id: UUID
|
||||
sort_order: int = Field(ge=1)
|
||||
heading: str = Field(min_length=1)
|
||||
purpose: str | None = None
|
||||
claims_to_support: list[str] = Field(default_factory=list)
|
||||
target_word_count: int | None = Field(default=None, ge=1)
|
||||
|
||||
|
||||
class PlanSummary(ContractModel):
|
||||
id: UUID
|
||||
article_id: UUID
|
||||
version: int = Field(ge=1)
|
||||
status: PlanReviewStatus
|
||||
recommended_title: str | None = None
|
||||
search_intent: str | None = None
|
||||
thesis: str | None = None
|
||||
sections: list[PlanSectionSummary] = Field(default_factory=list)
|
||||
claims_to_prove: list[str] = Field(default_factory=list)
|
||||
risks: list[str] = Field(default_factory=list)
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class ResearchArtifactSummary(ContractModel):
|
||||
artifact_type: str = Field(min_length=1)
|
||||
source_url: str = Field(min_length=1)
|
||||
object_key: str = Field(min_length=1)
|
||||
content_hash: str = Field(min_length=1)
|
||||
metadata: JsonObject = Field(default_factory=dict)
|
||||
|
||||
|
||||
class ResearchArtifactManifestSummary(ContractModel):
|
||||
id: UUID
|
||||
article_id: UUID
|
||||
agent_job_id: UUID
|
||||
s3_prefix: str = Field(min_length=1)
|
||||
artifacts: list[ResearchArtifactSummary] = Field(default_factory=list)
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class EvidenceSummary(ContractModel):
|
||||
id: UUID
|
||||
article_id: UUID
|
||||
source_title: str = Field(min_length=1)
|
||||
source_url: str = Field(min_length=1)
|
||||
source_type: str = Field(min_length=1)
|
||||
source_quality_score: float = Field(ge=0, le=1)
|
||||
summary: str = Field(min_length=1)
|
||||
supports_claims: list[UUID] = Field(default_factory=list)
|
||||
artifact_manifest_id: UUID
|
||||
retrieved_at: datetime
|
||||
|
||||
|
||||
class ClaimSummary(ContractModel):
|
||||
id: UUID
|
||||
article_id: UUID
|
||||
section_id: UUID | None = None
|
||||
claim_text: str = Field(min_length=1)
|
||||
support_status: ClaimSupportStatus
|
||||
risk_level: ClaimRiskLevel
|
||||
evidence_item_ids: list[UUID] = Field(default_factory=list)
|
||||
|
||||
|
||||
class DraftSummary(ContractModel):
|
||||
id: UUID
|
||||
article_id: UUID
|
||||
version: int = Field(ge=1)
|
||||
title: str = Field(min_length=1)
|
||||
slug: str = Field(min_length=1)
|
||||
meta_title: str | None = None
|
||||
meta_description: str | None = None
|
||||
body_object_key: str | None = None
|
||||
status: ArticleWorkflowStatus
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class AssetSummary(ContractModel):
|
||||
id: UUID
|
||||
article_id: UUID
|
||||
asset_type: AssetType
|
||||
title: str = Field(min_length=1)
|
||||
prompt: str | None = None
|
||||
file_url: str | None = None
|
||||
alt_text: str | None = None
|
||||
caption: str | None = None
|
||||
status: AssetStatus
|
||||
|
||||
|
||||
class ReviewSummary(ContractModel):
|
||||
id: UUID
|
||||
article_id: UUID
|
||||
review_type: ReviewType
|
||||
status: ReviewStatus
|
||||
reviewer_id: UUID | None = None
|
||||
notes: str | None = None
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class PublishCommitSummary(ContractModel):
|
||||
id: UUID
|
||||
article_id: UUID
|
||||
target_site_id: UUID
|
||||
repository_url: str = Field(min_length=1)
|
||||
branch: str = Field(min_length=1)
|
||||
commit_sha: str | None = None
|
||||
content_bundle_manifest: JsonObject = Field(default_factory=dict)
|
||||
status: PublishingStatus
|
||||
deployment_status: str | None = None
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class RunnerFileRef(ContractModel):
|
||||
path: str = Field(min_length=1)
|
||||
content_hash: str | None = None
|
||||
|
||||
|
||||
class AgentJobSummary(ContractModel):
|
||||
id: UUID
|
||||
article_id: UUID
|
||||
job_type: AgentJobType
|
||||
agent_profile: str = Field(min_length=1)
|
||||
status: AgentJobStatus
|
||||
workspace_path: str | None = None
|
||||
input_files: list[RunnerFileRef] = Field(default_factory=list)
|
||||
output_files: list[RunnerFileRef] = Field(default_factory=list)
|
||||
error_category: AgentJobErrorCategory | None = None
|
||||
error_message: str | None = None
|
||||
started_at: datetime | None = None
|
||||
finished_at: datetime | None = None
|
||||
|
||||
|
||||
class AgentJobOutput(ContractModel):
|
||||
status: AgentJobStatus
|
||||
output_files: list[RunnerFileRef] = Field(default_factory=list)
|
||||
error_category: AgentJobErrorCategory | None = None
|
||||
error_message: str | None = None
|
||||
artifacts: list[ResearchArtifactSummary] = Field(default_factory=list)
|
||||
payload: JsonObject = Field(default_factory=dict)
|
||||
|
||||
|
||||
class ArticleDetailResponse(ContractModel):
|
||||
article: ArticleSummary
|
||||
target_site: TargetSiteConfig | None = None
|
||||
plan: PlanSummary | None = None
|
||||
draft: DraftSummary | None = None
|
||||
evidence: list[EvidenceSummary] = Field(default_factory=list)
|
||||
claims: list[ClaimSummary] = Field(default_factory=list)
|
||||
assets: list[AssetSummary] = Field(default_factory=list)
|
||||
reviews: list[ReviewSummary] = Field(default_factory=list)
|
||||
agent_jobs: list[AgentJobSummary] = Field(default_factory=list)
|
||||
research_manifests: list[ResearchArtifactManifestSummary] = Field(
|
||||
default_factory=list
|
||||
)
|
||||
publish_commit: PublishCommitSummary | None = None
|
||||
@@ -0,0 +1,105 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, TypeAdapter
|
||||
|
||||
from .enums import (
|
||||
AgentJobErrorCategory,
|
||||
AgentJobStatus,
|
||||
AgentJobType,
|
||||
ArticleWorkflowStatus,
|
||||
AssetStatus,
|
||||
AssetType,
|
||||
ClaimRiskLevel,
|
||||
ClaimSupportStatus,
|
||||
PlanReviewStatus,
|
||||
PublishingStatus,
|
||||
ReviewStatus,
|
||||
ReviewType,
|
||||
Role,
|
||||
ScriptConfigVersionStatus,
|
||||
)
|
||||
from .models import (
|
||||
AgentJobOutput,
|
||||
AgentJobSummary,
|
||||
ArticleCreateRequest,
|
||||
ArticleCreateResponse,
|
||||
ArticleDetailResponse,
|
||||
ArticleListResponse,
|
||||
ArticleSummary,
|
||||
AssetSummary,
|
||||
ClaimSummary,
|
||||
DraftSummary,
|
||||
EvidenceSummary,
|
||||
PlanSectionSummary,
|
||||
PlanSummary,
|
||||
PublishCommitSummary,
|
||||
PublishingRules,
|
||||
ResearchArtifactManifestSummary,
|
||||
ResearchArtifactSummary,
|
||||
ReviewSummary,
|
||||
RunnerFileRef,
|
||||
ScriptConfigVersionSummary,
|
||||
TargetSiteConfig,
|
||||
UserSummary,
|
||||
)
|
||||
|
||||
|
||||
CONTRACT_ENUMS: tuple[type[Enum], ...] = (
|
||||
Role,
|
||||
ArticleWorkflowStatus,
|
||||
AgentJobStatus,
|
||||
AgentJobType,
|
||||
AgentJobErrorCategory,
|
||||
ClaimSupportStatus,
|
||||
ClaimRiskLevel,
|
||||
PublishingStatus,
|
||||
AssetStatus,
|
||||
AssetType,
|
||||
PlanReviewStatus,
|
||||
ReviewStatus,
|
||||
ReviewType,
|
||||
ScriptConfigVersionStatus,
|
||||
)
|
||||
|
||||
CONTRACT_SCHEMA_MODELS: tuple[type[BaseModel], ...] = (
|
||||
UserSummary,
|
||||
PublishingRules,
|
||||
TargetSiteConfig,
|
||||
ScriptConfigVersionSummary,
|
||||
ArticleCreateRequest,
|
||||
ArticleSummary,
|
||||
ArticleCreateResponse,
|
||||
ArticleListResponse,
|
||||
PlanSectionSummary,
|
||||
PlanSummary,
|
||||
ResearchArtifactSummary,
|
||||
ResearchArtifactManifestSummary,
|
||||
EvidenceSummary,
|
||||
ClaimSummary,
|
||||
DraftSummary,
|
||||
AssetSummary,
|
||||
ReviewSummary,
|
||||
PublishCommitSummary,
|
||||
RunnerFileRef,
|
||||
AgentJobSummary,
|
||||
AgentJobOutput,
|
||||
ArticleDetailResponse,
|
||||
)
|
||||
|
||||
|
||||
def inject_contract_schemas(openapi_schema: dict[str, Any]) -> None:
|
||||
schemas = openapi_schema.setdefault("components", {}).setdefault("schemas", {})
|
||||
|
||||
for enum in CONTRACT_ENUMS:
|
||||
schemas[enum.__name__] = TypeAdapter(enum).json_schema(
|
||||
ref_template="#/components/schemas/{model}"
|
||||
)
|
||||
|
||||
for model in CONTRACT_SCHEMA_MODELS:
|
||||
schema = model.model_json_schema(ref_template="#/components/schemas/{model}")
|
||||
for name, definition in schema.pop("$defs", {}).items():
|
||||
schemas.setdefault(name, definition)
|
||||
schemas[model.__name__] = schema
|
||||
@@ -0,0 +1,83 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from .enums import ArticleWorkflowStatus
|
||||
|
||||
|
||||
ARTICLE_WORKFLOW_STATUSES: tuple[ArticleWorkflowStatus, ...] = (
|
||||
ArticleWorkflowStatus.ARTICLE_BRIEF_CREATED,
|
||||
ArticleWorkflowStatus.BOUNDARY_QUESTIONS_GENERATED,
|
||||
ArticleWorkflowStatus.BOUNDARY_ANSWERS_SUBMITTED,
|
||||
ArticleWorkflowStatus.PLAN_GENERATED,
|
||||
ArticleWorkflowStatus.PLAN_REVIEW_REQUIRED,
|
||||
ArticleWorkflowStatus.PLAN_REVISION_REQUIRED,
|
||||
ArticleWorkflowStatus.RESEARCH_RUNNING,
|
||||
ArticleWorkflowStatus.EVIDENCE_MATRIX_READY,
|
||||
ArticleWorkflowStatus.PARALLEL_PRODUCTION_RUNNING,
|
||||
ArticleWorkflowStatus.DRAFT_ASSEMBLED,
|
||||
ArticleWorkflowStatus.SEO_AND_LANGUAGE_REVIEW_READY,
|
||||
ArticleWorkflowStatus.FINAL_REVIEW_REQUIRED,
|
||||
ArticleWorkflowStatus.FINAL_REVISION_REQUIRED,
|
||||
ArticleWorkflowStatus.PUBLISH_DRY_RUN_REQUIRED,
|
||||
ArticleWorkflowStatus.PUBLISH_COMMIT_READY,
|
||||
ArticleWorkflowStatus.PUBLISH_COMMIT_CREATED,
|
||||
)
|
||||
|
||||
|
||||
ARTICLE_WORKFLOW_TRANSITIONS: dict[
|
||||
ArticleWorkflowStatus, tuple[ArticleWorkflowStatus, ...]
|
||||
] = {
|
||||
ArticleWorkflowStatus.ARTICLE_BRIEF_CREATED: (
|
||||
ArticleWorkflowStatus.BOUNDARY_QUESTIONS_GENERATED,
|
||||
),
|
||||
ArticleWorkflowStatus.BOUNDARY_QUESTIONS_GENERATED: (
|
||||
ArticleWorkflowStatus.BOUNDARY_ANSWERS_SUBMITTED,
|
||||
),
|
||||
ArticleWorkflowStatus.BOUNDARY_ANSWERS_SUBMITTED: (
|
||||
ArticleWorkflowStatus.PLAN_GENERATED,
|
||||
),
|
||||
ArticleWorkflowStatus.PLAN_GENERATED: (
|
||||
ArticleWorkflowStatus.PLAN_REVIEW_REQUIRED,
|
||||
),
|
||||
ArticleWorkflowStatus.PLAN_REVIEW_REQUIRED: (
|
||||
ArticleWorkflowStatus.PLAN_REVISION_REQUIRED,
|
||||
ArticleWorkflowStatus.RESEARCH_RUNNING,
|
||||
),
|
||||
ArticleWorkflowStatus.PLAN_REVISION_REQUIRED: (
|
||||
ArticleWorkflowStatus.PLAN_GENERATED,
|
||||
),
|
||||
ArticleWorkflowStatus.RESEARCH_RUNNING: (
|
||||
ArticleWorkflowStatus.EVIDENCE_MATRIX_READY,
|
||||
),
|
||||
ArticleWorkflowStatus.EVIDENCE_MATRIX_READY: (
|
||||
ArticleWorkflowStatus.PARALLEL_PRODUCTION_RUNNING,
|
||||
),
|
||||
ArticleWorkflowStatus.PARALLEL_PRODUCTION_RUNNING: (
|
||||
ArticleWorkflowStatus.DRAFT_ASSEMBLED,
|
||||
),
|
||||
ArticleWorkflowStatus.DRAFT_ASSEMBLED: (
|
||||
ArticleWorkflowStatus.SEO_AND_LANGUAGE_REVIEW_READY,
|
||||
),
|
||||
ArticleWorkflowStatus.SEO_AND_LANGUAGE_REVIEW_READY: (
|
||||
ArticleWorkflowStatus.FINAL_REVIEW_REQUIRED,
|
||||
),
|
||||
ArticleWorkflowStatus.FINAL_REVIEW_REQUIRED: (
|
||||
ArticleWorkflowStatus.FINAL_REVISION_REQUIRED,
|
||||
ArticleWorkflowStatus.PUBLISH_DRY_RUN_REQUIRED,
|
||||
),
|
||||
ArticleWorkflowStatus.FINAL_REVISION_REQUIRED: (
|
||||
ArticleWorkflowStatus.DRAFT_ASSEMBLED,
|
||||
),
|
||||
ArticleWorkflowStatus.PUBLISH_DRY_RUN_REQUIRED: (
|
||||
ArticleWorkflowStatus.PUBLISH_COMMIT_READY,
|
||||
),
|
||||
ArticleWorkflowStatus.PUBLISH_COMMIT_READY: (
|
||||
ArticleWorkflowStatus.PUBLISH_COMMIT_CREATED,
|
||||
),
|
||||
ArticleWorkflowStatus.PUBLISH_COMMIT_CREATED: (),
|
||||
}
|
||||
|
||||
|
||||
def next_workflow_statuses(
|
||||
status: ArticleWorkflowStatus,
|
||||
) -> tuple[ArticleWorkflowStatus, ...]:
|
||||
return ARTICLE_WORKFLOW_TRANSITIONS[status]
|
||||
@@ -1,10 +1,15 @@
|
||||
from typing import Any
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.openapi.utils import get_openapi
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from src.infrastructure.dependencies import check_dependencies
|
||||
from src.domain.contracts.openapi import inject_contract_schemas
|
||||
from src.presentation.routes.articles import router as articles_router
|
||||
|
||||
|
||||
app = FastAPI(title="AI Content Pipeline Backend")
|
||||
app.include_router(articles_router)
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
@@ -14,6 +19,8 @@ def health() -> dict[str, str]:
|
||||
|
||||
@app.get("/health/dependencies")
|
||||
def dependency_health() -> JSONResponse:
|
||||
from src.infrastructure.dependencies import check_dependencies
|
||||
|
||||
dependencies = check_dependencies()
|
||||
is_ok = all(status == "ok" for status in dependencies.values())
|
||||
|
||||
@@ -25,3 +32,20 @@ def dependency_health() -> JSONResponse:
|
||||
"dependencies": dependencies,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def custom_openapi() -> dict[str, Any]:
|
||||
if app.openapi_schema:
|
||||
return app.openapi_schema
|
||||
|
||||
openapi_schema = get_openapi(
|
||||
title=app.title,
|
||||
version=app.version,
|
||||
routes=app.routes,
|
||||
)
|
||||
inject_contract_schemas(openapi_schema)
|
||||
app.openapi_schema = openapi_schema
|
||||
return app.openapi_schema
|
||||
|
||||
|
||||
app.openapi = custom_openapi
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
"""Backend HTTP route modules."""
|
||||
@@ -0,0 +1,18 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, status
|
||||
|
||||
from src.application.articles import build_placeholder_article_response
|
||||
from src.domain.contracts import ArticleCreateRequest, ArticleCreateResponse
|
||||
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["articles"])
|
||||
|
||||
|
||||
@router.post(
|
||||
"/articles",
|
||||
response_model=ArticleCreateResponse,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
)
|
||||
def create_article(request: ArticleCreateRequest) -> ArticleCreateResponse:
|
||||
return build_placeholder_article_response(request)
|
||||
Reference in New Issue
Block a user