Skip to content

Latest commit

 

History

History
1037 lines (830 loc) · 27.8 KB

File metadata and controls

1037 lines (830 loc) · 27.8 KB

🚀 Advanced Usage Guide

This guide covers advanced patterns, best practices, and sophisticated use cases for azubiheft-api.

📚 Table of Contents


🏗️ Architecture Patterns

Session Management Architecture

import { Session, SessionOptions } from 'azubiheft-api';

// Singleton pattern for application-wide session
class SessionManager {
  private static instance: SessionManager;
  private session: Session | null = null;
  private lastActivity: Date = new Date();

  private constructor() {}

  static getInstance(): SessionManager {
    if (!SessionManager.instance) {
      SessionManager.instance = new SessionManager();
    }
    return SessionManager.instance;
  }

  async getSession(forceRefresh = false): Promise<Session> {
    if (!this.session || forceRefresh || this.isSessionExpired()) {
      await this.createNewSession();
    }

    this.lastActivity = new Date();
    return this.session!;
  }

  private async createNewSession(): Promise<void> {
    const options: SessionOptions = {
      timeout: 60000,
      userAgent: 'MyApp/2.0.0'
    };

    this.session = new Session(options);
    await this.session.login({
      username: process.env.AZUBIHEFT_USERNAME!,
      password: process.env.AZUBIHEFT_PASSWORD!
    });
  }

  private isSessionExpired(): boolean {
    const TIMEOUT = 30 * 60 * 1000; // 30 minutes
    return Date.now() - this.lastActivity.getTime() > TIMEOUT;
  }
}

Repository Pattern Implementation

import { Session, Entry, ReportEntry, Subject } from 'azubiheft-api';

interface ReportRepository {
  findByDate(date: Date): Promise<ReportEntry[]>;
  findByDateRange(start: Date, end: Date): Promise<ReportEntry[]>;
  save(entries: Entry[]): Promise<void>;
  delete(date: Date, entryNumber?: number): Promise<void>;
}

class AzubiheftReportRepository implements ReportRepository {
  constructor(private session: Session) {}

  async findByDate(date: Date): Promise<ReportEntry[]> {
    return await this.session.getReport(date);
  }

  async findByDateRange(start: Date, end: Date): Promise<ReportEntry[]> {
    const reports: ReportEntry[] = [];
    const currentDate = new Date(start);

    while (currentDate <= end) {
      try {
        const dayReports = await this.session.getReport(currentDate);
        reports.push(...dayReports);
      } catch (error) {
        console.warn(`No reports for ${currentDate.toDateString()}`);
      }
      currentDate.setDate(currentDate.getDate() + 1);
    }

    return reports;
  }

  async save(entries: Entry[]): Promise<void> {
    await this.session.writeReports(entries);
  }

  async delete(date: Date, entryNumber?: number): Promise<void> {
    await this.session.deleteReport(date, entryNumber);
  }
}

Command Pattern for Complex Operations

interface Command {
  execute(): Promise<void>;
  undo(): Promise<void>;
  canUndo(): boolean;
}

class AddReportCommand implements Command {
  private executed = false;

  constructor(
    private repository: ReportRepository,
    private entry: Entry
  ) {}

  async execute(): Promise<void> {
    await this.repository.save([this.entry]);
    this.executed = true;
  }

  async undo(): Promise<void> {
    if (this.executed) {
      await this.repository.delete(this.entry.date);
      this.executed = false;
    }
  }

  canUndo(): boolean {
    return this.executed;
  }
}

class CommandManager {
  private history: Command[] = [];
  private currentIndex = -1;

  async execute(command: Command): Promise<void> {
    await command.execute();

    // Remove any commands after current index
    this.history.splice(this.currentIndex + 1);

    this.history.push(command);
    this.currentIndex++;
  }

  async undo(): Promise<void> {
    if (this.currentIndex >= 0) {
      const command = this.history[this.currentIndex];
      if (command.canUndo()) {
        await command.undo();
        this.currentIndex--;
      }
    }
  }
}

⚡ Performance Optimization

Connection Pooling and Caching

class PerformantSessionManager {
  private sessionPool: Session[] = [];
  private cache = new Map<string, { data: any; timestamp: number }>();
  private readonly CACHE_TTL = 5 * 60 * 1000; // 5 minutes

  async getOptimalSession(): Promise<Session> {
    // Return available session from pool or create new one
    if (this.sessionPool.length > 0) {
      return this.sessionPool.pop()!;
    }

    return await this.createNewSession();
  }

  async releaseSession(session: Session): Promise<void> {
    // Return session to pool for reuse
    this.sessionPool.push(session);
  }

  async getCachedData<T>(key: string, fetcher: () => Promise<T>): Promise<T> {
    const cached = this.cache.get(key);

    if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) {
      return cached.data as T;
    }

    const data = await fetcher();
    this.cache.set(key, { data, timestamp: Date.now() });
    return data;
  }

  async getSubjectsOptimized(): Promise<Subject[]> {
    return this.getCachedData('subjects', async () => {
      const session = await this.getOptimalSession();
      try {
        return await session.getSubjects();
      } finally {
        await this.releaseSession(session);
      }
    });
  }
}

Batch Processing with Queues

interface QueueItem {
  id: string;
  operation: () => Promise<any>;
  priority: number;
  retries: number;
}

class OperationQueue {
  private queue: QueueItem[] = [];
  private processing = false;
  private concurrency = 3;
  private activeOperations = 0;

  async add(operation: () => Promise<any>, priority = 0): Promise<string> {
    const id = `op_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;

    this.queue.push({
      id,
      operation,
      priority,
      retries: 0
    });

    this.queue.sort((a, b) => b.priority - a.priority);
    this.processQueue();

    return id;
  }

  private async processQueue(): Promise<void> {
    if (this.processing || this.activeOperations >= this.concurrency) {
      return;
    }

    this.processing = true;

    while (this.queue.length > 0 && this.activeOperations < this.concurrency) {
      const item = this.queue.shift()!;
      this.executeOperation(item);
    }

    this.processing = false;
  }

  private async executeOperation(item: QueueItem): Promise<void> {
    this.activeOperations++;

    try {
      await item.operation();
    } catch (error) {
      if (item.retries < 3) {
        item.retries++;
        this.queue.unshift(item); // Retry at front of queue
      } else {
        console.error(`Operation ${item.id} failed after 3 retries:`, error);
      }
    } finally {
      this.activeOperations--;
      this.processQueue();
    }
  }
}

Memory-Efficient Large Dataset Processing

async function* processLargeDataset(
  repository: ReportRepository,
  startDate: Date,
  endDate: Date,
  batchSize = 7 // Process week by week
): AsyncGenerator<ReportEntry[], void, unknown> {
  const currentDate = new Date(startDate);

  while (currentDate <= endDate) {
    const batchEnd = new Date(currentDate);
    batchEnd.setDate(currentDate.getDate() + batchSize - 1);

    if (batchEnd > endDate) {
      batchEnd.setTime(endDate.getTime());
    }

    const batch = await repository.findByDateRange(currentDate, batchEnd);

    if (batch.length > 0) {
      yield batch;
    }

    currentDate.setDate(batchEnd.getDate() + 1);
  }
}

// Usage example
async function analyzeYearOfData(repository: ReportRepository) {
  const startDate = new Date('2024-01-01');
  const endDate = new Date('2024-12-31');

  for await (const batch of processLargeDataset(repository, startDate, endDate)) {
    // Process each batch without loading entire year into memory
    console.log(`Processing batch of ${batch.length} entries`);

    // Your processing logic here
    await processBatch(batch);
  }
}

🔒 Security Best Practices

Credential Management

interface SecureCredentials {
  username: string;
  password: string;
  mfaToken?: string;
}

class CredentialManager {
  private static instance: CredentialManager;
  private credentials: SecureCredentials | null = null;

  private constructor() {}

  static getInstance(): CredentialManager {
    if (!CredentialManager.instance) {
      CredentialManager.instance = new CredentialManager();
    }
    return CredentialManager.instance;
  }

  async loadCredentials(): Promise<SecureCredentials> {
    if (this.credentials) {
      return this.credentials;
    }

    // Load from secure sources
    this.credentials = {
      username: this.getFromSecureSource('AZUBIHEFT_USERNAME'),
      password: this.getFromSecureSource('AZUBIHEFT_PASSWORD'),
      mfaToken: this.getFromSecureSource('AZUBIHEFT_MFA_TOKEN', false)
    };

    return this.credentials;
  }

  private getFromSecureSource(key: string, required = true): string {
    // Try environment variables first
    let value = process.env[key];

    if (!value && typeof window !== 'undefined') {
      // Browser environment - get from secure storage
      value = this.getFromSecureStorage(key);
    }

    if (!value && required) {
      throw new Error(`Required credential ${key} not found`);
    }

    return value || '';
  }

  private getFromSecureStorage(key: string): string | null {
    // Implementation would depend on your secure storage solution
    // Could be encrypted localStorage, secure vault, etc.
    return localStorage.getItem(`secure_${key}`);
  }

  clearCredentials(): void {
    this.credentials = null;
    // Clear from all storage locations
    if (typeof window !== 'undefined') {
      Object.keys(localStorage).forEach(key => {
        if (key.startsWith('secure_')) {
          localStorage.removeItem(key);
        }
      });
    }
  }
}

Input Sanitization and Validation

class InputValidator {
  static validateReportMessage(message: string): string {
    if (!message || typeof message !== 'string') {
      throw new Error('Message must be a non-empty string');
    }

    // Remove potentially dangerous characters
    const sanitized = message
      .replace(/[<>]/g, '') // Remove angle brackets
      .replace(/javascript:/gi, '') // Remove javascript: protocol
      .replace(/on\w+=/gi, '') // Remove event handlers
      .trim();

    if (sanitized.length === 0) {
      throw new Error('Message cannot be empty after sanitization');
    }

    if (sanitized.length > 5000) {
      throw new Error('Message too long (max 5000 characters)');
    }

    return sanitized;
  }

  static validateTimeString(timeString: string): string {
    if (!timeString || typeof timeString !== 'string') {
      throw new Error('Time string must be provided');
    }

    const timePattern = /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/;
    if (!timePattern.test(timeString)) {
      throw new Error('Invalid time format. Expected HH:MM');
    }

    return timeString;
  }

  static validateDate(date: any): Date {
    let validDate: Date;

    if (date instanceof Date) {
      validDate = date;
    } else if (typeof date === 'string') {
      validDate = new Date(date);
    } else {
      throw new Error('Date must be Date object or ISO string');
    }

    if (isNaN(validDate.getTime())) {
      throw new Error('Invalid date provided');
    }

    // Check if date is in reasonable range
    const now = new Date();
    const oneYearAgo = new Date(now.getFullYear() - 1, 0, 1);
    const oneYearFromNow = new Date(now.getFullYear() + 1, 11, 31);

    if (validDate < oneYearAgo || validDate > oneYearFromNow) {
      throw new Error('Date must be within reasonable range (last year to next year)');
    }

    return validDate;
  }
}

Audit Logging

interface AuditLogEntry {
  timestamp: Date;
  userId: string;
  action: string;
  resource: string;
  details: Record<string, any>;
  ipAddress?: string;
  userAgent?: string;
}

class AuditLogger {
  private logs: AuditLogEntry[] = [];

  async logAction(
    userId: string,
    action: string,
    resource: string,
    details: Record<string, any> = {},
    context?: { ipAddress?: string; userAgent?: string }
  ): Promise<void> {
    const entry: AuditLogEntry = {
      timestamp: new Date(),
      userId,
      action,
      resource,
      details: this.sanitizeDetails(details),
      ipAddress: context?.ipAddress,
      userAgent: context?.userAgent
    };

    this.logs.push(entry);

    // In production, send to logging service
    await this.persistLog(entry);
  }

  private sanitizeDetails(details: Record<string, any>): Record<string, any> {
    const sanitized = { ...details };

    // Remove sensitive information
    const sensitiveKeys = ['password', 'token', 'secret', 'key'];
    sensitiveKeys.forEach(key => {
      if (key in sanitized) {
        sanitized[key] = '[REDACTED]';
      }
    });

    return sanitized;
  }

  private async persistLog(entry: AuditLogEntry): Promise<void> {
    // Implementation would send to logging service
    console.log('AUDIT:', JSON.stringify(entry));
  }

  getRecentLogs(limit = 100): AuditLogEntry[] {
    return this.logs.slice(-limit);
  }
}

🤖 Automation Strategies

Event-Driven Automation

interface AutomationEvent {
  type: string;
  payload: any;
  timestamp: Date;
}

class AutomationEngine {
  private eventHandlers = new Map<string, Array<(event: AutomationEvent) => Promise<void>>>();
  private scheduledTasks = new Map<string, NodeJS.Timeout>();

  on(eventType: string, handler: (event: AutomationEvent) => Promise<void>): void {
    if (!this.eventHandlers.has(eventType)) {
      this.eventHandlers.set(eventType, []);
    }
    this.eventHandlers.get(eventType)!.push(handler);
  }

  async emit(eventType: string, payload: any): Promise<void> {
    const event: AutomationEvent = {
      type: eventType,
      payload,
      timestamp: new Date()
    };

    const handlers = this.eventHandlers.get(eventType) || [];
    await Promise.all(handlers.map(handler => handler(event)));
  }

  scheduleDaily(time: string, task: () => Promise<void>): string {
    const taskId = `daily_${Date.now()}`;

    const scheduleNext = () => {
      const now = new Date();
      const [hours, minutes] = time.split(':').map(Number);

      const scheduledTime = new Date(now);
      scheduledTime.setHours(hours, minutes, 0, 0);

      // If time has passed today, schedule for tomorrow
      if (scheduledTime <= now) {
        scheduledTime.setDate(scheduledTime.getDate() + 1);
      }

      const delay = scheduledTime.getTime() - now.getTime();

      this.scheduledTasks.set(taskId, setTimeout(async () => {
        try {
          await task();
        } catch (error) {
          console.error(`Scheduled task ${taskId} failed:`, error);
        }
        scheduleNext(); // Schedule next execution
      }, delay));
    };

    scheduleNext();
    return taskId;
  }

  cancelScheduledTask(taskId: string): void {
    const timeout = this.scheduledTasks.get(taskId);
    if (timeout) {
      clearTimeout(timeout);
      this.scheduledTasks.delete(taskId);
    }
  }
}

// Usage example
const automation = new AutomationEngine();

// Set up event handlers
automation.on('report_added', async (event) => {
  console.log('Report added:', event.payload);
  // Trigger additional processing
});

automation.on('week_completed', async (event) => {
  // Generate weekly summary
  console.log('Week completed, generating summary...');
});

// Schedule daily tasks
automation.scheduleDaily('18:00', async () => {
  // Daily summary task
  console.log('Generating daily summary...');
});

Smart Template System

interface Template {
  id: string;
  name: string;
  pattern: string;
  variables: Record<string, any>;
  conditions?: Array<(context: any) => boolean>;
}

class SmartTemplateEngine {
  private templates: Map<string, Template> = new Map();

  addTemplate(template: Template): void {
    this.templates.set(template.id, template);
  }

  async generateContent(templateId: string, context: Record<string, any>): Promise<string> {
    const template = this.templates.get(templateId);
    if (!template) {
      throw new Error(`Template ${templateId} not found`);
    }

    // Check conditions
    if (template.conditions) {
      const conditionsMet = template.conditions.every(condition => condition(context));
      if (!conditionsMet) {
        throw new Error(`Template conditions not met for ${templateId}`);
      }
    }

    // Replace variables in pattern
    let content = template.pattern;

    // Built-in variables
    const builtInVars = {
      date: new Date().toLocaleDateString(),
      time: new Date().toLocaleTimeString(),
      dayOfWeek: new Date().toLocaleDateString('en', { weekday: 'long' }),
      ...template.variables,
      ...context
    };

    // Replace placeholders
    for (const [key, value] of Object.entries(builtInVars)) {
      const placeholder = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
      content = content.replace(placeholder, String(value));
    }

    // Advanced transformations
    content = await this.applyTransformations(content, context);

    return content;
  }

  private async applyTransformations(content: string, context: any): Promise<string> {
    // Apply conditional blocks
    content = content.replace(
      /\{\{#if\s+(\w+)\}\}(.*?)\{\{\/if\}\}/gs,
      (match, condition, block) => {
        return context[condition] ? block : '';
      }
    );

    // Apply loops
    content = content.replace(
      /\{\{#each\s+(\w+)\}\}(.*?)\{\{\/each\}\}/gs,
      (match, arrayName, block) => {
        const array = context[arrayName] || [];
        return array.map((item: any, index: number) => {
          let itemBlock = block;
          itemBlock = itemBlock.replace(/\{\{@index\}\}/g, index.toString());
          itemBlock = itemBlock.replace(/\{\{this\}\}/g, item.toString());
          return itemBlock;
        }).join('');
      }
    );

    return content;
  }
}

// Usage example
const templateEngine = new SmartTemplateEngine();

templateEngine.addTemplate({
  id: 'daily_work',
  name: 'Daily Work Report',
  pattern: `Worked on {{project}} for {{duration}}. {{#if achievements}}Key achievements: {{#each achievements}}{{this}}{{/each}}{{/if}}`,
  variables: {
    project: 'Current Project'
  },
  conditions: [
    (context) => context.duration && context.duration !== '00:00'
  ]
});

📊 Data Analytics & Insights

Advanced Analytics Engine

interface MetricDefinition {
  name: string;
  calculate: (data: ReportEntry[]) => number | string;
  format?: (value: any) => string;
}

class AnalyticsEngine {
  private metrics: Map<string, MetricDefinition> = new Map();

  constructor() {
    this.registerBuiltInMetrics();
  }

  private registerBuiltInMetrics(): void {
    this.addMetric({
      name: 'total_hours',
      calculate: (data) => {
        const totalMinutes = data.reduce((sum, entry) => {
          return sum + TimeHelper.timeStringToMinutes(entry.duration);
        }, 0);
        return TimeHelper.minutesToTimeString(totalMinutes);
      }
    });

    this.addMetric({
      name: 'productivity_score',
      calculate: (data) => {
        // Complex productivity calculation
        const weights = { 'Betrieb': 1.0, 'Schule': 0.8, 'ÜBA': 0.9 };
        const weightedMinutes = data.reduce((sum, entry) => {
          const minutes = TimeHelper.timeStringToMinutes(entry.duration);
          const weight = weights[entry.type as keyof typeof weights] || 0.5;
          return sum + (minutes * weight);
        }, 0);
        return Math.round((weightedMinutes / (8 * 60)) * 100); // Percentage of 8-hour day
      },
      format: (value) => `${value}%`
    });

    this.addMetric({
      name: 'learning_velocity',
      calculate: (data) => {
        const learningEntries = data.filter(entry =>
          entry.text.toLowerCase().includes('learn') ||
          entry.type === 'Schule'
        );
        return learningEntries.length;
      },
      format: (value) => `${value} learning activities`
    });
  }

  addMetric(metric: MetricDefinition): void {
    this.metrics.set(metric.name, metric);
  }

  async generateInsights(data: ReportEntry[]): Promise<Record<string, any>> {
    const insights: Record<string, any> = {};

    for (const [name, metric] of this.metrics) {
      try {
        const value = metric.calculate(data);
        insights[name] = {
          value,
          formatted: metric.format ? metric.format(value) : value
        };
      } catch (error) {
        console.error(`Error calculating metric ${name}:`, error);
        insights[name] = { value: null, error: error.message };
      }
    }

    return insights;
  }

  async generateTrendAnalysis(
    historicalData: Array<{ period: string; data: ReportEntry[] }>
  ): Promise<Record<string, any>> {
    const trends: Record<string, any> = {};

    for (const [metricName] of this.metrics) {
      const values = historicalData.map(period => {
        const insights = this.generateInsights(period.data);
        return {
          period: period.period,
          value: insights[metricName]?.value || 0
        };
      });

      trends[metricName] = {
        values,
        trend: this.calculateTrend(values.map(v => v.value)),
        summary: this.generateTrendSummary(values)
      };
    }

    return trends;
  }

  private calculateTrend(values: number[]): 'increasing' | 'decreasing' | 'stable' {
    if (values.length < 2) return 'stable';

    const slope = this.calculateLinearRegression(values).slope;

    if (Math.abs(slope) < 0.1) return 'stable';
    return slope > 0 ? 'increasing' : 'decreasing';
  }

  private calculateLinearRegression(values: number[]): { slope: number; intercept: number } {
    const n = values.length;
    const sumX = values.reduce((sum, _, i) => sum + i, 0);
    const sumY = values.reduce((sum, val) => sum + val, 0);
    const sumXY = values.reduce((sum, val, i) => sum + (i * val), 0);
    const sumXX = values.reduce((sum, _, i) => sum + (i * i), 0);

    const slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);
    const intercept = (sumY - slope * sumX) / n;

    return { slope, intercept };
  }

  private generateTrendSummary(values: Array<{ period: string; value: number }>): string {
    const latest = values[values.length - 1]?.value || 0;
    const previous = values[values.length - 2]?.value || 0;
    const change = latest - previous;
    const changePercent = previous !== 0 ? Math.round((change / previous) * 100) : 0;

    if (Math.abs(changePercent) < 5) {
      return 'Stable performance';
    }

    return changePercent > 0
      ? `Improved by ${changePercent}% from last period`
      : `Decreased by ${Math.abs(changePercent)}% from last period`;
  }
}

Predictive Analytics

class PredictiveAnalytics {
  async predictNextWeekHours(historicalData: ReportEntry[]): Promise<{
    predicted: string;
    confidence: number;
    factors: string[];
  }> {
    // Group data by week
    const weeklyData = this.groupByWeek(historicalData);

    if (weeklyData.length < 4) {
      return {
        predicted: '40:00',
        confidence: 0.3,
        factors: ['Insufficient historical data']
      };
    }

    // Calculate weekly totals
    const weeklyHours = weeklyData.map(week => {
      const totalMinutes = week.entries.reduce((sum, entry) => {
        return sum + TimeHelper.timeStringToMinutes(entry.duration);
      }, 0);
      return totalMinutes;
    });

    // Simple moving average with trend adjustment
    const recentWeeks = weeklyHours.slice(-4);
    const average = recentWeeks.reduce((sum, val) => sum + val, 0) / recentWeeks.length;

    // Calculate trend
    const trend = this.calculateTrend(recentWeeks);
    const trendAdjustment = trend * 0.1; // 10% trend influence

    const predicted = Math.round(average + trendAdjustment);
    const confidence = this.calculateConfidence(weeklyHours);

    return {
      predicted: TimeHelper.minutesToTimeString(predicted),
      confidence,
      factors: this.identifyInfluencingFactors(historicalData)
    };
  }

  private groupByWeek(data: ReportEntry[]): Array<{ week: number; year: number; entries: ReportEntry[] }> {
    const weeks = new Map<string, ReportEntry[]>();

    data.forEach(entry => {
      // Simplified week grouping - in real implementation, use actual dates
      const weekKey = '2024-W01'; // Placeholder
      if (!weeks.has(weekKey)) {
        weeks.set(weekKey, []);
      }
      weeks.get(weekKey)!.push(entry);
    });

    return Array.from(weeks.entries()).map(([key, entries]) => ({
      week: 1, // Placeholder
      year: 2024, // Placeholder
      entries
    }));
  }

  private calculateTrend(values: number[]): number {
    // Linear regression slope
    const n = values.length;
    const sumX = values.reduce((sum, _, i) => sum + i, 0);
    const sumY = values.reduce((sum, val) => sum + val, 0);
    const sumXY = values.reduce((sum, val, i) => sum + (i * val), 0);
    const sumXX = values.reduce((sum, _, i) => sum + (i * i), 0);

    return (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);
  }

  private calculateConfidence(values: number[]): number {
    if (values.length < 3) return 0.3;

    // Calculate coefficient of variation
    const mean = values.reduce((sum, val) => sum + val, 0) / values.length;
    const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length;
    const standardDeviation = Math.sqrt(variance);
    const coefficientOfVariation = standardDeviation / mean;

    // Higher consistency = higher confidence
    return Math.max(0.1, Math.min(0.9, 1 - coefficientOfVariation));
  }

  private identifyInfluencingFactors(data: ReportEntry[]): string[] {
    const factors: string[] = [];

    // Analyze patterns
    const typeDistribution = this.analyzeTypeDistribution(data);
    const timePatterns = this.analyzeTimePatterns(data);

    if (typeDistribution.workHeavy) {
      factors.push('Work-heavy schedule detected');
    }

    if (timePatterns.overtime) {
      factors.push('Recent overtime trend');
    }

    if (timePatterns.consistent) {
      factors.push('Consistent daily patterns');
    }

    return factors;
  }

  private analyzeTypeDistribution(data: ReportEntry[]): { workHeavy: boolean } {
    const workEntries = data.filter(e => e.type === 'Betrieb').length;
    const totalEntries = data.length;

    return {
      workHeavy: workEntries / totalEntries > 0.7
    };
  }

  private analyzeTimePatterns(data: ReportEntry[]): { overtime: boolean; consistent: boolean } {
    // Simplified analysis
    const dailyTotals = new Map<string, number>();

    data.forEach(entry => {
      const day = '2024-01-01'; // Placeholder - would use actual date
      const minutes = TimeHelper.timeStringToMinutes(entry.duration);
      dailyTotals.set(day, (dailyTotals.get(day) || 0) + minutes);
    });

    const totals = Array.from(dailyTotals.values());
    const averageDaily = totals.reduce((sum, val) => sum + val, 0) / totals.length;
    const hasOvertime = totals.some(total => total > 8 * 60); // > 8 hours

    // Calculate consistency (low variance = high consistency)
    const variance = totals.reduce((sum, val) => sum + Math.pow(val - averageDaily, 2), 0) / totals.length;
    const consistent = variance < (2 * 60 * 2 * 60); // Less than 2 hours variance

    return {
      overtime: hasOvertime,
      consistent
    };
  }
}

This advanced usage guide demonstrates sophisticated patterns for building production-ready applications with azubiheft-api. The patterns shown here can be adapted and extended based on your specific requirements.

Continue reading the other documentation files for troubleshooting, migration guides, and complete API reference.