@talkpilot/core-db 1.3.15 → 1.3.17
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.
- package/README.md +20 -10
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/municipal/index.d.ts +1 -1
- package/dist/municipal/index.d.ts.map +1 -1
- package/dist/municipal/tickets/tickets.statistics.getters.d.ts.map +1 -1
- package/dist/municipal/tickets/tickets.statistics.getters.js +2 -6
- package/dist/municipal/tickets/tickets.statistics.getters.js.map +1 -1
- package/dist/talkpilot/calls/calls.constants.d.ts +2 -0
- package/dist/talkpilot/calls/calls.constants.d.ts.map +1 -1
- package/dist/talkpilot/calls/calls.constants.js +3 -1
- package/dist/talkpilot/calls/calls.constants.js.map +1 -1
- package/dist/talkpilot/calls/calls.getters.d.ts +2 -1
- package/dist/talkpilot/calls/calls.getters.d.ts.map +1 -1
- package/dist/talkpilot/calls/calls.getters.js +3 -1
- package/dist/talkpilot/calls/calls.getters.js.map +1 -1
- package/dist/talkpilot/calls/calls.types.d.ts +2 -1
- package/dist/talkpilot/calls/calls.types.d.ts.map +1 -1
- package/dist/talkpilot/calls/dashboard/calls.dashboard.d.ts.map +1 -1
- package/dist/talkpilot/calls/dashboard/calls.dashboard.js +16 -13
- package/dist/talkpilot/calls/dashboard/calls.dashboard.js.map +1 -1
- package/dist/talkpilot/calls/dashboard/calls.dashboard.types.d.ts +2 -2
- package/dist/talkpilot/calls/dashboard/calls.dashboard.types.d.ts.map +1 -1
- package/dist/talkpilot/clientsConfig/clientsConfig.getters.d.ts +0 -3
- package/dist/talkpilot/clientsConfig/clientsConfig.getters.d.ts.map +1 -1
- package/dist/talkpilot/clientsConfig/clientsConfig.getters.js +1 -17
- package/dist/talkpilot/clientsConfig/clientsConfig.getters.js.map +1 -1
- package/dist/talkpilot/clientsConfig/clientsConfig.types.d.ts +2 -1
- package/dist/talkpilot/clientsConfig/clientsConfig.types.d.ts.map +1 -1
- package/dist/test-utils/factories/index.d.ts +0 -1
- package/dist/test-utils/factories/index.d.ts.map +1 -1
- package/dist/test-utils/factories/index.js +0 -1
- package/dist/test-utils/factories/index.js.map +1 -1
- package/dist/test-utils/factories/websitalk/scans.d.ts.map +1 -1
- package/dist/test-utils/factories/websitalk/scans.js +1 -2
- package/dist/test-utils/factories/websitalk/scans.js.map +1 -1
- package/dist/websitalk/index.d.ts +0 -1
- package/dist/websitalk/index.d.ts.map +1 -1
- package/dist/websitalk/index.js +0 -1
- package/dist/websitalk/index.js.map +1 -1
- package/dist/websitalk/scans/index.d.ts +0 -1
- package/dist/websitalk/scans/index.d.ts.map +1 -1
- package/dist/websitalk/scans/index.js +0 -1
- package/dist/websitalk/scans/index.js.map +1 -1
- package/dist/websitalk/scans/scans.constants.d.ts +10 -4
- package/dist/websitalk/scans/scans.constants.d.ts.map +1 -1
- package/dist/websitalk/scans/scans.constants.js +13 -7
- package/dist/websitalk/scans/scans.constants.js.map +1 -1
- package/dist/websitalk/scans/scans.getters.d.ts +3 -10
- package/dist/websitalk/scans/scans.getters.d.ts.map +1 -1
- package/dist/websitalk/scans/scans.getters.js +9 -48
- package/dist/websitalk/scans/scans.getters.js.map +1 -1
- package/dist/websitalk/scans/scans.types.d.ts +8 -2
- package/dist/websitalk/scans/scans.types.d.ts.map +1 -1
- package/dist/websitalk/scans/scans.utils.d.ts +3 -0
- package/dist/websitalk/scans/scans.utils.d.ts.map +1 -0
- package/dist/websitalk/scans/scans.utils.js +7 -0
- package/dist/websitalk/scans/scans.utils.js.map +1 -0
- package/package.json +1 -1
- package/src/index.ts +0 -1
- package/src/municipal/index.ts +1 -1
- package/src/municipal/tickets/tickets.statistics.getters.ts +3 -16
- package/src/talkpilot/calls/__tests__/calls.dashboard.spec.ts +81 -1
- package/src/talkpilot/calls/__tests__/calls.spec.ts +13 -0
- package/src/talkpilot/calls/__tests__/calls.statistics.spec.ts +4 -6
- package/src/talkpilot/calls/calls.constants.ts +3 -0
- package/src/talkpilot/calls/calls.getters.ts +11 -1
- package/src/talkpilot/calls/calls.types.ts +12 -1
- package/src/talkpilot/calls/dashboard/calls.dashboard.ts +16 -13
- package/src/talkpilot/calls/dashboard/calls.dashboard.types.ts +2 -2
- package/src/talkpilot/clientsConfig/__tests__/clientsConfig.getters.spec.ts +5 -57
- package/src/talkpilot/clientsConfig/__tests__/clientsConfig.spec.ts +4 -0
- package/src/talkpilot/clientsConfig/clientsConfig.getters.ts +0 -24
- package/src/talkpilot/clientsConfig/clientsConfig.types.ts +2 -1
- package/src/test-utils/factories/index.ts +0 -1
- package/src/test-utils/factories/websitalk/scans.ts +1 -2
- package/src/websitalk/index.ts +0 -1
- package/src/websitalk/scans/__tests__/scans.spec.ts +26 -121
- package/src/websitalk/scans/index.ts +0 -1
- package/src/websitalk/scans/scans.getters.ts +17 -67
- package/src/websitalk/scans/scans.types.ts +6 -3
- package/tsconfig.json +4 -4
- package/src/test-utils/factories/websitalk/websiteUrls.ts +0 -17
- package/src/websitalk/scans/scans.constants.ts +0 -12
- package/src/websitalk/websiteUrls/__tests__/websiteUrls.spec.ts +0 -77
- package/src/websitalk/websiteUrls/index.ts +0 -2
- package/src/websitalk/websiteUrls/websiteUrls.getters.ts +0 -65
- package/src/websitalk/websiteUrls/websiteUrls.types.ts +0 -13
package/dist/websitalk/index.js
CHANGED
|
@@ -17,7 +17,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
exports.ObjectId = exports.getDb = exports.setDb = exports.websitalkMongodbClient = void 0;
|
|
18
18
|
const mongodb_1 = require("mongodb");
|
|
19
19
|
__exportStar(require("./scans"), exports);
|
|
20
|
-
__exportStar(require("./websiteUrls"), exports);
|
|
21
20
|
var mongodb_client_1 = require("./mongodb-client");
|
|
22
21
|
Object.defineProperty(exports, "websitalkMongodbClient", { enumerable: true, get: function () { return mongodb_client_1.websitalkMongodbClient; } });
|
|
23
22
|
let db;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/websitalk/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,qCAAwD;AAExD,0CAAwB;AACxB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/websitalk/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,qCAAwD;AAExD,0CAAwB;AACxB,mDAA0D;AAAjD,wHAAA,sBAAsB,OAAA;AAE/B,IAAI,EAAM,CAAC;AACJ,MAAM,KAAK,GAAG,CAAC,CAAK,EAAE,EAAE;IAC7B,EAAE,GAAG,CAAC,CAAC;AACT,CAAC,CAAC;AAFW,QAAA,KAAK,SAEhB;AAEK,MAAM,KAAK,GAAG,GAAO,EAAE;IAC5B,IAAI,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IAC5D,OAAO,EAAE,CAAC;AACZ,CAAC,CAAC;AAHW,QAAA,KAAK,SAGhB;AACW,QAAA,QAAQ,GAAG,kBAAa,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/websitalk/scans/index.ts"],"names":[],"mappings":"AAAA,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/websitalk/scans/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC"}
|
|
@@ -14,7 +14,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./scans.constants"), exports);
|
|
18
17
|
__exportStar(require("./scans.getters"), exports);
|
|
19
18
|
__exportStar(require("./scans.types"), exports);
|
|
20
19
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/websitalk/scans/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/websitalk/scans/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,kDAAgC;AAChC,gDAA8B"}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
export declare const SCAN_STATUSES: {
|
|
2
|
+
readonly CHECKING: "CHECKING";
|
|
3
|
+
readonly PREPARING: "PREPARING";
|
|
4
|
+
readonly SCANNING: "SCANNING";
|
|
5
|
+
readonly PROCESSING: "PROCESSING";
|
|
6
|
+
readonly COMPLETED: "COMPLETED";
|
|
7
|
+
readonly FAILED: "FAILED";
|
|
8
|
+
readonly OVERWRITTEN: "OVERWRITTEN";
|
|
9
|
+
};
|
|
10
|
+
export declare const FINISHED_SCAN_STATUSES: readonly ["COMPLETED", "FAILED", "OVERWRITTEN"];
|
|
5
11
|
//# sourceMappingURL=scans.constants.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scans.constants.d.ts","sourceRoot":"","sources":["../../../src/websitalk/scans/scans.constants.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"scans.constants.d.ts","sourceRoot":"","sources":["../../../src/websitalk/scans/scans.constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa;;;;;;;;CAQhB,CAAC;AAEX,eAAO,MAAM,sBAAsB,iDAIzB,CAAC"}
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.
|
|
3
|
+
exports.FINISHED_SCAN_STATUSES = exports.SCAN_STATUSES = void 0;
|
|
4
|
+
exports.SCAN_STATUSES = {
|
|
5
|
+
CHECKING: "CHECKING",
|
|
6
|
+
PREPARING: "PREPARING",
|
|
7
|
+
SCANNING: "SCANNING",
|
|
8
|
+
PROCESSING: "PROCESSING",
|
|
9
|
+
COMPLETED: "COMPLETED",
|
|
10
|
+
FAILED: "FAILED",
|
|
11
|
+
OVERWRITTEN: "OVERWRITTEN",
|
|
12
|
+
};
|
|
5
13
|
exports.FINISHED_SCAN_STATUSES = [
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
14
|
+
exports.SCAN_STATUSES.COMPLETED,
|
|
15
|
+
exports.SCAN_STATUSES.FAILED,
|
|
16
|
+
exports.SCAN_STATUSES.OVERWRITTEN,
|
|
9
17
|
];
|
|
10
|
-
const isInProgressScan = (scan) => !exports.FINISHED_SCAN_STATUSES.includes(scan.status);
|
|
11
|
-
exports.isInProgressScan = isInProgressScan;
|
|
12
18
|
//# sourceMappingURL=scans.constants.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scans.constants.js","sourceRoot":"","sources":["../../../src/websitalk/scans/scans.constants.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"scans.constants.js","sourceRoot":"","sources":["../../../src/websitalk/scans/scans.constants.ts"],"names":[],"mappings":";;;AAAa,QAAA,aAAa,GAAG;IAC3B,QAAQ,EAAE,UAAU;IACpB,SAAS,EAAE,WAAW;IACtB,QAAQ,EAAE,UAAU;IACpB,UAAU,EAAE,YAAY;IACxB,SAAS,EAAE,WAAW;IACtB,MAAM,EAAE,QAAQ;IAChB,WAAW,EAAE,aAAa;CAClB,CAAC;AAEE,QAAA,sBAAsB,GAAG;IACpC,qBAAa,CAAC,SAAS;IACvB,qBAAa,CAAC,MAAM;IACpB,qBAAa,CAAC,WAAW;CACjB,CAAC"}
|
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
import { Collection, Filter, ObjectId } from "mongodb";
|
|
2
2
|
import { QueryOptions } from "../../talkpilot/utils/query.utils";
|
|
3
|
-
import type { Scan, ScanDoc } from "./scans.types";
|
|
3
|
+
import type { ActiveScanStatus, Scan, ScanDoc } from "./scans.types";
|
|
4
4
|
export declare const getScansCollection: () => Collection<Scan>;
|
|
5
5
|
export declare const getScansByClientAndDateRange: (clientId: string, startDate: Date, endDate: Date) => Promise<ScanDoc[]>;
|
|
6
6
|
export declare const getScanFieldsByClientAndDateRange: <K extends keyof Scan>(clientId: string, startDate: Date, endDate: Date, fields: K[]) => Promise<Pick<ScanDoc, "_id" | K>[]>;
|
|
7
|
-
export declare const createScanDoc: (scan: Pick<Scan, "clientId" | "
|
|
8
|
-
export declare const
|
|
9
|
-
export declare const getScansByWebsiteUrl: (websiteUrlId: string) => Promise<ScanDoc[]>;
|
|
10
|
-
export declare const getLatestScanByWebsiteUrl: (websiteUrlId: string) => Promise<ScanDoc | null>;
|
|
11
|
-
export declare const getInProgressScanByWebsiteUrl: (websiteUrlId: string) => Promise<ScanDoc | null>;
|
|
12
|
-
export declare const findInProgressScansByClient: (clientId: string) => Promise<ScanDoc[]>;
|
|
7
|
+
export declare const createScanDoc: (scan: Pick<Scan, "clientId" | "baseUrl" | "notifyPhoneNumber">) => Promise<import("mongodb").InsertOneResult<Scan>>;
|
|
8
|
+
export declare const getActiveScanStatus: (clientId: string) => Promise<ActiveScanStatus>;
|
|
13
9
|
export declare const updateScanDoc: (scanId: ObjectId, updates: Partial<Omit<Scan, "clientId" | "createdAt">>) => Promise<ScanDoc | null>;
|
|
14
10
|
export declare const findScansByQuery: (query: Filter<Scan>, options?: QueryOptions) => Promise<ScanDoc[]>;
|
|
15
11
|
export declare const countScans: (query: Filter<Scan>) => Promise<number>;
|
|
16
|
-
export declare const hasScansForWebsiteUrl: (websiteUrlId: string) => Promise<boolean>;
|
|
17
|
-
export declare const getLatestCompletedScanByWebsiteUrl: (websiteUrlId: string) => Promise<ScanDoc | null>;
|
|
18
|
-
export declare const deleteScansByWebsiteUrl: (websiteUrlId: string) => Promise<number>;
|
|
19
12
|
//# sourceMappingURL=scans.getters.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scans.getters.d.ts","sourceRoot":"","sources":["../../../src/websitalk/scans/scans.getters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEvD,OAAO,EAEL,YAAY,EACb,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"scans.getters.d.ts","sourceRoot":"","sources":["../../../src/websitalk/scans/scans.getters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEvD,OAAO,EAEL,YAAY,EACb,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EACV,gBAAgB,EAChB,IAAI,EACJ,OAAO,EAER,MAAM,eAAe,CAAC;AAEvB,eAAO,MAAM,kBAAkB,QAAO,UAAU,CAAC,IAAI,CAEpD,CAAC;AAEF,eAAO,MAAM,4BAA4B,GACvC,UAAU,MAAM,EAChB,WAAW,IAAI,EACf,SAAS,IAAI,KACZ,OAAO,CAAC,OAAO,EAAE,CAUnB,CAAC;AAEF,eAAO,MAAM,iCAAiC,GAAI,CAAC,SAAS,MAAM,IAAI,EACpE,UAAU,MAAM,EAChB,WAAW,IAAI,EACf,SAAS,IAAI,EACb,QAAQ,CAAC,EAAE,KACV,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,GAAG,CAAC,CAAC,EAAE,CAkBpC,CAAC;AAEF,eAAO,MAAM,aAAa,GACxB,MAAM,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,GAAG,mBAAmB,CAAC,qDAa/D,CAAC;AAIF,eAAO,MAAM,mBAAmB,GAC9B,UAAU,MAAM,KACf,OAAO,CAAC,gBAAgB,CAW1B,CAAC;AAEF,eAAO,MAAM,aAAa,GACxB,QAAQ,QAAQ,EAChB,SAAS,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,WAAW,CAAC,CAAC,KACrD,OAAO,CAAC,OAAO,GAAG,IAAI,CAMxB,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAC3B,OAAO,MAAM,CAAC,IAAI,CAAC,EACnB,UAAU,YAAY,KACrB,OAAO,CAAC,OAAO,EAAE,CAGnB,CAAC;AAEF,eAAO,MAAM,UAAU,GAAU,OAAO,MAAM,CAAC,IAAI,CAAC,KAAG,OAAO,CAAC,MAAM,CAEpE,CAAC"}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
const mongodb_1 = require("mongodb");
|
|
3
|
+
exports.countScans = exports.findScansByQuery = exports.updateScanDoc = exports.getActiveScanStatus = exports.createScanDoc = exports.getScanFieldsByClientAndDateRange = exports.getScansByClientAndDateRange = exports.getScansCollection = void 0;
|
|
5
4
|
const index_1 = require("../index");
|
|
6
5
|
const query_utils_1 = require("../../talkpilot/utils/query.utils");
|
|
7
|
-
const scans_constants_1 = require("./scans.constants");
|
|
8
6
|
const getScansCollection = () => {
|
|
9
7
|
return (0, index_1.getDb)().collection("scans");
|
|
10
8
|
};
|
|
@@ -40,7 +38,7 @@ exports.getScanFieldsByClientAndDateRange = getScanFieldsByClientAndDateRange;
|
|
|
40
38
|
const createScanDoc = (scan) => {
|
|
41
39
|
return (0, exports.getScansCollection)().insertOne({
|
|
42
40
|
...scan,
|
|
43
|
-
status:
|
|
41
|
+
status: "CHECKING",
|
|
44
42
|
activeTasksCount: 0,
|
|
45
43
|
pagesScanned: 0,
|
|
46
44
|
pagesSkipped: 0,
|
|
@@ -51,39 +49,15 @@ const createScanDoc = (scan) => {
|
|
|
51
49
|
});
|
|
52
50
|
};
|
|
53
51
|
exports.createScanDoc = createScanDoc;
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return (0, exports.getScansCollection)()
|
|
60
|
-
.find({ websiteUrlId })
|
|
61
|
-
.sort({ createdAt: -1 })
|
|
62
|
-
.toArray();
|
|
63
|
-
};
|
|
64
|
-
exports.getScansByWebsiteUrl = getScansByWebsiteUrl;
|
|
65
|
-
const getLatestScanByWebsiteUrl = async (websiteUrlId) => {
|
|
66
|
-
return (0, exports.getScansCollection)().findOne({ websiteUrlId }, { sort: { createdAt: -1 } });
|
|
67
|
-
};
|
|
68
|
-
exports.getLatestScanByWebsiteUrl = getLatestScanByWebsiteUrl;
|
|
69
|
-
const getInProgressScanByWebsiteUrl = async (websiteUrlId) => {
|
|
70
|
-
const latestScan = await (0, exports.getLatestScanByWebsiteUrl)(websiteUrlId);
|
|
71
|
-
if (!latestScan || !(0, scans_constants_1.isInProgressScan)(latestScan)) {
|
|
72
|
-
return null;
|
|
52
|
+
const FINISHED_SCAN_STATUSES = ["COMPLETED", "FAILED"];
|
|
53
|
+
const getActiveScanStatus = async (clientId) => {
|
|
54
|
+
const latestScan = await (0, exports.getScansCollection)().findOne({ clientId }, { sort: { createdAt: -1 } });
|
|
55
|
+
if (!latestScan || FINISHED_SCAN_STATUSES.includes(latestScan.status)) {
|
|
56
|
+
return { isActive: false };
|
|
73
57
|
}
|
|
74
|
-
return latestScan;
|
|
75
|
-
};
|
|
76
|
-
exports.getInProgressScanByWebsiteUrl = getInProgressScanByWebsiteUrl;
|
|
77
|
-
const findInProgressScansByClient = async (clientId) => {
|
|
78
|
-
return (0, exports.getScansCollection)()
|
|
79
|
-
.find({
|
|
80
|
-
clientId,
|
|
81
|
-
status: { $nin: scans_constants_1.FINISHED_SCAN_STATUSES },
|
|
82
|
-
})
|
|
83
|
-
.sort({ createdAt: -1 })
|
|
84
|
-
.toArray();
|
|
58
|
+
return { isActive: true, status: latestScan.status };
|
|
85
59
|
};
|
|
86
|
-
exports.
|
|
60
|
+
exports.getActiveScanStatus = getActiveScanStatus;
|
|
87
61
|
const updateScanDoc = async (scanId, updates) => {
|
|
88
62
|
return await (0, exports.getScansCollection)().findOneAndUpdate({ _id: scanId }, { $set: { ...updates, updatedAt: new Date() } }, { returnDocument: "after" });
|
|
89
63
|
};
|
|
@@ -97,17 +71,4 @@ const countScans = async (query) => {
|
|
|
97
71
|
return (0, exports.getScansCollection)().countDocuments(query);
|
|
98
72
|
};
|
|
99
73
|
exports.countScans = countScans;
|
|
100
|
-
const hasScansForWebsiteUrl = async (websiteUrlId) => {
|
|
101
|
-
return (await (0, exports.countScans)({ websiteUrlId })) > 0;
|
|
102
|
-
};
|
|
103
|
-
exports.hasScansForWebsiteUrl = hasScansForWebsiteUrl;
|
|
104
|
-
const getLatestCompletedScanByWebsiteUrl = async (websiteUrlId) => {
|
|
105
|
-
return (0, exports.getScansCollection)().findOne({ websiteUrlId, status: "COMPLETED" }, { sort: { createdAt: -1 } });
|
|
106
|
-
};
|
|
107
|
-
exports.getLatestCompletedScanByWebsiteUrl = getLatestCompletedScanByWebsiteUrl;
|
|
108
|
-
const deleteScansByWebsiteUrl = async (websiteUrlId) => {
|
|
109
|
-
const result = await (0, exports.getScansCollection)().deleteMany({ websiteUrlId });
|
|
110
|
-
return result.deletedCount;
|
|
111
|
-
};
|
|
112
|
-
exports.deleteScansByWebsiteUrl = deleteScansByWebsiteUrl;
|
|
113
74
|
//# sourceMappingURL=scans.getters.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scans.getters.js","sourceRoot":"","sources":["../../../src/websitalk/scans/scans.getters.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"scans.getters.js","sourceRoot":"","sources":["../../../src/websitalk/scans/scans.getters.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { WithId } from "mongodb";
|
|
2
|
-
export type ScanStatus = "CHECKING" | "PREPARING" | "SCANNING" | "PROCESSING" | "COMPLETED" | "FAILED"
|
|
2
|
+
export type ScanStatus = "CHECKING" | "PREPARING" | "SCANNING" | "PROCESSING" | "COMPLETED" | "FAILED";
|
|
3
|
+
export type ActiveScanStatus = {
|
|
4
|
+
isActive: true;
|
|
5
|
+
status: ScanStatus;
|
|
6
|
+
} | {
|
|
7
|
+
isActive: false;
|
|
8
|
+
};
|
|
3
9
|
export type SiteChromeEntry = {
|
|
4
10
|
url: string;
|
|
5
11
|
urlCanonical?: string | null;
|
|
@@ -24,7 +30,7 @@ export type SkippedEntry = {
|
|
|
24
30
|
};
|
|
25
31
|
export type Scan = {
|
|
26
32
|
clientId: string;
|
|
27
|
-
|
|
33
|
+
baseUrl: string;
|
|
28
34
|
notifyPhoneNumber?: string | null;
|
|
29
35
|
status: ScanStatus;
|
|
30
36
|
activeTasksCount: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scans.types.d.ts","sourceRoot":"","sources":["../../../src/websitalk/scans/scans.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC,MAAM,MAAM,UAAU,GAClB,UAAU,GACV,WAAW,GACX,UAAU,GACV,YAAY,GACZ,WAAW,GACX,QAAQ,
|
|
1
|
+
{"version":3,"file":"scans.types.d.ts","sourceRoot":"","sources":["../../../src/websitalk/scans/scans.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC,MAAM,MAAM,UAAU,GAClB,UAAU,GACV,WAAW,GACX,UAAU,GACV,YAAY,GACZ,WAAW,GACX,QAAQ,CAAC;AAEb,MAAM,MAAM,gBAAgB,GACxB;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GACtC;IAAE,QAAQ,EAAE,KAAK,CAAA;CAAE,CAAC;AAExB,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,SAAS,CAAC,EAAE,IAAI,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,MAAM,EAAE,UAAU,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,UAAU,EAAE,eAAe,CAAC;IAC5B,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scans.utils.d.ts","sourceRoot":"","sources":["../../../src/websitalk/scans/scans.utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAc,MAAM,eAAe,CAAC;AAGtD,eAAO,MAAM,gBAAgB,GAAI,MAAM,IAAI,KAAG,OAC4B,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isInProgressScan = void 0;
|
|
4
|
+
const scans_constants_1 = require("./scans.constants");
|
|
5
|
+
const isInProgressScan = (scan) => !scans_constants_1.FINISHED_SCAN_STATUSES.includes(scan.status);
|
|
6
|
+
exports.isInProgressScan = isInProgressScan;
|
|
7
|
+
//# sourceMappingURL=scans.utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scans.utils.js","sourceRoot":"","sources":["../../../src/websitalk/scans/scans.utils.ts"],"names":[],"mappings":";;;AACA,uDAA2D;AAEpD,MAAM,gBAAgB,GAAG,CAAC,IAAU,EAAW,EAAE,CACtD,CAAE,wCAAgD,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAD9D,QAAA,gBAAgB,oBAC8C"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
package/src/municipal/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ export * from "./tickets";
|
|
|
7
7
|
export * from "./systemInstructions";
|
|
8
8
|
export * from "./muniIssues";
|
|
9
9
|
export * from "./utils/types";
|
|
10
|
-
export
|
|
10
|
+
export { CityName } from "./utils/types";
|
|
11
11
|
export { municipalDataMongodbClient } from "./mongodb-client";
|
|
12
12
|
|
|
13
13
|
let db: Db;
|
|
@@ -83,12 +83,7 @@ export const findCallSidsWithDraftTicketsByCity = async (
|
|
|
83
83
|
dateScope?: TicketStatsDateScope,
|
|
84
84
|
): Promise<string[]> => {
|
|
85
85
|
const rows = await runTicketStatsAggregate<{ _id: unknown }>([
|
|
86
|
-
{
|
|
87
|
-
$match: ticketsWithCallSidMatch(
|
|
88
|
-
cityName,
|
|
89
|
-
resolveTicketStatsDateRange(dateScope),
|
|
90
|
-
),
|
|
91
|
-
},
|
|
86
|
+
{ $match: ticketsWithCallSidMatch(cityName, resolveTicketStatsDateRange(dateScope)) },
|
|
92
87
|
{ $match: { $expr: isDraftTicketExpr } },
|
|
93
88
|
{ $group: { _id: "$callSid" } },
|
|
94
89
|
]);
|
|
@@ -101,16 +96,8 @@ export const findCallSidDraftTicketCountsByCity = async (
|
|
|
101
96
|
cityName: CityName,
|
|
102
97
|
dateScope?: TicketStatsDateScope,
|
|
103
98
|
): Promise<Map<string, number>> => {
|
|
104
|
-
const rows = await runTicketStatsAggregate<{
|
|
105
|
-
|
|
106
|
-
ticketCount: number;
|
|
107
|
-
}>([
|
|
108
|
-
{
|
|
109
|
-
$match: ticketsWithCallSidMatch(
|
|
110
|
-
cityName,
|
|
111
|
-
resolveTicketStatsDateRange(dateScope),
|
|
112
|
-
),
|
|
113
|
-
},
|
|
99
|
+
const rows = await runTicketStatsAggregate<{ _id: unknown; ticketCount: number }>([
|
|
100
|
+
{ $match: ticketsWithCallSidMatch(cityName, resolveTicketStatsDateRange(dateScope)) },
|
|
114
101
|
{ $match: { $expr: isDraftTicketExpr } },
|
|
115
102
|
{ $group: { _id: "$callSid", ticketCount: { $sum: 1 } } },
|
|
116
103
|
]);
|
|
@@ -35,7 +35,7 @@ describe("getDashboardStats", () => {
|
|
|
35
35
|
expect(result.kpis.totalCalls).toBe(0);
|
|
36
36
|
expect(result.kpis.completedCount).toBe(0);
|
|
37
37
|
expect(result.kpis.avgDurationSeconds).toBe(0);
|
|
38
|
-
expect(result.kpis.
|
|
38
|
+
expect(result.kpis.inCallCount).toBe(0);
|
|
39
39
|
expect(result.kpis.timeSavedMinutes).toBe(0);
|
|
40
40
|
// ~30 day range -> daily buckets, gap-filled with zeros across the whole range
|
|
41
41
|
expect(result.charts.volumeGranularity).toBe("day");
|
|
@@ -146,4 +146,84 @@ describe("getDashboardStats", () => {
|
|
|
146
146
|
const empty = result.charts.volumeData.filter((d) => d.completed === 0);
|
|
147
147
|
expect(empty).toHaveLength(22);
|
|
148
148
|
});
|
|
149
|
+
|
|
150
|
+
it("should count in-progress and answered as inCallCount, not completed", async () => {
|
|
151
|
+
const clientId = "client-dash-in-call";
|
|
152
|
+
|
|
153
|
+
await getClientsConfigCollection().insertOne({
|
|
154
|
+
clientId,
|
|
155
|
+
timezone: "UTC",
|
|
156
|
+
products: {},
|
|
157
|
+
} as any);
|
|
158
|
+
|
|
159
|
+
await getCallsCollection().insertMany([
|
|
160
|
+
{
|
|
161
|
+
clientId,
|
|
162
|
+
status: "in-progress",
|
|
163
|
+
callLength: 30,
|
|
164
|
+
createdAt: new Date("2026-05-10T10:00:00Z"),
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
clientId,
|
|
168
|
+
status: "answered",
|
|
169
|
+
callLength: 45,
|
|
170
|
+
createdAt: new Date("2026-05-10T11:00:00Z"),
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
clientId,
|
|
174
|
+
status: "completed",
|
|
175
|
+
callLength: 120,
|
|
176
|
+
createdAt: new Date("2026-05-10T12:00:00Z"),
|
|
177
|
+
},
|
|
178
|
+
] as any);
|
|
179
|
+
|
|
180
|
+
const result = await getDashboardStats({
|
|
181
|
+
clientId,
|
|
182
|
+
startDate: "2026-05-01T00:00:00.000Z",
|
|
183
|
+
endDate: "2026-05-31T23:59:59.999Z",
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
expect(result.kpis.inCallCount).toBe(2);
|
|
187
|
+
expect(result.kpis.completedCount).toBe(1);
|
|
188
|
+
expect(result.kpis.totalCalls).toBe(3);
|
|
189
|
+
expect(result.kpis.avgDurationSeconds).toBe(120);
|
|
190
|
+
expect(result.kpis.timeSavedMinutes).toBe(2);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("should not count physical busy status as inCall or answered duration", async () => {
|
|
194
|
+
const clientId = "client-dash-busy";
|
|
195
|
+
|
|
196
|
+
await getClientsConfigCollection().insertOne({
|
|
197
|
+
clientId,
|
|
198
|
+
timezone: "UTC",
|
|
199
|
+
products: {},
|
|
200
|
+
} as any);
|
|
201
|
+
|
|
202
|
+
await getCallsCollection().insertMany([
|
|
203
|
+
{
|
|
204
|
+
clientId,
|
|
205
|
+
status: "busy",
|
|
206
|
+
callLength: 60,
|
|
207
|
+
createdAt: new Date("2026-05-10T10:00:00Z"),
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
clientId,
|
|
211
|
+
status: "completed",
|
|
212
|
+
callLength: 90,
|
|
213
|
+
createdAt: new Date("2026-05-10T11:00:00Z"),
|
|
214
|
+
},
|
|
215
|
+
] as any);
|
|
216
|
+
|
|
217
|
+
const result = await getDashboardStats({
|
|
218
|
+
clientId,
|
|
219
|
+
startDate: "2026-05-01T00:00:00.000Z",
|
|
220
|
+
endDate: "2026-05-31T23:59:59.999Z",
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
expect(result.kpis.inCallCount).toBe(0);
|
|
224
|
+
expect(result.kpis.completedCount).toBe(1);
|
|
225
|
+
expect(result.kpis.totalCalls).toBe(1);
|
|
226
|
+
expect(result.kpis.avgDurationSeconds).toBe(90);
|
|
227
|
+
expect(result.kpis.timeSavedMinutes).toBe(2);
|
|
228
|
+
});
|
|
149
229
|
});
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
getCallsByClientAndDateRange,
|
|
10
10
|
getCallsCollection,
|
|
11
11
|
pushToolExecution,
|
|
12
|
+
updateCallStatusByCallSid,
|
|
12
13
|
} from "../calls.getters";
|
|
13
14
|
import type { ToolExecution } from "../calls.types";
|
|
14
15
|
import { ObjectId } from "mongodb";
|
|
@@ -60,6 +61,18 @@ describe("db.calls", () => {
|
|
|
60
61
|
expect(result.some((c) => c.callSid === call.callSid)).toBe(true);
|
|
61
62
|
});
|
|
62
63
|
|
|
64
|
+
it("should set current status only via updateCallStatusByCallSid", async () => {
|
|
65
|
+
const call = createOutGoingCallDoc({ status: "ringing" });
|
|
66
|
+
await createCallDoc(call);
|
|
67
|
+
|
|
68
|
+
const updated = await updateCallStatusByCallSid(call.callSid, "in-progress");
|
|
69
|
+
|
|
70
|
+
expect(updated?.status).toBe("in-progress");
|
|
71
|
+
expect(await getCallByCallSid(call.callSid)).toMatchObject({
|
|
72
|
+
status: "in-progress",
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
63
76
|
describe("findCallsByQuery()", () => {
|
|
64
77
|
it("should find calls by status with limit and sort", async () => {
|
|
65
78
|
const clientId = "client123";
|
|
@@ -317,9 +317,9 @@ describe("calls statistics getters", () => {
|
|
|
317
317
|
|
|
318
318
|
it("should respect wrap-around hour window boundaries on both the first and last day", async () => {
|
|
319
319
|
await incoming("CA-jun8-morning", "2026-06-08T07:00:00.000Z"); // Jun 8 10:00 local — excluded (first-day guard)
|
|
320
|
-
await incoming("CA-jun8-night",
|
|
320
|
+
await incoming("CA-jun8-night", "2026-06-08T20:30:00.000Z"); // Jun 8 23:30 local — included
|
|
321
321
|
await incoming("CA-jun9-morning", "2026-06-09T05:00:00.000Z"); // Jun 9 08:00 local — included
|
|
322
|
-
await incoming("CA-jun9-noon",
|
|
322
|
+
await incoming("CA-jun9-noon", "2026-06-09T10:30:00.000Z"); // Jun 9 13:30 local — excluded (>= hourTo)
|
|
323
323
|
await incoming("CA-jun9-evening", "2026-06-09T20:30:00.000Z"); // Jun 9 23:30 local — excluded (last-day guard)
|
|
324
324
|
|
|
325
325
|
const twoDay = { startStr: "2026-06-08", endStr: "2026-06-09" } as const;
|
|
@@ -333,11 +333,9 @@ describe("calls statistics getters", () => {
|
|
|
333
333
|
|
|
334
334
|
// Hourly: hours [23, 0, ..., 12] — only the 2 included calls appear.
|
|
335
335
|
const hourly = await aggregateCallsHourlyByRange(f);
|
|
336
|
-
expect(hourly.map((b) => b.hour)).toEqual([
|
|
337
|
-
23, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
|
|
338
|
-
]);
|
|
336
|
+
expect(hourly.map((b) => b.hour)).toEqual([23, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
|
|
339
337
|
expect(hourly.find((b) => b.hour === 23)?.totalCalls).toBe(1); // Jun 8 23:30
|
|
340
|
-
expect(hourly.find((b) => b.hour === 8)?.totalCalls).toBe(1);
|
|
338
|
+
expect(hourly.find((b) => b.hour === 8)?.totalCalls).toBe(1); // Jun 9 08:00
|
|
341
339
|
expect(hourly.reduce((sum, b) => sum + b.totalCalls, 0)).toBe(2);
|
|
342
340
|
});
|
|
343
341
|
|
|
@@ -4,6 +4,9 @@ export const CONFERENCE_ROLE_CUSTOMER = "customer" as const;
|
|
|
4
4
|
/** Supervisor role within a conference call. */
|
|
5
5
|
export const CONFERENCE_ROLE_SUPERVISOR = "supervisor" as const;
|
|
6
6
|
|
|
7
|
+
/** Live call — «בשיחה». Excludes Twilio `busy` (line busy / «תפוס»). */
|
|
8
|
+
export const IN_CALL_STATUSES = ["in-progress", "answered"] as const;
|
|
9
|
+
|
|
7
10
|
/** Hours in a single day. */
|
|
8
11
|
export const HOURS_PER_DAY = 24;
|
|
9
12
|
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
CallUpdateParams,
|
|
6
6
|
getDb,
|
|
7
7
|
} from "../index";
|
|
8
|
-
import type { CountOpts, DateRange, ToolExecution } from "./calls.types";
|
|
8
|
+
import type { CountOpts, DateRange, ToolExecution, CallStatus } from "./calls.types";
|
|
9
9
|
import { Filter, ObjectId } from "mongodb";
|
|
10
10
|
import * as process from "node:process";
|
|
11
11
|
import { applyQueryOptions } from "../utils/query.utils";
|
|
@@ -60,6 +60,16 @@ export const updateCallByCallSid = async (
|
|
|
60
60
|
);
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
+
export const updateCallStatusByCallSid = async (
|
|
64
|
+
callSid: string,
|
|
65
|
+
status: CallStatus,
|
|
66
|
+
): Promise<CallDoc | null> =>
|
|
67
|
+
getCallsCollection().findOneAndUpdate(
|
|
68
|
+
{ callSid },
|
|
69
|
+
{ $set: { status, updatedAt: new Date() } },
|
|
70
|
+
{ returnDocument: "after" },
|
|
71
|
+
);
|
|
72
|
+
|
|
63
73
|
export const pushToolExecution = async (
|
|
64
74
|
callSid: string,
|
|
65
75
|
execution: ToolExecution,
|
|
@@ -12,6 +12,17 @@ export type ConferenceRole =
|
|
|
12
12
|
| typeof CONFERENCE_ROLE_SUPERVISOR
|
|
13
13
|
| null;
|
|
14
14
|
|
|
15
|
+
export type CallStatus =
|
|
16
|
+
| "initiated"
|
|
17
|
+
| "ringing"
|
|
18
|
+
| "in-progress"
|
|
19
|
+
| "answered"
|
|
20
|
+
| "completed"
|
|
21
|
+
| "busy"
|
|
22
|
+
| "no-answer"
|
|
23
|
+
| "failed"
|
|
24
|
+
| "canceled";
|
|
25
|
+
|
|
15
26
|
export type Call = {
|
|
16
27
|
callSid: string;
|
|
17
28
|
flowId: ObjectId;
|
|
@@ -25,7 +36,7 @@ export type Call = {
|
|
|
25
36
|
isOutgoingCall: boolean;
|
|
26
37
|
callLength: number;
|
|
27
38
|
transcription?: TranscriptionSegment[];
|
|
28
|
-
status?:
|
|
39
|
+
status?: CallStatus;
|
|
29
40
|
leads?: LeadProperty[];
|
|
30
41
|
summary?: string;
|
|
31
42
|
recordingUrl?: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import dayjs from "dayjs";
|
|
2
2
|
import { getCallsCollection } from "../calls.getters";
|
|
3
|
+
import { IN_CALL_STATUSES } from "../calls.constants";
|
|
3
4
|
import { getClientConfig } from "../../clientsConfig";
|
|
4
5
|
import utc from "dayjs/plugin/utc";
|
|
5
6
|
import timezonePlugin from "dayjs/plugin/timezone";
|
|
@@ -22,7 +23,7 @@ export type {
|
|
|
22
23
|
|
|
23
24
|
const DEFAULT_KPI_DATA = {
|
|
24
25
|
completedCount: 0,
|
|
25
|
-
|
|
26
|
+
inCallCount: 0,
|
|
26
27
|
answeredDuration: 0,
|
|
27
28
|
};
|
|
28
29
|
|
|
@@ -183,16 +184,14 @@ function buildKpisPipeline() {
|
|
|
183
184
|
completedCount: {
|
|
184
185
|
$sum: { $cond: [{ $eq: ["$status", "completed"] }, 1, 0] },
|
|
185
186
|
},
|
|
186
|
-
|
|
187
|
-
$sum: {
|
|
187
|
+
inCallCount: {
|
|
188
|
+
$sum: {
|
|
189
|
+
$cond: [{ $in: ["$status", [...IN_CALL_STATUSES]] }, 1, 0],
|
|
190
|
+
},
|
|
188
191
|
},
|
|
189
192
|
answeredDuration: {
|
|
190
193
|
$sum: {
|
|
191
|
-
$cond: [
|
|
192
|
-
{ $in: ["$status", ["completed", "busy"]] },
|
|
193
|
-
"$callLength",
|
|
194
|
-
0,
|
|
195
|
-
],
|
|
194
|
+
$cond: [{ $eq: ["$status", "completed"] }, "$callLength", 0],
|
|
196
195
|
},
|
|
197
196
|
},
|
|
198
197
|
},
|
|
@@ -258,16 +257,20 @@ export async function getDashboardStats(
|
|
|
258
257
|
const kpiData = aggregatedResult?.kpis?.[0] ?? DEFAULT_KPI_DATA;
|
|
259
258
|
const volumeDataRaw = aggregatedResult?.volumeData ?? [];
|
|
260
259
|
|
|
261
|
-
const
|
|
260
|
+
const completedCount = kpiData.completedCount;
|
|
261
|
+
const inCallCount = kpiData.inCallCount;
|
|
262
|
+
const totalCalls = completedCount + inCallCount;
|
|
262
263
|
const answeredDuration = kpiData.answeredDuration ?? 0;
|
|
263
264
|
|
|
264
265
|
const kpis: DashboardSummaryMetrics = {
|
|
265
|
-
totalCalls
|
|
266
|
+
totalCalls,
|
|
266
267
|
avgDurationSeconds:
|
|
267
|
-
|
|
268
|
+
completedCount > 0
|
|
269
|
+
? Math.round(answeredDuration / completedCount)
|
|
270
|
+
: 0,
|
|
268
271
|
timeSavedMinutes: Math.round(answeredDuration / 60),
|
|
269
|
-
completedCount
|
|
270
|
-
|
|
272
|
+
completedCount,
|
|
273
|
+
inCallCount,
|
|
271
274
|
};
|
|
272
275
|
|
|
273
276
|
const bucketKeys = generateBucketKeys(
|