Ship all P0/P1/P2 gaps + 11 AI features
P0 Critical (7): - STOP/UNSUBSCRIBE keyword → CANCEL (PECR compliance) - Rate limiting on pledge creation (10/IP/5min) - Terms of Service + Privacy Policy pages - WhatsApp onboarding gate (persistent dashboard banner) - Demo account seeding (demo@pnpl.app) - Footer legal links - Basic accessibility (aria labels on donor flow) P1 Within 2 Weeks (8): - Pledge editing by staff (PATCH amount, name, email, phone, rail) - Donor self-cancel page (/p/cancel) + API - Donor 'My Pledges' lookup page (/p/my-pledges) - Bulk QR code download (print-ready HTML) - Public event progress bar (/e/[slug]/progress) - Email-only donor handling (honest status + WhatsApp fallback) - Email verification (format + disposable domain blocking) - Organisations page rewrite (multi-campaign, not multi-org) P2 Within First Month (10): - Event cloning with QR sources - Account deletion (GDPR Article 17) - Daily digest cron via WhatsApp - AI-6 Smart reminder timing (due date anchoring, cultural sensitivity) - H1 Duplicate donor detection (email, phone, Jaro-Winkler name) - H5 Bank CSV format presets (10 UK banks) - H16 Partial payment matching (underpay, overpay, instalment) - H10 Activity logging (audit trail for staff actions) - AI nudge endpoint + AI column mapping + AI event setup wizard - AI anomaly detection wired into daily digest AI Features (11): smart reconciliation, social proof, auto column mapper, daily digest, impact storyteller, smart timing, nudge composer, event wizard, NLU concierge, anomaly detection, bank presets 22 new files, 15 modified files, 0 TypeScript errors, clean build.
This commit is contained in:
@@ -81,6 +81,61 @@ export async function POST(request: NextRequest) {
|
||||
},
|
||||
})
|
||||
|
||||
// AI-1: Smart match unmatched rows using AI fuzzy matching
|
||||
const unmatchedRows = results.filter(r => r.confidence === "none" && r.bankRow.amount > 0)
|
||||
if (unmatchedRows.length > 0 && unmatchedRows.length <= 30) {
|
||||
try {
|
||||
const { smartMatch } = await import("@/lib/ai")
|
||||
const candidates = openPledges.map((p: { id: string; reference: string; amountPence: number }) => ({
|
||||
ref: p.reference,
|
||||
amount: p.amountPence,
|
||||
donor: "", // We don't have donor name in the query above, but AI can match by amount + description
|
||||
}))
|
||||
|
||||
for (const row of unmatchedRows) {
|
||||
const aiResult = await smartMatch(
|
||||
`${row.bankRow.description} ${row.bankRow.reference}`.trim(),
|
||||
candidates
|
||||
)
|
||||
if (aiResult.matchedRef && aiResult.confidence >= 0.85) {
|
||||
const pledgeInfo = pledgeMap.get(aiResult.matchedRef)
|
||||
if (pledgeInfo) {
|
||||
// Mark as AI match (partial confidence, needs review)
|
||||
const idx = results.indexOf(row)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const updated: any = {
|
||||
...row,
|
||||
pledgeId: pledgeInfo.id,
|
||||
pledgeReference: aiResult.matchedRef,
|
||||
confidence: "partial",
|
||||
matchedAmount: row.bankRow.amount,
|
||||
}
|
||||
results[idx] = updated
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[AI] Smart match failed:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Update import stats with AI matches
|
||||
await prisma.import.update({
|
||||
where: { id: importRecord.id },
|
||||
data: {
|
||||
matchedCount: results.filter(r => r.confidence === "exact").length,
|
||||
unmatchedCount: results.filter(r => r.confidence === "none").length,
|
||||
stats: {
|
||||
totalRows: rows.length,
|
||||
credits: rows.filter(r => r.amount > 0).length,
|
||||
exactMatches: results.filter(r => r.confidence === "exact").length,
|
||||
partialMatches: results.filter(r => r.confidence === "partial").length,
|
||||
aiMatches: results.filter(r => r.confidence === "partial").length,
|
||||
unmatched: results.filter(r => r.confidence === "none").length,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Auto-confirm exact matches
|
||||
const confirmed: string[] = []
|
||||
for (const result of results) {
|
||||
|
||||
Reference in New Issue
Block a user