Task 008 implement boundary questions loop

This commit is contained in:
2026-05-21 19:46:26 +03:00
parent 8aa92ea728
commit b7e2fa34b2
27 changed files with 1883 additions and 24 deletions
@@ -14,6 +14,7 @@ from src.domain.contracts import (
AgentJobSummary,
AgentJobType,
ArticleSummary,
BoundaryQuestionSummary,
PublishingRules,
PublishingStatus,
ArticleWorkflowStatus,
@@ -40,6 +41,7 @@ class BackendRepository:
self.users = UsersRepository(self)
self.target_sites = TargetSitesRepository(self)
self.articles = ArticlesRepository(self)
self.boundary_questions = BoundaryQuestionsRepository(self)
self.agent_jobs = AgentJobsRepository(self)
self.script_config_versions = ScriptConfigVersionsRepository(self)
self.script_config_version_events = ScriptConfigVersionAuditEventsRepository(self)
@@ -519,6 +521,26 @@ class ArticlesRepository:
raise LookupError(f"Article not found: {article_id}")
return _article_summary_from_row(row)
def update_status(
self,
*,
article_id: UUID,
status: ArticleWorkflowStatus,
updated_at: datetime,
) -> ArticleSummary:
placeholder = self._repository.placeholder()
with self._repository.connection() as connection:
connection.execute(
f"""
UPDATE articles
SET status = {placeholder}, updated_at = {placeholder}
WHERE id = {placeholder}
""",
(status.value, _datetime_value(updated_at), str(article_id)),
)
return self.get(article_id)
def create_workflow_event(
self,
*,
@@ -593,6 +615,141 @@ class ArticlesRepository:
return [_workflow_event_from_row(row) for row in rows]
class BoundaryQuestionsRepository:
def __init__(self, repository: BackendRepository) -> None:
self._repository = repository
def replace_for_article(
self,
*,
article_id: UUID,
questions: list[JsonObject],
created_at: datetime,
) -> list[BoundaryQuestionSummary]:
placeholder = self._repository.placeholder()
with self._repository.connection() as connection:
connection.execute(
f"DELETE FROM boundary_questions WHERE article_id = {placeholder}",
(str(article_id),),
)
for question in questions:
connection.execute(
f"""
INSERT INTO boundary_questions (
id,
article_id,
sort_order,
category,
question,
answer,
is_required,
created_at,
updated_at
)
VALUES (
{placeholder},
{placeholder},
{placeholder},
{placeholder},
{placeholder},
{placeholder},
{placeholder},
{placeholder},
{placeholder}
)
""",
(
str(uuid4()),
str(article_id),
question["sort_order"],
question["category"],
question["question"],
question.get("answer"),
_bool_value(question.get("is_required", True)),
_datetime_value(created_at),
_datetime_value(created_at),
),
)
return self.list_for_article(article_id)
def list_for_article(self, article_id: UUID) -> list[BoundaryQuestionSummary]:
placeholder = self._repository.placeholder()
with self._repository.connection() as connection:
rows = connection.execute(
f"""
SELECT
id,
article_id,
sort_order,
category,
question,
answer,
is_required,
created_at,
updated_at
FROM boundary_questions
WHERE article_id = {placeholder}
ORDER BY sort_order
""",
(str(article_id),),
).fetchall()
return [_boundary_question_from_row(row) for row in rows]
def get(self, *, article_id: UUID, question_id: UUID) -> BoundaryQuestionSummary:
placeholder = self._repository.placeholder()
with self._repository.connection() as connection:
row = connection.execute(
f"""
SELECT
id,
article_id,
sort_order,
category,
question,
answer,
is_required,
created_at,
updated_at
FROM boundary_questions
WHERE article_id = {placeholder} AND id = {placeholder}
""",
(str(article_id), str(question_id)),
).fetchone()
if row is None:
raise LookupError(f"Boundary question not found: {question_id}")
return _boundary_question_from_row(row)
def update_answer(
self,
*,
article_id: UUID,
question_id: UUID,
answer: str | None,
updated_at: datetime,
) -> BoundaryQuestionSummary:
self.get(article_id=article_id, question_id=question_id)
placeholder = self._repository.placeholder()
with self._repository.connection() as connection:
connection.execute(
f"""
UPDATE boundary_questions
SET answer = {placeholder}, updated_at = {placeholder}
WHERE article_id = {placeholder} AND id = {placeholder}
""",
(
answer,
_datetime_value(updated_at),
str(article_id),
str(question_id),
),
)
return self.get(article_id=article_id, question_id=question_id)
class AgentJobsRepository:
def __init__(self, repository: BackendRepository) -> None:
self._repository = repository
@@ -1247,6 +1404,20 @@ def _workflow_event_from_row(row: Any) -> WorkflowEventSummary:
)
def _boundary_question_from_row(row: Any) -> BoundaryQuestionSummary:
return BoundaryQuestionSummary(
id=_row_value(row, "id"),
article_id=_row_value(row, "article_id"),
sort_order=_row_value(row, "sort_order"),
category=_row_value(row, "category"),
question=_row_value(row, "question"),
answer=_row_value(row, "answer"),
is_required=bool(_row_value(row, "is_required")),
created_at=_row_value(row, "created_at"),
updated_at=_row_value(row, "updated_at"),
)
def _agent_job_summary_from_row(row: Any) -> AgentJobSummary:
return AgentJobSummary(
id=_row_value(row, "id"),
@@ -1331,6 +1502,10 @@ def _datetime_value(value: datetime | None) -> str | None:
return value.isoformat()
def _bool_value(value: bool) -> bool:
return bool(value)
def _article_status_value(value: ArticleWorkflowStatus | None) -> str | None:
if value is None:
return None