@talkpilot/core-db 1.3.71 → 1.3.72

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 (33) hide show
  1. package/README.md +1 -0
  2. package/dist/municipal/muniIssues/index.d.ts +2 -2
  3. package/dist/municipal/muniIssues/index.d.ts.map +1 -1
  4. package/dist/municipal/muniIssues/index.js +2 -1
  5. package/dist/municipal/muniIssues/index.js.map +1 -1
  6. package/dist/municipal/muniIssues/muniIssues.constants.d.ts +2 -0
  7. package/dist/municipal/muniIssues/muniIssues.constants.d.ts.map +1 -1
  8. package/dist/municipal/muniIssues/muniIssues.constants.js +8 -1
  9. package/dist/municipal/muniIssues/muniIssues.constants.js.map +1 -1
  10. package/dist/municipal/muniIssues/muniIssues.getters.d.ts +6 -1
  11. package/dist/municipal/muniIssues/muniIssues.getters.d.ts.map +1 -1
  12. package/dist/municipal/muniIssues/muniIssues.getters.js +31 -1
  13. package/dist/municipal/muniIssues/muniIssues.getters.js.map +1 -1
  14. package/dist/municipal/muniIssues/muniIssues.schema.d.ts.map +1 -1
  15. package/dist/municipal/muniIssues/muniIssues.schema.js +13 -0
  16. package/dist/municipal/muniIssues/muniIssues.schema.js.map +1 -1
  17. package/dist/municipal/muniIssues/muniIssues.setters.d.ts +5 -1
  18. package/dist/municipal/muniIssues/muniIssues.setters.d.ts.map +1 -1
  19. package/dist/municipal/muniIssues/muniIssues.setters.js +39 -2
  20. package/dist/municipal/muniIssues/muniIssues.setters.js.map +1 -1
  21. package/dist/municipal/muniIssues/muniIssues.types.d.ts +23 -2
  22. package/dist/municipal/muniIssues/muniIssues.types.d.ts.map +1 -1
  23. package/package.json +1 -1
  24. package/src/municipal/muniIssues/__tests__/muniIssues.getters.spec.ts +60 -0
  25. package/src/municipal/muniIssues/__tests__/muniIssues.setters.spec.ts +74 -1
  26. package/src/municipal/muniIssues/__tests__/muniIssues.setup.spec.ts +1 -0
  27. package/src/municipal/muniIssues/index.ts +5 -0
  28. package/src/municipal/muniIssues/muniIssues.constants.ts +8 -0
  29. package/src/municipal/muniIssues/muniIssues.getters.ts +46 -2
  30. package/src/municipal/muniIssues/muniIssues.schema.ts +14 -0
  31. package/src/municipal/muniIssues/muniIssues.setters.ts +59 -3
  32. package/src/municipal/muniIssues/muniIssues.types.ts +34 -4
  33. package/src/talkpilot/calls/__tests__/calls.spec.ts +519 -519
@@ -1,5 +1,6 @@
1
1
  import type { Document, IndexDescription } from "mongodb";
2
2
  import {
3
+ MUNI_ISSUE_STATUSES,
3
4
  MUNI_ISSUE_SYNC_STATUSES,
4
5
  MUNI_ISSUE_TEXT_MAX_LENGTH,
5
6
  MUNI_ISSUE_NAME_MAX_LENGTH,
@@ -55,12 +56,25 @@ export const muniIssuesJsonSchema: Document = {
55
56
  },
56
57
  },
57
58
  createdAt: { bsonType: "date" },
59
+ status: { enum: [...MUNI_ISSUE_STATUSES] },
60
+ response: {
61
+ bsonType: "object",
62
+ required: ["text", "createdAt"],
63
+ properties: {
64
+ text: { bsonType: "string", maxLength: MUNI_ISSUE_TEXT_MAX_LENGTH },
65
+ imageUrl: { bsonType: "string" },
66
+ createdAt: { bsonType: "date" },
67
+ },
68
+ },
69
+ updatedAt: { bsonType: "date" },
58
70
  },
59
71
  };
60
72
 
61
73
  export const muniIssuesIndexes: IndexDescription[] = [
62
74
  { key: { callSid: 1 }, name: "callSid_1" },
63
75
  { key: { createdAt: -1 }, name: "createdAt_-1" },
76
+ // Complaint list: one municipality's issues, newest first.
77
+ { key: { clientId: 1, createdAt: -1 }, name: "clientId_1_createdAt_-1" },
64
78
  // Unique: one stored document per Jira ticket, guarding against duplicate writes.
65
79
  { key: { "jira.key": 1 }, name: "jira_key_unique", unique: true },
66
80
  ];
@@ -1,5 +1,10 @@
1
- import { ObjectId } from "mongodb";
2
- import { CreateMuniIssueInput, MuniIssue } from "./muniIssues.types";
1
+ import { Filter, ObjectId } from "mongodb";
2
+ import {
3
+ CreateMuniIssueInput,
4
+ MuniIssue,
5
+ MuniIssueStatus,
6
+ SetMuniIssueResponseInput,
7
+ } from "./muniIssues.types";
3
8
  import { getMuniIssuesCollection } from "./muniIssues.getters";
4
9
  import { ensureMuniIssuesCollectionOnce } from "./muniIssues.setup";
5
10
 
@@ -10,6 +15,7 @@ export const createMuniIssue = async (
10
15
  ): Promise<ObjectId> => {
11
16
  await ensureMuniIssuesCollectionOnce();
12
17
 
18
+ const now = new Date();
13
19
  const issue: MuniIssue = {
14
20
  _id: id,
15
21
  ...issueData,
@@ -17,8 +23,58 @@ export const createMuniIssue = async (
17
23
  ...issueData.jira,
18
24
  syncStatus: issueData.jira.syncStatus ?? "created",
19
25
  },
20
- createdAt: new Date(),
26
+ status: "in_review",
27
+ createdAt: now,
28
+ updatedAt: now,
21
29
  };
22
30
  const { insertedId } = await getMuniIssuesCollection().insertOne(issue);
23
31
  return insertedId;
24
32
  };
33
+
34
+ // An issue of this client that is tracked (has a status) and not yet answered.
35
+ // The `response` condition is the lock: once answered, no further writes match.
36
+ const openIssueFilter = (id: string, clientId: string): Filter<MuniIssue> => ({
37
+ _id: new ObjectId(id),
38
+ clientId,
39
+ status: { $exists: true },
40
+ response: { $exists: false },
41
+ });
42
+
43
+ /** Changes the status of an unanswered issue. Returns false if none matched. */
44
+ export const updateMuniIssueStatus = async (
45
+ id: string,
46
+ clientId: string,
47
+ status: MuniIssueStatus,
48
+ ): Promise<boolean> => {
49
+ if (!ObjectId.isValid(id)) return false;
50
+ await ensureMuniIssuesCollectionOnce();
51
+
52
+ const { matchedCount } = await getMuniIssuesCollection().updateOne(
53
+ openIssueFilter(id, clientId),
54
+ { $set: { status, updatedAt: new Date() } },
55
+ );
56
+ return matchedCount > 0;
57
+ };
58
+
59
+ /** Saves the single TalkPilot response, which locks the issue. Returns false if none matched. */
60
+ export const setMuniIssueResponse = async (
61
+ id: string,
62
+ clientId: string,
63
+ { status, text, imageUrl }: SetMuniIssueResponseInput,
64
+ ): Promise<boolean> => {
65
+ if (!ObjectId.isValid(id)) return false;
66
+ await ensureMuniIssuesCollectionOnce();
67
+
68
+ const now = new Date();
69
+ const { matchedCount } = await getMuniIssuesCollection().updateOne(
70
+ openIssueFilter(id, clientId),
71
+ {
72
+ $set: {
73
+ status,
74
+ response: { text, ...(imageUrl && { imageUrl }), createdAt: now },
75
+ updatedAt: now,
76
+ },
77
+ },
78
+ );
79
+ return matchedCount > 0;
80
+ };
@@ -1,10 +1,22 @@
1
1
  import { ObjectId } from "mongodb";
2
2
 
3
3
  import { CityName } from "../utils/types";
4
- import { MUNI_ISSUE_SYNC_STATUSES } from "./muniIssues.constants";
4
+ import {
5
+ MUNI_ISSUE_STATUSES,
6
+ MUNI_ISSUE_SYNC_STATUSES,
7
+ } from "./muniIssues.constants";
5
8
 
6
9
  export type MuniIssueSyncStatus = (typeof MUNI_ISSUE_SYNC_STATUSES)[number];
7
10
 
11
+ export type MuniIssueStatus = (typeof MUNI_ISSUE_STATUSES)[number];
12
+
13
+ /** The single TalkPilot response; once set, the complaint is locked. */
14
+ export type MuniIssueResponse = {
15
+ text: string;
16
+ imageUrl?: string;
17
+ createdAt: Date;
18
+ };
19
+
8
20
  export type MuniIssueContent = {
9
21
  issuerName: string;
10
22
  issuerRole: string;
@@ -25,13 +37,31 @@ export type MuniIssue = {
25
37
  issueContent: MuniIssueContent;
26
38
  jira: MuniIssueJira;
27
39
  createdAt: Date;
40
+ // Complaint tracking (CP-1888). Optional: issues created before it have none
41
+ // of these, which is how they are kept out of tracking.
42
+ status?: MuniIssueStatus;
43
+ response?: MuniIssueResponse;
44
+ updatedAt?: Date;
28
45
  };
29
46
 
30
- // _id/createdAt are set on insert; jira.syncStatus defaults to "created"
31
- // because the issue is only stored after its Jira ticket exists (CP-1115).
47
+ // _id/createdAt/status/updatedAt are set on insert; jira.syncStatus defaults to
48
+ // "created" because the issue is only stored after its Jira ticket exists (CP-1115).
32
49
  export type CreateMuniIssueInput = Omit<
33
50
  MuniIssue,
34
- "_id" | "createdAt" | "jira"
51
+ "_id" | "createdAt" | "jira" | "status" | "response" | "updatedAt"
35
52
  > & {
36
53
  jira: { key: string; syncStatus?: MuniIssueSyncStatus };
37
54
  };
55
+
56
+ export type SetMuniIssueResponseInput = {
57
+ status: MuniIssueStatus;
58
+ text: string;
59
+ imageUrl?: string;
60
+ };
61
+
62
+ /** `answered` filters by whether a TalkPilot response exists; omit it for all issues. */
63
+ export type FindMuniIssuesOptions = {
64
+ answered?: boolean;
65
+ skip?: number;
66
+ limit?: number;
67
+ };