mirror of
https://github.com/FranP-code/inbox-negotiator.git
synced 2025-10-13 00:42:26 +00:00
Enhance AI negotiation email generation with user personal data integration
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
# Debt Negotiation AI Edge Function
|
# Debt Negotiation AI Edge Function
|
||||||
|
|
||||||
This function generates FDCPA-compliant negotiation responses using AI analysis:
|
This function generates FDCPA-compliant negotiation responses using AI analysis:
|
||||||
- Analyzes debt details and vendor information
|
- Analyzes debt details and vendor information
|
||||||
- Generates personalized negotiation strategies
|
- Generates personalized negotiation strategies
|
||||||
@@ -8,38 +8,58 @@
|
|||||||
- Ensures FDCPA compliance
|
- Ensures FDCPA compliance
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createClient } from 'npm:@supabase/supabase-js@2';
|
import { createClient } from "npm:@supabase/supabase-js@2";
|
||||||
import { generateObject } from 'npm:ai@4.3.16';
|
import { generateObject } from "npm:ai@4.3.16";
|
||||||
import { createGoogleGenerativeAI } from 'npm:@ai-sdk/google@1.2.19';
|
import { createGoogleGenerativeAI } from "npm:@ai-sdk/google@1.2.19";
|
||||||
import { z } from 'npm:zod@3.23.8';
|
import { z } from "npm:zod@3.23.8";
|
||||||
|
|
||||||
const corsHeaders = {
|
const corsHeaders = {
|
||||||
'Access-Control-Allow-Origin': '*',
|
"Access-Control-Allow-Origin": "*",
|
||||||
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
|
"Access-Control-Allow-Headers":
|
||||||
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
"authorization, x-client-info, apikey, content-type",
|
||||||
|
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Schema for AI negotiation strategy
|
// Schema for AI-generated negotiation email
|
||||||
const negotiationStrategySchema = z.object({
|
const negotiationEmailSchema = z.object({
|
||||||
strategy: z.enum(['extension', 'installment', 'settlement', 'dispute']).describe('The recommended negotiation strategy'),
|
subject: z.string().describe("The email subject line"),
|
||||||
confidenceLevel: z.number().min(0).max(1).describe('Confidence in the strategy recommendation'),
|
body: z.string().describe(
|
||||||
projectedSavings: z.number().min(0).describe('Estimated savings from this strategy'),
|
"The complete email body text with proper formatting and placeholders for missing data",
|
||||||
reasoning: z.string().describe('Explanation of why this strategy was chosen'),
|
),
|
||||||
|
strategy: z.enum(["extension", "installment", "settlement", "dispute"])
|
||||||
|
.describe("The recommended negotiation strategy"),
|
||||||
|
confidenceLevel: z.number().min(0).max(1).describe(
|
||||||
|
"Confidence in the strategy recommendation",
|
||||||
|
),
|
||||||
|
projectedSavings: z.number().min(0).describe(
|
||||||
|
"Estimated savings from this strategy",
|
||||||
|
),
|
||||||
|
reasoning: z.string().describe("Explanation of why this strategy was chosen"),
|
||||||
customTerms: z.object({
|
customTerms: z.object({
|
||||||
extensionDays: z.number().optional().describe('Days for extension if applicable'),
|
extensionDays: z.number().optional().describe(
|
||||||
installmentMonths: z.number().optional().describe('Number of months for installment plan'),
|
"Days for extension if applicable",
|
||||||
settlementPercentage: z.number().optional().describe('Settlement percentage (0-1) if applicable'),
|
),
|
||||||
monthlyPayment: z.number().optional().describe('Monthly payment amount for installments'),
|
installmentMonths: z.number().optional().describe(
|
||||||
}).describe('Custom terms based on the strategy'),
|
"Number of months for installment plan",
|
||||||
|
),
|
||||||
|
settlementPercentage: z.number().optional().describe(
|
||||||
|
"Settlement percentage (0-1) if applicable",
|
||||||
|
),
|
||||||
|
monthlyPayment: z.number().optional().describe(
|
||||||
|
"Monthly payment amount for installments",
|
||||||
|
),
|
||||||
|
}).describe("Custom terms based on the strategy"),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Schema for AI-generated negotiation letter
|
interface PersonalData {
|
||||||
const negotiationLetterSchema = z.object({
|
full_name?: string;
|
||||||
letter: z.string().describe('The complete negotiation letter text'),
|
address_line_1?: string;
|
||||||
tone: z.enum(['formal', 'respectful', 'assertive', 'conciliatory']).describe('The tone used in the letter'),
|
address_line_2?: string;
|
||||||
keyPoints: z.array(z.string()).describe('Key negotiation points included in the letter'),
|
city?: string;
|
||||||
fdcpaCompliant: z.boolean().describe('Whether the letter meets FDCPA compliance requirements'),
|
state?: string;
|
||||||
});
|
zip_code?: string;
|
||||||
|
phone_number?: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface DebtRecord {
|
interface DebtRecord {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -48,6 +68,7 @@ interface DebtRecord {
|
|||||||
raw_email: string;
|
raw_email: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
due_date?: string;
|
due_date?: string;
|
||||||
|
user_id?: string;
|
||||||
metadata?: {
|
metadata?: {
|
||||||
isDebtCollection?: boolean;
|
isDebtCollection?: boolean;
|
||||||
subject?: string;
|
subject?: string;
|
||||||
@@ -55,264 +76,182 @@ interface DebtRecord {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// AI-powered negotiation strategy generator
|
// AI-powered negotiation email generator
|
||||||
async function generateNegotiationStrategy(record: DebtRecord) {
|
async function generateNegotiationEmail(
|
||||||
|
record: DebtRecord,
|
||||||
|
personalData: PersonalData,
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const googleApiKey = Deno.env.get('GOOGLE_GENERATIVE_AI_API_KEY');
|
const googleApiKey = Deno.env.get("GOOGLE_GENERATIVE_AI_API_KEY");
|
||||||
if (!googleApiKey) {
|
if (!googleApiKey) {
|
||||||
console.warn('Google API key not configured, falling back to rule-based strategy');
|
console.warn("Google API key not configured, falling back to template");
|
||||||
return generateFallbackStrategy(record);
|
return generateFallbackEmail(record, personalData);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await generateObject({
|
const result = await generateObject({
|
||||||
model: createGoogleGenerativeAI({
|
model: createGoogleGenerativeAI({
|
||||||
apiKey: googleApiKey,
|
apiKey: googleApiKey,
|
||||||
})('gemini-2.5-flash-preview-04-17'),
|
})("gemini-2.5-flash-preview-04-17"),
|
||||||
system: `You are an expert debt negotiation advisor specializing in FDCPA-compliant strategies.
|
system:
|
||||||
Analyze the debt details and recommend the best negotiation approach. Consider:
|
`You are an expert debt negotiation advisor specializing in FDCPA-compliant email generation.
|
||||||
- Debt amount and type
|
Create professional, formal negotiation emails that:
|
||||||
- Vendor/creditor information
|
- Include appropriate subject line and email body
|
||||||
- Legal compliance requirements
|
- Follow Fair Debt Collection Practices Act requirements
|
||||||
- Realistic settlement possibilities
|
- Use the provided personal data in proper letter format
|
||||||
- Consumer protection laws
|
- Include specific negotiation terms based on debt amount
|
||||||
|
- Use {{ variable }} syntax for missing or uncertain data (like account numbers, specific dates)
|
||||||
|
- Maintain professional tone throughout
|
||||||
|
- Include proper business letter formatting
|
||||||
|
|
||||||
Strategy guidelines:
|
Strategy guidelines based on amount:
|
||||||
- Extension: For temporary hardship, usually < $500
|
- Extension: For temporary hardship, usually < $500
|
||||||
- Installment: For manageable monthly payments, $500-$2000
|
- Installment: For manageable monthly payments, $500-$2000
|
||||||
- Settlement: For significant savings, typically $2000+
|
- Settlement: For significant savings, typically $2000+
|
||||||
- Dispute: If debt validity is questionable
|
- Dispute: If debt validity is questionable
|
||||||
|
|
||||||
Always ensure FDCPA compliance and realistic expectations.`,
|
For missing personal data, use appropriate placeholders.
|
||||||
prompt: `Analyze this debt for negotiation strategy:
|
For uncertain information like account numbers, use {{ Account Number }} format.`,
|
||||||
|
prompt: `Generate a complete negotiation email for this debt:
|
||||||
|
|
||||||
Amount: $${record.amount}
|
Debt Amount: $${record.amount}
|
||||||
Vendor: ${record.vendor}
|
Vendor: ${record.vendor}
|
||||||
Description: ${record.description || 'Not specified'}
|
Description: ${record.description || "Not specified"}
|
||||||
Due Date: ${record.due_date || 'Not specified'}
|
Due Date: ${record.due_date || "Not specified"}
|
||||||
Email Content Preview: ${record.raw_email.substring(0, 500)}...
|
Email Content Preview: ${record.raw_email.substring(0, 500)}...
|
||||||
|
|
||||||
Recommend the best negotiation strategy with specific terms.`,
|
Personal Data Available:
|
||||||
schema: negotiationStrategySchema,
|
- Full Name: ${personalData.full_name || "{{ Full Name }}"}
|
||||||
|
- Address: ${personalData.address_line_1 || "{{ Address Line 1 }}"} ${
|
||||||
|
personalData.address_line_2 ? personalData.address_line_2 : ""
|
||||||
|
}
|
||||||
|
- City: ${personalData.city || "{{ City }}"}
|
||||||
|
- State: ${personalData.state || "{{ State }}"}
|
||||||
|
- Zip: ${personalData.zip_code || "{{ Zip Code }}"}
|
||||||
|
- Phone: ${personalData.phone_number || "{{ Phone Number }}"}
|
||||||
|
|
||||||
|
Create a professional negotiation email with subject and body.`,
|
||||||
|
schema: negotiationEmailSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
return result.object;
|
return result.object;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('AI strategy generation error:', error);
|
console.error("AI email generation error:", error);
|
||||||
return generateFallbackStrategy(record);
|
return generateFallbackEmail(record, personalData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback rule-based strategy when AI is unavailable
|
// Fallback email generation when AI is unavailable
|
||||||
function generateFallbackStrategy(record: DebtRecord) {
|
function generateFallbackEmail(record: DebtRecord, personalData: PersonalData) {
|
||||||
let strategy: 'extension' | 'installment' | 'settlement' | 'dispute' = 'extension';
|
let strategy: "extension" | "installment" | "settlement" | "dispute" =
|
||||||
|
"extension";
|
||||||
let projectedSavings = 0;
|
let projectedSavings = 0;
|
||||||
let customTerms = {};
|
let customTerms = {};
|
||||||
|
|
||||||
if (record.amount < 500) {
|
if (record.amount < 500) {
|
||||||
strategy = 'extension';
|
strategy = "extension";
|
||||||
projectedSavings = 0;
|
projectedSavings = 0;
|
||||||
customTerms = { extensionDays: 30 };
|
customTerms = { extensionDays: 30 };
|
||||||
} else if (record.amount >= 500 && record.amount < 2000) {
|
} else if (record.amount >= 500 && record.amount < 2000) {
|
||||||
strategy = 'installment';
|
strategy = "installment";
|
||||||
projectedSavings = record.amount * 0.1;
|
projectedSavings = record.amount * 0.1;
|
||||||
customTerms = {
|
customTerms = {
|
||||||
installmentMonths: 3,
|
installmentMonths: 3,
|
||||||
monthlyPayment: Math.round(record.amount / 3 * 100) / 100
|
monthlyPayment: Math.round(record.amount / 3 * 100) / 100,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
strategy = 'settlement';
|
strategy = "settlement";
|
||||||
projectedSavings = record.amount * 0.4;
|
projectedSavings = record.amount * 0.4;
|
||||||
customTerms = { settlementPercentage: 0.6 };
|
customTerms = { settlementPercentage: 0.6 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const subject =
|
||||||
|
`Account Number: {{ Account Number }} - Payment Arrangement Request`;
|
||||||
|
const body = generateNegotiationLetter(record, strategy, personalData);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
subject,
|
||||||
|
body,
|
||||||
strategy,
|
strategy,
|
||||||
confidenceLevel: 0.7,
|
confidenceLevel: 0.7,
|
||||||
projectedSavings,
|
projectedSavings,
|
||||||
reasoning: 'Generated using rule-based fallback logic',
|
reasoning: "Generated using rule-based fallback logic",
|
||||||
customTerms,
|
customTerms,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// AI-powered negotiation letter generator
|
// Generate negotiation letter for fallback
|
||||||
async function generateNegotiationLetter(record: DebtRecord, strategy: any) {
|
function generateNegotiationLetter(
|
||||||
try {
|
record: DebtRecord,
|
||||||
const googleApiKey = Deno.env.get('GOOGLE_GENERATIVE_AI_API_KEY');
|
strategy: string,
|
||||||
if (!googleApiKey) {
|
personalData: PersonalData,
|
||||||
console.warn('Google API key not configured, falling back to template');
|
): string {
|
||||||
return generateFallbackLetter(record, strategy);
|
const senderInfo = `${personalData.full_name || "{{ Full Name }}"}
|
||||||
}
|
${personalData.address_line_1 || "{{ Address Line 1 }}"} ${
|
||||||
|
personalData.address_line_2 ? personalData.address_line_2 : ""
|
||||||
const result = await generateObject({
|
|
||||||
model: createGoogleGenerativeAI({
|
|
||||||
apiKey: googleApiKey,
|
|
||||||
})('gemini-2.5-flash-preview-04-17'),
|
|
||||||
system: `You are an expert at writing FDCPA-compliant debt negotiation letters.
|
|
||||||
Create professional, respectful letters that:
|
|
||||||
- Follow Fair Debt Collection Practices Act requirements
|
|
||||||
- Are appropriately formal but human
|
|
||||||
- Include specific negotiation terms
|
|
||||||
- Maintain consumer rights protections
|
|
||||||
- Are personalized to the specific situation
|
|
||||||
|
|
||||||
Always include FDCPA compliance language and validation requests.
|
|
||||||
Keep tone professional but not overly legal or intimidating.`,
|
|
||||||
prompt: `Generate a negotiation letter for this debt:
|
|
||||||
|
|
||||||
Debt Amount: $${record.amount}
|
|
||||||
Vendor: ${record.vendor}
|
|
||||||
Strategy: ${strategy.strategy}
|
|
||||||
Custom Terms: ${JSON.stringify(strategy.customTerms)}
|
|
||||||
Reasoning: ${strategy.reasoning}
|
|
||||||
|
|
||||||
Create a complete, ready-to-send negotiation letter.`,
|
|
||||||
schema: negotiationLetterSchema,
|
|
||||||
});
|
|
||||||
|
|
||||||
return result.object;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('AI letter generation error:', error);
|
|
||||||
return generateFallbackLetter(record, strategy);
|
|
||||||
}
|
}
|
||||||
}
|
${personalData.city || "{{ City }}"}, ${personalData.state || "{{ State }}"} ${
|
||||||
|
personalData.zip_code || "{{ Zip Code }}"
|
||||||
// Fallback letter generation
|
|
||||||
function generateFallbackLetter(record: DebtRecord, strategy: any) {
|
|
||||||
const letter = generateNegotiationResponse(record, strategy.strategy);
|
|
||||||
return {
|
|
||||||
letter,
|
|
||||||
tone: 'formal' as const,
|
|
||||||
keyPoints: ['Payment arrangement', 'FDCPA compliance', 'Good faith negotiation'],
|
|
||||||
fdcpaCompliant: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Deno.serve(async (req) => {
|
|
||||||
try {
|
|
||||||
if (req.method === 'OPTIONS') {
|
|
||||||
return new Response(null, { headers: corsHeaders });
|
|
||||||
}
|
|
||||||
|
|
||||||
const { record }: { record: DebtRecord } = await req.json();
|
|
||||||
|
|
||||||
const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
|
|
||||||
const supabaseKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
|
|
||||||
const supabase = createClient(supabaseUrl, supabaseKey);
|
|
||||||
|
|
||||||
// Generate AI-powered negotiation strategy
|
|
||||||
const strategy = await generateNegotiationStrategy(record);
|
|
||||||
|
|
||||||
// Generate AI-powered negotiation letter
|
|
||||||
const letterResult = await generateNegotiationLetter(record, strategy);
|
|
||||||
|
|
||||||
// Update debt record with AI-generated content
|
|
||||||
const { error: updateError } = await supabase
|
|
||||||
.from('debts')
|
|
||||||
.update({
|
|
||||||
negotiated_plan: letterResult.letter,
|
|
||||||
projected_savings: strategy.projectedSavings,
|
|
||||||
status: 'negotiating',
|
|
||||||
metadata: {
|
|
||||||
...record.metadata,
|
|
||||||
aiStrategy: {
|
|
||||||
strategy: strategy.strategy,
|
|
||||||
confidence: strategy.confidenceLevel,
|
|
||||||
reasoning: strategy.reasoning,
|
|
||||||
customTerms: strategy.customTerms,
|
|
||||||
letterTone: letterResult.tone,
|
|
||||||
keyPoints: letterResult.keyPoints,
|
|
||||||
fdcpaCompliant: letterResult.fdcpaCompliant,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.eq('id', record.id);
|
|
||||||
|
|
||||||
if (updateError) {
|
|
||||||
throw updateError;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log the action
|
|
||||||
await supabase
|
|
||||||
.from('audit_logs')
|
|
||||||
.insert({
|
|
||||||
debt_id: record.id,
|
|
||||||
action: 'negotiation_generated',
|
|
||||||
details: {
|
|
||||||
strategy: strategy.strategy,
|
|
||||||
amount: record.amount,
|
|
||||||
projected_savings: strategy.projectedSavings,
|
|
||||||
ai_confidence: strategy.confidenceLevel,
|
|
||||||
reasoning: strategy.reasoning
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify({
|
|
||||||
success: true,
|
|
||||||
strategy: strategy.strategy,
|
|
||||||
projected_savings: strategy.projectedSavings,
|
|
||||||
confidence: strategy.confidenceLevel,
|
|
||||||
reasoning: strategy.reasoning
|
|
||||||
}),
|
|
||||||
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
||||||
);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Negotiation function error:', error);
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify({ error: error.message }),
|
|
||||||
{
|
|
||||||
status: 500,
|
|
||||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
function generateNegotiationResponse(record: DebtRecord, strategy: string): string {
|
${personalData.phone_number || "{{ Phone Number }}"}
|
||||||
const vendorDomain = record.vendor.includes('@') ? record.vendor.split('@')[1] : record.vendor;
|
|
||||||
const companyName = vendorDomain.split('.')[0].toUpperCase();
|
|
||||||
|
|
||||||
const baseResponse = `Dear ${companyName} Collections Department,
|
|
||||||
|
|
||||||
I am writing in response to your recent communication regarding account balance of $${record.amount.toFixed(2)}.
|
{{ Date }}`;
|
||||||
|
|
||||||
I acknowledge this debt and am committed to resolving this matter in good faith. Due to current financial circumstances, I would like to propose the following arrangement:
|
const vendorDomain = record.vendor.includes("@")
|
||||||
|
? record.vendor.split("@")[1]
|
||||||
|
: record.vendor;
|
||||||
|
const companyName = vendorDomain.split(".")[0].toUpperCase();
|
||||||
|
|
||||||
`;
|
const recipientInfo = `${companyName} Collections Department
|
||||||
|
{{ Collection Agency Address }}`;
|
||||||
|
|
||||||
|
const baseResponse = `${senderInfo}
|
||||||
|
|
||||||
|
${recipientInfo}
|
||||||
|
|
||||||
|
Subject: Account Number: {{ Account Number }}
|
||||||
|
|
||||||
|
To Whom It May Concern,
|
||||||
|
|
||||||
|
This letter is regarding the debt associated with the account number referenced above, originally with ${record.vendor}, in the amount of $${
|
||||||
|
record.amount.toFixed(2)
|
||||||
|
}.
|
||||||
|
|
||||||
|
I am writing to propose a payment arrangement to resolve this matter.`;
|
||||||
|
|
||||||
|
let proposal = "";
|
||||||
|
|
||||||
let proposal = '';
|
|
||||||
|
|
||||||
switch (strategy) {
|
switch (strategy) {
|
||||||
case 'extension':
|
case "extension":
|
||||||
proposal = `I respectfully request a 30-day extension to arrange full payment. I anticipate being able to settle this account in full by ${getDateAfterDays(30)}.
|
proposal =
|
||||||
|
` I respectfully request a 30-day extension to arrange full payment. I anticipate being able to settle this account in full by {{ Proposed Payment Date }}.
|
||||||
|
|
||||||
During this extension period, I request that no additional fees or interest be applied to maintain the current balance.`;
|
During this extension period, I request that no additional fees or interest be applied to maintain the current balance.`;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'installment':
|
case "installment":
|
||||||
const monthlyPayment = (record.amount / 3).toFixed(2);
|
const monthlyPayment = (record.amount / 3).toFixed(2);
|
||||||
proposal = `I would like to propose a 3-month payment plan with the following terms:
|
proposal = ` I am able to pay the full balance of $${
|
||||||
- Monthly payments of $${monthlyPayment}
|
record.amount.toFixed(2)
|
||||||
- First payment due: ${getDateAfterDays(30)}
|
} through an installment plan. I propose to make three (3) equal monthly payments of $${monthlyPayment}, with the first payment to be made on {{ Proposed Start Date }}.`;
|
||||||
- Subsequent payments on the same date for the following 2 months
|
|
||||||
- No additional fees or interest during the payment plan period`;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'settlement':
|
case "settlement":
|
||||||
const settlementAmount = (record.amount * 0.6).toFixed(2);
|
const settlementAmount = (record.amount * 0.6).toFixed(2);
|
||||||
proposal = `I would like to propose a lump-sum settlement offer of $${settlementAmount} (60% of the current balance) to resolve this matter completely.
|
proposal =
|
||||||
|
` I would like to propose a lump-sum settlement offer of $${settlementAmount} (60% of the current balance) to resolve this matter completely.
|
||||||
|
|
||||||
This settlement would be paid within 10 business days of written acceptance of this offer. Upon payment, I request written confirmation that this account will be considered paid in full and closed.`;
|
This settlement would be paid within 10 business days of written acceptance of this offer. Upon payment, I request written confirmation that this account will be considered paid in full and closed.`;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'dispute':
|
case "dispute":
|
||||||
proposal = `I am formally disputing this debt and requesting validation under Section 809(b) of the Fair Debt Collection Practices Act.
|
proposal =
|
||||||
|
` I am formally disputing this debt and requesting validation under Section 809(b) of the Fair Debt Collection Practices Act.
|
||||||
|
|
||||||
Please provide:
|
Please provide:
|
||||||
- Verification of the debt amount
|
- Verification of the debt amount
|
||||||
- Name and address of the original creditor
|
- Name and address of the original creditor
|
||||||
- Copy of any judgment (if applicable)
|
- Copy of any judgment (if applicable)
|
||||||
- Verification of your authority to collect this debt
|
- Verification of your authority to collect this debt
|
||||||
|
|
||||||
Until proper validation is provided, I request that all collection activities cease.`;
|
Until proper validation is provided, I request that all collection activities cease.`;
|
||||||
@@ -321,25 +260,115 @@ Until proper validation is provided, I request that all collection activities ce
|
|||||||
|
|
||||||
const closingResponse = `
|
const closingResponse = `
|
||||||
|
|
||||||
Please confirm receipt of this correspondence and your acceptance of the proposed arrangement. I am committed to honoring any agreement we reach and appreciate your consideration of this proposal.
|
Please confirm in writing your acceptance of this installment plan. Upon receiving your written agreement, I will begin making the proposed payments according to the schedule.
|
||||||
|
|
||||||
I look forward to your prompt response so we can resolve this matter efficiently.
|
In accordance with the Fair Debt Collection Practices Act (FDCPA), I request validation of this debt. Please provide verification of the debt, including documentation showing the original creditor, the amount owed, and that you are legally authorized to collect this debt. I understand that you must cease collection efforts until this validation is provided.
|
||||||
|
|
||||||
|
I look forward to your prompt response and confirmation of this payment arrangement.
|
||||||
|
|
||||||
Sincerely,
|
Sincerely,
|
||||||
[Account Holder Name]
|
${personalData.full_name || "{{ Your Typed Name }}"}`;
|
||||||
|
|
||||||
---
|
|
||||||
This correspondence is sent in accordance with the Fair Debt Collection Practices Act (FDCPA). If you are a debt collector, this serves as formal notice that I am disputing this debt and requesting validation as outlined under Section 809(b) of the FDCPA.`;
|
|
||||||
|
|
||||||
return baseResponse + proposal + closingResponse;
|
return baseResponse + proposal + closingResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDateAfterDays(days: number): string {
|
Deno.serve(async (req) => {
|
||||||
const date = new Date();
|
try {
|
||||||
date.setDate(date.getDate() + days);
|
if (req.method === "OPTIONS") {
|
||||||
return date.toLocaleDateString('en-US', {
|
return new Response(null, { headers: corsHeaders });
|
||||||
year: 'numeric',
|
}
|
||||||
month: 'long',
|
|
||||||
day: 'numeric'
|
const { record }: { record: DebtRecord } = await req.json();
|
||||||
});
|
|
||||||
}
|
const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
|
||||||
|
const supabaseKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
|
||||||
|
const supabase = createClient(supabaseUrl, supabaseKey);
|
||||||
|
|
||||||
|
// Fetch user personal data
|
||||||
|
const { data: userData, error: userError } = await supabase
|
||||||
|
.from("users")
|
||||||
|
.select(
|
||||||
|
"full_name, address_line_1, address_line_2, city, state, zip_code, phone_number",
|
||||||
|
)
|
||||||
|
.eq("id", record.user_id)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (userError) {
|
||||||
|
console.error("Error fetching user data:", userError);
|
||||||
|
}
|
||||||
|
|
||||||
|
const personalData: PersonalData = userData || {};
|
||||||
|
|
||||||
|
// Generate AI-powered negotiation email
|
||||||
|
const emailResult = await generateNegotiationEmail(record, personalData);
|
||||||
|
|
||||||
|
// Update debt record with AI-generated content
|
||||||
|
const { error: updateError } = await supabase
|
||||||
|
.from("debts")
|
||||||
|
.update({
|
||||||
|
negotiated_plan:
|
||||||
|
`Subject: ${emailResult.subject}\n\n${emailResult.body}`,
|
||||||
|
projected_savings: emailResult.projectedSavings,
|
||||||
|
status: "negotiating",
|
||||||
|
metadata: {
|
||||||
|
...record.metadata,
|
||||||
|
aiEmail: {
|
||||||
|
subject: emailResult.subject,
|
||||||
|
body: emailResult.body,
|
||||||
|
strategy: emailResult.strategy,
|
||||||
|
confidence: emailResult.confidenceLevel,
|
||||||
|
reasoning: emailResult.reasoning,
|
||||||
|
customTerms: emailResult.customTerms,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.eq("id", record.id);
|
||||||
|
|
||||||
|
if (updateError) {
|
||||||
|
throw updateError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the action
|
||||||
|
await supabase
|
||||||
|
.from("audit_logs")
|
||||||
|
.insert({
|
||||||
|
debt_id: record.id,
|
||||||
|
action: "negotiation_generated",
|
||||||
|
details: {
|
||||||
|
strategy: emailResult.strategy,
|
||||||
|
amount: record.amount,
|
||||||
|
projected_savings: emailResult.projectedSavings,
|
||||||
|
ai_confidence: emailResult.confidenceLevel,
|
||||||
|
reasoning: emailResult.reasoning,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
strategy: emailResult.strategy,
|
||||||
|
projected_savings: emailResult.projectedSavings,
|
||||||
|
confidence: emailResult.confidenceLevel,
|
||||||
|
reasoning: emailResult.reasoning,
|
||||||
|
subject: emailResult.subject,
|
||||||
|
body: emailResult.body,
|
||||||
|
}),
|
||||||
|
{ headers: { ...corsHeaders, "Content-Type": "application/json" } },
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Negotiation function error:", error);
|
||||||
|
const errorMessage = error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "An unknown error occurred" +
|
||||||
|
(Deno.env.get("NODE_ENV") === "development"
|
||||||
|
? `: ${JSON.stringify(error)}`
|
||||||
|
: "");
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({ error: errorMessage }),
|
||||||
|
{
|
||||||
|
status: 500,
|
||||||
|
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user