maintainx-core-workflow-b
Execute MaintainX secondary workflow: Asset and Location management. Use when managing equipment assets, organizing locations/facilities, building asset hierarchies, and tracking equipment maintenance history. Trigger with phrases like "maintainx asset", "maintainx location", "equipment tracking", "asset management", "facility hierarchy". allowed-tools: Read, Write, Edit, Bash(npm:*), Bash(curl:*), Grep 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)
Installation
This skill is included in the maintainx-pack plugin:
/plugin install maintainx-pack@claude-code-plugins-plus
Click to copy
Instructions
# MaintainX Core Workflow B: Asset & Location Management
## Overview
Manage equipment assets and locations in MaintainX. Assets represent equipment that requires maintenance; locations organize your facilities.
## Prerequisites
- Completed `maintainx-install-auth` setup
- Understanding of asset hierarchy concepts
- MaintainX account with asset management permissions
## Asset Hierarchy Model
```
Organization
โโโ Location: Main Plant
โ โโโ Sub-Location: Building A
โ โ โโโ Asset: HVAC Unit A1
โ โ โ โโโ Sub-Asset: Compressor
โ โ โโโ Asset: Conveyor Line 1
โ โโโ Sub-Location: Building B
โ โโโ Asset: Boiler System
โโโ Location: Warehouse
โโโ Asset: Forklift Fleet
โโโ Sub-Asset: Forklift #1
โโโ Sub-Asset: Forklift #2
```
## Instructions
### Step 1: Query Locations
```typescript
// src/workflows/asset-location.ts
import { MaintainXClient } from '../api/maintainx-client';
interface Location {
id: string;
name: string;
address?: string;
parentId?: string;
children?: Location[];
}
// Get all locations
async function getAllLocations(client: MaintainXClient): Promise {
const allLocations: Location[] = [];
let cursor: string | undefined;
do {
const response = await client.getLocations({ cursor, limit: 100 });
allLocations.push(...response.locations);
cursor = response.nextCursor || undefined;
} while (cursor);
return allLocations;
}
// Build location hierarchy tree
function buildLocationTree(locations: Location[]): Location[] {
const locationMap = new Map();
const rootLocations: Location[] = [];
// First pass: create map
locations.forEach(loc => {
locationMap.set(loc.id, { ...loc, children: [] });
});
// Second pass: build tree
locations.forEach(loc => {
const node = locationMap.get(loc.id)!;
if (loc.parentId && locationMap.has(loc.parentId)) {
const parent = locationMap.get(loc.parentId)!;
parent.children!.push(node);
} else {
rootLocations.push(node);
}
});
return rootLocations;
}
// Print location tree
function printLocationTree(locations: Location[], indent = 0) {
const prefix = ' '.repeat(indent);
locations.forEach(loc => {
console.log(`${prefix}โโโ ${loc.name} (${loc.id})`);
if (loc.children && loc.children.length > 0) {
printLocationTree(loc.children, indent + 1);
}
});
}
// Usage
async function displayLocationHierarchy(client: MaintainXClient) {
console.log('=== Location Hierarchy ===\n');
const locations = await getAllLocations(client);
const tree = buildLocationTree(locations);
printLocationTree(tree);
}
```
### Step 2: Query Assets
```typescript
interface Asset {
id: string;
name: string;
serialNumber?: string;
model?: string;
manufacturer?: string;
status: 'OPERATIONAL' | 'NON_OPERATIONAL' | 'DECOMMISSIONED';
locationId?: string;
location?: Location;
parentAssetId?: string;
customFields?: Record;
createdAt: string;
updatedAt: string;
}
// Get all assets
async function getAllAssets(client: MaintainXClient): Promise {
const allAssets: Asset[] = [];
let cursor: string | undefined;
do {
const response = await client.getAssets({ cursor, limit: 100 });
allAssets.push(...response.assets);
cursor = response.nextCursor || undefined;
} while (cursor);
return allAssets;
}
// Get assets by location
async function getAssetsByLocation(
client: MaintainXClient,
locationId: string
): Promise {
const response = await client.getAssets({ locationId, limit: 100 });
return response.assets;
}
// Get asset details
async function getAssetDetails(
client: MaintainXClient,
assetId: string
): Promise {
return client.getAsset(assetId);
}
```
### Step 3: Asset Analysis
```typescript
interface AssetAnalysis {
totalAssets: number;
byStatus: Record;
byLocation: Record;
byManufacturer: Record;
noLocation: Asset[];
}
async function analyzeAssets(client: MaintainXClient): Promise {
const assets = await getAllAssets(client);
const analysis: AssetAnalysis = {
totalAssets: assets.length,
byStatus: {},
byLocation: {},
byManufacturer: {},
noLocation: [],
};
assets.forEach(asset => {
// Count by status
const status = asset.status || 'UNKNOWN';
analysis.byStatus[status] = (analysis.byStatus[status] || 0) + 1;
// Count by location
if (asset.location?.name) {
const loc = asset.location.name;
analysis.byLocation[loc] = (analysis.byLocation[loc] || 0) + 1;
} else {
analysis.noLocation.push(asset);
}
// Count by manufacturer
if (asset.manufacturer) {
analysis.byManufacturer[asset.manufacturer] =
(analysis.byManufacturer[asset.manufacturer] || 0) + 1;
}
});
return analysis;
}
// Print analysis report
function printAssetReport(analysis: AssetAnalysis) {
console.log('=== Asset Analysis Report ===\n');
console.log(`Total Assets: ${analysis.totalAssets}\n`);
console.log('By Status:');
Object.entries(analysis.byStatus).forEach(([status, count]) => {
console.log(` ${status}: ${count}`);
});
console.log('\nBy Location:');
Object.entries(analysis.byLocation)
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.forEach(([loc, count]) => {
console.log(` ${loc}: ${count}`);
});
console.log('\nBy Manufacturer:');
Object.entries(analysis.byManufacturer)
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.forEach(([mfr, count]) => {
console.log(` ${mfr}: ${count}`);
});
if (analysis.noLocation.length > 0) {
console.log(`\nAssets without location: ${analysis.noLocation.length}`);
}
}
```
### Step 4: Asset Work Order History
```typescript
// Get maintenance history for an asset
async function getAssetMaintenanceHistory(
client: MaintainXClient,
assetId: string
) {
const workOrders = await client.getWorkOrders({
assetId,
limit: 100,
});
// Analyze work order history
const history = {
total: workOrders.workOrders.length,
completed: workOrders.workOrders.filter(wo => wo.status === 'DONE').length,
open: workOrders.workOrders.filter(wo => wo.status === 'OPEN').length,
inProgress: workOrders.workOrders.filter(wo => wo.status === 'IN_PROGRESS').length,
recentWorkOrders: workOrders.workOrders.slice(0, 5),
};
return history;
}
// Generate asset report card
async function generateAssetReportCard(
client: MaintainXClient,
assetId: string
) {
const asset = await getAssetDetails(client, assetId);
const history = await getAssetMaintenanceHistory(client, assetId);
console.log('=== Asset Report Card ===\n');
console.log(`Name: ${asset.name}`);
console.log(`ID: ${asset.id}`);
console.log(`Status: ${asset.status}`);
console.log(`Serial Number: ${asset.serialNumber || 'N/A'}`);
console.log(`Manufacturer: ${asset.manufacturer || 'N/A'}`);
console.log(`Model: ${asset.model || 'N/A'}`);
console.log(`Location: ${asset.location?.name || 'Not assigned'}`);
console.log(`\nMaintenance History:`);
console.log(` Total Work Orders: ${history.total}`);
console.log(` Completed: ${history.completed}`);
console.log(` Open: ${history.open}`);
console.log(` In Progress: ${history.inProgress}`);
if (history.recentWorkOrders.length > 0) {
console.log('\nRecent Work Orders:');
history.recentWorkOrders.forEach(wo => {
console.log(` - ${wo.title} (${wo.status})`);
});
}
return { asset, history };
}
```
### Step 5: Location-Based Asset View
```typescript
// Get assets organized by location
async function getAssetsGroupedByLocation(client: MaintainXClient) {
const locations = await getAllLocations(client);
const assets = await getAllAssets(client);
const assetsByLocation: Map = new Map();
// Group assets by location
assets.forEach(asset => {
const locId = asset.locationId || 'UNASSIGNED';
if (!assetsByLocation.has(locId)) {
assetsByLocation.set(locId, []);
}
assetsByLocation.get(locId)!.push(asset);
});
// Create location map for names
const locationMap = new Map(
locations.map(loc => [loc.id, loc.name])
);
// Print organized view
console.log('=== Assets by Location ===\n');
assetsByLocation.forEach((locAssets, locId) => {
const locName = locationMap.get(locId) || locId;
console.log(`\n${locName} (${locAssets.length} assets):`);
locAssets.forEach(asset => {
const status = asset.status === 'OPERATIONAL' ? '[OK]' : '[!]';
console.log(` ${status} ${asset.name}`);
});
});
return assetsByLocation;
}
```
### Step 6: Preventive Maintenance Planning
```typescript
interface PMSchedule {
assetId: string;
assetName: string;
lastMaintenanceDate?: Date;
nextDueDate: Date;
frequency: string; // e.g., "30 days", "quarterly"
tasks: string[];
}
// Plan preventive maintenance based on asset types
function generatePMSchedule(assets: Asset[]): PMSchedule[] {
const schedules: PMSchedule[] = [];
assets.forEach(asset => {
// Skip non-operational assets
if (asset.status !== 'OPERATIONAL') return;
// Example PM schedules based on asset type (infer from name)
const name = asset.name.toLowerCase();
if (name.includes('hvac') || name.includes('air')) {
schedules.push({
assetId: asset.id,
assetName: asset.name,
nextDueDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
frequency: '30 days',
tasks: [
'Replace air filters',
'Check refrigerant levels',
'Inspect belts and pulleys',
'Clean coils',
'Test thermostat',
],
});
} else if (name.includes('pump')) {
schedules.push({
assetId: asset.id,
assetName: asset.name,
nextDueDate: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000),
frequency: '90 days',
tasks: [
'Check seal condition',
'Inspect impeller',
'Verify flow rates',
'Lubricate bearings',
],
});
} else if (name.includes('conveyor')) {
schedules.push({
assetId: asset.id,
assetName: asset.name,
nextDueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
frequency: '7 days',
tasks: [
'Inspect belt tension',
'Check motor operation',
'Lubricate rollers',
'Clean sensors',
],
});
}
});
return schedules;
}
// Create work orders from PM schedule
async function createPMWorkOrders(
client: MaintainXClient,
schedules: PMSchedule[]
) {
const created = [];
for (const schedule of schedules) {
const workOrder = await client.createWorkOrder({
title: `Scheduled PM - ${schedule.assetName}`,
description: `
## Preventive Maintenance
Frequency: ${schedule.frequency}
## Tasks
${schedule.tasks.map(t => `- [ ] ${t}`).join('\n')}
`,
priority: 'MEDIUM',
assetId: schedule.assetId,
dueDate: schedule.nextDueDate.toISOString(),
});
created.push(workOrder);
}
return created;
}
```
## Output
- Complete location hierarchy view
- Asset inventory analysis
- Asset maintenance history
- Location-based asset groupings
- Preventive maintenance schedules
## Error Handling
| Error | Cause | Solution |
|-------|-------|----------|
| 404 Not Found | Invalid asset/location ID | Verify ID exists |
| Empty Results | No data or wrong filter | Check query parameters |
| Pagination issues | Missing cursor handling | Use pagination helper |
| Permission denied | Insufficient access | Verify user permissions |
## Asset Status Reference
| Status | Description | Action |
|--------|-------------|--------|
| OPERATIONAL | Working normally | Schedule PM |
| NON_OPERATIONAL | Not functioning | Create repair WO |
| DECOMMISSIONED | Retired from use | Archive/remove |
## Resources
- [MaintainX Assets Guide](https://help.getmaintainx.com/)
- [MaintainX Locations](https://help.getmaintainx.com/)
- [API Documentation](https://maintainx.dev/)
## Next Steps
For troubleshooting common issues, see `maintainx-common-errors`.