🎯

data-transformers

🎯Skill

from dadbodgeoff/drift

VibeIndex|
What it does

Transforms and aggregates data consistently across API routes, providing centralized logic for categorization, ranking, and trend analysis.

πŸ“¦

Part of

dadbodgeoff/drift(69 items)

data-transformers

Installation

npm installInstall npm package
npm install -g driftdetect
npm installInstall npm package
npm install -g driftdetect@latest
npm installInstall npm package
npm install -g driftdetect-mcp
Claude Desktop ConfigurationAdd this to your claude_desktop_config.json
{ "mcpServers": { "drift": { "command": "driftdetect-mcp" } } ...
πŸ“– Extracted from docs: dadbodgeoff/drift
5Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

Centralized transformation logic for consistent data shaping across API routes. Includes aggregators, rankers, trend calculators, and data sanitizers.

Overview

# Data Transformers

Centralized transformation logic for consistent data shaping across API routes.

When to Use This Skill

  • Data transformation is scattered across routes
  • Need consistent output formats across endpoints
  • Want testable, reusable transformation functions
  • Building dashboards with aggregated data

Core Concepts

Centralize all transformation logic in one place:

  • Aggregators (category totals, counts)
  • Rankers (top-N by score)
  • Trend calculators (comparing periods)
  • Sanitizers (validate and clean data)

```

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”

β”‚ Raw Data │────▢│ Transformers │────▢│ API Output β”‚

β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

```

Implementation

TypeScript

```typescript

// lib/transformers.ts

// ============================================

// Category Aggregation

// ============================================

interface CategoryTotals {

[category: string]: number;

}

function aggregateCategories(

items: Array<{ category: string; count?: number }>

): CategoryTotals {

const totals: CategoryTotals = {};

for (const item of items) {

const category = item.category?.toUpperCase() || 'OTHER';

totals[category] = (totals[category] || 0) + (item.count ?? 1);

}

return totals;

}

function categoriesToBreakdown(

totals: CategoryTotals,

previousTotals?: CategoryTotals

): Array<{ category: string; count: number; percentage: number; trend: string }> {

const total = Object.values(totals).reduce((sum, count) => sum + count, 0);

return Object.entries(totals)

.map(([category, count]) => {

let trend: 'increasing' | 'stable' | 'decreasing' = 'stable';

if (previousTotals) {

const prevCount = previousTotals[category] ?? 0;

const change = count - prevCount;

if (change > prevCount * 0.1) trend = 'increasing';

else if (change < -prevCount * 0.1) trend = 'decreasing';

}

return {

category,

count,

percentage: total > 0 ? count / total : 0,

trend,

};

})

.sort((a, b) => b.count - a.count);

}

// ============================================

// Ranking

// ============================================

interface Rankable {

score: number;

count: number;

}

function rankItems(

items: T[],

limit = 5

): (T & { rank: number })[] {

return items

.sort((a, b) => {

if (b.score !== a.score) return b.score - a.score;

return b.count - a.count;

})

.slice(0, limit)

.map((item, index) => ({ ...item, rank: index + 1 }));

}

// ============================================

// Trend Calculation

// ============================================

type SimpleTrend = 'increasing' | 'stable' | 'decreasing';

function calculateTrend(current: number, previous: number): SimpleTrend {

if (previous === 0) return 'stable';

const change = (current - previous) / previous;

if (change > 0.1) return 'increasing';

if (change < -0.1) return 'decreasing';

return 'stable';

}

function calculateRollingAverage(values: number[], window = 7): number {

if (values.length === 0) return 0;

const slice = values.slice(-window);

return slice.reduce((sum, v) => sum + v, 0) / slice.length;

}

function calculatePercentChange(current: number, previous: number): number {

if (previous === 0) return current > 0 ? 100 : 0;

return ((current - previous) / previous) * 100;

}

// ============================================

// Data Sanitization

// ============================================

interface Hotspot {

country: string;

countryCode: string;

lat: number;

lon: number;

riskScore: number;

eventCount: number;

}

function sanitizeHotspot(raw: Partial): Hotspot | null {

if (!raw.country || !raw.countryCode) return null;

return {

country: raw.country,

countryCode: raw.countryCode,

lat: raw.lat ?? 0,

lon: raw.lon ?? 0,

riskScore: Math.min(100, Math.max(0, raw.riskScore ?? 0)),

eventCount: Math.max(0, raw.eventCount ?? 0),

};

}

function filterValidHotspots(hotspots: Partial[]): Hotspot[] {

return hotspots

.map(sanitizeHotspot)

.filter((h): h is Hotspot => h !== null);

}

// ============================================

// String Utilities

// ============================================

function truncate(str: string, maxLen: number): string {

if (!str) return '';

return str.length > maxLen ? str.slice(0, maxLen - 3) + '...' : str;

}

function slugify(str: string): string {

return str

.toLowerCase()

.replace(/[^\w\s-]/g, '')

.replace(/\s+/g, '-')

.replace(/-+/g, '-')

.trim();

}

// ============================================

// Date Utilities

// ============================================

function formatRelativeTime(date: Date): string {

const now = new Date();

const diffMs = now.getTime() - date.getTime();

const diffMins = Math.floor(diffMs / 60000);

const diffHours = Math.floor(diffMs / 3600000);

const diffDays = Math.floor(diffMs / 86400000);

if (diffMins < 1) return 'just now';

if (diffMins < 60) return ${diffMins}m ago;

if (diffHours < 24) return ${diffHours}h ago;

if (diffDays < 7) return ${diffDays}d ago;

return date.toLocaleDateString();

}

export {

aggregateCategories,

categoriesToBreakdown,

rankItems,

calculateTrend,

calculateRollingAverage,

calculatePercentChange,

sanitizeHotspot,

filterValidHotspots,

truncate,

slugify,

formatRelativeTime,

};

```

Usage Examples

API Route

```typescript

// api/dashboard/route.ts

import {

aggregateCategories,

rankItems,

filterValidHotspots

} from '@/lib/transformers';

export async function GET() {

const rawData = await fetchFromDatabase();

return Response.json({

categories: aggregateCategories(rawData.predictions),

topHotspots: rankItems(filterValidHotspots(rawData.hotspots), 5),

trend: calculateTrend(rawData.todayCount, rawData.yesterdayCount),

});

}

```

Dashboard Component

```typescript

const breakdown = categoriesToBreakdown(

currentTotals,

previousTotals

);

// Returns:

// [

// { category: 'MILITARY', count: 150, percentage: 0.45, trend: 'increasing' },

// { category: 'POLITICAL', count: 100, percentage: 0.30, trend: 'stable' },

// ...

// ]

```

Best Practices

  1. One file for all transformers - easy to find and test
  2. Pure functions - no side effects, predictable output
  3. Handle edge cases - empty arrays, missing fields, null values
  4. Type safety - use TypeScript generics where appropriate
  5. Export from types package - share across frontend and backend

Common Mistakes

  • Scattering transformation logic across routes
  • Not handling edge cases (empty arrays, null values)
  • Mutating input data instead of returning new objects
  • Missing type guards for nullable returns
  • Not testing transformers in isolation

Related Patterns

  • api-client - Use transformers in API responses
  • validation-quarantine - Validate before transforming
  • snapshot-aggregation - Aggregate data for dashboards

More from this repository10

🎯
feature-flags🎯Skill

Enables controlled feature rollouts, A/B testing, and selective feature access through configurable flags for gradual deployment and user targeting.

🎯
design-tokens🎯Skill

Generates a comprehensive, type-safe design token system with WCAG AA color compliance and multi-framework support for consistent visual design.

🎯
file-uploads🎯Skill

Securely validates, scans, and processes file uploads with multi-stage checks, malware detection, and race condition prevention.

🎯
ai-coaching🎯Skill

Guides users through articulating creative intent by extracting structured parameters and detecting conversation readiness.

🎯
environment-config🎯Skill

Validates and centralizes environment variables with type safety, fail-fast startup checks, and multi-environment support.

🎯
community-feed🎯Skill

Generates efficient social feed with cursor pagination, trending algorithms, and engagement tracking for infinite scroll experiences.

🎯
cloud-storage🎯Skill

Enables secure, multi-tenant cloud file storage with signed URLs, direct uploads, and visibility control for user-uploaded assets.

🎯
email-service🎯Skill

Simplifies email sending, templating, and tracking with robust SMTP integration and support for multiple email providers and transactional workflows.

🎯
error-sanitization🎯Skill

Sanitizes error messages by logging full details server-side while exposing only generic, safe messages to prevent sensitive information leakage.

🎯
batch-processing🎯Skill

Optimizes database operations by collecting and batching independent records, improving throughput by 30-40% with built-in fallback processing.