πŸ“… let's chat! explore the endless possibilities creating industries that don't exist. click here

maintainx-migration-deep-dive

Execute complete platform migrations to or from MaintainX. Use when migrating from legacy CMMS systems, performing major re-platforming, or transitioning to MaintainX from spreadsheets or other tools. Trigger with phrases like "migrate to maintainx", "maintainx migration", "cmms migration", "switch to maintainx", "maintainx data migration". allowed-tools: Read, Write, Edit, Bash(npm:*), Bash(node:*) version: 1.0.0 license: MIT author: Jeremy Longshore <jeremy@intentsolutions.io>

Allowed Tools

No tools specified

Provided by Plugin

maintainx-pack

Claude Code skill pack for MaintainX CMMS (24 skills)

saas packs v1.0.0
View Plugin

Installation

This skill is included in the maintainx-pack plugin:

/plugin install maintainx-pack@claude-code-plugins-plus

Click to copy

Instructions

# MaintainX Migration Deep Dive ## Overview Comprehensive guide for migrating to MaintainX from legacy CMMS systems, spreadsheets, or other maintenance management tools. ## Prerequisites - MaintainX account with API access - Access to source system data - Understanding of both systems' data models ## Migration Strategy ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Migration Strategy Overview β”‚ β”‚ β”‚ β”‚ Phase 1 Phase 2 Phase 3 Phase 4 β”‚ β”‚ ASSESS PREPARE MIGRATE VALIDATE β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚Inventory β”‚ β”‚Data β”‚ β”‚Parallel β”‚ β”‚Testing β”‚ β”‚ β”‚ β”‚data │───▢│cleansing │───▢│run │───▢│& cutover β”‚ β”‚ β”‚ β”‚ β”‚ β”‚& mapping β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ Week 1-2 Week 3-4 Week 5-6 Week 7-8 β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ## Instructions ### Step 1: Source System Assessment ```typescript // scripts/migration-assessment.ts interface SourceSystemAssessment { system: string; dataVolume: { workOrders: number; assets: number; locations: number; users: number; procedures: number; }; dataQuality: { completeness: number; // Percentage duplicates: number; invalidRecords: number; }; customizations: string[]; integrations: string[]; risks: string[]; } async function assessSourceSystem(sourceData: any): Promise { console.log('=== Migration Assessment ===\n'); const assessment: SourceSystemAssessment = { system: sourceData.systemName || 'Unknown', dataVolume: { workOrders: 0, assets: 0, locations: 0, users: 0, procedures: 0, }, dataQuality: { completeness: 0, duplicates: 0, invalidRecords: 0, }, customizations: [], integrations: [], risks: [], }; // Count records console.log('Counting records...'); assessment.dataVolume.workOrders = sourceData.workOrders?.length || 0; assessment.dataVolume.assets = sourceData.assets?.length || 0; assessment.dataVolume.locations = sourceData.locations?.length || 0; assessment.dataVolume.users = sourceData.users?.length || 0; assessment.dataVolume.procedures = sourceData.procedures?.length || 0; // Analyze data quality console.log('Analyzing data quality...'); const workOrders = sourceData.workOrders || []; // Check completeness (required fields) const requiredFields = ['title', 'createdAt']; let completeRecords = 0; workOrders.forEach((wo: any) => { const hasAllRequired = requiredFields.every(f => wo[f]); if (hasAllRequired) completeRecords++; }); assessment.dataQuality.completeness = workOrders.length > 0 ? (completeRecords / workOrders.length) * 100 : 100; // Check for duplicates const titles = workOrders.map((wo: any) => wo.title); const uniqueTitles = new Set(titles); assessment.dataQuality.duplicates = titles.length - uniqueTitles.size; // Identify risks if (assessment.dataVolume.workOrders > 10000) { assessment.risks.push('Large data volume may require batched migration'); } if (assessment.dataQuality.completeness < 80) { assessment.risks.push('Low data completeness - may need data enrichment'); } if (assessment.dataQuality.duplicates > 100) { assessment.risks.push('Significant duplicates - deduplication recommended'); } return assessment; } // Print assessment report function printAssessmentReport(assessment: SourceSystemAssessment): void { console.log('\n=== Assessment Report ===\n'); console.log('Data Volume:'); console.log(` Work Orders: ${assessment.dataVolume.workOrders.toLocaleString()}`); console.log(` Assets: ${assessment.dataVolume.assets.toLocaleString()}`); console.log(` Locations: ${assessment.dataVolume.locations.toLocaleString()}`); console.log(` Users: ${assessment.dataVolume.users.toLocaleString()}`); console.log(` Procedures: ${assessment.dataVolume.procedures.toLocaleString()}`); console.log('\nData Quality:'); console.log(` Completeness: ${assessment.dataQuality.completeness.toFixed(1)}%`); console.log(` Duplicates: ${assessment.dataQuality.duplicates}`); console.log(` Invalid Records: ${assessment.dataQuality.invalidRecords}`); if (assessment.risks.length > 0) { console.log('\nRisks:'); assessment.risks.forEach(r => console.log(` - ${r}`)); } } ``` ### Step 2: Data Mapping ```typescript // src/migration/data-mapping.ts interface FieldMapping { sourceField: string; targetField: string; transform?: (value: any) => any; required: boolean; defaultValue?: any; } interface DataMapping { workOrders: FieldMapping[]; assets: FieldMapping[]; locations: FieldMapping[]; users: FieldMapping[]; } // Common mappings from legacy systems const legacyCmmsToMaintainX: DataMapping = { workOrders: [ { sourceField: 'wo_number', targetField: 'externalId', required: false }, { sourceField: 'wo_title', targetField: 'title', required: true }, { sourceField: 'wo_description', targetField: 'description', required: false }, { sourceField: 'wo_priority', targetField: 'priority', required: false, defaultValue: 'MEDIUM', transform: (value) => { const map: Record = { '1': 'HIGH', 'high': 'HIGH', 'urgent': 'HIGH', '2': 'MEDIUM', 'medium': 'MEDIUM', 'normal': 'MEDIUM', '3': 'LOW', 'low': 'LOW', 'minor': 'LOW', }; return map[String(value).toLowerCase()] || 'MEDIUM'; }, }, { sourceField: 'wo_status', targetField: 'status', required: false, defaultValue: 'OPEN', transform: (value) => { const map: Record = { 'new': 'OPEN', 'open': 'OPEN', 'pending': 'OPEN', 'in_progress': 'IN_PROGRESS', 'active': 'IN_PROGRESS', 'working': 'IN_PROGRESS', 'hold': 'ON_HOLD', 'waiting': 'ON_HOLD', 'paused': 'ON_HOLD', 'complete': 'DONE', 'completed': 'DONE', 'closed': 'DONE', 'done': 'DONE', }; return map[String(value).toLowerCase()] || 'OPEN'; }, }, { sourceField: 'created_date', targetField: 'createdAt', required: false }, { sourceField: 'due_date', targetField: 'dueDate', required: false }, { sourceField: 'equipment_id', targetField: 'assetId', required: false }, { sourceField: 'location_id', targetField: 'locationId', required: false }, ], assets: [ { sourceField: 'asset_id', targetField: 'externalId', required: false }, { sourceField: 'asset_name', targetField: 'name', required: true }, { sourceField: 'serial_number', targetField: 'serialNumber', required: false }, { sourceField: 'model_number', targetField: 'model', required: false }, { sourceField: 'manufacturer', targetField: 'manufacturer', required: false }, { sourceField: 'asset_status', targetField: 'status', required: false, defaultValue: 'OPERATIONAL', transform: (value) => { const map: Record = { 'active': 'OPERATIONAL', 'operational': 'OPERATIONAL', 'running': 'OPERATIONAL', 'down': 'NON_OPERATIONAL', 'broken': 'NON_OPERATIONAL', 'offline': 'NON_OPERATIONAL', 'retired': 'DECOMMISSIONED', 'decommissioned': 'DECOMMISSIONED', }; return map[String(value).toLowerCase()] || 'OPERATIONAL'; }, }, ], locations: [ { sourceField: 'location_id', targetField: 'externalId', required: false }, { sourceField: 'location_name', targetField: 'name', required: true }, { sourceField: 'address', targetField: 'address', required: false }, { sourceField: 'parent_location_id', targetField: 'parentId', required: false }, ], users: [ { sourceField: 'user_id', targetField: 'externalId', required: false }, { sourceField: 'first_name', targetField: 'firstName', required: true }, { sourceField: 'last_name', targetField: 'lastName', required: true }, { sourceField: 'email', targetField: 'email', required: true }, ], }; // Apply mapping to transform data function applyMapping( sourceRecords: any[], mappings: FieldMapping[] ): T[] { return sourceRecords.map(source => { const target: any = {}; mappings.forEach(mapping => { let value = source[mapping.sourceField]; // Apply transform if defined if (mapping.transform && value !== undefined && value !== null) { value = mapping.transform(value); } // Apply default if value is missing if ((value === undefined || value === null) && mapping.defaultValue !== undefined) { value = mapping.defaultValue; } // Skip if no value and not required if (value === undefined || value === null) { if (mapping.required) { throw new Error(`Required field missing: ${mapping.sourceField}`); } return; } target[mapping.targetField] = value; }); return target as T; }); } ``` ### Step 3: Migration Execution ```typescript // src/migration/migrator.ts interface MigrationPlan { phases: MigrationPhase[]; rollbackPlan: RollbackStep[]; validationRules: ValidationRule[]; } interface MigrationPhase { name: string; order: number; resource: 'locations' | 'assets' | 'users' | 'workOrders'; batchSize: number; dependsOn?: string[]; } interface MigrationResult { phase: string; status: 'success' | 'partial' | 'failed'; processed: number; succeeded: number; failed: number; errors: MigrationError[]; duration: number; } class MaintainXMigrator { private client: MaintainXClient; private idMapping: Map = new Map(); // sourceId -> maintainxId async executeMigration( sourceData: any, plan: MigrationPlan ): Promise { const results: MigrationResult[] = []; // Sort phases by order and dependencies const sortedPhases = this.sortPhases(plan.phases); for (const phase of sortedPhases) { console.log(`\n=== Executing Phase: ${phase.name} ===\n`); const result = await this.executePhase( phase, sourceData[phase.resource], plan.validationRules ); results.push(result); // Stop if critical phase failed if (result.status === 'failed') { console.error(`Phase ${phase.name} failed. Migration halted.`); break; } } return results; } private async executePhase( phase: MigrationPhase, sourceRecords: any[], validationRules: ValidationRule[] ): Promise { const startTime = Date.now(); const result: MigrationResult = { phase: phase.name, status: 'success', processed: 0, succeeded: 0, failed: 0, errors: [], duration: 0, }; // Transform data const mappings = legacyCmmsToMaintainX[phase.resource]; let transformedRecords: any[]; try { transformedRecords = applyMapping(sourceRecords, mappings); } catch (error: any) { result.status = 'failed'; result.errors.push({ record: null, error: error.message }); result.duration = Date.now() - startTime; return result; } // Process in batches for (let i = 0; i < transformedRecords.length; i += phase.batchSize) { const batch = transformedRecords.slice(i, i + phase.batchSize); for (const record of batch) { result.processed++; try { // Resolve ID references if (record.assetId && this.idMapping.has(record.assetId)) { record.assetId = this.idMapping.get(record.assetId); } if (record.locationId && this.idMapping.has(record.locationId)) { record.locationId = this.idMapping.get(record.locationId); } if (record.parentId && this.idMapping.has(record.parentId)) { record.parentId = this.idMapping.get(record.parentId); } // Create in MaintainX const created = await this.createRecord(phase.resource, record); // Store ID mapping for references if (record.externalId) { this.idMapping.set(record.externalId, created.id); } result.succeeded++; } catch (error: any) { result.failed++; result.errors.push({ record, error: error.message, }); } } console.log(`Progress: ${result.processed}/${transformedRecords.length}`); // Rate limit protection await new Promise(r => setTimeout(r, 100)); } result.status = result.failed === 0 ? 'success' : result.succeeded > 0 ? 'partial' : 'failed'; result.duration = Date.now() - startTime; return result; } private async createRecord(resource: string, data: any): Promise { switch (resource) { case 'workOrders': return this.client.createWorkOrder(data); // Add other resources as API supports default: throw new Error(`Unsupported resource: ${resource}`); } } } ``` ### Step 4: Validation & Verification ```typescript // src/migration/validation.ts interface ValidationReport { phase: string; totalRecords: number; passed: number; failed: number; failures: ValidationFailure[]; } interface ValidationFailure { sourceId: string; targetId?: string; issues: string[]; } class MigrationValidator { async validateMigration( sourceData: any[], targetData: any[] ): Promise { const report: ValidationReport = { phase: 'post-migration', totalRecords: sourceData.length, passed: 0, failed: 0, failures: [], }; // Create lookup maps const sourceMap = new Map(sourceData.map(s => [s.externalId || s.id, s])); const targetMap = new Map(targetData.map(t => [t.externalId, t])); // Validate each source record exists in target for (const [sourceId, source] of sourceMap) { const target = targetMap.get(sourceId); const issues: string[] = []; if (!target) { issues.push('Record not found in target system'); } else { // Validate critical fields match if (source.title !== target.title) { issues.push(`Title mismatch: "${source.title}" vs "${target.title}"`); } // Add more field validations as needed } if (issues.length === 0) { report.passed++; } else { report.failed++; report.failures.push({ sourceId, targetId: target?.id, issues, }); } } return report; } async generateReconciliationReport( sourceData: any[], maintainxClient: MaintainXClient ): Promise { console.log('=== Migration Reconciliation Report ===\n'); // Get counts from MaintainX const woResponse = await maintainxClient.getWorkOrders({ limit: 1 }); const assetResponse = await maintainxClient.getAssets({ limit: 1 }); const locationResponse = await maintainxClient.getLocations({ limit: 1 }); console.log('Record Counts:'); console.log(` Source work orders: ${sourceData.workOrders?.length || 0}`); console.log(` MaintainX work orders: ${woResponse.workOrders.length}+`); console.log(''); console.log(` Source assets: ${sourceData.assets?.length || 0}`); console.log(` MaintainX assets: ${assetResponse.assets.length}+`); console.log(''); console.log(` Source locations: ${sourceData.locations?.length || 0}`); console.log(` MaintainX locations: ${locationResponse.locations.length}+`); } } ``` ### Step 5: Rollback Procedures ```typescript // src/migration/rollback.ts interface RollbackPlan { steps: RollbackStep[]; checkpoint: Date; } interface RollbackStep { order: number; description: string; execute: () => Promise; } class MigrationRollback { private client: MaintainXClient; private migratedIds: Map = new Map(); // resource -> ids // Track migrated records for potential rollback trackMigrated(resource: string, id: string): void { if (!this.migratedIds.has(resource)) { this.migratedIds.set(resource, []); } this.migratedIds.get(resource)!.push(id); } // Generate rollback plan generateRollbackPlan(): RollbackPlan { const plan: RollbackPlan = { steps: [], checkpoint: new Date(), }; // Note: Actual rollback depends on available API endpoints // MaintainX may not support programmatic deletion plan.steps.push({ order: 1, description: 'Document migrated record IDs', execute: async () => { const report = { timestamp: new Date().toISOString(), records: Object.fromEntries(this.migratedIds), }; await fs.writeFile( `rollback-data-${Date.now()}.json`, JSON.stringify(report, null, 2) ); console.log('Rollback data saved'); }, }); plan.steps.push({ order: 2, description: 'Contact MaintainX support for bulk deletion', execute: async () => { console.log('Manual step required:'); console.log('1. Contact MaintainX support'); console.log('2. Provide list of migrated record IDs'); console.log('3. Request bulk deletion or archival'); }, }); plan.steps.push({ order: 3, description: 'Verify rollback completion', execute: async () => { // Verify records are removed console.log('Verify records have been removed from MaintainX'); }, }); return plan; } async executeRollback(plan: RollbackPlan): Promise { console.log('=== Executing Rollback ===\n'); for (const step of plan.steps.sort((a, b) => a.order - b.order)) { console.log(`Step ${step.order}: ${step.description}`); await step.execute(); console.log('Done\n'); } console.log('Rollback complete'); } } ``` ## Migration Checklist - [ ] Source system assessed - [ ] Data mapping defined - [ ] Test migration completed (subset) - [ ] Full migration executed - [ ] Validation passed - [ ] User acceptance testing - [ ] Cutover completed - [ ] Source system decommissioned ## Output - Assessment report - Data mapping configuration - Migration results - Validation report - Rollback plan ## Timeline Template | Week | Phase | Activities | |------|-------|------------| | 1-2 | Assessment | Data inventory, quality analysis | | 3-4 | Preparation | Mapping, test migration | | 5-6 | Migration | Parallel run, data migration | | 7-8 | Validation | Testing, cutover, decommission | ## Resources - [MaintainX API Documentation](https://maintainx.dev/) - [MaintainX Import Guide](https://help.getmaintainx.com/) - [Data Migration Best Practices](https://martinfowler.com/articles/data-migration-patterns.html) ## Next Steps Congratulations! You have completed the MaintainX skill pack. For additional support, contact MaintainX at support@getmaintainx.com.

Skill file: plugins/saas-packs/maintainx-pack/skills/maintainx-migration-deep-dive/SKILL.md