Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions platforms/dreamsync-api/src/services/AIMatchingService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,62 @@ export class AIMatchingService {

// Process any existing matches that haven't been messaged yet
await this.processUnmessagedMatches();

// Send no-match messages to ALL users with any non-null wishlist (even empty) who did not get a match this run
const allWishlistUserIds = await this.getAllWishlistUserIdsForNoMatch();
await this.sendNoMatchNotifications(allWishlistUserIds, matchResults);
});
}

/**
* Get all user IDs that have at least one active wishlist (non-null record), regardless of content/empty.
* Used for no-match notifications so we include users whose wishlists may be considered "empty".
*/
private async getAllWishlistUserIdsForNoMatch(): Promise<string[]> {
const rows = await this.wishlistRepository
.createQueryBuilder("wishlist")
.select("DISTINCT wishlist.userId")
.leftJoin("wishlist.user", "user")
.where("wishlist.isActive = :isActive", { isActive: true })
.andWhere("user.isPrivate = :isPrivate", { isPrivate: false })
.getRawMany<Record<string, string>>();
const userIds = rows.map(r => Object.values(r)[0]).filter(Boolean) as string[];
console.log(`📋 [no-match] Found ${userIds.length} distinct users with at least one active wishlist (including empty wishlists)`);
return userIds;
}
Comment on lines +141 to +156
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Limit no‑match candidates to wishlists actually analyzed (and non‑empty).
The current query scans all active wishlists (including empty) and ignores the 200‑wishlist cap used in this run, so users not processed could receive a false “no match” message. This also conflicts with the “non‑empty wishlist” requirement.

✅ Suggested fix to align eligibility with the processed, non‑empty wishlists
-            const allWishlistUserIds = await this.getAllWishlistUserIdsForNoMatch();
-            await this.sendNoMatchNotifications(allWishlistUserIds, matchResults);
+            const allWishlistUserIds = this.getAllWishlistUserIdsForNoMatch(wishlistData);
+            await this.sendNoMatchNotifications(allWishlistUserIds, matchResults);
-    /**
-     * Get all user IDs that have at least one active wishlist (non-null record), regardless of content/empty.
-     * Used for no-match notifications so we include users whose wishlists may be considered "empty".
-     */
-    private async getAllWishlistUserIdsForNoMatch(): Promise<string[]> {
-        const rows = await this.wishlistRepository
-            .createQueryBuilder("wishlist")
-            .select("DISTINCT wishlist.userId")
-            .leftJoin("wishlist.user", "user")
-            .where("wishlist.isActive = :isActive", { isActive: true })
-            .andWhere("user.isPrivate = :isPrivate", { isPrivate: false })
-            .getRawMany<Record<string, string>>();
-        const userIds = rows.map(r => Object.values(r)[0]).filter(Boolean) as string[];
-        console.log(`📋 [no-match] Found ${userIds.length} distinct users with at least one active wishlist (including empty wishlists)`);
-        return userIds;
-    }
+    /**
+     * Get user IDs from the wishlists processed in this run (non-empty).
+     * Keeps no-match notifications aligned with the actual matching scope.
+     */
+    private getAllWishlistUserIdsForNoMatch(wishlistData: WishlistData[]): string[] {
+        const userIds = [...new Set(wishlistData.map(w => w.userId))];
+        console.log(`📋 [no-match] Found ${userIds.length} users with non-empty wishlists processed in this run`);
+        return userIds;
+    }
🤖 Prompt for AI Agents
In `@platforms/dreamsync-api/src/services/AIMatchingService.ts` around lines 141 -
156, The current getAllWishlistUserIdsForNoMatch collects all active wishlist
owners (including empty wishlists) and ignores the run's 200‑wishlist processing
cap; change the query in getAllWishlistUserIdsForNoMatch (using
wishlistRepository.createQueryBuilder("wishlist")) to only include wishlists
that were actually analyzed: add a predicate to exclude empty wishlists (e.g.,
where wishlist.items IS NOT NULL or wishlist.itemCount > 0 or the appropriate
non-empty column for your schema) and apply the same limit/offset or 200‑item
cap used elsewhere in this run so the returned userIds exactly match the
processed, non‑empty wishlists. Ensure the distinct userId select remains and
keep the processLogger/console message updated if desired.


/**
* Send no-match notifications to users who have a wishlist (even if empty) but did not get a match this run.
*/
private async sendNoMatchNotifications(allWishlistUserIds: string[], matchResults: MatchResult[]): Promise<void> {
const matchedUserIds = new Set(matchResults.flatMap(r => r.userIds));
const noMatchUserIds = allWishlistUserIds.filter(userId => !matchedUserIds.has(userId));

console.log(`📋 [no-match] Total wishlist users: ${allWishlistUserIds.length}, matched this run: ${matchedUserIds.size}, will send no-match to: ${noMatchUserIds.length}`);

if (noMatchUserIds.length === 0) {
console.log("✅ [no-match] No users to notify (all wishlist users got a match this run)");
return;
}

console.log(`📨 [no-match] Sending no-match notifications to ${noMatchUserIds.length} users`);
let sentCount = 0;
let errorCount = 0;
for (let i = 0; i < noMatchUserIds.length; i++) {
const userId = noMatchUserIds[i];
try {
console.log(`📨 [no-match] (${i + 1}/${noMatchUserIds.length}) Sending no-match notification to user: ${userId}`);
await this.notificationService.sendNoMatchNotification(userId);
sentCount++;
console.log(`✅ [no-match] Sent to user ${userId} (${sentCount}/${noMatchUserIds.length})`);
} catch (error) {
errorCount++;
console.error(`❌ [no-match] Error sending no-match notification to user ${userId}:`, error);
}
}
console.log(`🎉 [no-match] Completed: ${sentCount} sent, ${errorCount} errors (total no-match candidates: ${noMatchUserIds.length})`);
}

/**
* Find and process matches that haven't been messaged yet
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,68 @@ Reply with the Match ID "${match.id}" to connect with the other ${otherUserIds.l
}
}

/**
* Send a no-match notification to a user who has a wishlist but did not get a match this run.
* Condition: they have made a wishlist (even if empty).
*/
async sendNoMatchNotification(userId: string): Promise<void> {
try {
console.log(`📨 [no-match] sendNoMatchNotification: starting for user ${userId}`);

const dreamsyncUser = await this.findDreamSyncUser();
if (!dreamsyncUser) {
console.error("[no-match] Cannot send: DreamSync user not found");
return;
}

const user = await this.userService.getUserById(userId);
if (!user) {
console.error(`[no-match] Cannot send: User ${userId} not found`);
return;
}
console.log(`📨 [no-match] User found: ${userId} (${user.name || user.ename || "no name"})`);

const chatResult = await this.findOrCreateMutualChat(userId);
if (!chatResult.chat) {
console.error(`[no-match] Cannot send: Could not find/create chat for user ${userId}`);
return;
}
console.log(`📨 [no-match] Chat ready: ${chatResult.chat.id} (wasCreated: ${chatResult.wasCreated})`);

const { chat, wasCreated } = chatResult;
if (wasCreated) {
console.log(`⏳ [no-match] Chat was just created, waiting 15 seconds before sending...`);
await new Promise(resolve => setTimeout(resolve, 15000));
console.log(`✅ [no-match] 15-second delay completed`);
}

const displayName = user.name || user.ename || "User";
const messageContent = `$$system-message$$

Dear ${displayName},

DreamSync tried to find matches with other users using your wishlist but no conclusive matches were found.

we would encourage you to add more details to your wishlist and add more topics you are interested in.

Best Wishes,
DreamSync`;
Comment on lines +722 to +732
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Polish the no‑match message copy.
Minor grammar/capitalization and repeated phrasing in a user‑facing message.

✍️ Suggested copy edit
-            const messageContent = `$$system-message$$
-
-Dear ${displayName},
-
-DreamSync tried to find matches with other users using your wishlist but no conclusive matches were found.
-
-we would encourage you to add more details to your wishlist and add more topics you are interested in.
-
-Best Wishes,
-DreamSync`;
+            const messageContent = `$$system-message$$
+
+Dear ${displayName},
+
+DreamSync tried to find matches with other users using your wishlist, but no conclusive matches were found.
+
+We encourage you to add more details to your wishlist and include more topics you're interested in.
+
+Best wishes,
+DreamSync`;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const displayName = user.name || user.ename || "User";
const messageContent = `$$system-message$$
Dear ${displayName},
DreamSync tried to find matches with other users using your wishlist but no conclusive matches were found.
we would encourage you to add more details to your wishlist and add more topics you are interested in.
Best Wishes,
DreamSync`;
const displayName = user.name || user.ename || "User";
const messageContent = `$$system-message$$
Dear ${displayName},
DreamSync tried to find matches with other users using your wishlist, but no conclusive matches were found.
We encourage you to add more details to your wishlist and include more topics you're interested in.
Best wishes,
DreamSync`;
🤖 Prompt for AI Agents
In `@platforms/dreamsync-api/src/services/MatchNotificationService.ts` around
lines 722 - 732, In MatchNotificationService update the no-match message string
assigned to messageContent (using displayName) to a clearer, grammatically
correct user-facing copy: replace the current body with a concise message such
as "Dear {displayName},\n\nWe couldn’t find any conclusive matches for your
wishlist this time. Try adding more details or topics to increase your chances
of finding a match.\n\nBest wishes,\nDreamSync" (preserve the $$system-message$$
wrapper if required), ensuring proper capitalization and removing repeated
phrasing.


const messageRepository = AppDataSource.getRepository(Message);
const message = messageRepository.create({
text: messageContent,
sender: dreamsyncUser,
group: chat,
isSystemMessage: true,
});

await messageRepository.save(message);
console.log(`✅ [no-match] Message saved for user ${userId} (messageId: ${message.id})`);
} catch (error) {
console.error(`❌ [no-match] Error sending no-match notification to user ${userId}:`, error);
}
}

/**
* Process a new match and send notifications
*/
Expand Down