ContentGuardianAgent consolidation:
- Merge 3 duplicate classes into single source in specialized/content_guardian.py
- Watchdog audit_committee() with heuristic scoring, coverage gaps, overlaps, alerts
- Remove misleading rejection_rate() helper; use acceptance_rate directly
- Integrate audit + alerts + trend signals into today_workflow_service.py
Team Activity page:
- QualityAuditPanel: health ring, per-agent critiques, coverage gaps, overlaps
- TrendSignalsPanel: opportunity cards with urgency/impact/coverage bars
- AlertBanner: persistent dismiss via POST /alerts/{id}/mark-read
- AgentHelpModal: dialog showing all 8 agents with descriptions, tools, schedule
- QualityAuditPanel action buttons: Fill gap -> /content-planning, Resolve overlap, View CTA on alerts/issues
- TrendSignalsPanel action buttons: Create content from this trend -> /blog-writer with trend context state
Onboarding system:
- Step 4 validation: no auto-pass via basic_ready; requires persona data or explicit progression
- Step 5 validation: logs warning on auto-pass without integration data
- OnboardingCompletionService: single DB session, transactional task creation, upsert pattern
- Business-without-website: nullable website_url on SIFIndexingTask and MarketTrendsTask
- DeepCompetitorAnalysisExecutor: 5-min timeout, 10-competitor cap, asyncio.wait_for
- Persona generation: async with 30s timeout, falls back to scheduler
- OnboardingProgressService.reset_onboarding(): resets session + pauses all DB tasks
- OnboardingControlService.reset_onboarding(): also cancels APScheduler jobs
- FinalStep TaskSchedulingPanel: shows scheduled/failed tasks after completion, 8s auto-redirect
- onboarding_completed agent activity event logged to feed
Documentation:
- docs-site/features/onboarding/: overview, steps, scheduler-tasks, technical-reference (4 pages)
- docs-site/mkdocs.yml: added Onboarding System nav section
- docs-site/features/sif-agents/: overview, agent-directory, committee-system, content-guardian (4 pages)
- docs-site/features/team-activity/: overview, quality-audit, trend-signals, alert-system (4 pages)
- docs-site/features/todays-workflow/: updated overview, technical-architecture, workflow-guide, api-reference
145 lines
6.1 KiB
Python
145 lines
6.1 KiB
Python
import asyncio
|
|
import unittest
|
|
import sys
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
if str(ROOT) not in sys.path:
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from services.intelligence.monitoring.semantic_dashboard import RealTimeSemanticMonitor, SemanticHealthMetric
|
|
from services.today_workflow_service import _ensure_pillar_coverage, PILLAR_IDS, validate_plan_contextuality
|
|
from services.intelligence.agents.specialized import ContentGuardianAgent as SifGuardian
|
|
from services.intelligence.agents.specialized_agents import ContentGuardianAgent as SpecializedGuardian
|
|
|
|
|
|
class _FakeIntelligence:
|
|
def __init__(self, results=None):
|
|
self._results = results or []
|
|
|
|
async def search(self, query: str, limit: int = 10):
|
|
return self._results
|
|
|
|
|
|
class _FakeCompetitorIndex:
|
|
async def search(self, query: str, limit: int = 5):
|
|
return [
|
|
{"id": "comp-1", "score": 0.82},
|
|
{"id": "comp-2", "score": 0.65},
|
|
]
|
|
|
|
|
|
class SIFReleaseReadinessTests(unittest.IsolatedAsyncioTestCase):
|
|
def test_single_strategy_architect_init_block(self):
|
|
source = Path("backend/services/intelligence/agents/agent_orchestrator.py").read_text()
|
|
self.assertEqual(source.count('if enabled_by_key.get("strategy_architect", True):'), 1)
|
|
|
|
async def test_semantic_health_returns_canonical_metric(self):
|
|
monitor = RealTimeSemanticMonitor.__new__(RealTimeSemanticMonitor)
|
|
monitor.user_id = "u1"
|
|
metric_list = [
|
|
SemanticHealthMetric("semantic_diversity", 0.8, 0.6, "healthy", "t", "d", []),
|
|
SemanticHealthMetric("authority_score", 0.3, 0.4, "critical", "t", "d", ["Improve authority"]),
|
|
]
|
|
async def _fake_metrics():
|
|
return metric_list
|
|
monitor._check_semantic_health = _fake_metrics
|
|
|
|
result = await RealTimeSemanticMonitor.check_semantic_health(monitor)
|
|
self.assertIsInstance(result, SemanticHealthMetric)
|
|
self.assertEqual(result.metric_name, "semantic_health")
|
|
self.assertEqual(result.status, "critical")
|
|
|
|
async def test_verify_originality_uses_real_scores_sif_guardian(self):
|
|
agent = SifGuardian.__new__(SifGuardian)
|
|
agent.ORIGINALITY_THRESHOLD = 0.75
|
|
agent.intelligence = _FakeIntelligence()
|
|
agent._log_agent_operation = lambda *args, **kwargs: None
|
|
|
|
result = await SifGuardian.verify_originality(agent, "This is sufficiently long text for originality analysis.", _FakeCompetitorIndex())
|
|
self.assertIn("originality_score", result)
|
|
self.assertLess(result["originality_score"], 1.0)
|
|
self.assertIn("warning", result)
|
|
self.assertEqual(result["method"], "competitor_index_search")
|
|
|
|
async def test_verify_originality_uses_real_scores_specialized_guardian(self):
|
|
agent = SpecializedGuardian.__new__(SpecializedGuardian)
|
|
agent.ORIGINALITY_THRESHOLD = 0.75
|
|
agent.intelligence = _FakeIntelligence()
|
|
agent._log_agent_operation = lambda *args, **kwargs: None
|
|
|
|
result = await SpecializedGuardian.verify_originality(agent, "This is sufficiently long text for originality analysis.", _FakeCompetitorIndex())
|
|
self.assertIn("originality_score", result)
|
|
self.assertLess(result["originality_score"], 1.0)
|
|
self.assertIn("warning", result)
|
|
self.assertEqual(result["method"], "competitor_index_search")
|
|
|
|
|
|
def test_validate_plan_contextuality_passes_with_evidence_links(self):
|
|
plan = {
|
|
"tasks": [
|
|
{
|
|
"pillarId": "plan",
|
|
"title": "Review strategy",
|
|
"description": "Use onboarding goals",
|
|
"metadata": {
|
|
"evidence_links": ["onboarding:business_goals", "alert:101"],
|
|
"reasoning": "Based on onboarding and alert",
|
|
},
|
|
}
|
|
]
|
|
}
|
|
grounding = {
|
|
"onboarding_data": {"business_goals": ["awareness"]},
|
|
"recent_agent_alerts": [{"alert_id": 101, "title": "Drop in traffic"}],
|
|
}
|
|
|
|
validation = validate_plan_contextuality(plan, grounding)
|
|
|
|
self.assertTrue(validation["is_contextual"])
|
|
self.assertEqual(validation["tasks_below_min_evidence"], 0)
|
|
|
|
def test_validate_plan_contextuality_flags_missing_evidence_links(self):
|
|
plan = {
|
|
"tasks": [
|
|
{
|
|
"pillarId": "generate",
|
|
"title": "Write generic post",
|
|
"description": "Create a post",
|
|
"metadata": {"reasoning": "General best practice"},
|
|
}
|
|
]
|
|
}
|
|
grounding = {
|
|
"onboarding_data": {"business_goals": ["awareness"]},
|
|
"recent_agent_alerts": [{"alert_id": 101, "title": "Drop in traffic"}],
|
|
}
|
|
|
|
validation = validate_plan_contextuality(plan, grounding)
|
|
|
|
self.assertFalse(validation["is_contextual"])
|
|
self.assertEqual(validation["tasks_below_min_evidence"], 1)
|
|
|
|
|
|
def test_demo_release_flag_guards_sensitive_routers(self):
|
|
source = Path("backend/alwrity_utils/router_manager.py").read_text()
|
|
self.assertIn("ALWRITY_DEMO_RELEASE", source)
|
|
self.assertIn("Skipping facebook_writer router in demo-release mode", source)
|
|
self.assertIn("Skipping linkedin router in demo-release mode", source)
|
|
self.assertIn("Skipping linkedin_image router in demo-release mode", source)
|
|
self.assertIn("Skipping persona router in demo-release mode", source)
|
|
|
|
def test_pillar_coverage_guardrail_backfills_missing(self):
|
|
tasks = [{"pillarId": "plan", "title": "Plan", "description": "d", "priority": "high", "estimatedTime": 10, "actionType": "navigate", "enabled": True}]
|
|
grounding = {"workflow_config": {"enforce_pillar_coverage": True}}
|
|
|
|
with patch("services.today_workflow_service._build_single_task_for_missing_pillar", return_value=None):
|
|
covered = _ensure_pillar_coverage(tasks, "u1", "2026-01-01", grounding)
|
|
|
|
pillars = {t["pillarId"] for t in covered}
|
|
self.assertEqual(pillars, set(PILLAR_IDS))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|