From 282dc79ff8d7a07a781b5a9d63d028427ffe3668 Mon Sep 17 00:00:00 2001 From: Aashish John Date: Fri, 19 Jun 2026 15:42:33 -0400 Subject: [PATCH 1/5] fix: use phone_numbers for down migration --- migrations/sqls/20260610000000-calling-routing-down.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/sqls/20260610000000-calling-routing-down.sql b/migrations/sqls/20260610000000-calling-routing-down.sql index 5fe5019..1425f48 100644 --- a/migrations/sqls/20260610000000-calling-routing-down.sql +++ b/migrations/sqls/20260610000000-calling-routing-down.sql @@ -10,7 +10,7 @@ declare v_purchasing_strategy sms.number_purchasing_strategy; begin -- Create the phone number record - insert into sms.all_phone_numbers ( + insert into sms.phone_numbers ( sending_location_id, phone_number ) From abcb2e53ca74942536ca846a3b3a30ae6a560bc2 Mon Sep 17 00:00:00 2001 From: Aashish John Date: Fri, 19 Jun 2026 15:43:47 -0400 Subject: [PATCH 2/5] perf: use sending location for outbound calls check --- src/apis/routing.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/apis/routing.ts b/src/apis/routing.ts index 9fc8743..d68d5ad 100644 --- a/src/apis/routing.ts +++ b/src/apis/routing.ts @@ -65,9 +65,10 @@ app.get('/get-number-for-contact', auth.client, async (req, res) => { rows: [{ count }], } = await client.query<{ count: string }>( `select count(*) from sms.outbound_calls - where from_number = $1 + where sending_location_id = $1 + and from_number = $2 and created_at > date_trunc('day', now() at time zone 'America/Los_Angeles') at time zone 'UTC'`, - [prevMapping.from_number] + [prevMapping.sending_location_id, prevMapping.from_number] ); const todayCallCount = parseInt(count, 10); From 99fc20c17807df71ef30e45bf182013263f996d2 Mon Sep 17 00:00:00 2001 From: Aashish John Date: Fri, 19 Jun 2026 15:44:17 -0400 Subject: [PATCH 3/5] perf: get available number and count in single query --- src/apis/routing.ts | 11 ++--------- src/lib/process-call.ts | 41 +++++++++++++++++++---------------------- 2 files changed, 21 insertions(+), 31 deletions(-) diff --git a/src/apis/routing.ts b/src/apis/routing.ts index d68d5ad..0de5422 100644 --- a/src/apis/routing.ts +++ b/src/apis/routing.ts @@ -5,8 +5,7 @@ import config from '../config'; import { pgPool } from '../db'; import { auth, ClientAuthenticatedRequest } from '../lib/auth'; import { - countAvailableCallingNumbers, - getCallingNumberForSendingLocation, + getCallingNumberWithCount, requestCallingNumber, } from '../lib/process-call'; import { getFromNumberMapping } from '../lib/process-message'; @@ -111,7 +110,7 @@ app.get('/get-number-for-contact', auth.client, async (req, res) => { .json({ error: 'No sending location found for contact' }); } - const fromNumber = await getCallingNumberForSendingLocation( + const { fromNumber, availableCount } = await getCallingNumberWithCount( client, sendingLocationId, daily_calling_limit @@ -132,12 +131,6 @@ app.get('/get-number-for-contact', auth.client, async (req, res) => { [fromNumber, sendingLocationId] ); - const availableCount = await countAvailableCallingNumbers( - client, - sendingLocationId, - daily_calling_limit - ); - if (availableCount <= sendingLocationMinCallingNumbers) { await requestCallingNumber(client, sendingLocationId); } diff --git a/src/lib/process-call.ts b/src/lib/process-call.ts index f92882c..c7e48e1 100644 --- a/src/lib/process-call.ts +++ b/src/lib/process-call.ts @@ -1,35 +1,32 @@ import { PoolOrPoolClient } from '../db'; import { chooseAreaCodeForSendingLocation } from './process-message'; -export const getCallingNumberForSendingLocation = async ( +export const getCallingNumberWithCount = async ( client: PoolOrPoolClient, sendingLocationId: string, dailyCallingLimit: number -): Promise => { +): Promise<{ fromNumber: string | null; availableCount: number }> => { const { rows: [row], - } = await client.query<{ phone_number: string | null }>( - `select phone_number - from sms.get_available_calling_numbers($1, $2) - order by priority asc, call_count asc - limit 1`, - [sendingLocationId, dailyCallingLimit] - ); - return row?.phone_number ?? null; -}; - -export const countAvailableCallingNumbers = async ( - client: PoolOrPoolClient, - sendingLocationId: string, - dailyCallingLimit: number -): Promise => { - const { - rows: [{ count }], - } = await client.query<{ count: string }>( - 'select count(*) from sms.get_available_calling_numbers($1, $2)', + } = await client.query<{ phone_number: string | null; count: string }>( + `with available as ( + select phone_number, priority, call_count + from sms.get_available_calling_numbers($1, $2) + ), + ranked as ( + select + first_value(phone_number) over (order by priority, call_count) as phone_number, + count(*) over () as count + from available + limit 1 + ) + select phone_number, count from ranked`, [sendingLocationId, dailyCallingLimit] ); - return parseInt(count, 10); + return { + fromNumber: row?.phone_number ?? null, + availableCount: row?.count ? parseInt(row.count, 10) : 0, + }; }; export const requestCallingNumber = async ( From 067c6f51dd0f4dc3b8ef16f5d1d67424e37137c6 Mon Sep 17 00:00:00 2001 From: Aashish John Date: Fri, 19 Jun 2026 15:46:47 -0400 Subject: [PATCH 4/5] fix: separate profile and calling limit check --- src/apis/routing.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/apis/routing.ts b/src/apis/routing.ts index 0de5422..af61232 100644 --- a/src/apis/routing.ts +++ b/src/apis/routing.ts @@ -44,9 +44,13 @@ app.get('/get-number-for-contact', auth.client, async (req, res) => { [profile_id] ); + if (!profile) { + return res.status(404).json({ error: 'Profile not found' }); + } + const { daily_calling_limit } = profile; - if (!profile || daily_calling_limit === null) { + if (daily_calling_limit === null) { return res.status(400).json({ error: 'Profile does not have calling configured (daily_calling_limit not set)', From a01effaf06e0620f4c2c9b2cf4e10b8eb5513143 Mon Sep 17 00:00:00 2001 From: Aashish John Date: Fri, 19 Jun 2026 16:12:52 -0400 Subject: [PATCH 5/5] chore: destructure prev mapping --- src/apis/routing.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/apis/routing.ts b/src/apis/routing.ts index af61232..3ff9f2f 100644 --- a/src/apis/routing.ts +++ b/src/apis/routing.ts @@ -64,6 +64,8 @@ app.get('/get-number-for-contact', auth.client, async (req, res) => { }); if (prevMapping !== undefined) { + const { sending_location_id, from_number } = prevMapping; + const { rows: [{ count }], } = await client.query<{ count: string }>( @@ -71,10 +73,10 @@ app.get('/get-number-for-contact', auth.client, async (req, res) => { where sending_location_id = $1 and from_number = $2 and created_at > date_trunc('day', now() at time zone 'America/Los_Angeles') at time zone 'UTC'`, - [prevMapping.sending_location_id, prevMapping.from_number] + [sending_location_id, from_number] ); - const todayCallCount = parseInt(count, 10); + const todayCallCount = parseInt(count, 10); if (todayCallCount < daily_calling_limit) { await client.query( `update sms.from_number_mappings