Route create_content by content_type instead of URL presence
Content Creation tasks with a URL were incorrectly routed to the optimization path. Now the scheduler sets content_type from Work Category, and the tool routes on that. Chat callers fall back to URL-based detection when content_type is empty. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>fix/customer-field-migration
parent
3ed7f2bbdd
commit
85d9fc7a1f
|
|
@ -435,7 +435,7 @@ class Scheduler:
|
||||||
field_mapping = mapping.get("field_mapping", {})
|
field_mapping = mapping.get("field_mapping", {})
|
||||||
missing_clickup = [field_mapping.get(f, f) for f in missing]
|
missing_clickup = [field_mapping.get(f, f) for f in missing]
|
||||||
msg = f"Skipped: missing required field(s): {', '.join(missing_clickup)}"
|
msg = f"Skipped: missing required field(s): {', '.join(missing_clickup)}"
|
||||||
log.info("Skipping ClickUp task %s (%s) — %s", task_id, task.name, msg)
|
log.debug("Skipping ClickUp task %s (%s) — %s", task_id, task.name, msg)
|
||||||
self._notify(
|
self._notify(
|
||||||
f"Skipped ClickUp task: **{task.name}**\n{msg}",
|
f"Skipped ClickUp task: **{task.name}**\n{msg}",
|
||||||
category="clickup",
|
category="clickup",
|
||||||
|
|
@ -453,6 +453,13 @@ class Scheduler:
|
||||||
args["clickup_task_id"] = task_id
|
args["clickup_task_id"] = task_id
|
||||||
args["clickup_task_status"] = task.status
|
args["clickup_task_status"] = task.status
|
||||||
|
|
||||||
|
# Map Work Category to content_type so create_content routes correctly
|
||||||
|
if tool_name == "create_content":
|
||||||
|
if task.task_type == "On Page Optimization":
|
||||||
|
args["content_type"] = "on page optimization"
|
||||||
|
elif task.task_type == "Content Creation":
|
||||||
|
args["content_type"] = "new content"
|
||||||
|
|
||||||
# Execute the skill via the tool registry
|
# Execute the skill via the tool registry
|
||||||
if hasattr(self.agent, "_tools") and self.agent._tools:
|
if hasattr(self.agent, "_tools") and self.agent._tools:
|
||||||
result = self.agent._tools.execute(tool_name, args)
|
result = self.agent._tools.execute(tool_name, args)
|
||||||
|
|
|
||||||
|
|
@ -741,10 +741,10 @@ def _sync_clickup_optimization_complete(
|
||||||
|
|
||||||
@tool(
|
@tool(
|
||||||
"create_content",
|
"create_content",
|
||||||
"Two-phase SEO content creation: Phase 1 researches + outlines, Phase 2 writes "
|
"SEO content creation. Set content_type='new content' for new pages "
|
||||||
"full content from the approved outline. Auto-detects phase from ClickUp task "
|
"(Phase 1 outline → Phase 2 full write), or content_type='on page optimization' "
|
||||||
"status ('outline approved' → Phase 2). "
|
"to optimize an existing page with Cora data. Auto-detects phase from ClickUp "
|
||||||
"Auto-detects content type from URL presence if not specified.",
|
"task status ('outline approved' → Phase 2). Ask the user which type if unclear.",
|
||||||
category="content",
|
category="content",
|
||||||
)
|
)
|
||||||
def create_content(
|
def create_content(
|
||||||
|
|
@ -758,8 +758,9 @@ def create_content(
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
keyword: Primary target keyword (e.g. "plumbing services").
|
keyword: Primary target keyword (e.g. "plumbing services").
|
||||||
url: Target page URL. If provided → on-page optimization; if empty → new content.
|
url: Target page URL (optional for new content, required for optimization).
|
||||||
content_type: Type of content. Auto-detected from URL if empty.
|
content_type: 'new content' or 'on page optimization'. Controls routing.
|
||||||
|
If empty, inferred from URL presence as fallback.
|
||||||
cli_flags: Optional flags (e.g. "service" for service page hint).
|
cli_flags: Optional flags (e.g. "service" for service page hint).
|
||||||
"""
|
"""
|
||||||
if not keyword:
|
if not keyword:
|
||||||
|
|
@ -805,9 +806,10 @@ def create_content(
|
||||||
|
|
||||||
capabilities_default = config.content.company_capabilities_default if config else ""
|
capabilities_default = config.content.company_capabilities_default if config else ""
|
||||||
|
|
||||||
# Optimization path: URL present → run Phase 3 test block pipeline
|
# Optimization path: content_type determines route (URL fallback for chat callers)
|
||||||
# (skips the outline gate entirely)
|
if content_type.lower() == "on page optimization":
|
||||||
if url:
|
if not url:
|
||||||
|
return "Error: On Page Optimization requires a URL (IMSURL field)."
|
||||||
return _run_optimization(
|
return _run_optimization(
|
||||||
agent=agent,
|
agent=agent,
|
||||||
config=config,
|
config=config,
|
||||||
|
|
|
||||||
|
|
@ -884,8 +884,8 @@ class TestSyncClickupOptimizationComplete:
|
||||||
|
|
||||||
class TestCreateContentRouting:
|
class TestCreateContentRouting:
|
||||||
@patch("cheddahbot.tools.content_creation._run_optimization")
|
@patch("cheddahbot.tools.content_creation._run_optimization")
|
||||||
def test_url_present_routes_to_optimization(self, mock_opt, tmp_db, tmp_path):
|
def test_explicit_optimization_routes_correctly(self, mock_opt, tmp_db, tmp_path):
|
||||||
"""When URL is present, create_content should call _run_optimization."""
|
"""When content_type='on page optimization', routes to _run_optimization."""
|
||||||
mock_opt.return_value = "## Optimization Complete"
|
mock_opt.return_value = "## Optimization Complete"
|
||||||
cfg = Config()
|
cfg = Config()
|
||||||
cfg.content = ContentConfig(outline_dir=str(tmp_path / "outlines"))
|
cfg.content = ContentConfig(outline_dir=str(tmp_path / "outlines"))
|
||||||
|
|
@ -898,14 +898,15 @@ class TestCreateContentRouting:
|
||||||
result = create_content(
|
result = create_content(
|
||||||
keyword="plumbing services",
|
keyword="plumbing services",
|
||||||
url="https://example.com/plumbing",
|
url="https://example.com/plumbing",
|
||||||
|
content_type="on page optimization",
|
||||||
ctx=ctx,
|
ctx=ctx,
|
||||||
)
|
)
|
||||||
mock_opt.assert_called_once()
|
mock_opt.assert_called_once()
|
||||||
assert result == "## Optimization Complete"
|
assert result == "## Optimization Complete"
|
||||||
|
|
||||||
@patch("cheddahbot.tools.content_creation._run_optimization")
|
@patch("cheddahbot.tools.content_creation._run_optimization")
|
||||||
def test_no_url_does_not_route_to_optimization(self, mock_opt, tmp_db, tmp_path):
|
def test_explicit_new_content_with_url_routes_to_phase1(self, mock_opt, tmp_db, tmp_path):
|
||||||
"""When URL is empty, create_content should NOT call _run_optimization."""
|
"""Content Creation with URL should go to Phase 1, NOT optimization."""
|
||||||
cfg = Config()
|
cfg = Config()
|
||||||
cfg.content = ContentConfig(outline_dir=str(tmp_path / "outlines"))
|
cfg.content = ContentConfig(outline_dir=str(tmp_path / "outlines"))
|
||||||
agent = MagicMock()
|
agent = MagicMock()
|
||||||
|
|
@ -918,15 +919,58 @@ class TestCreateContentRouting:
|
||||||
}
|
}
|
||||||
result = create_content(
|
result = create_content(
|
||||||
keyword="new keyword",
|
keyword="new keyword",
|
||||||
url="",
|
url="https://example.com/future-page",
|
||||||
|
content_type="new content",
|
||||||
ctx=ctx,
|
ctx=ctx,
|
||||||
)
|
)
|
||||||
mock_opt.assert_not_called()
|
mock_opt.assert_not_called()
|
||||||
assert "Phase 1 Complete" in result
|
assert "Phase 1 Complete" in result
|
||||||
|
|
||||||
|
@patch("cheddahbot.tools.content_creation._run_optimization")
|
||||||
|
def test_optimization_without_url_returns_error(self, mock_opt, tmp_db, tmp_path):
|
||||||
|
"""On Page Optimization without URL should return an error."""
|
||||||
|
cfg = Config()
|
||||||
|
cfg.content = ContentConfig(outline_dir=str(tmp_path / "outlines"))
|
||||||
|
ctx = {
|
||||||
|
"agent": MagicMock(),
|
||||||
|
"config": cfg,
|
||||||
|
"db": tmp_db,
|
||||||
|
"clickup_task_id": "",
|
||||||
|
}
|
||||||
|
result = create_content(
|
||||||
|
keyword="plumbing services",
|
||||||
|
url="",
|
||||||
|
content_type="on page optimization",
|
||||||
|
ctx=ctx,
|
||||||
|
)
|
||||||
|
mock_opt.assert_not_called()
|
||||||
|
assert "Error" in result
|
||||||
|
assert "URL" in result
|
||||||
|
|
||||||
|
@patch("cheddahbot.tools.content_creation._run_optimization")
|
||||||
|
def test_fallback_url_routes_to_optimization(self, mock_opt, tmp_db, tmp_path):
|
||||||
|
"""When content_type is empty and URL present, falls back to optimization."""
|
||||||
|
mock_opt.return_value = "## Optimization Complete"
|
||||||
|
cfg = Config()
|
||||||
|
cfg.content = ContentConfig(outline_dir=str(tmp_path / "outlines"))
|
||||||
|
ctx = {
|
||||||
|
"agent": MagicMock(),
|
||||||
|
"config": cfg,
|
||||||
|
"db": tmp_db,
|
||||||
|
"clickup_task_id": "routing_test",
|
||||||
|
}
|
||||||
|
result = create_content(
|
||||||
|
keyword="plumbing services",
|
||||||
|
url="https://example.com/plumbing",
|
||||||
|
content_type="",
|
||||||
|
ctx=ctx,
|
||||||
|
)
|
||||||
|
mock_opt.assert_called_once()
|
||||||
|
assert result == "## Optimization Complete"
|
||||||
|
|
||||||
@patch("cheddahbot.tools.content_creation._run_optimization")
|
@patch("cheddahbot.tools.content_creation._run_optimization")
|
||||||
def test_new_content_still_calls_phase1(self, mock_opt, tmp_db, tmp_path):
|
def test_new_content_still_calls_phase1(self, mock_opt, tmp_db, tmp_path):
|
||||||
"""Regression: new content (no URL) still goes through _run_phase1."""
|
"""Regression: new content (no URL, no content_type) still goes through _run_phase1."""
|
||||||
cfg = Config()
|
cfg = Config()
|
||||||
cfg.content = ContentConfig(outline_dir=str(tmp_path / "outlines"))
|
cfg.content = ContentConfig(outline_dir=str(tmp_path / "outlines"))
|
||||||
agent = MagicMock()
|
agent = MagicMock()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue