/**
* Service for generating weekly reports from session notes
*/
import type { SessionNote, SessionPattern, ComplexityLevel } from '../types/session.js';
export interface WeeklyReport {
startDate: string;
endDate: string;
totalSessions: number;
totalFiles: number;
totalCommands: number;
projectBreakdown: Record<string, number>;
patternBreakdown: Record<SessionPattern, number>;
complexityBreakdown: Record<ComplexityLevel, number>;
sessions: SessionNote[];
}
/**
* Generate a weekly report from session notes
*/
export function generateWeeklyReport(notes: SessionNote[], weekStartDate?: Date): WeeklyReport {
// Determine week boundaries
const now = weekStartDate || new Date();
const startOfWeek = new Date(now);
const dayOfWeek = now.getDay();
// Adjust so Monday is start of week (0=Sunday becomes 6 days back, 1=Monday becomes 0 days back)
const daysToSubtract = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
startOfWeek.setDate(now.getDate() - daysToSubtract); // Start of week (Monday)
startOfWeek.setHours(0, 0, 0, 0);
const endOfWeek = new Date(startOfWeek);
endOfWeek.setDate(startOfWeek.getDate() + 7);
// Filter notes for this week
const weekNotes = notes.filter(note => {
const noteDate = new Date(note.timestamp);
return noteDate >= startOfWeek && noteDate < endOfWeek;
});
// Calculate statistics
const totalFiles = weekNotes.reduce((sum, note) =>
sum + (note.fileChanges?.length || 0), 0
);
const totalCommands = weekNotes.reduce((sum, note) =>
sum + (note.commands?.length || 0), 0
);
// Project breakdown
const projectBreakdown: Record<string, number> = {};
for (const note of weekNotes) {
const project = note.projectName || 'Unknown';
projectBreakdown[project] = (projectBreakdown[project] || 0) + 1;
}
// Pattern breakdown
const patternBreakdown: Record<SessionPattern, number> = {
'new-feature': 0,
'bug-fix': 0,
'refactoring': 0,
'documentation': 0,
'configuration': 0,
'testing': 0,
'mixed': 0,
};
for (const note of weekNotes) {
if (note.analysis?.pattern) {
patternBreakdown[note.analysis.pattern]++;
}
}
// Complexity breakdown
const complexityBreakdown: Record<ComplexityLevel, number> = {
'simple': 0,
'moderate': 0,
'complex': 0,
};
for (const note of weekNotes) {
if (note.analysis?.complexity) {
complexityBreakdown[note.analysis.complexity]++;
}
}
return {
startDate: startOfWeek.toISOString(),
endDate: endOfWeek.toISOString(),
totalSessions: weekNotes.length,
totalFiles,
totalCommands,
projectBreakdown,
patternBreakdown,
complexityBreakdown,
sessions: weekNotes,
};
}
/**
* Format weekly report as markdown
*/
export function formatWeeklyReport(report: WeeklyReport): string {
const lines: string[] = [];
// Title
const startDate = new Date(report.startDate).toLocaleDateString();
const endDate = new Date(report.endDate).toLocaleDateString();
lines.push(`# Weekly Coding Report`);
lines.push(`## ${startDate} - ${endDate}`);
lines.push('');
// Summary
lines.push('## Summary');
lines.push('');
lines.push(`- **Total Sessions**: ${report.totalSessions}`);
lines.push(`- **Files Changed**: ${report.totalFiles}`);
lines.push(`- **Commands Executed**: ${report.totalCommands}`);
lines.push('');
// Project breakdown
if (Object.keys(report.projectBreakdown).length > 0) {
lines.push('## Projects');
lines.push('');
const sortedProjects = Object.entries(report.projectBreakdown)
.sort((a, b) => b[1] - a[1]);
for (const [project, count] of sortedProjects) {
lines.push(`- **${project}**: ${count} sessions`);
}
lines.push('');
}
// Pattern breakdown
lines.push('## Work Patterns');
lines.push('');
const patterns = Object.entries(report.patternBreakdown)
.filter(([_, count]) => count > 0)
.sort((a, b) => b[1] - a[1]);
for (const [pattern, count] of patterns) {
const emoji = getPatternEmoji(pattern as SessionPattern);
lines.push(`- ${emoji} **${formatPatternName(pattern)}**: ${count} sessions`);
}
lines.push('');
// Complexity breakdown
lines.push('## Complexity Distribution');
lines.push('');
for (const [complexity, count] of Object.entries(report.complexityBreakdown)) {
if (count > 0) {
const emoji = getComplexityEmoji(complexity as ComplexityLevel);
lines.push(`- ${emoji} **${complexity.charAt(0).toUpperCase() + complexity.slice(1)}**: ${count} sessions`);
}
}
lines.push('');
// Sessions list
lines.push('## Sessions This Week');
lines.push('');
let sessionNumber = 1;
for (const session of report.sessions) {
// Format date as "Nov 30, 23:45"
const date = new Date(session.timestamp);
const dateStr = date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric'
});
const timeStr = date.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
hour12: false
});
const shortDate = `${dateStr}, ${timeStr}`;
// Get topic or fallback to summary
const topic = session.topic ||
session.summary.substring(0, 50) + (session.summary.length > 50 ? '...' : '');
// Get project name
const project = session.projectName || 'General';
// Format: "1. Topic - **Project** (Date Time)"
lines.push(`${sessionNumber}. ${topic} - **${project}** (${shortDate})`);
lines.push(` ${session.summary}`); // Indent summary for readability
lines.push('');
sessionNumber++;
}
return lines.join('\n');
}
function getPatternEmoji(pattern: SessionPattern): string {
const emojis: Record<SessionPattern, string> = {
'new-feature': '🆕',
'bug-fix': '🐛',
'refactoring': '♻️',
'documentation': '📝',
'configuration': '⚙️',
'testing': '✅',
'mixed': '🔀',
};
return emojis[pattern] || '📌';
}
function getComplexityEmoji(complexity: ComplexityLevel): string {
const emojis: Record<ComplexityLevel, string> = {
'simple': '🟢',
'moderate': '🟡',
'complex': '🔴',
};
return emojis[complexity] || '⚪';
}
function formatPatternName(pattern: string): string {
return pattern.split('-').map(word =>
word.charAt(0).toUpperCase() + word.slice(1)
).join(' ');
}