@talkpilot/core-db 1.3.88 → 1.3.89

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/README.md +65 -12
  2. package/dist/talkpilot/auditLogs/auditLogs.getters.d.ts +8 -0
  3. package/dist/talkpilot/auditLogs/auditLogs.getters.d.ts.map +1 -0
  4. package/dist/talkpilot/auditLogs/auditLogs.getters.js +26 -0
  5. package/dist/talkpilot/auditLogs/auditLogs.getters.js.map +1 -0
  6. package/dist/talkpilot/auditLogs/auditLogs.types.d.ts +11 -0
  7. package/dist/talkpilot/auditLogs/auditLogs.types.d.ts.map +1 -0
  8. package/dist/talkpilot/auditLogs/auditLogs.types.js +3 -0
  9. package/dist/talkpilot/auditLogs/auditLogs.types.js.map +1 -0
  10. package/dist/talkpilot/auditLogs/index.d.ts +3 -0
  11. package/dist/talkpilot/auditLogs/index.d.ts.map +1 -0
  12. package/dist/talkpilot/auditLogs/index.js +19 -0
  13. package/dist/talkpilot/auditLogs/index.js.map +1 -0
  14. package/dist/talkpilot/calls/calls.constants.d.ts +11 -1
  15. package/dist/talkpilot/calls/calls.constants.d.ts.map +1 -1
  16. package/dist/talkpilot/calls/calls.constants.js +11 -1
  17. package/dist/talkpilot/calls/calls.constants.js.map +1 -1
  18. package/dist/talkpilot/calls/calls.getters.d.ts +8 -1
  19. package/dist/talkpilot/calls/calls.getters.d.ts.map +1 -1
  20. package/dist/talkpilot/calls/calls.getters.js +216 -1
  21. package/dist/talkpilot/calls/calls.getters.js.map +1 -1
  22. package/dist/talkpilot/calls/calls.types.d.ts +45 -0
  23. package/dist/talkpilot/calls/calls.types.d.ts.map +1 -1
  24. package/dist/talkpilot/clients/clients.constants.d.ts +4 -0
  25. package/dist/talkpilot/clients/clients.constants.d.ts.map +1 -0
  26. package/dist/talkpilot/clients/clients.constants.js +7 -0
  27. package/dist/talkpilot/clients/clients.constants.js.map +1 -0
  28. package/dist/talkpilot/clients/clients.getters.d.ts +6 -0
  29. package/dist/talkpilot/clients/clients.getters.d.ts.map +1 -1
  30. package/dist/talkpilot/clients/clients.getters.js +62 -1
  31. package/dist/talkpilot/clients/clients.getters.js.map +1 -1
  32. package/dist/talkpilot/clients/clients.lifecycle.d.ts +10 -0
  33. package/dist/talkpilot/clients/clients.lifecycle.d.ts.map +1 -0
  34. package/dist/talkpilot/clients/clients.lifecycle.js +109 -0
  35. package/dist/talkpilot/clients/clients.lifecycle.js.map +1 -0
  36. package/dist/talkpilot/clients/clients.types.d.ts +53 -0
  37. package/dist/talkpilot/clients/clients.types.d.ts.map +1 -1
  38. package/dist/talkpilot/clients/index.d.ts +2 -0
  39. package/dist/talkpilot/clients/index.d.ts.map +1 -1
  40. package/dist/talkpilot/clients/index.js +2 -0
  41. package/dist/talkpilot/clients/index.js.map +1 -1
  42. package/dist/talkpilot/contextNotes/contextNotes.getters.d.ts +1 -7
  43. package/dist/talkpilot/contextNotes/contextNotes.getters.d.ts.map +1 -1
  44. package/dist/talkpilot/contextNotes/contextNotes.getters.js +1 -37
  45. package/dist/talkpilot/contextNotes/contextNotes.getters.js.map +1 -1
  46. package/dist/talkpilot/contextNotes/contextNotes.types.d.ts +0 -6
  47. package/dist/talkpilot/contextNotes/contextNotes.types.d.ts.map +1 -1
  48. package/dist/talkpilot/index.d.ts +1 -0
  49. package/dist/talkpilot/index.d.ts.map +1 -1
  50. package/dist/talkpilot/index.js +1 -0
  51. package/dist/talkpilot/index.js.map +1 -1
  52. package/dist/talkpilot/phone_numbers/phone_numbers.getter.d.ts +9 -1
  53. package/dist/talkpilot/phone_numbers/phone_numbers.getter.d.ts.map +1 -1
  54. package/dist/talkpilot/phone_numbers/phone_numbers.getter.js +70 -1
  55. package/dist/talkpilot/phone_numbers/phone_numbers.getter.js.map +1 -1
  56. package/dist/talkpilot/phone_numbers/phone_numbers.types.d.ts +5 -0
  57. package/dist/talkpilot/phone_numbers/phone_numbers.types.d.ts.map +1 -1
  58. package/dist/test-utils/factories/index.d.ts +2 -0
  59. package/dist/test-utils/factories/index.d.ts.map +1 -1
  60. package/dist/test-utils/factories/index.js +2 -0
  61. package/dist/test-utils/factories/index.js.map +1 -1
  62. package/dist/test-utils/factories/talkpilot/clients.d.ts +14 -0
  63. package/dist/test-utils/factories/talkpilot/clients.d.ts.map +1 -0
  64. package/dist/test-utils/factories/talkpilot/clients.js +19 -0
  65. package/dist/test-utils/factories/talkpilot/clients.js.map +1 -0
  66. package/dist/test-utils/factories/talkpilot/results.d.ts +16 -0
  67. package/dist/test-utils/factories/talkpilot/results.d.ts.map +1 -0
  68. package/dist/test-utils/factories/talkpilot/results.js +40 -0
  69. package/dist/test-utils/factories/talkpilot/results.js.map +1 -0
  70. package/dist/utils/regex.utils.d.ts +2 -0
  71. package/dist/utils/regex.utils.d.ts.map +1 -0
  72. package/dist/utils/regex.utils.js +6 -0
  73. package/dist/utils/regex.utils.js.map +1 -0
  74. package/dist/websitalk/scanResources/scanResources.getters.d.ts.map +1 -1
  75. package/dist/websitalk/scanResources/scanResources.getters.js +2 -1
  76. package/dist/websitalk/scanResources/scanResources.getters.js.map +1 -1
  77. package/dist/websitalk/scanResources/scanResources.utils.d.ts +1 -1
  78. package/dist/websitalk/scanResources/scanResources.utils.d.ts.map +1 -1
  79. package/dist/websitalk/scanResources/scanResources.utils.js +3 -3
  80. package/dist/websitalk/scanResources/scanResources.utils.js.map +1 -1
  81. package/package.json +1 -1
  82. package/src/talkpilot/auditLogs/__tests__/auditLogs.spec.ts +91 -0
  83. package/src/talkpilot/auditLogs/auditLogs.getters.ts +26 -0
  84. package/src/talkpilot/auditLogs/auditLogs.types.ts +12 -0
  85. package/src/talkpilot/auditLogs/index.ts +2 -0
  86. package/src/talkpilot/calls/__tests__/calls.statistics.getters.spec.ts +559 -0
  87. package/src/talkpilot/calls/calls.constants.ts +14 -0
  88. package/src/talkpilot/calls/calls.getters.ts +320 -0
  89. package/src/talkpilot/calls/calls.types.ts +52 -0
  90. package/src/talkpilot/clients/__tests__/clients.getters.spec.ts +224 -0
  91. package/src/talkpilot/clients/__tests__/clients.lifecycle.spec.ts +194 -0
  92. package/src/talkpilot/clients/clients.constants.ts +4 -0
  93. package/src/talkpilot/clients/clients.getters.ts +91 -2
  94. package/src/talkpilot/clients/clients.lifecycle.ts +132 -0
  95. package/src/talkpilot/clients/clients.types.ts +58 -0
  96. package/src/talkpilot/clients/index.ts +2 -0
  97. package/src/talkpilot/contextNotes/__tests__/contextNotes.getters.spec.ts +0 -141
  98. package/src/talkpilot/contextNotes/contextNotes.getters.ts +1 -75
  99. package/src/talkpilot/contextNotes/contextNotes.types.ts +0 -7
  100. package/src/talkpilot/index.ts +1 -0
  101. package/src/talkpilot/phone_numbers/__tests__/phone_numbers.spec.ts +284 -13
  102. package/src/talkpilot/phone_numbers/phone_numbers.getter.ts +89 -1
  103. package/src/talkpilot/phone_numbers/phone_numbers.types.ts +6 -0
  104. package/src/test-utils/factories/index.ts +2 -0
  105. package/src/test-utils/factories/talkpilot/clients.ts +21 -0
  106. package/src/test-utils/factories/talkpilot/results.ts +43 -0
  107. package/src/utils/regex.utils.ts +2 -0
  108. package/src/websitalk/scanResources/scanResources.getters.ts +2 -6
  109. package/src/websitalk/scanResources/scanResources.utils.ts +2 -3
  110. package/dist/configuration/gptLive.seed.d.ts +0 -2
  111. package/dist/configuration/gptLive.seed.d.ts.map +0 -1
  112. package/dist/configuration/gptLive.seed.js +0 -59
  113. package/dist/configuration/gptLive.seed.js.map +0 -1
  114. package/dist/talkpilot/clients/clients.quota.constants.d.ts +0 -2
  115. package/dist/talkpilot/clients/clients.quota.constants.d.ts.map +0 -1
  116. package/dist/talkpilot/clients/clients.quota.constants.js +0 -5
  117. package/dist/talkpilot/clients/clients.quota.constants.js.map +0 -1
  118. package/dist/talkpilot/clients/clients.quota.utils.d.ts +0 -8
  119. package/dist/talkpilot/clients/clients.quota.utils.d.ts.map +0 -1
  120. package/dist/talkpilot/clients/clients.quota.utils.js +0 -45
  121. package/dist/talkpilot/clients/clients.quota.utils.js.map +0 -1
package/README.md CHANGED
@@ -37,6 +37,7 @@ L1:# @talkpilot/core-db
37
37
  - **TalkPilot domain** – Imports like `findAgents`, `getFlows`, `findCalls`, and `vectorSearchCalls` live in `src/talkpilot`. These functions are the canonical access pattern for call history, session metadata, and provider configs.
38
38
  - **Municipal domain** – Helpers such as `findStreets`, `getMunicipalCities`, `findDepartmentSubjects`, and `createTicket` live under `src/municipal` and feed MIS workflows (street hints, subject matching, Ash Bina tickets).
39
39
  - **Configuration domain** – `getPromptByName`, `findPrompts`, `getAllPrompts`, and `getPromptsByProduct` live under `src/configuration` and back MIS's `moked_106` prompt lookups (defaults, overridable per-client via `clientConfig.toolsPrompts`).
40
+ - **Websitalk domain** (`scans`, `websiteUrls`) – ⚠️ **Not included in `clientExistsInAnyCollection`/`deleteClientDataAcrossDomains`.** Those collections hold the client's URLs, which an external website-talk API uses as a lookup key to clean up its own Mongo collections *and* other, non-Mongo data stores tied to the client. Deleting a client fully requires calling that external API separately — see the warning on `deleteClientDataAcrossDomains` in `src/talkpilot/clients/clients.lifecycle.ts`.
40
41
 
41
42
  ## Environment variables
42
43
 
@@ -178,17 +179,18 @@ Refer to `DEVELOPMENT.md` for the full walkthrough, token instructions, and fact
178
179
  | 1.3.47 | Added optional `accountOwnerEmail` field to `WebsiteTalkProduct` (CP-1715): the recipient for the end-of-call summary email, set once from the client's login email and editable in Settings. |
179
180
  | 1.3.48 | Fixed `getScanPages` search (CP-1726): user search input was passed unescaped into MongoDB's `$regex`, so a URL/title containing regex-special characters (e.g. `?`) failed to match even when the document existed. Added `escapeRegex()` and applied it before building the `url`/`title` `$regex` filter. No API changes. |
180
181
  | 1.3.49 | Internal tools configuration on flows (CP-1723): `Flow.internalTools` keyed by tool name (`enabled`, optional `maxCalls`, per-parameter `description`/`value` overrides) with matching `$jsonSchema` validator, plus `createFlow` / `updateFlowById` mutators and the `FlowUpdateParams` type so consumers stop writing to the `flows` collection directly. |
182
+ | 1.3.52 | Client-lifecycle support for the TalkPilot Dashboard (PE-869): `clientExistsInAnyCollection`/`deleteClientDataAcrossDomains` (dynamic scan across talkpilot/municipal domains only, no hardcoded collection list; `auditLogs` is excluded so a client's own deletion record survives its deletion), new `auditLogs` collection & `insertAuditLog`/`findAuditLogs`/`countAuditLogs`, `getAllClientCredits` on `clients`. Added `results` statistics getters for the dashboard: `aggregateActiveCallsSummary`, `aggregateTodayCallsSummary`, `aggregateClientCallStats`, `aggregateSessionCallStats`, and `findClientIdsByCallSids` (for backOffice-server). On `phone_numbers`: fixed `getClientPhones`/`getAllClientPhoneMappings` to deduplicate append-only history records instead of returning raw duplicate rows per client; added `getDistinctClientPhonePairs`, `findClientsByPhoneNumbers`, and `deletePhoneNumberRecords` (removes every record for a phone number, across all clients, once it's released back to Twilio). website-talk (`scans`/`websiteUrls`) is intentionally NOT touched by `deleteClientDataAcrossDomains` — see the Websitalk domain note above; callers must call the external website-talk deletion API separately. No breaking changes. |
181
183
  | 1.3.57 | Phone-number bind getters: list by client, exclusive flow assign, unbind, and `flow_id` match for ObjectId or leftover string. `getClientByClientId`. |
182
184
  | 1.3.61 | Optional `activeFlowId` on `WebsiteTalkProduct` (CP-1849). WebsiteTalk updates this flow instead of the flow on a phone number. |
183
185
  | 1.3.62 | Pause/Resume support for WebsiteTalk scans (CP-1752): `PAUSED` and `CANCELED` added to `SCAN_STATUSES` (`PAUSED` deliberately excluded from `FINISHED_SCAN_STATUSES` — pausing isn't done, `CANCELED` is). Added `Scan.pausedAt`, tracked across pause/resume so elapsed-time display can freeze correctly instead of counting the paused duration. |
184
186
  | 1.3.64 | New `callTokenUsage` collection with `incrementCallTokenUsage` (CP-1810): per-call realtime token usage, one document per `callSid`. |
185
187
  | 1.3.65 | `Flow.beforeCallEvent` as an optional HTTP config object (`url` required; `timeoutMs`, `sendUserAuthToken`, `allowedKeys`, `wait`). `afterCallEvent` remains an optional Pub/Sub string. |
188
+ | 1.3.66 | Added `calls`-based stats getters for the dashboard: `aggregateActiveCallsSummary`, `aggregateTodayCallsSummary`, `aggregateClientCallStats`, `aggregateSessionCallStats`, `getAllClientsLastCallDate`, `findClientIdsByCallSids` — read the flat `calls` collection directly instead of unwinding `results.calls[]`, with a full per-status breakdown (`ALL_STATUSES_FOR_BREAKDOWN`) instead of a fixed 5-status set. **Removed** the 1.3.52 `results`-based getters of the same names — they were never released, so this isn't a breaking change for any consumer. |
186
189
  | 1.3.69 | Municipal `residents` collection: optional resident fields (`cityName`, `firstName`, `lastName`, `idNumber`, `phoneNumber`, `address`) with exact-match getters by phone, by idNumber, and by phone+idNumber (city-scoped). Schema + indexes exported; collection is created out of band. |
187
190
  | 1.3.71 | Who ended a call (CP-1873): optional `Call.endedBy` (`system` / `agent` / `user`), `CallEndedByValues`, and `setCallEndedBy` (first write wins). **Breaking** vs 1.3.69–1.3.70: the value `resident` was replaced by `user`. |
188
191
  | 1.3.74 | Added `human` to `CallEndedByValues` (CP-1873): a human agent ended the call, after a transfer or in a conference. |
189
192
  | 1.3.82 | Complaint tracking on `muniIssues` (CP-1888): optional `status` / `response` / `updatedAt`; an editable TalkPilot response with up to `MUNI_ISSUE_MAX_RESPONSE_IMAGES` (5) `response.imageUrls` and `response.updatedAt`; `updateMuniIssueStatus`, `setMuniIssueResponse`, `findMuniIssuesByClient`, `countMuniIssuesByClient`, `getMuniIssueByIdForClient`; index `clientId_1_createdAt_-1`. |
190
- | 1.3.88 | Single-note writes on `contextNotes` (CP-1998): append, update, or remove one entry without replacing the rest of the list. |
191
-
193
+ | 1.3.89 | Backoffice support: roles (`BACKOFFICE_ROLES`, `getBackofficeRole`, `setBackofficeRole`, `countBackofficeAdmins`), `findClientsPage`, `findClientIdsWithCalls`, `findLastCallDatesByClientIds`, `findPhonesByClientIds`; `deleteClientDataAcrossDomains` returns `auditLogged` / `auditError`; an "Unknown" session bucket (`UNKNOWN_SESSION_ID`). **Breaking vs 1.3.66:** `aggregateClientCallStats` returns `{ rows, total }` and takes `clientIds` / `skip` / `limit`; `getAllClientsLastCallDate`, `getAllClientCredits` and `getAllClientPhoneMappings` are removed (only ever published from this branch, never on `main`). |
192
194
 
193
195
  ### 1.1.9
194
196
 
@@ -777,6 +779,24 @@ No — purely additive.
777
779
 
778
780
  No API changes. **WTIS** (the only current consumer of `getScanPages`) should upgrade to 1.3.48 or later to pick up the fix.
779
781
 
782
+ ### 1.3.52 — Client-lifecycle support for the TalkPilot Dashboard (PE-869)
783
+
784
+ Adds centralized client deletion across the talkpilot/municipal domains, with an audit trail, plus supporting getters for the client-management dashboard.
785
+
786
+ **Added**
787
+
788
+ | Area | What |
789
+ | --- | --- |
790
+ | `clients` | `clientExistsInAnyCollection` / `deleteClientDataAcrossDomains` — dynamically scan and delete a client's data across every talkpilot/municipal collection (no hardcoded collection list). `auditLogs` is always excluded, so a client's own deletion record survives its own deletion. `getAllClientCredits`. |
791
+ | `auditLogs` (new collection) | `insertAuditLog`, `findAuditLogs` (paginated), `countAuditLogs`. |
792
+ | `results` | `aggregateActiveCallsSummary`, `aggregateTodayCallsSummary`, `aggregateClientCallStats`, `aggregateSessionCallStats` — call-volume/status dashboards. `findClientIdsByCallSids` — resolves clientId per callSid, added for backOffice-server. |
793
+ | `phone_numbers` | Fixed `getClientPhones`/`getAllClientPhoneMappings` to deduplicate append-only history records. Added `getDistinctClientPhonePairs`, `findClientsByPhoneNumbers`, `deletePhoneNumberRecords` (clears every record for a number once released back to Twilio). |
794
+
795
+ ⚠️ website-talk (`scans`/`websiteUrls`) is intentionally **not** touched by `deleteClientDataAcrossDomains` — see the Websitalk domain note above. Callers must call the external website-talk deletion API separately for a full client delete.
796
+
797
+ **Do I need to update my app?**
798
+
799
+ No — purely additive, no breaking changes. Consumers opt in by importing the new getters above.
780
800
  ### 1.3.57 — Phone-number bind getters
781
801
 
782
802
  TalkPilot can bind any owned number to a flow without querying Mongo in the service layer. `flow_id` is matched as ObjectId or as a leftover string.
@@ -811,6 +831,32 @@ Optional `activeFlowId` on `WebsiteTalkProduct`. WTIS reads this field when it u
811
831
 
812
832
  No — the field is optional. **WTIS** should upgrade to **1.3.61** so the call-script update can use it.
813
833
 
834
+ ### 1.3.66 — Calls-based stats aggregations
835
+
836
+ Adds stats getters that read the flat `calls` collection directly instead of unwinding `results.calls[]`, with a full per-status breakdown instead of a fixed 5-status set.
837
+
838
+ **Added**
839
+
840
+ | Export | Purpose |
841
+ | --- | --- |
842
+ | `aggregateActiveCallsSummary` | Active-calls-right-now summary, from `calls`. |
843
+ | `aggregateTodayCallsSummary` | Status/type/minutes summary for a date range (or all-time with no range given), from `calls`. |
844
+ | `aggregateClientCallStats` | Per-client breakdown with a full per-status `statusCounts` map and `lastCallDate`, from `calls`. |
845
+ | `aggregateSessionCallStats` | Per-session breakdown with a full per-status `statusCounts` map, from `calls`. |
846
+ | `getAllClientsLastCallDate` | Last call date per client, from `calls`. |
847
+ | `findClientIdsByCallSids` | Resolves clientId per callSid, from `calls`. |
848
+
849
+ The session breakdown groups by `sessionId` (not `sessionName`) so a renamed session doesn't split its stats across two rows; the name shown is a live lookup against the current `sessions` collection.
850
+
851
+ ⚠️ **Note:** the `calls` collection only reliably covers call volume from around late October 2025 onward (full per-status accuracy from mid-2026) — older history lives only in `results`. An "all time" aggregation on these getters will undercount clients whose history predates that cutover.
852
+
853
+ **Removed**
854
+
855
+ The `results`-based getters added in 1.3.52 (`aggregateActiveCallsSummary`, `aggregateTodayCallsSummary`, `aggregateClientCallStats`, `aggregateSessionCallStats`, `findClientIdsByCallSids`) are replaced by the `calls`-based getters above, under the same names. 1.3.52 was never released, so no consumer depended on them.
856
+
857
+ **Do I need to update my app?**
858
+
859
+ No — these are new getters, and the 1.3.52 ones they replace were never released, so nothing existing depends on either.
814
860
  ### 1.3.71 — Who ended the call (CP-1873)
815
861
 
816
862
  Records who ended a connected call. Unset for calls that never connected (busy, no-answer) and for calls moved to a conference.
@@ -837,22 +883,29 @@ New `CallEndedByValues.Human` (`"human"`): a human agent ended the call, either
837
883
 
838
884
  No — purely additive. **TalkPilot Server** should upgrade to **1.3.74** to write it.
839
885
 
840
- ### 1.3.85 — Single context-note writes (CP-1998)
841
-
842
- Append, update, or remove one note on a client/product document without replacing the rest of the `notes` array. `appendContextNoteEntry` creates the document when none exists yet. `setContextNoteEntries` still replaces the whole list.
886
+ ### 1.3.89 — Backoffice support and the "Unknown" session bucket
843
887
 
844
888
  **Added**
845
889
 
846
- | Export | Purpose |
847
- | --- | --- |
848
- | `ContextNoteEntryWrite` | Fields the caller sets: `title`, `content`, `activeFrom`, `expiresAt`. The getter assigns `_id`, `createdAt`, and `updatedAt`. |
849
- | `appendContextNoteEntry(clientId, product, entry, now?)` | Pushes one note and upserts the document. Returns the stored entry. |
850
- | `updateContextNoteEntryFields(clientId, product, noteId, entry, now?)` | Replaces one note's writable fields and `updatedAt`. Leaves `createdAt` and the other notes alone. Returns `null` when the note is missing. |
851
- | `pullContextNoteEntry(clientId, product, noteId)` | Removes one note by id. Returns `false` when nothing changed. |
890
+ - Backoffice roles: `BACKOFFICE_ROLES`, `BackofficeRole`, `getBackofficeRole`, `setBackofficeRole`, `countBackofficeAdmins` (stored in `roles.backoffice`).
891
+ - `findClientsPage`: a page of clients with `total`, filtered by `clientId`, `clientIds`, `excludeClientIds` or a `search` on the clientId or phone numbers.
892
+ - Per-client lookups: `findClientIdsWithCalls`, `findLastCallDatesByClientIds`, `findPhonesByClientIds`.
893
+ - `deleteClientDataAcrossDomains` returns `auditLogged`, and `auditError` when it is `false`.
894
+ - `UNKNOWN_SESSION_ID`.
895
+
896
+ **Changed**
897
+
898
+ - `aggregateClientCallStats` accepts `clientIds`, `skip` and `limit` and returns `{ rows, total }` instead of an array. **Breaking vs 1.3.66.**
899
+ - `aggregateSessionCallStats`: only `isIncomingCall: true` calls are "Incoming Call"; non-incoming calls with no session get their own "Unknown" row.
900
+ - `getDistinctClientPhonePairs` takes an optional `clientIds`.
901
+
902
+ **Removed** (only ever in 1.3.66, never on `main`)
903
+
904
+ - `getAllClientsLastCallDate` (use `findLastCallDatesByClientIds`), `getAllClientCredits` (use `findClientsPage`, which returns `credits`) and `getAllClientPhoneMappings` (use `getDistinctClientPhonePairs` or `findPhonesByClientIds`).
852
905
 
853
906
  **Do I need to update my app?**
854
907
 
855
- No — purely additive. Use these instead of `setContextNoteEntries` when only one note changes.
908
+ Only if you use 1.3.66 (the backoffice): adapt to the changes above. **Backoffice** should upgrade to **1.3.89**.
856
909
 
857
910
  ## 🛠 CI/CD & deployment
858
911
 
@@ -0,0 +1,8 @@
1
+ import { Collection } from "mongodb";
2
+ import { QueryOptions } from "../utils/query.utils";
3
+ import { AuditLogDoc, AuditLogEntry } from "./auditLogs.types";
4
+ export declare const getAuditLogsCollection: () => Collection<AuditLogEntry>;
5
+ export declare const insertAuditLog: (entry: AuditLogEntry) => Promise<void>;
6
+ export declare const findAuditLogs: (options: Pick<QueryOptions, "skip"> & Required<Pick<QueryOptions, "limit">>) => Promise<AuditLogDoc[]>;
7
+ export declare const countAuditLogs: () => Promise<number>;
8
+ //# sourceMappingURL=auditLogs.getters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auditLogs.getters.d.ts","sourceRoot":"","sources":["../../../src/talkpilot/auditLogs/auditLogs.getters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC,OAAO,EAAqB,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAE/D,eAAO,MAAM,sBAAsB,QAAO,UAAU,CAAC,aAAa,CAEjE,CAAC;AAEF,eAAO,MAAM,cAAc,GAAU,OAAO,aAAa,KAAG,OAAO,CAAC,IAAI,CAEvE,CAAC;AAEF,eAAO,MAAM,aAAa,GACxB,SAAS,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,KAC1E,OAAO,CAAC,WAAW,EAAE,CAMvB,CAAC;AAEF,eAAO,MAAM,cAAc,QAAa,OAAO,CAAC,MAAM,CAErD,CAAC"}
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.countAuditLogs = exports.findAuditLogs = exports.insertAuditLog = exports.getAuditLogsCollection = void 0;
4
+ const index_1 = require("../index");
5
+ const query_utils_1 = require("../utils/query.utils");
6
+ const getAuditLogsCollection = () => {
7
+ return (0, index_1.getDb)().collection("auditLogs");
8
+ };
9
+ exports.getAuditLogsCollection = getAuditLogsCollection;
10
+ const insertAuditLog = async (entry) => {
11
+ await (0, exports.getAuditLogsCollection)().insertOne(entry);
12
+ };
13
+ exports.insertAuditLog = insertAuditLog;
14
+ const findAuditLogs = async (options) => {
15
+ const cursor = (0, exports.getAuditLogsCollection)().find({});
16
+ return (0, query_utils_1.applyQueryOptions)(cursor, {
17
+ sort: { timestamp: -1 },
18
+ ...options,
19
+ }).toArray();
20
+ };
21
+ exports.findAuditLogs = findAuditLogs;
22
+ const countAuditLogs = async () => {
23
+ return (0, exports.getAuditLogsCollection)().countDocuments({});
24
+ };
25
+ exports.countAuditLogs = countAuditLogs;
26
+ //# sourceMappingURL=auditLogs.getters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auditLogs.getters.js","sourceRoot":"","sources":["../../../src/talkpilot/auditLogs/auditLogs.getters.ts"],"names":[],"mappings":";;;AACA,oCAAiC;AACjC,sDAAuE;AAGhE,MAAM,sBAAsB,GAAG,GAA8B,EAAE;IACpE,OAAO,IAAA,aAAK,GAAE,CAAC,UAAU,CAAgB,WAAW,CAAC,CAAC;AACxD,CAAC,CAAC;AAFW,QAAA,sBAAsB,0BAEjC;AAEK,MAAM,cAAc,GAAG,KAAK,EAAE,KAAoB,EAAiB,EAAE;IAC1E,MAAM,IAAA,8BAAsB,GAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAClD,CAAC,CAAC;AAFW,QAAA,cAAc,kBAEzB;AAEK,MAAM,aAAa,GAAG,KAAK,EAChC,OAA2E,EACnD,EAAE;IAC1B,MAAM,MAAM,GAAG,IAAA,8BAAsB,GAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjD,OAAO,IAAA,+BAAiB,EAAC,MAAM,EAAE;QAC/B,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE;QACvB,GAAG,OAAO;KACX,CAAC,CAAC,OAAO,EAAE,CAAC;AACf,CAAC,CAAC;AARW,QAAA,aAAa,iBAQxB;AAEK,MAAM,cAAc,GAAG,KAAK,IAAqB,EAAE;IACxD,OAAO,IAAA,8BAAsB,GAAE,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;AACrD,CAAC,CAAC;AAFW,QAAA,cAAc,kBAEzB"}
@@ -0,0 +1,11 @@
1
+ import { WithId } from "mongodb";
2
+ export type AuditLogEntry = {
3
+ clientId: string;
4
+ adminEmail: string;
5
+ action: "delete";
6
+ timestamp: Date;
7
+ allSucceeded?: boolean;
8
+ failedCollections?: string[];
9
+ };
10
+ export type AuditLogDoc = WithId<AuditLogEntry>;
11
+ //# sourceMappingURL=auditLogs.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auditLogs.types.d.ts","sourceRoot":"","sources":["../../../src/talkpilot/auditLogs/auditLogs.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,QAAQ,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=auditLogs.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auditLogs.types.js","sourceRoot":"","sources":["../../../src/talkpilot/auditLogs/auditLogs.types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ export * from "./auditLogs.types";
2
+ export * from "./auditLogs.getters";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/talkpilot/auditLogs/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./auditLogs.types"), exports);
18
+ __exportStar(require("./auditLogs.getters"), exports);
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/talkpilot/auditLogs/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,oDAAkC;AAClC,sDAAoC"}
@@ -6,6 +6,14 @@ export declare const CONFERENCE_ROLE_SUPERVISOR: "supervisor";
6
6
  export declare const IN_CALL_STATUSES: readonly ["in-progress", "answered"];
7
7
  /** Terminal Twilio call statuses — no further status callbacks expected. */
8
8
  export declare const FINAL_STATUSES: readonly ["no-answer", "completed", "busy", "failed", "canceled"];
9
+ /** sessionId used for calls that arrived without an existing session (incoming calls). */
10
+ export declare const INCOMING_SESSION_ID = "incomingCall";
11
+ /** sessionId used in the per-session breakdown for calls with no session that are not incoming. */
12
+ export declare const UNKNOWN_SESSION_ID = "unknownSession";
13
+ /** Twilio call statuses considered "currently active" for the live calls summary. */
14
+ export declare const ACTIVE_STATUSES: string[];
15
+ /** Lookback window for the "active calls right now" summary. */
16
+ export declare const ACTIVE_CALLS_WINDOW_MS: number;
9
17
  /** Hours in a single day. */
10
18
  export declare const HOURS_PER_DAY = 24;
11
19
  /** Max execution time (ms) for calls statistics aggregations. */
@@ -31,7 +39,9 @@ export declare const CallStatusValues: {
31
39
  readonly Failed: "failed";
32
40
  readonly Canceled: "canceled";
33
41
  };
34
- export declare const ALL_CALL_STATUSES: ("in-progress" | "answered" | "no-answer" | "completed" | "busy" | "failed" | "canceled" | "initiated" | "ringing")[];
42
+ export declare const ALL_CALL_STATUSES: ("failed" | "answered" | "in-progress" | "no-answer" | "completed" | "busy" | "canceled" | "initiated" | "ringing")[];
43
+ /** All statuses shown in the breakdown tables — kept separate so CallStatusValues stays untouched. */
44
+ export declare const ALL_STATUSES_FOR_BREAKDOWN: readonly [...("failed" | "answered" | "in-progress" | "no-answer" | "completed" | "busy" | "canceled" | "initiated" | "ringing")[], "queued"];
35
45
  /** Who ended a connected call: our platform, the AI agent (end flow), the caller, or a human agent. */
36
46
  export declare const CallEndedByValues: {
37
47
  readonly System: "system";
@@ -1 +1 @@
1
- {"version":3,"file":"calls.constants.d.ts","sourceRoot":"","sources":["../../../src/talkpilot/calls/calls.constants.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,eAAO,MAAM,wBAAwB,EAAG,UAAmB,CAAC;AAE5D,gDAAgD;AAChD,eAAO,MAAM,0BAA0B,EAAG,YAAqB,CAAC;AAEhE,wEAAwE;AACxE,eAAO,MAAM,gBAAgB,sCAAuC,CAAC;AAErE,4EAA4E;AAC5E,eAAO,MAAM,cAAc,mEAMjB,CAAC;AAEX,6BAA6B;AAC7B,eAAO,MAAM,aAAa,KAAK,CAAC;AAEhC,iEAAiE;AACjE,eAAO,MAAM,iBAAiB,QAAS,CAAC;AAExC;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAC/C,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAE9C,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAC9C,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAEhD,eAAO,MAAM,gBAAgB;;;;;;;;;;CAUnB,CAAC;AAEX,eAAO,MAAM,iBAAiB,uHAAkC,CAAC;AAEjE,uGAAuG;AACvG,eAAO,MAAM,iBAAiB;;;;;CAKpB,CAAC"}
1
+ {"version":3,"file":"calls.constants.d.ts","sourceRoot":"","sources":["../../../src/talkpilot/calls/calls.constants.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,eAAO,MAAM,wBAAwB,EAAG,UAAmB,CAAC;AAE5D,gDAAgD;AAChD,eAAO,MAAM,0BAA0B,EAAG,YAAqB,CAAC;AAEhE,wEAAwE;AACxE,eAAO,MAAM,gBAAgB,sCAAuC,CAAC;AAErE,4EAA4E;AAC5E,eAAO,MAAM,cAAc,mEAMjB,CAAC;AAEX,0FAA0F;AAC1F,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAElD,mGAAmG;AACnG,eAAO,MAAM,kBAAkB,mBAAmB,CAAC;AAEnD,qFAAqF;AACrF,eAAO,MAAM,eAAe,UAAkB,CAAC;AAE/C,gEAAgE;AAChE,eAAO,MAAM,sBAAsB,QAAqB,CAAC;AAEzD,6BAA6B;AAC7B,eAAO,MAAM,aAAa,KAAK,CAAC;AAEhC,iEAAiE;AACjE,eAAO,MAAM,iBAAiB,QAAS,CAAC;AAExC;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAC/C,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAE9C,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAC9C,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAEhD,eAAO,MAAM,gBAAgB;;;;;;;;;;CAUnB,CAAC;AAEX,eAAO,MAAM,iBAAiB,uHAAkC,CAAC;AAEjE,sGAAsG;AACtG,eAAO,MAAM,0BAA0B,+IAA4C,CAAC;AACpF,uGAAuG;AACvG,eAAO,MAAM,iBAAiB;;;;;CAKpB,CAAC"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CallEndedByValues = exports.ALL_CALL_STATUSES = exports.CallStatusValues = exports.TOOL_EXECUTIONS_DEFAULT_LIMIT = exports.TOOL_EXECUTIONS_DEFAULT_SKIP = exports.TREND_WEEK_BUCKET_MIN_MONTHS = exports.TREND_MONTH_BUCKET_MIN_MONTHS = exports.STATS_MAX_TIME_MS = exports.HOURS_PER_DAY = exports.FINAL_STATUSES = exports.IN_CALL_STATUSES = exports.CONFERENCE_ROLE_SUPERVISOR = exports.CONFERENCE_ROLE_CUSTOMER = void 0;
3
+ exports.CallEndedByValues = exports.ALL_STATUSES_FOR_BREAKDOWN = exports.ALL_CALL_STATUSES = exports.CallStatusValues = exports.TOOL_EXECUTIONS_DEFAULT_LIMIT = exports.TOOL_EXECUTIONS_DEFAULT_SKIP = exports.TREND_WEEK_BUCKET_MIN_MONTHS = exports.TREND_MONTH_BUCKET_MIN_MONTHS = exports.STATS_MAX_TIME_MS = exports.HOURS_PER_DAY = exports.ACTIVE_CALLS_WINDOW_MS = exports.ACTIVE_STATUSES = exports.UNKNOWN_SESSION_ID = exports.INCOMING_SESSION_ID = exports.FINAL_STATUSES = exports.IN_CALL_STATUSES = exports.CONFERENCE_ROLE_SUPERVISOR = exports.CONFERENCE_ROLE_CUSTOMER = void 0;
4
4
  /** Customer role within a conference call. */
5
5
  exports.CONFERENCE_ROLE_CUSTOMER = "customer";
6
6
  /** Supervisor role within a conference call. */
@@ -15,6 +15,14 @@ exports.FINAL_STATUSES = [
15
15
  "failed",
16
16
  "canceled",
17
17
  ];
18
+ /** sessionId used for calls that arrived without an existing session (incoming calls). */
19
+ exports.INCOMING_SESSION_ID = "incomingCall";
20
+ /** sessionId used in the per-session breakdown for calls with no session that are not incoming. */
21
+ exports.UNKNOWN_SESSION_ID = "unknownSession";
22
+ /** Twilio call statuses considered "currently active" for the live calls summary. */
23
+ exports.ACTIVE_STATUSES = ["in-progress"];
24
+ /** Lookback window for the "active calls right now" summary. */
25
+ exports.ACTIVE_CALLS_WINDOW_MS = 2 * 60 * 60 * 1000;
18
26
  /** Hours in a single day. */
19
27
  exports.HOURS_PER_DAY = 24;
20
28
  /** Max execution time (ms) for calls statistics aggregations. */
@@ -41,6 +49,8 @@ exports.CallStatusValues = {
41
49
  Canceled: "canceled",
42
50
  };
43
51
  exports.ALL_CALL_STATUSES = Object.values(exports.CallStatusValues);
52
+ /** All statuses shown in the breakdown tables — kept separate so CallStatusValues stays untouched. */
53
+ exports.ALL_STATUSES_FOR_BREAKDOWN = [...exports.ALL_CALL_STATUSES, "queued"];
44
54
  /** Who ended a connected call: our platform, the AI agent (end flow), the caller, or a human agent. */
45
55
  exports.CallEndedByValues = {
46
56
  System: "system",
@@ -1 +1 @@
1
- {"version":3,"file":"calls.constants.js","sourceRoot":"","sources":["../../../src/talkpilot/calls/calls.constants.ts"],"names":[],"mappings":";;;AAAA,8CAA8C;AACjC,QAAA,wBAAwB,GAAG,UAAmB,CAAC;AAE5D,gDAAgD;AACnC,QAAA,0BAA0B,GAAG,YAAqB,CAAC;AAEhE,wEAAwE;AAC3D,QAAA,gBAAgB,GAAG,CAAC,aAAa,EAAE,UAAU,CAAU,CAAC;AAErE,4EAA4E;AAC/D,QAAA,cAAc,GAAG;IAC5B,WAAW;IACX,WAAW;IACX,MAAM;IACN,QAAQ;IACR,UAAU;CACF,CAAC;AAEX,6BAA6B;AAChB,QAAA,aAAa,GAAG,EAAE,CAAC;AAEhC,iEAAiE;AACpD,QAAA,iBAAiB,GAAG,MAAM,CAAC;AAExC;;;;;GAKG;AACU,QAAA,6BAA6B,GAAG,CAAC,CAAC;AAClC,QAAA,4BAA4B,GAAG,CAAC,CAAC;AAEjC,QAAA,4BAA4B,GAAG,CAAC,CAAC;AACjC,QAAA,6BAA6B,GAAG,EAAE,CAAC;AAEnC,QAAA,gBAAgB,GAAG;IAC9B,SAAS,EAAE,WAAW;IACtB,OAAO,EAAE,SAAS;IAClB,UAAU,EAAE,aAAa;IACzB,QAAQ,EAAE,UAAU;IACpB,SAAS,EAAE,WAAW;IACtB,IAAI,EAAE,MAAM;IACZ,QAAQ,EAAE,WAAW;IACrB,MAAM,EAAE,QAAQ;IAChB,QAAQ,EAAE,UAAU;CACZ,CAAC;AAEE,QAAA,iBAAiB,GAAG,MAAM,CAAC,MAAM,CAAC,wBAAgB,CAAC,CAAC;AAEjE,uGAAuG;AAC1F,QAAA,iBAAiB,GAAG;IAC/B,MAAM,EAAE,QAAQ;IAChB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,MAAM;IACZ,UAAU,EAAE,YAAY;CAChB,CAAC"}
1
+ {"version":3,"file":"calls.constants.js","sourceRoot":"","sources":["../../../src/talkpilot/calls/calls.constants.ts"],"names":[],"mappings":";;;AAAA,8CAA8C;AACjC,QAAA,wBAAwB,GAAG,UAAmB,CAAC;AAE5D,gDAAgD;AACnC,QAAA,0BAA0B,GAAG,YAAqB,CAAC;AAEhE,wEAAwE;AAC3D,QAAA,gBAAgB,GAAG,CAAC,aAAa,EAAE,UAAU,CAAU,CAAC;AAErE,4EAA4E;AAC/D,QAAA,cAAc,GAAG;IAC5B,WAAW;IACX,WAAW;IACX,MAAM;IACN,QAAQ;IACR,UAAU;CACF,CAAC;AAEX,0FAA0F;AAC7E,QAAA,mBAAmB,GAAG,cAAc,CAAC;AAElD,mGAAmG;AACtF,QAAA,kBAAkB,GAAG,gBAAgB,CAAC;AAEnD,qFAAqF;AACxE,QAAA,eAAe,GAAG,CAAC,aAAa,CAAC,CAAC;AAE/C,gEAAgE;AACnD,QAAA,sBAAsB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAEzD,6BAA6B;AAChB,QAAA,aAAa,GAAG,EAAE,CAAC;AAEhC,iEAAiE;AACpD,QAAA,iBAAiB,GAAG,MAAM,CAAC;AAExC;;;;;GAKG;AACU,QAAA,6BAA6B,GAAG,CAAC,CAAC;AAClC,QAAA,4BAA4B,GAAG,CAAC,CAAC;AAEjC,QAAA,4BAA4B,GAAG,CAAC,CAAC;AACjC,QAAA,6BAA6B,GAAG,EAAE,CAAC;AAEnC,QAAA,gBAAgB,GAAG;IAC9B,SAAS,EAAE,WAAW;IACtB,OAAO,EAAE,SAAS;IAClB,UAAU,EAAE,aAAa;IACzB,QAAQ,EAAE,UAAU;IACpB,SAAS,EAAE,WAAW;IACtB,IAAI,EAAE,MAAM;IACZ,QAAQ,EAAE,WAAW;IACrB,MAAM,EAAE,QAAQ;IAChB,QAAQ,EAAE,UAAU;CACZ,CAAC;AAEE,QAAA,iBAAiB,GAAG,MAAM,CAAC,MAAM,CAAC,wBAAgB,CAAC,CAAC;AAEjE,sGAAsG;AACzF,QAAA,0BAA0B,GAAG,CAAC,GAAG,yBAAiB,EAAE,QAAQ,CAAU,CAAC;AACpF,uGAAuG;AAC1F,QAAA,iBAAiB,GAAG;IAC/B,MAAM,EAAE,QAAQ;IAChB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,MAAM;IACZ,UAAU,EAAE,YAAY;CAChB,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { Call, CallDoc, CallQueryOptions, CallUpdateParams } from "../index";
2
- import type { CountOpts, DateRange, ToolExecution, CallStatus, CallEndedBy } from "./calls.types";
2
+ import type { CountOpts, DateRange, ToolExecution, CallStatus, ActiveCallsSummary, TodayCallsSummary, ClientCallStatsPage, ClientCallStatsParams, SessionCallStats, StatsDateRangeParams, CallEndedBy } from "./calls.types";
3
3
  import { Filter, ObjectId } from "mongodb";
4
4
  import { DashboardHeatmapMetric } from "./dashboard/calls.dashboard.types";
5
5
  export declare const getCallsCollection: () => import("mongodb").Collection<Call>;
@@ -41,4 +41,11 @@ export declare function getCallsStatsForDateRange(clientId: string, startStr: st
41
41
  * Aggregate calls by hour for a given date (createdAt converted to given timezone). Hour format "HH:mm".
42
42
  */
43
43
  export declare function getCallsHourlyAggregation(clientId: string, dateStr: string, timezone: string): Promise<DashboardHeatmapMetric[]>;
44
+ export declare const findClientIdsWithCalls: ({ dateFrom, dateTo, timezone, }: StatsDateRangeParams) => Promise<string[]>;
45
+ export declare const aggregateActiveCallsSummary: (clientId?: string) => Promise<ActiveCallsSummary>;
46
+ export declare const aggregateTodayCallsSummary: ({ dateFrom, dateTo, clientId, timezone, }: StatsDateRangeParams) => Promise<TodayCallsSummary>;
47
+ export declare const aggregateClientCallStats: ({ dateFrom, dateTo, clientId, clientIds, timezone, skip, limit, }: ClientCallStatsParams) => Promise<ClientCallStatsPage>;
48
+ export declare const aggregateSessionCallStats: ({ dateFrom, dateTo, clientId, timezone, }: StatsDateRangeParams) => Promise<SessionCallStats[]>;
49
+ export declare const findLastCallDatesByClientIds: (clientIds: string[]) => Promise<Map<string, Date>>;
50
+ export declare const findClientIdsByCallSids: (callSids: string[]) => Promise<Map<string, string>>;
44
51
  //# sourceMappingURL=calls.getters.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"calls.getters.d.ts","sourceRoot":"","sources":["../../../src/talkpilot/calls/calls.getters.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"calls.getters.d.ts","sourceRoot":"","sources":["../../../src/talkpilot/calls/calls.getters.ts"],"names":[],"mappings":"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"}
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.range = exports.countCalls = exports.findCallsByQuery = exports.getCallsByClientAndDateRange = exports.pushToolExecution = exports.setCallEndedBy = exports.updateCallStatusByCallSid = exports.getToolExecutionsByCallSid = exports.updateCallByCallSid = exports.createCallDoc = exports.getCallsByFlow = exports.getCallsByClient = exports.getCallsByPhoneNumber = exports.getCallByCallSid = exports.getCallsCollection = void 0;
36
+ exports.findClientIdsByCallSids = exports.findLastCallDatesByClientIds = exports.aggregateSessionCallStats = exports.aggregateClientCallStats = exports.aggregateTodayCallsSummary = exports.aggregateActiveCallsSummary = exports.findClientIdsWithCalls = exports.range = exports.countCalls = exports.findCallsByQuery = exports.getCallsByClientAndDateRange = exports.pushToolExecution = exports.setCallEndedBy = exports.updateCallStatusByCallSid = exports.getToolExecutionsByCallSid = exports.updateCallByCallSid = exports.createCallDoc = exports.getCallsByFlow = exports.getCallsByClient = exports.getCallsByPhoneNumber = exports.getCallByCallSid = exports.getCallsCollection = void 0;
37
37
  exports.countCallsByPhoneInRange = countCallsByPhoneInRange;
38
38
  exports.startOfDay = startOfDay;
39
39
  exports.addDays = addDays;
@@ -43,6 +43,7 @@ const index_1 = require("../index");
43
43
  const calls_constants_1 = require("./calls.constants");
44
44
  const process = __importStar(require("node:process"));
45
45
  const query_utils_1 = require("../utils/query.utils");
46
+ const date_utils_1 = require("../../utils/date.utils");
46
47
  const getCallsCollection = () => {
47
48
  return (0, index_1.getDb)().collection("calls");
48
49
  };
@@ -246,4 +247,218 @@ async function getCallsHourlyAggregation(clientId, dateStr, timezone) {
246
247
  calls: r.calls,
247
248
  }));
248
249
  }
250
+ // One $sum/$cond accumulator per status in ALL_STATUSES_FOR_BREAKDOWN.
251
+ const buildStatusCountFields = (field) => Object.fromEntries(calls_constants_1.ALL_STATUSES_FOR_BREAKDOWN.map((status) => [
252
+ status,
253
+ { $sum: { $cond: [{ $eq: [field, status] }, 1, 0] } },
254
+ ]));
255
+ const extractStatusCounts = (row) => Object.fromEntries(calls_constants_1.ALL_STATUSES_FOR_BREAKDOWN.map((status) => [status, row[status] ?? 0]));
256
+ const dateRangeFilter = (dateFrom, dateTo, timezone) => {
257
+ const today = (0, date_utils_1.formatYmdInTz)(new Date(), timezone);
258
+ return {
259
+ start: (0, date_utils_1.startOfCalendarDayInTz)(dateFrom ?? today, timezone),
260
+ end: (0, date_utils_1.endOfCalendarDayInTz)(dateTo ?? today, timezone),
261
+ };
262
+ };
263
+ const buildCallsStatsMatch = ({ dateFrom, dateTo, clientId, clientIds, timezone = "UTC", }) => {
264
+ const range = dateFrom || dateTo ? dateRangeFilter(dateFrom, dateTo, timezone) : null;
265
+ const clientConditions = [
266
+ ...(clientId ? [{ clientId }] : []),
267
+ ...(clientIds ? [{ clientId: { $in: clientIds } }] : []),
268
+ ];
269
+ return {
270
+ ...(range ? { createdAt: { $gte: range.start, $lte: range.end } } : {}),
271
+ ...(clientConditions.length > 0 ? { $and: clientConditions } : {}),
272
+ };
273
+ };
274
+ const findClientIdsWithCalls = async ({ dateFrom, dateTo, timezone = "UTC", }) => (0, exports.getCallsCollection)().distinct("clientId", buildCallsStatsMatch({ dateFrom, dateTo, timezone }));
275
+ exports.findClientIdsWithCalls = findClientIdsWithCalls;
276
+ const aggregateActiveCallsSummary = async (clientId) => {
277
+ const coll = (0, exports.getCallsCollection)();
278
+ const twoHoursAgo = new Date(Date.now() - calls_constants_1.ACTIVE_CALLS_WINDOW_MS);
279
+ const match = {
280
+ status: { $in: calls_constants_1.ACTIVE_STATUSES },
281
+ updatedAt: { $gte: twoHoursAgo },
282
+ ...(clientId ? { clientId } : {}),
283
+ };
284
+ const facetResult = await coll
285
+ .aggregate([
286
+ { $match: match },
287
+ {
288
+ $facet: {
289
+ counts: [
290
+ {
291
+ $group: {
292
+ _id: null,
293
+ total: { $sum: 1 },
294
+ incoming: { $sum: { $cond: ["$isIncomingCall", 1, 0] } },
295
+ outgoing: { $sum: { $cond: ["$isOutgoingCall", 1, 0] } },
296
+ },
297
+ },
298
+ ],
299
+ activeClients: [{ $group: { _id: "$clientId" } }],
300
+ },
301
+ },
302
+ ])
303
+ .next();
304
+ const counts = facetResult?.counts?.[0];
305
+ return {
306
+ total: counts?.total ?? 0,
307
+ incoming: counts?.incoming ?? 0,
308
+ outgoing: counts?.outgoing ?? 0,
309
+ activeClientIds: (facetResult?.activeClients ?? []).map((d) => d._id || "Unknown"),
310
+ };
311
+ };
312
+ exports.aggregateActiveCallsSummary = aggregateActiveCallsSummary;
313
+ const aggregateTodayCallsSummary = async ({ dateFrom, dateTo, clientId, timezone = "UTC", }) => {
314
+ const coll = (0, exports.getCallsCollection)();
315
+ const match = buildCallsStatsMatch({ dateFrom, dateTo, clientId, timezone });
316
+ const facetResult = await coll
317
+ .aggregate([
318
+ { $match: match },
319
+ {
320
+ $facet: {
321
+ byStatus: [{ $group: { _id: "$status", count: { $sum: 1 } } }],
322
+ byType: [
323
+ {
324
+ $group: {
325
+ _id: { $cond: ["$isIncomingCall", "incoming", "outgoing"] },
326
+ count: { $sum: 1 },
327
+ },
328
+ },
329
+ ],
330
+ total: [{ $group: { _id: null, count: { $sum: 1 } } }],
331
+ totalSeconds: [{ $group: { _id: null, seconds: { $sum: "$callLength" } } }],
332
+ },
333
+ },
334
+ ])
335
+ .next();
336
+ const byStatus = {};
337
+ (facetResult?.byStatus ?? []).forEach((s) => {
338
+ byStatus[s._id] = s.count;
339
+ });
340
+ const byType = {};
341
+ (facetResult?.byType ?? []).forEach((t) => {
342
+ byType[t._id] = t.count;
343
+ });
344
+ const totalSeconds = facetResult?.totalSeconds?.[0]?.seconds ?? 0;
345
+ return {
346
+ total: facetResult?.total?.[0]?.count ?? 0,
347
+ incoming: byType.incoming ?? 0,
348
+ outgoing: byType.outgoing ?? 0,
349
+ completed: byStatus.completed ?? 0,
350
+ failed: byStatus.failed ?? 0,
351
+ busy: byStatus.busy ?? 0,
352
+ noAnswer: byStatus["no-answer"] ?? 0,
353
+ totalMinutes: Math.ceil(totalSeconds / 60),
354
+ };
355
+ };
356
+ exports.aggregateTodayCallsSummary = aggregateTodayCallsSummary;
357
+ const aggregateClientCallStats = async ({ dateFrom, dateTo, clientId, clientIds, timezone = "UTC", skip = 0, limit, }) => {
358
+ const [result] = await (0, exports.getCallsCollection)()
359
+ .aggregate([
360
+ { $match: buildCallsStatsMatch({ dateFrom, dateTo, clientId, clientIds, timezone }) },
361
+ {
362
+ $group: {
363
+ _id: "$clientId",
364
+ totalCalls: { $sum: 1 },
365
+ ...buildStatusCountFields("$status"),
366
+ totalSeconds: { $sum: "$callLength" },
367
+ lastCallDate: { $max: "$createdAt" },
368
+ },
369
+ },
370
+ {
371
+ $lookup: {
372
+ from: (0, index_1.getClientsCollection)().collectionName,
373
+ localField: "_id",
374
+ foreignField: "clientId",
375
+ as: "client",
376
+ },
377
+ },
378
+ { $match: { "client.0": { $exists: true } } },
379
+ { $project: { client: 0 } },
380
+ { $sort: { totalCalls: -1, _id: 1 } },
381
+ {
382
+ $facet: {
383
+ rows: [{ $skip: skip }, ...(limit ? [{ $limit: limit }] : [])],
384
+ total: [{ $count: "count" }],
385
+ },
386
+ },
387
+ ])
388
+ .toArray();
389
+ return {
390
+ rows: (result?.rows ?? []).map((r) => ({
391
+ clientId: r._id,
392
+ totalCalls: r.totalCalls,
393
+ statusCounts: extractStatusCounts(r),
394
+ totalMinutes: Math.ceil((r.totalSeconds || 0) / 60),
395
+ lastCallDate: r.lastCallDate,
396
+ })),
397
+ total: result?.total[0]?.count ?? 0,
398
+ };
399
+ };
400
+ exports.aggregateClientCallStats = aggregateClientCallStats;
401
+ const aggregateSessionCallStats = async ({ dateFrom, dateTo, clientId, timezone = "UTC", }) => {
402
+ const coll = (0, exports.getCallsCollection)();
403
+ const match = buildCallsStatsMatch({ dateFrom, dateTo, clientId, timezone });
404
+ const sessionGroupKey = {
405
+ $cond: [
406
+ { $eq: ["$isIncomingCall", true] },
407
+ calls_constants_1.INCOMING_SESSION_ID,
408
+ { $ifNull: ["$sessionId", calls_constants_1.UNKNOWN_SESSION_ID] },
409
+ ],
410
+ };
411
+ const rows = await coll
412
+ .aggregate([
413
+ { $match: match },
414
+ {
415
+ $group: {
416
+ _id: sessionGroupKey,
417
+ totalCalls: { $sum: 1 },
418
+ ...buildStatusCountFields("$status"),
419
+ },
420
+ },
421
+ {
422
+ $lookup: {
423
+ from: "sessions",
424
+ localField: "_id",
425
+ foreignField: "_id",
426
+ as: "sessionDoc",
427
+ },
428
+ },
429
+ { $sort: { totalCalls: -1 } },
430
+ ])
431
+ .toArray();
432
+ return rows.map((r) => ({
433
+ sessionId: r._id.toString(),
434
+ // No session, or a deleted/unnamed one, has no name to look up: that is "Unknown".
435
+ sessionName: r._id === calls_constants_1.INCOMING_SESSION_ID
436
+ ? "Incoming Call"
437
+ : r.sessionDoc[0]?.session_name || "Unknown",
438
+ totalCalls: r.totalCalls,
439
+ statusCounts: extractStatusCounts(r),
440
+ }));
441
+ };
442
+ exports.aggregateSessionCallStats = aggregateSessionCallStats;
443
+ const findLastCallDatesByClientIds = async (clientIds) => {
444
+ if (clientIds.length === 0)
445
+ return new Map();
446
+ const rows = await (0, exports.getCallsCollection)()
447
+ .aggregate([
448
+ { $match: { clientId: { $in: clientIds } } },
449
+ { $group: { _id: "$clientId", lastCallDate: { $max: "$createdAt" } } },
450
+ ])
451
+ .toArray();
452
+ return new Map(rows.map((r) => [r._id, r.lastCallDate]));
453
+ };
454
+ exports.findLastCallDatesByClientIds = findLastCallDatesByClientIds;
455
+ const findClientIdsByCallSids = async (callSids) => {
456
+ if (callSids.length === 0)
457
+ return new Map();
458
+ const docs = await (0, exports.getCallsCollection)()
459
+ .find({ callSid: { $in: callSids } }, { projection: { _id: 0, clientId: 1, callSid: 1 } })
460
+ .toArray();
461
+ return new Map(docs.map((d) => [d.callSid, d.clientId]));
462
+ };
463
+ exports.findClientIdsByCallSids = findClientIdsByCallSids;
249
464
  //# sourceMappingURL=calls.getters.js.map