// Smart formatting utilities for MCP Sigmund
import {
QueryParams,
} from './types.js';
// Provides display hints and user-friendly formatting suggestions
export interface DisplayHint {
display_hint: string;
user_profile: string;
suggested_format: {
show_account_number?: 'full' | 'partial' | 'hidden';
currency_format?: 'symbol' | 'code' | 'both';
include_pending?: boolean;
show_technical_details?: boolean;
transaction_display?: 'simple' | 'detailed' | 'professional';
date_format?: 'relative' | 'absolute' | 'both';
group_by?: 'date' | 'category' | 'merchant' | 'none';
};
visual_hint?: string;
context_hints?: string[];
}
export interface SmartResponse<T> {
data: T;
display_hint: string;
user_profile: string;
suggested_format: DisplayHint['suggested_format'];
visual_hint?: string;
context_hints?: string[];
timestamp: string;
}
// Display hint configurations for different query types
export const DISPLAY_HINTS: { [key: string]: DisplayHint } = {
// Account and balance queries
simple_balance: {
display_hint: 'simple_balance',
user_profile: 'retail_banking',
suggested_format: {
show_account_number: 'partial',
currency_format: 'symbol',
include_pending: false,
show_technical_details: false,
transaction_display: 'simple',
date_format: 'relative',
group_by: 'none',
},
context_hints: [
'Show clear, single balance amount',
'Hide account numbers except last 4 digits',
'Use friendly currency symbols',
],
},
account_summary: {
display_hint: 'account_summary',
user_profile: 'retail_banking',
suggested_format: {
show_account_number: 'partial',
currency_format: 'symbol',
include_pending: true,
show_technical_details: false,
transaction_display: 'simple',
date_format: 'relative',
group_by: 'none',
},
context_hints: [
'Show account details with balance',
'Include recent transaction summary',
'Use friendly descriptions',
],
},
// Transaction queries
transaction_list: {
display_hint: 'transaction_list',
user_profile: 'retail_banking',
suggested_format: {
show_account_number: 'hidden',
currency_format: 'symbol',
include_pending: false,
show_technical_details: false,
transaction_display: 'simple',
date_format: 'relative',
group_by: 'date',
},
context_hints: [
'Show merchant names instead of technical descriptions',
'Group by date for easy reading',
'Hide reference numbers and technical codes',
],
},
recent_transactions: {
display_hint: 'recent_transactions',
user_profile: 'retail_banking',
suggested_format: {
show_account_number: 'hidden',
currency_format: 'symbol',
include_pending: false,
show_technical_details: false,
transaction_display: 'simple',
date_format: 'relative',
group_by: 'none',
},
visual_hint: 'transaction_timeline',
context_hints: [
'Show last few transactions in simple format',
'Use "Today", "Yesterday", "3 days ago" for dates',
'Focus on merchant names and amounts',
],
},
// Analysis queries
spending_analysis: {
display_hint: 'spending_analysis',
user_profile: 'retail_banking',
suggested_format: {
show_account_number: 'hidden',
currency_format: 'symbol',
include_pending: false,
show_technical_details: false,
transaction_display: 'simple',
date_format: 'absolute',
group_by: 'category',
},
visual_hint: 'spending_pie_chart',
context_hints: [
'Group spending by category',
'Show visual breakdown with percentages',
'Use friendly category names',
'Highlight top spending areas',
],
},
cashflow_analysis: {
display_hint: 'cashflow_analysis',
user_profile: 'retail_banking',
suggested_format: {
show_account_number: 'hidden',
currency_format: 'symbol',
include_pending: false,
show_technical_details: false,
transaction_display: 'simple',
date_format: 'absolute',
group_by: 'date',
},
visual_hint: 'cashflow_line_chart',
context_hints: [
'Show monthly income vs expenses',
'Use clear visual indicators for positive/negative',
'Group by month for trend analysis',
],
},
financial_overview: {
display_hint: 'financial_overview',
user_profile: 'retail_banking',
suggested_format: {
show_account_number: 'partial',
currency_format: 'symbol',
include_pending: true,
show_technical_details: false,
transaction_display: 'simple',
date_format: 'absolute',
group_by: 'none',
},
context_hints: [
'Show high-level financial health',
'Include key metrics and trends',
'Use clear, non-technical language',
],
},
// Professional/Developer queries
professional_report: {
display_hint: 'professional_report',
user_profile: 'business',
suggested_format: {
show_account_number: 'full',
currency_format: 'both',
include_pending: true,
show_technical_details: true,
transaction_display: 'professional',
date_format: 'absolute',
group_by: 'category',
},
context_hints: [
'Include all technical details',
'Show full account numbers',
'Use professional terminology',
'Suitable for accounting/tax purposes',
],
},
developer_data: {
display_hint: 'developer_data',
user_profile: 'developer',
suggested_format: {
show_account_number: 'full',
currency_format: 'code',
include_pending: true,
show_technical_details: true,
transaction_display: 'detailed',
date_format: 'absolute',
group_by: 'none',
},
context_hints: [
'Include all raw data fields',
'Show technical IDs and references',
'Use precise data formats',
'Suitable for integration/development',
],
},
};
// Context detection based on user query patterns
export function detectUserContext(query: string, params: QueryParams): string {
const queryLower = query.toLowerCase();
const hasParams = Object.keys(params || {}).length > 0;
// Professional context indicators
if (
queryLower.includes('tax') ||
queryLower.includes('accountant') ||
queryLower.includes('business') ||
queryLower.includes('professional') ||
queryLower.includes('report') ||
queryLower.includes('export')
) {
return 'professional_report';
}
// Developer context indicators
if (
queryLower.includes('api') ||
queryLower.includes('json') ||
queryLower.includes('raw') ||
queryLower.includes('data') ||
queryLower.includes('integration') ||
queryLower.includes('developer')
) {
return 'developer_data';
}
// Simple balance queries
if (queryLower.includes('balance') && !hasParams) {
return 'simple_balance';
}
// Account summary queries
if (queryLower.includes('account') && !queryLower.includes('transaction')) {
return 'account_summary';
}
// Recent transactions
if (
queryLower.includes('recent') ||
queryLower.includes('last') ||
(queryLower.includes('transaction') &&
params?.limit &&
typeof params.limit === 'number' &&
params.limit <= 10)
) {
return 'recent_transactions';
}
// Transaction lists
if (queryLower.includes('transaction') || queryLower.includes('history')) {
return 'transaction_list';
}
// Analysis queries
if (
queryLower.includes('spending') ||
queryLower.includes('expense') ||
queryLower.includes('category')
) {
return 'spending_analysis';
}
if (
queryLower.includes('cashflow') ||
queryLower.includes('income') ||
queryLower.includes('monthly')
) {
return 'cashflow_analysis';
}
if (
queryLower.includes('overview') ||
queryLower.includes('summary') ||
queryLower.includes('financial')
) {
return 'financial_overview';
}
// Default to simple balance for unknown queries
return 'simple_balance';
}
// Get display hint configuration
export function getDisplayHint(context: string): DisplayHint {
return (
DISPLAY_HINTS[context as keyof typeof DISPLAY_HINTS] ||
DISPLAY_HINTS.simple_balance
);
}
// Create smart response with display hints
export function createSmartResponse<T>(
data: T,
query: string,
params: QueryParams = {}
): SmartResponse<T> {
const context = detectUserContext(query, params);
const hint = getDisplayHint(context);
return {
data,
display_hint: hint.display_hint,
user_profile: hint.user_profile,
suggested_format: hint.suggested_format,
visual_hint: hint.visual_hint,
context_hints: hint.context_hints,
timestamp: new Date().toISOString(),
};
}
// Transaction description simplification
export function simplifyTransactionDescription(description: string): string {
if (!description) return 'Transaction';
const desc = description.toLowerCase();
// Replace technical terms with friendly ones
const replacements: { [key: string]: string } = {
'sepa-basislastschrift': 'Automatic payment',
'sepa basislastschrift': 'Automatic payment',
'pos mit pin': 'Card payment',
'pos mit pin.': 'Card payment',
barauszahlung: 'Cash withdrawal',
gehalt: 'Salary',
miete: 'Rent',
lastschrift: 'Direct debit',
überweisung: 'Transfer',
dauerauftrag: 'Standing order',
einzahlung: 'Deposit',
abhebung: 'Withdrawal',
};
let simplified = description;
// Apply replacements
for (const [technical, friendly] of Object.entries(replacements)) {
if (desc.includes(technical)) {
simplified = friendly;
break;
}
}
// Clean up common patterns
simplified = simplified
.replace(/^pos mit pin\.?\s*/i, 'Card payment - ')
.replace(/^sepa-basislastschrift\s*/i, 'Automatic payment - ')
.replace(/^barauszahlung,?\s*/i, 'Cash withdrawal - ')
.replace(/\s+mit\s+pin\.?$/i, '')
.replace(/\s+pin\.?$/i, '');
return simplified || 'Transaction';
}
// Format currency based on suggestion
export function formatCurrency(
amount: number,
currency: string,
format: 'symbol' | 'code' | 'both' = 'symbol'
): string {
const symbols: { [key: string]: string } = {
EUR: '€',
USD: '$',
GBP: '£',
CHF: 'CHF',
};
const symbol = symbols[currency] || currency;
const formattedAmount = new Intl.NumberFormat('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(Math.abs(amount));
switch (format) {
case 'symbol':
return `${symbol}${formattedAmount}`;
case 'code':
return `${formattedAmount} ${currency}`;
case 'both':
return `${symbol}${formattedAmount} (${currency})`;
default:
return `${symbol}${formattedAmount}`;
}
}
// Format account number based on suggestion
export function formatAccountNumber(
accountNumber: string,
format: 'full' | 'partial' | 'hidden' = 'partial'
): string {
if (!accountNumber) return '';
switch (format) {
case 'full':
return accountNumber;
case 'partial':
return `****${accountNumber.slice(-4)}`;
case 'hidden':
return '****';
default:
return `****${accountNumber.slice(-4)}`;
}
}