mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-16 08:25:18 +02:00
fix: number pool initialization in multi telephony setup
If there are multiple telephony configurations, the form number should be initialized from the campaigns given telephonic configuration rather than the organization default telephonic configuration.
This commit is contained in:
parent
81a363b06e
commit
6d93be3ef6
31 changed files with 1105 additions and 238 deletions
|
|
@ -3,7 +3,7 @@
|
|||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from sqlalchemy import Float, Integer, and_, cast, func
|
||||
from sqlalchemy import Float, Integer, Text, and_, cast, func
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
|
||||
from api.db.models import WorkflowRunModel
|
||||
|
|
@ -48,8 +48,10 @@ ATTRIBUTE_FIELD_MAPPING = {
|
|||
"tokenUsage": "cost_info.total_cost_usd",
|
||||
"runId": "id",
|
||||
"workflowId": "workflow_id",
|
||||
"campaignId": "campaign_id",
|
||||
"callTags": "gathered_context.call_tags",
|
||||
"phoneNumber": "initial_context.phone",
|
||||
"callerNumber": "initial_context.caller_number",
|
||||
"calledNumber": "initial_context.called_number",
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -69,7 +71,8 @@ def apply_workflow_run_filters(
|
|||
- runId: Filter by workflow run ID (exact match)
|
||||
- workflowId: Filter by workflow ID (exact match)
|
||||
- callTags: Filter by gathered_context.call_tags (array of strings)
|
||||
- phoneNumber: Filter by initial_context.phone (text search)
|
||||
- callerNumber: Filter by initial_context.caller_number (text search)
|
||||
- calledNumber: Filter by initial_context.called_number (text search)
|
||||
|
||||
Args:
|
||||
base_query: The base SQLAlchemy query to apply filters to
|
||||
|
|
@ -119,6 +122,12 @@ def apply_workflow_run_filters(
|
|||
WorkflowRunModel.workflow_id == value["value"]
|
||||
)
|
||||
|
||||
elif filter_type == "number" and field == "campaign_id":
|
||||
if value.get("value") is not None:
|
||||
filter_conditions.append(
|
||||
WorkflowRunModel.campaign_id == value["value"]
|
||||
)
|
||||
|
||||
elif filter_type == "dateRange" and field == "created_at":
|
||||
# Same as attribute-based dateRange
|
||||
if value.get("from"):
|
||||
|
|
@ -169,15 +178,30 @@ def apply_workflow_run_filters(
|
|||
call_tags = gathered_context_jsonb.op("->")("call_tags")
|
||||
filter_conditions.append(call_tags.op("@>")(func.cast(tags, JSONB)))
|
||||
|
||||
elif filter_type == "text" and field == "initial_context.phone":
|
||||
# Filter by phone number (contains search)
|
||||
elif filter_type == "text" and field == "initial_context.caller_number":
|
||||
phone = value.get("value", "").strip()
|
||||
if phone:
|
||||
# Use ->> operator for compatibility with all PostgreSQL versions
|
||||
# Cast ->> result to Text so .contains() emits LIKE,
|
||||
# not the JSONB @> operator (the default for untyped exprs).
|
||||
filter_conditions.append(
|
||||
cast(WorkflowRunModel.initial_context, JSONB)
|
||||
.op("->>")("phone")
|
||||
.contains(phone)
|
||||
cast(
|
||||
cast(WorkflowRunModel.initial_context, JSONB).op("->>")(
|
||||
"caller_number"
|
||||
),
|
||||
Text,
|
||||
).contains(phone)
|
||||
)
|
||||
|
||||
elif filter_type == "text" and field == "initial_context.called_number":
|
||||
phone = value.get("value", "").strip()
|
||||
if phone:
|
||||
filter_conditions.append(
|
||||
cast(
|
||||
cast(WorkflowRunModel.initial_context, JSONB).op("->>")(
|
||||
"called_number"
|
||||
),
|
||||
Text,
|
||||
).contains(phone)
|
||||
)
|
||||
|
||||
elif filter_type == "numberRange":
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ class OrganizationUsageClient(BaseDBClient):
|
|||
limit: int = 50,
|
||||
offset: int = 0,
|
||||
filters: Optional[list[dict]] = None,
|
||||
) -> tuple[list[dict], int]:
|
||||
) -> tuple[list[dict], int, float, int]:
|
||||
"""Get paginated workflow runs with usage for an organization."""
|
||||
async with self.async_session() as session:
|
||||
query = (
|
||||
|
|
@ -267,7 +267,15 @@ class OrganizationUsageClient(BaseDBClient):
|
|||
|
||||
# Only allow specific filters for usage history endpoint
|
||||
# This ensures security and prevents unexpected filter attributes
|
||||
allowed_filters = {"duration", "dispositionCode", "phoneNumber"}
|
||||
allowed_filters = {
|
||||
"duration",
|
||||
"dispositionCode",
|
||||
"callerNumber",
|
||||
"calledNumber",
|
||||
"runId",
|
||||
"workflowId",
|
||||
"campaignId",
|
||||
}
|
||||
sanitized_filters = []
|
||||
|
||||
if filters:
|
||||
|
|
@ -315,13 +323,15 @@ class OrganizationUsageClient(BaseDBClient):
|
|||
total_tokens += dograh_tokens
|
||||
total_duration_seconds += int(round(call_duration))
|
||||
|
||||
# Extract phone number from initial_context based on call_type.
|
||||
ic = run.initial_context or {}
|
||||
caller_number = ic.get("caller_number")
|
||||
called_number = ic.get("called_number") or ic.get("phone_number")
|
||||
# DEPRECATED: phone_number — use caller_number/called_number.
|
||||
# Inbound runs only have caller_number/called_number; the
|
||||
# caller_number is the customer. Outbound runs use the
|
||||
# phone_number key written by the dispatchers.
|
||||
ic = run.initial_context or {}
|
||||
if run.call_type == "inbound":
|
||||
phone_number = ic.get("caller_number")
|
||||
phone_number = caller_number
|
||||
else:
|
||||
phone_number = ic.get("phone_number")
|
||||
|
||||
|
|
@ -341,6 +351,8 @@ class OrganizationUsageClient(BaseDBClient):
|
|||
"recording_url": run.recording_url,
|
||||
"transcript_url": run.transcript_url,
|
||||
"phone_number": phone_number,
|
||||
"caller_number": caller_number,
|
||||
"called_number": called_number,
|
||||
"call_type": run.call_type,
|
||||
"disposition": disposition,
|
||||
"initial_context": run.initial_context,
|
||||
|
|
@ -355,6 +367,66 @@ class OrganizationUsageClient(BaseDBClient):
|
|||
|
||||
return formatted_runs, total_count, total_tokens, total_duration_seconds
|
||||
|
||||
async def get_usage_runs_for_report(
|
||||
self,
|
||||
organization_id: int,
|
||||
start_date: Optional[datetime] = None,
|
||||
end_date: Optional[datetime] = None,
|
||||
filters: Optional[list[dict]] = None,
|
||||
) -> list:
|
||||
"""Get filtered runs for an organization-scoped usage CSV report.
|
||||
|
||||
Mirrors the filter allowlist used by `get_usage_history`, but selects
|
||||
only the columns needed by `build_run_report_csv` and returns every
|
||||
matching run (no pagination).
|
||||
"""
|
||||
async with self.async_session() as session:
|
||||
query = (
|
||||
select(
|
||||
WorkflowRunModel.id,
|
||||
WorkflowRunModel.workflow_id,
|
||||
WorkflowRunModel.definition_id,
|
||||
WorkflowRunModel.campaign_id,
|
||||
WorkflowRunModel.created_at,
|
||||
WorkflowRunModel.initial_context,
|
||||
WorkflowRunModel.gathered_context,
|
||||
WorkflowRunModel.cost_info,
|
||||
WorkflowRunModel.public_access_token,
|
||||
)
|
||||
.join(WorkflowModel, WorkflowRunModel.workflow_id == WorkflowModel.id)
|
||||
.join(UserModel, WorkflowModel.user_id == UserModel.id)
|
||||
.where(
|
||||
UserModel.selected_organization_id == organization_id,
|
||||
WorkflowRunModel.cost_info.isnot(None),
|
||||
)
|
||||
.order_by(WorkflowRunModel.created_at.desc())
|
||||
)
|
||||
|
||||
if start_date:
|
||||
query = query.where(WorkflowRunModel.created_at >= start_date)
|
||||
if end_date:
|
||||
query = query.where(WorkflowRunModel.created_at <= end_date)
|
||||
|
||||
allowed_filters = {
|
||||
"duration",
|
||||
"dispositionCode",
|
||||
"callerNumber",
|
||||
"calledNumber",
|
||||
"runId",
|
||||
"workflowId",
|
||||
"campaignId",
|
||||
}
|
||||
sanitized_filters = []
|
||||
if filters:
|
||||
for filter_item in filters:
|
||||
if filter_item.get("attribute") in allowed_filters:
|
||||
sanitized_filters.append(filter_item)
|
||||
|
||||
query = apply_workflow_run_filters(query, sanitized_filters)
|
||||
|
||||
result = await session.execute(query)
|
||||
return list(result.all())
|
||||
|
||||
async def get_daily_usage_breakdown(
|
||||
self,
|
||||
organization_id: int,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue