297 lines
8.1 KiB
TypeScript
297 lines
8.1 KiB
TypeScript
import { ExerciseActivity, Consumer, SocialActivity } from '../consumer/types';
|
|
import { getWeekRanges, isDateInRange } from '../dateFunctions';
|
|
|
|
function formatCategories(
|
|
categories?: Record<
|
|
string,
|
|
{ preference_score: number; monthly_budget: number }
|
|
>
|
|
): string {
|
|
if (!categories || Object.keys(categories).length === 0) {
|
|
return '(No spending categories defined)';
|
|
}
|
|
|
|
return Object.entries(categories)
|
|
.map(([category, data]) => {
|
|
const weeklyBudget = Math.round(data.monthly_budget / 4.33);
|
|
const preferenceLevel =
|
|
data.preference_score >= 8
|
|
? 'high'
|
|
: data.preference_score >= 5
|
|
? 'moderate'
|
|
: 'low';
|
|
return `- ${category}: €${weeklyBudget}/week (${preferenceLevel} preference)`;
|
|
})
|
|
.join('\n');
|
|
}
|
|
|
|
function formatHabits(habits: {
|
|
exercise: ExerciseActivity[];
|
|
social: SocialActivity[];
|
|
}): string {
|
|
const sections: string[] = [];
|
|
|
|
if (habits.exercise?.length) {
|
|
sections.push(
|
|
'Activity Pattern:\n' +
|
|
habits.exercise
|
|
.map(
|
|
ex =>
|
|
`- ${ex.frequency} ${ex.activity} sessions (${ex.duration_minutes}min)`
|
|
)
|
|
.join('\n')
|
|
);
|
|
}
|
|
|
|
if (habits.social?.length) {
|
|
sections.push(
|
|
'Social Pattern:\n' +
|
|
habits.social
|
|
.map(soc => `- ${soc.frequency} ${soc.activity}`)
|
|
.join('\n')
|
|
);
|
|
}
|
|
|
|
return sections.join('\n\n');
|
|
}
|
|
|
|
function formatContext(context: {
|
|
stress_triggers: string[];
|
|
reward_behaviors: string[];
|
|
upcoming_events: Array<{ name: string; date: string; importance: number }>;
|
|
}): string {
|
|
const sections: string[] = [];
|
|
|
|
if (context.upcoming_events?.length) {
|
|
sections.push(
|
|
'Key Events:\n' +
|
|
context.upcoming_events
|
|
.map(event => {
|
|
const impact =
|
|
event.importance >= 8
|
|
? 'significant'
|
|
: event.importance >= 5
|
|
? 'moderate'
|
|
: 'minor';
|
|
return `- ${event.name} (${event.date}) - Priority: ${event.importance}/10\n Expected spending impact: ${impact}`;
|
|
})
|
|
.join('\n')
|
|
);
|
|
}
|
|
|
|
if (context.stress_triggers?.length) {
|
|
sections.push(
|
|
'Stress Factors:\n' +
|
|
context.stress_triggers.map(trigger => `- ${trigger}`).join('\n')
|
|
);
|
|
}
|
|
|
|
if (context.reward_behaviors?.length) {
|
|
sections.push(
|
|
'Reward Activities:\n' +
|
|
context.reward_behaviors.map(behavior => `- ${behavior}`).join('\n')
|
|
);
|
|
}
|
|
|
|
return sections.join('\n\n');
|
|
}
|
|
|
|
function formatConsumerCore(core: Consumer['core']): string {
|
|
const sections = [
|
|
`${core.name} is a ${core.age}-year-old ${core.occupation.title} at ${core.occupation.location}`,
|
|
`Living: ${core.household.status}, ${core.home.ownership} ${core.home.type} in ${core.home.location}`,
|
|
formatHousehold(core.household)
|
|
];
|
|
|
|
return sections.filter(Boolean).join('\n');
|
|
}
|
|
|
|
function formatHousehold(household: Consumer['core']['household']): string {
|
|
const sections = [];
|
|
|
|
if (household.members.length) {
|
|
sections.push(`Household Members: ${household.members.join(', ')}`);
|
|
}
|
|
|
|
if (household.pets.length) {
|
|
sections.push(
|
|
`Pets: ${household.pets.map(pet => `${pet.name} (${pet.type})`).join(', ')}`
|
|
);
|
|
}
|
|
|
|
return sections.join('\n');
|
|
}
|
|
|
|
function formatDailySchedule(
|
|
weekday: Record<
|
|
string,
|
|
{
|
|
activity: string;
|
|
location: string;
|
|
duration_minutes: number;
|
|
}
|
|
>
|
|
): string {
|
|
return Object.entries(weekday)
|
|
.map(
|
|
([time, details]) =>
|
|
`- ${time}: ${details.activity} (${details.location})`
|
|
)
|
|
.join('\n');
|
|
}
|
|
|
|
function formatCommute(commute: Consumer['routines']['commute']): string {
|
|
return `${commute.method} (${commute.route.join(' → ')})
|
|
Regular stops: ${commute.regular_stops
|
|
.map(stop => `${stop.frequency} ${stop.purpose} at ${stop.location}`)
|
|
.join(', ')}`;
|
|
}
|
|
|
|
function formatPurchasingStyle(impulsiveScore: number): string {
|
|
if (impulsiveScore <= 3) return 'Highly planned, rarely impulsive';
|
|
if (impulsiveScore <= 5)
|
|
return 'Mostly planned, occasional impulse purchases';
|
|
if (impulsiveScore <= 7) return 'Balance of planned and impulse purchases';
|
|
return 'Frequently makes impulse purchases';
|
|
}
|
|
|
|
function formatSubscriptions(
|
|
subscriptions: Consumer['finances']['subscriptions']
|
|
): string {
|
|
return subscriptions
|
|
.map(
|
|
sub =>
|
|
`- ${sub.name}: €${sub.amount} (${sub.frequency})${sub.auto_payment ? ' [automatic]' : ''}`
|
|
)
|
|
.join('\n');
|
|
}
|
|
|
|
function formatBrands(brands: Consumer['preferences']['brands']): string {
|
|
return brands
|
|
.map(brand => `${brand.name} (loyalty: ${brand.loyalty_score}/10)`)
|
|
.join(', ');
|
|
}
|
|
|
|
function formatWeekSection(
|
|
range: { start: Date; end: Date },
|
|
weekNum: number,
|
|
consumer: Consumer
|
|
): string {
|
|
return `=== WEEK ${weekNum}: ${range.start.toISOString().split('T')[0]} to ${range.end.toISOString().split('T')[0]} ===
|
|
|
|
Context:
|
|
${formatWeekContext(range, consumer)}
|
|
${formatPurchaseOpportunities(consumer)}`;
|
|
}
|
|
|
|
function formatWeekContext(
|
|
range: { start: Date; end: Date },
|
|
consumer: Consumer
|
|
): string {
|
|
const contexts = [];
|
|
|
|
if (range.start.getDate() <= 5) {
|
|
contexts.push('Post-salary period');
|
|
}
|
|
|
|
const events = consumer.context.upcoming_events.filter(event =>
|
|
isDateInRange(new Date(event.date), range.start, range.end)
|
|
);
|
|
|
|
if (events.length) {
|
|
contexts.push(`Events: ${events.map(e => e.name).join(', ')}`);
|
|
}
|
|
|
|
return contexts.map(c => `- ${c}`).join('\n');
|
|
}
|
|
|
|
function formatPurchaseOpportunities(consumer: Consumer): string {
|
|
const opportunities = [];
|
|
|
|
if (consumer.routines.commute.regular_stops.length) {
|
|
opportunities.push('Regular purchase points:');
|
|
consumer.routines.commute.regular_stops.forEach(stop => {
|
|
opportunities.push(
|
|
`- ${stop.frequency} ${stop.purpose} at ${stop.location}`
|
|
);
|
|
});
|
|
}
|
|
|
|
if (consumer.habits.exercise.length) {
|
|
opportunities.push('Activity-related purchases:');
|
|
consumer.habits.exercise.forEach(ex => {
|
|
opportunities.push(`- ${ex.frequency} ${ex.activity} sessions`);
|
|
});
|
|
}
|
|
|
|
if (consumer.habits.social.length) {
|
|
opportunities.push('Social spending occasions:');
|
|
consumer.habits.social.forEach(soc => {
|
|
opportunities.push(`- ${soc.frequency} ${soc.activity}`);
|
|
});
|
|
}
|
|
|
|
return opportunities.join('\n');
|
|
}
|
|
|
|
export async function generatePrompt(
|
|
consumer: Consumer,
|
|
reflectionThreshold: number,
|
|
targetDate: Date,
|
|
numWeeks: number
|
|
): Promise<string> {
|
|
const weekRanges = getWeekRanges(targetDate, numWeeks);
|
|
|
|
return `consumer PROFILE:
|
|
${formatConsumerCore(consumer.core)}
|
|
|
|
Daily Schedule:
|
|
${formatDailySchedule(consumer.routines.weekday)}
|
|
Commute: ${formatCommute(consumer.routines.commute)}
|
|
|
|
Weekend Activities:
|
|
${consumer.routines.weekend.map(activity => `- ${activity}`).join('\n')}
|
|
|
|
${formatHabits(consumer.habits)}
|
|
|
|
Financial Profile:
|
|
- Income: €${consumer.core.occupation.income.toLocaleString()}/year
|
|
- Payment Methods: ${consumer.preferences.payment_methods.join(', ')}
|
|
- Price Sensitivity: ${consumer.preferences.price_sensitivity}/10
|
|
- Purchasing Style: ${formatPurchasingStyle(consumer.finances.spending_patterns.impulsive_score)}
|
|
|
|
Monthly Fixed Expenses:
|
|
${formatSubscriptions(consumer.finances.subscriptions)}
|
|
|
|
Spending Categories:
|
|
${formatCategories(consumer.finances.spending_patterns.categories)}
|
|
|
|
Brand Preferences:
|
|
${formatBrands(consumer.preferences.brands)}
|
|
|
|
Dietary Preferences:
|
|
${consumer.preferences.diet.join(', ')}
|
|
|
|
${formatContext(consumer.context)}
|
|
|
|
PURCHASE GENERATION GUIDELINES:
|
|
|
|
Generate ${numWeeks} weeks of purchases for ${consumer.core.name}:
|
|
|
|
${weekRanges
|
|
.map((range, i) => formatWeekSection(range, i + 1, consumer))
|
|
.join('\n\n')}
|
|
|
|
Purchase Format:
|
|
[DATE] [TIME] | [LOCATION] | [ITEMS] | €[AMOUNT] | [CATEGORY] | [PLANNED/IMPULSE]
|
|
|
|
Reflection Guidelines (for purchases over €${reflectionThreshold}):
|
|
- Consider purchase significance and context
|
|
- Reference lifestyle impact and usage patterns
|
|
- Include relation to events or stress factors
|
|
- Account for social context where relevant
|
|
|
|
Reflection Format:
|
|
[DATE] | Mood: [MOOD] | [REFLECTION] | Satisfaction: [SCORE]/10`;
|
|
}
|