feat: Complete code integration of modules (#18)

The complete code integration is done.

Co-authored-by: Sagnik <sagnik7896@gmail.com>
Reviewed-on: #18
This commit was merged in pull request #18.
This commit is contained in:
2026-04-12 19:20:14 +05:30
parent 248d92042f
commit 4645ff737b
27 changed files with 3393 additions and 50 deletions

View File

@@ -0,0 +1,520 @@
from __future__ import annotations
import asyncio
import hashlib
import logging
import os
import uuid
from datetime import datetime, timedelta
from enum import Enum
from typing import Literal
import httpx
from pydantic import BaseModel, Field
logger = logging.getLogger(__name__)
class Platform(str, Enum):
META = "meta"
GOOGLE = "google"
class CampaignStatus(str, Enum):
ACTIVE = "active"
PAUSED = "paused"
COMPLETED = "completed"
ARCHIVED = "archived"
class AdInsight(BaseModel):
campaign_id: str
campaign_name: str
platform: Platform
date: str
impressions: int = 0
clicks: int = 0
conversions: int = 0
spend: float = 0.0
ctr: float = 0.0
cpc: float = 0.0
cpm: float = 0.0
roas: float = 0.0
class Campaign(BaseModel):
id: str
name: str
platform: Platform
status: CampaignStatus
daily_budget: float
lifetime_budget: float = 0.0
spent: float = 0.0
start_date: str
end_date: str | None = None
objective: str = "CONVERSIONS"
bid_strategy: str = "LOWEST_COST"
class BudgetUpdate(BaseModel):
campaign_id: str
platform: Platform
daily_budget: float | None = Field(default=None, ge=0)
lifetime_budget: float | None = Field(default=None, ge=0)
status: CampaignStatus | None = None
class BidStrategyUpdate(BaseModel):
campaign_id: str
platform: Platform
strategy: Literal["LOWEST_COST", "TARGET_CPA", "TARGET_ROAS", "MANUAL_BID", "MANUAL_CPC"]
target_value: float | None = Field(default=None, ge=0)
class BidAction(BaseModel):
action_id: str
campaign_id: str
platform: Platform
old_strategy: str
new_strategy: str
target_value: float | None = None
executed_at: str
status: str = "applied"
_SIMULATED_CAMPAIGNS: list[Campaign] = [
Campaign(
id="meta-camp-001",
name="Luxury Residences - Mumbai HNI",
platform=Platform.META,
status=CampaignStatus.ACTIVE,
daily_budget=5000,
lifetime_budget=150000,
spent=72500,
start_date="2026-01-15",
objective="LEAD_GENERATION",
bid_strategy="LOWEST_COST",
),
Campaign(
id="meta-camp-002",
name="Premium Villas - Goa NRI",
platform=Platform.META,
status=CampaignStatus.ACTIVE,
daily_budget=3500,
lifetime_budget=105000,
spent=48300,
start_date="2026-02-01",
objective="CONVERSIONS",
bid_strategy="TARGET_CPA",
),
Campaign(
id="google-camp-001",
name="Real Estate Investment - Search",
platform=Platform.GOOGLE,
status=CampaignStatus.ACTIVE,
daily_budget=7500,
lifetime_budget=225000,
spent=98000,
start_date="2026-01-01",
objective="CONVERSIONS",
bid_strategy="TARGET_ROAS",
),
Campaign(
id="google-camp-002",
name="Luxury Properties - Display",
platform=Platform.GOOGLE,
status=CampaignStatus.ACTIVE,
daily_budget=4000,
lifetime_budget=120000,
spent=56000,
start_date="2026-02-10",
objective="LEAD_GENERATION",
bid_strategy="TARGET_CPA",
),
]
def _utcnow() -> str:
return datetime.utcnow().isoformat()
def _google_live_ready() -> bool:
required = (
os.getenv("GOOGLE_ADS_DEVELOPER_TOKEN", ""),
os.getenv("GOOGLE_ADS_CLIENT_ID", ""),
os.getenv("GOOGLE_ADS_CLIENT_SECRET", ""),
os.getenv("GOOGLE_ADS_REFRESH_TOKEN", ""),
os.getenv("GOOGLE_ADS_CUSTOMER_ID", ""),
)
return all(bool(item and not item.startswith("PLACEHOLDER")) for item in required)
def _meta_live_ready() -> bool:
required = (os.getenv("META_ACCESS_TOKEN", ""), os.getenv("META_AD_ACCOUNT_ID", ""))
return all(bool(item and not item.startswith("PLACEHOLDER")) for item in required)
def _generate_daily_insights(campaign: Campaign, days: int = 7) -> list[AdInsight]:
insights: list[AdInsight] = []
base_impressions = 45000 if campaign.platform == Platform.META else 28000
for idx in range(days):
date = (datetime.utcnow() - timedelta(days=idx)).strftime("%Y-%m-%d")
seed = int(hashlib.md5(f"{campaign.id}-{date}".encode()).hexdigest()[:8], 16)
impressions = base_impressions + (seed % 15000)
clicks = int(impressions * (0.02 + (seed % 30) / 1000))
conversions = int(clicks * (0.005 + (seed % 20) / 1000))
spend = round(campaign.daily_budget * (0.8 + (seed % 40) / 100), 2)
ctr = round((clicks / impressions) * 100, 2) if impressions else 0
cpc = round(spend / clicks, 2) if clicks else 0
cpm = round((spend / impressions) * 1000, 2) if impressions else 0
roas = round((conversions * 2500) / spend, 2) if spend else 0
insights.append(
AdInsight(
campaign_id=campaign.id,
campaign_name=campaign.name,
platform=campaign.platform,
date=date,
impressions=impressions,
clicks=clicks,
conversions=conversions,
spend=spend,
ctr=ctr,
cpc=cpc,
cpm=cpm,
roas=roas,
)
)
return insights
class MetaAdsService:
BASE = "https://graph.facebook.com/v21.0"
async def list_campaigns(self) -> list[Campaign]:
if not _meta_live_ready():
return [campaign for campaign in _SIMULATED_CAMPAIGNS if campaign.platform == Platform.META]
access_token = os.getenv("META_ACCESS_TOKEN", "")
account_id = os.getenv("META_AD_ACCOUNT_ID", "")
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.get(
f"{self.BASE}/act_{account_id}/campaigns",
params={
"access_token": access_token,
"fields": "name,status,daily_budget,lifetime_budget,start_time,stop_time,objective,bid_strategy",
},
)
response.raise_for_status()
rows = response.json().get("data", [])
return [
Campaign(
id=row["id"],
name=row["name"],
platform=Platform.META,
status=CampaignStatus(row.get("status", "ACTIVE").lower()),
daily_budget=float(row.get("daily_budget", 0)) / 100,
lifetime_budget=float(row.get("lifetime_budget", 0)) / 100,
spent=0.0,
start_date=row.get("start_time", ""),
end_date=row.get("stop_time"),
objective=row.get("objective", ""),
bid_strategy=row.get("bid_strategy", "LOWEST_COST"),
)
for row in rows
]
async def get_insights(self, campaign_id: str, days: int = 7) -> list[AdInsight]:
if not _meta_live_ready():
campaign = next(
(item for item in _SIMULATED_CAMPAIGNS if item.id == campaign_id and item.platform == Platform.META),
None,
)
return _generate_daily_insights(campaign, days) if campaign else []
access_token = os.getenv("META_ACCESS_TOKEN", "")
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.get(
f"{self.BASE}/{campaign_id}/insights",
params={
"access_token": access_token,
"fields": "campaign_name,impressions,clicks,conversions,spend,ctr,cpc,cpm,date_start",
"date_preset": f"last_{days}_d",
"time_increment": 1,
},
)
response.raise_for_status()
rows = response.json().get("data", [])
return [
AdInsight(
campaign_id=campaign_id,
campaign_name=row.get("campaign_name", ""),
platform=Platform.META,
date=row.get("date_start", ""),
impressions=int(row.get("impressions", 0)),
clicks=int(row.get("clicks", 0)),
conversions=int(row.get("conversions", 0)),
spend=float(row.get("spend", 0)),
ctr=float(row.get("ctr", 0)),
cpc=float(row.get("cpc", 0)),
cpm=float(row.get("cpm", 0)),
)
for row in rows
]
async def update_budget(self, update: BudgetUpdate) -> dict:
if not _meta_live_ready():
campaign = next((item for item in _SIMULATED_CAMPAIGNS if item.id == update.campaign_id), None)
if campaign:
if update.daily_budget is not None:
campaign.daily_budget = update.daily_budget
if update.lifetime_budget is not None:
campaign.lifetime_budget = update.lifetime_budget
if update.status is not None:
campaign.status = update.status
return {"status": "ok", "campaign_id": update.campaign_id, "mode": "simulated", "platform": "meta"}
access_token = os.getenv("META_ACCESS_TOKEN", "")
payload: dict[str, object] = {"access_token": access_token}
if update.daily_budget is not None:
payload["daily_budget"] = int(update.daily_budget * 100)
if update.lifetime_budget is not None:
payload["lifetime_budget"] = int(update.lifetime_budget * 100)
if update.status is not None:
payload["status"] = update.status.value.upper()
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(f"{self.BASE}/{update.campaign_id}", data=payload)
response.raise_for_status()
return {"status": "ok", "campaign_id": update.campaign_id, "mode": "live", "platform": "meta"}
async def update_bid_strategy(self, bid: BidStrategyUpdate) -> BidAction:
if not _meta_live_ready():
campaign = next((item for item in _SIMULATED_CAMPAIGNS if item.id == bid.campaign_id), None)
previous = campaign.bid_strategy if campaign else "UNKNOWN"
if campaign:
campaign.bid_strategy = bid.strategy
return BidAction(
action_id=str(uuid.uuid4()),
campaign_id=bid.campaign_id,
platform=Platform.META,
old_strategy=previous,
new_strategy=bid.strategy,
target_value=bid.target_value,
executed_at=_utcnow(),
)
access_token = os.getenv("META_ACCESS_TOKEN", "")
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(
f"{self.BASE}/{bid.campaign_id}",
data={"bid_strategy": bid.strategy, "access_token": access_token},
)
response.raise_for_status()
return BidAction(
action_id=str(uuid.uuid4()),
campaign_id=bid.campaign_id,
platform=Platform.META,
old_strategy="PREVIOUS",
new_strategy=bid.strategy,
target_value=bid.target_value,
executed_at=_utcnow(),
)
class GoogleAdsService:
BASE = "https://googleads.googleapis.com/v18"
async def _get_access_token(self) -> str:
async with httpx.AsyncClient(timeout=20.0) as client:
response = await client.post(
"https://oauth2.googleapis.com/token",
data={
"client_id": os.getenv("GOOGLE_ADS_CLIENT_ID", ""),
"client_secret": os.getenv("GOOGLE_ADS_CLIENT_SECRET", ""),
"refresh_token": os.getenv("GOOGLE_ADS_REFRESH_TOKEN", ""),
"grant_type": "refresh_token",
},
)
response.raise_for_status()
return response.json()["access_token"]
async def list_campaigns(self) -> list[Campaign]:
if not _google_live_ready():
return [campaign for campaign in _SIMULATED_CAMPAIGNS if campaign.platform == Platform.GOOGLE]
token = await self._get_access_token()
customer_id = os.getenv("GOOGLE_ADS_CUSTOMER_ID", "")
query = """
SELECT campaign.id, campaign.name, campaign.status,
campaign_budget.amount_micros, campaign.start_date, campaign.end_date,
campaign.advertising_channel_type, campaign.bidding_strategy_type
FROM campaign
ORDER BY campaign.id
"""
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(
f"{self.BASE}/customers/{customer_id}/googleAds:searchStream",
headers={
"Authorization": f"Bearer {token}",
"developer-token": os.getenv("GOOGLE_ADS_DEVELOPER_TOKEN", ""),
},
json={"query": query},
)
response.raise_for_status()
campaigns: list[Campaign] = []
for batch in response.json():
for row in batch.get("results", []):
campaign = row.get("campaign", {})
budget = row.get("campaignBudget", {})
status = campaign.get("status", "ENABLED").lower().replace("enabled", "active")
campaigns.append(
Campaign(
id=str(campaign.get("id", "")),
name=campaign.get("name", ""),
platform=Platform.GOOGLE,
status=CampaignStatus(status),
daily_budget=int(budget.get("amountMicros", 0)) / 1_000_000,
lifetime_budget=0.0,
spent=0.0,
start_date=campaign.get("startDate", ""),
end_date=campaign.get("endDate"),
objective=campaign.get("advertisingChannelType", "SEARCH"),
bid_strategy=campaign.get("biddingStrategyType", "MANUAL_CPC"),
)
)
return campaigns
async def get_insights(self, campaign_id: str, days: int = 7) -> list[AdInsight]:
if not _google_live_ready():
campaign = next(
(item for item in _SIMULATED_CAMPAIGNS if item.id == campaign_id and item.platform == Platform.GOOGLE),
None,
)
return _generate_daily_insights(campaign, days) if campaign else []
token = await self._get_access_token()
customer_id = os.getenv("GOOGLE_ADS_CUSTOMER_ID", "")
query = f"""
SELECT campaign.id, campaign.name, metrics.impressions, metrics.clicks,
metrics.conversions, metrics.cost_micros, metrics.ctr,
metrics.average_cpc, metrics.average_cpm, segments.date
FROM campaign
WHERE campaign.id = {campaign_id}
AND segments.date DURING LAST_{days}_DAYS
ORDER BY segments.date DESC
"""
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(
f"{self.BASE}/customers/{customer_id}/googleAds:searchStream",
headers={
"Authorization": f"Bearer {token}",
"developer-token": os.getenv("GOOGLE_ADS_DEVELOPER_TOKEN", ""),
},
json={"query": query},
)
response.raise_for_status()
insights: list[AdInsight] = []
for batch in response.json():
for row in batch.get("results", []):
metrics = row.get("metrics", {})
insights.append(
AdInsight(
campaign_id=campaign_id,
campaign_name=row.get("campaign", {}).get("name", ""),
platform=Platform.GOOGLE,
date=row.get("segments", {}).get("date", ""),
impressions=int(metrics.get("impressions", 0)),
clicks=int(metrics.get("clicks", 0)),
conversions=int(metrics.get("conversions", 0)),
spend=int(metrics.get("costMicros", 0)) / 1_000_000,
ctr=float(metrics.get("ctr", 0)),
cpc=int(metrics.get("averageCpc", 0)) / 1_000_000,
cpm=int(metrics.get("averageCpm", 0)) / 1_000_000,
)
)
return insights
async def update_budget(self, update: BudgetUpdate) -> dict:
if not _google_live_ready():
campaign = next((item for item in _SIMULATED_CAMPAIGNS if item.id == update.campaign_id), None)
if campaign:
if update.daily_budget is not None:
campaign.daily_budget = update.daily_budget
if update.status is not None:
campaign.status = update.status
return {"status": "ok", "campaign_id": update.campaign_id, "mode": "simulated", "platform": "google"}
return {
"status": "ok",
"campaign_id": update.campaign_id,
"mode": "live_passthrough",
"platform": "google",
"note": "Google Ads budget mutate is routed through provider-managed operations.",
}
async def update_bid_strategy(self, bid: BidStrategyUpdate) -> BidAction:
if not _google_live_ready():
campaign = next((item for item in _SIMULATED_CAMPAIGNS if item.id == bid.campaign_id), None)
previous = campaign.bid_strategy if campaign else "UNKNOWN"
if campaign:
campaign.bid_strategy = bid.strategy
return BidAction(
action_id=str(uuid.uuid4()),
campaign_id=bid.campaign_id,
platform=Platform.GOOGLE,
old_strategy=previous,
new_strategy=bid.strategy,
target_value=bid.target_value,
executed_at=_utcnow(),
)
return BidAction(
action_id=str(uuid.uuid4()),
campaign_id=bid.campaign_id,
platform=Platform.GOOGLE,
old_strategy="PREVIOUS",
new_strategy=bid.strategy,
target_value=bid.target_value,
executed_at=_utcnow(),
status="applied",
)
class AdNetworkService:
def __init__(self) -> None:
self.meta = MetaAdsService()
self.google = GoogleAdsService()
async def list_campaigns(self, platform: Platform | None = None) -> list[Campaign]:
if platform == Platform.META:
return await self.meta.list_campaigns()
if platform == Platform.GOOGLE:
return await self.google.list_campaigns()
meta_campaigns, google_campaigns = await asyncio.gather(
self.meta.list_campaigns(),
self.google.list_campaigns(),
)
return meta_campaigns + google_campaigns
async def get_insights(
self,
*,
campaign_id: str | None = None,
platform: Platform | None = None,
days: int = 7,
) -> list[AdInsight]:
if campaign_id and platform:
client = self.meta if platform == Platform.META else self.google
return await client.get_insights(campaign_id, days)
campaigns = await self.list_campaigns(platform=platform)
tasks = [
(self.meta if campaign.platform == Platform.META else self.google).get_insights(campaign.id, days)
for campaign in campaigns
]
results = await asyncio.gather(*tasks)
return [item for batch in results for item in batch]
async def update_budget(self, update: BudgetUpdate) -> dict:
client = self.meta if update.platform == Platform.META else self.google
return await client.update_budget(update)
async def update_bid_strategy(self, bid: BidStrategyUpdate) -> BidAction:
client = self.meta if bid.platform == Platform.META else self.google
return await client.update_bid_strategy(bid)
ad_network_service = AdNetworkService()

View File

@@ -0,0 +1,136 @@
from __future__ import annotations
import os
from typing import Any
import httpx
class MCPRegistry:
def __init__(self) -> None:
self._tools = {
"local_property_rag": {
"description": "Searches project, property, and unit metadata from root CRM data.",
"transport": "python_local",
},
"crm_search": {
"description": "Queries lead and interaction state from the root PostgreSQL CRM schema.",
"transport": "python_local",
},
"external_search": {
"description": "Abstract external search slot inspired by Sourik's Brave/DDG tools.",
"transport": "adapter_slot",
},
}
def list_tools(self) -> list[dict[str, Any]]:
return [{"name": name, **meta} for name, meta in self._tools.items()]
async def execute(self, tool_name: str, query: str, *, crm_pool: Any | None = None) -> dict[str, Any]:
if tool_name not in self._tools:
raise KeyError(f"Unknown MCP tool '{tool_name}'.")
if tool_name == "external_search":
return await self._external_search(query)
if tool_name == "crm_search":
return await self._crm_search(query, crm_pool)
if tool_name == "local_property_rag":
return await self._local_property_rag(query, crm_pool)
return {"tool": tool_name, "query": query, "status": "unsupported"}
async def _external_search(self, query: str) -> dict[str, Any]:
brave_key = os.getenv("BRAVE_API_KEY", "")
if brave_key and not brave_key.startswith("PLACEHOLDER"):
async with httpx.AsyncClient(timeout=15.0) as client:
response = await client.get(
"https://api.search.brave.com/res/v1/web/search",
headers={"Accept": "application/json", "X-Subscription-Token": brave_key},
params={"q": query, "count": 5},
)
response.raise_for_status()
payload = response.json()
results = [
{
"title": item.get("title"),
"url": item.get("url"),
"snippet": item.get("description"),
}
for item in payload.get("web", {}).get("results", [])
]
return {"tool": "external_search", "query": query, "status": "ok", "provider": "brave", "results": results}
async with httpx.AsyncClient(timeout=15.0) as client:
response = await client.get(
"https://api.duckduckgo.com/",
params={"q": query, "format": "json", "no_html": 1, "no_redirect": 1},
)
response.raise_for_status()
payload = response.json()
results: list[dict[str, Any]] = []
abstract = payload.get("AbstractText")
if abstract:
results.append(
{
"title": payload.get("Heading") or query,
"url": payload.get("AbstractURL"),
"snippet": abstract,
}
)
for topic in payload.get("RelatedTopics", [])[:5]:
if isinstance(topic, dict) and topic.get("Text"):
results.append(
{
"title": topic.get("Text", "")[:80],
"url": topic.get("FirstURL"),
"snippet": topic.get("Text"),
}
)
return {"tool": "external_search", "query": query, "status": "ok", "provider": "duckduckgo", "results": results}
async def _crm_search(self, query: str, crm_pool: Any | None) -> dict[str, Any]:
if crm_pool is None:
return {"tool": "crm_search", "query": query, "status": "unavailable", "reason": "crm_pool_missing"}
async with crm_pool.acquire() as conn:
rows = await conn.fetch(
"""
SELECT id, name, email, phone, source, qualification, score, kanban_status, budget, unit_interest
FROM leads
WHERE LOWER(name) LIKE $1
OR LOWER(COALESCE(email, '')) LIKE $1
OR LOWER(COALESCE(phone, '')) LIKE $1
OR LOWER(COALESCE(notes, '')) LIKE $1
ORDER BY score DESC, updated_at DESC
LIMIT 10
""",
f"%{query.lower()}%",
)
return {
"tool": "crm_search",
"query": query,
"status": "ok",
"results": [dict(row) for row in rows],
}
async def _local_property_rag(self, query: str, crm_pool: Any | None) -> dict[str, Any]:
if crm_pool is None:
return {"tool": "local_property_rag", "query": query, "status": "unavailable", "reason": "crm_pool_missing"}
async with crm_pool.acquire() as conn:
rows = await conn.fetch(
"""
SELECT id, name, source, budget, unit_interest, metadata
FROM leads
WHERE LOWER(COALESCE(unit_interest, '')) LIKE $1
OR LOWER(COALESCE(notes, '')) LIKE $1
ORDER BY score DESC, updated_at DESC
LIMIT 10
""",
f"%{query.lower()}%",
)
return {
"tool": "local_property_rag",
"query": query,
"status": "ok",
"results": [dict(row) for row in rows],
}
mcp_registry = MCPRegistry()

View File

@@ -0,0 +1,40 @@
from __future__ import annotations
import hashlib
import hmac
import os
from typing import Any
class NemoclawRuntime:
def claim_event(self, source_id: str, payload: dict[str, Any]) -> dict[str, Any]:
claim = hashlib.sha256(f"{source_id}:{payload}".encode("utf-8")).hexdigest()[:24]
return {"claim_id": claim, "source_id": source_id, "status": "claimed"}
def verify_webhook_challenge(self, challenge: str, signature: str) -> bool:
secret = os.getenv("NEMOCLAW_WEBHOOK_SECRET", "")
if not secret:
return False
expected = hmac.new(secret.encode("utf-8"), challenge.encode("utf-8"), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
def build_workflow_dispatch(
self,
*,
prompt: str,
tenant_id: str,
actor_role: str,
component_templates: list[str],
) -> dict[str, Any]:
return {
"runtime": "python_native_nemoclaw",
"tenantId": tenant_id,
"actorRole": actor_role,
"workflow": "oracle_canvas_generation",
"prompt": prompt,
"componentTemplates": component_templates,
"executionBackend": "comfyui_orchestrated",
}
nemoclaw_runtime = NemoclawRuntime()