@assetlab/mcp-server 2.2.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4696,6 +4696,137 @@ export function registerWriteTools(server, client) {
4696
4696
  }
4697
4697
  });
4698
4698
  // ============================================================
4699
+ // Infrastructure Lifecycle Events (scope: infrastructure_lifecycle_events)
4700
+ // ============================================================
4701
+ const lifecycleEventScope = {
4702
+ feature_class: z
4703
+ .string()
4704
+ .regex(/^[a-z][a-z0-9_]{0,49}$/)
4705
+ .optional()
4706
+ .describe('Feature class code the strategy scope applies to (omit for a material-wide scope; at least one of feature_class/material is required)'),
4707
+ material: z
4708
+ .string()
4709
+ .max(200)
4710
+ .optional()
4711
+ .describe('Material the scope applies to, exactly as features carry it (e.g. "PVC")'),
4712
+ diameter_min_mm: z
4713
+ .number()
4714
+ .positive()
4715
+ .optional()
4716
+ .describe('Lower bound of a diameter band (requires material); bands ladder like rates'),
4717
+ };
4718
+ const lifecycleEventFields = {
4719
+ event_class: z
4720
+ .enum(['preventive', 'rehabilitation'])
4721
+ .describe('Event type — preventative maintenance or rehabilitation'),
4722
+ trigger_condition_max: z
4723
+ .number()
4724
+ .int()
4725
+ .min(1)
4726
+ .max(99)
4727
+ .describe('Upper bound of the trigger window — the event fires when projected condition falls to this'),
4728
+ trigger_condition_min: z
4729
+ .number()
4730
+ .int()
4731
+ .min(0)
4732
+ .max(98)
4733
+ .optional()
4734
+ .describe('Lower bound of the trigger window (default 0); a feature already below it has missed the event'),
4735
+ impact_method: z
4736
+ .enum(['add_years', 'reset_condition'])
4737
+ .describe('Effect: add years of life, or reset condition to a value'),
4738
+ impact_add_years: z
4739
+ .number()
4740
+ .positive()
4741
+ .max(100)
4742
+ .optional()
4743
+ .describe('Years added (required when impact_method is add_years)'),
4744
+ impact_reset_to: z
4745
+ .number()
4746
+ .int()
4747
+ .min(1)
4748
+ .max(100)
4749
+ .optional()
4750
+ .describe('Condition after the event (required when impact_method is reset_condition)'),
4751
+ cost_method: z
4752
+ .enum(['per_unit', 'fixed'])
4753
+ .optional()
4754
+ .describe("Costing: per unit (uses the feature's measured quantity and unit) or a fixed amount (default per_unit)"),
4755
+ unit_cost: z
4756
+ .number()
4757
+ .min(0)
4758
+ .optional()
4759
+ .describe('Cost per unit (current dollars, never indexed)'),
4760
+ fixed_cost: z.number().min(0).optional().describe('Fixed cost (current dollars)'),
4761
+ cost_source: z
4762
+ .string()
4763
+ .max(200)
4764
+ .optional()
4765
+ .describe('Provenance of the cost ("Engineering 2026", a tender reference)'),
4766
+ max_applications: z
4767
+ .number()
4768
+ .int()
4769
+ .min(1)
4770
+ .max(10)
4771
+ .optional()
4772
+ .describe("How many times the event may fire over a feature's life (default 1)"),
4773
+ min_years_between: z
4774
+ .number()
4775
+ .min(1)
4776
+ .max(100)
4777
+ .optional()
4778
+ .describe('Minimum years between firings of a recurring event (default 1)'),
4779
+ sort_order: z.number().int().min(0).optional().describe('Evaluation order within the strategy'),
4780
+ };
4781
+ server.tool('create_infrastructure_lifecycle_event', 'Create a lifecycle strategy event. Events attach to a SCOPE (feature_class code, material, optional diameter band) — never to individual features; every feature resolves the most specific matching scope, like replacement rates. Replacement is NOT an event (it is priced by the rates and scheduled by the renewal forecast) — model the interventions BEFORE replacement: crack sealing, relining, resurfacing. List existing events first to reuse a scope. Requires infrastructure_lifecycle_events:write scope.', {
4782
+ name: z.string().max(200).describe('Event name (required, e.g. "Crack Sealing")'),
4783
+ ...lifecycleEventScope,
4784
+ ...lifecycleEventFields,
4785
+ }, async (params) => {
4786
+ try {
4787
+ const result = await client.create('infrastructure-lifecycle-events', buildBody(params));
4788
+ return formatResult(result);
4789
+ }
4790
+ catch (err) {
4791
+ return formatError(err);
4792
+ }
4793
+ });
4794
+ server.tool('update_infrastructure_lifecycle_event', 'Update a lifecycle strategy event by ID. Editing re-confirms the cost (cost_reviewed_on is server-set). Set is_active false to disable an event without deleting it — projections recompute immediately. Requires infrastructure_lifecycle_events:write scope.', {
4795
+ id: z.string().uuid().describe('Lifecycle event ID'),
4796
+ name: z.string().max(200).optional().describe('Event name'),
4797
+ is_active: z.boolean().optional().describe('Disable/enable the event'),
4798
+ event_class: lifecycleEventFields.event_class.optional(),
4799
+ trigger_condition_max: lifecycleEventFields.trigger_condition_max.optional(),
4800
+ trigger_condition_min: lifecycleEventFields.trigger_condition_min,
4801
+ impact_method: lifecycleEventFields.impact_method.optional(),
4802
+ impact_add_years: lifecycleEventFields.impact_add_years,
4803
+ impact_reset_to: lifecycleEventFields.impact_reset_to,
4804
+ cost_method: lifecycleEventFields.cost_method,
4805
+ unit_cost: lifecycleEventFields.unit_cost,
4806
+ fixed_cost: lifecycleEventFields.fixed_cost,
4807
+ cost_source: lifecycleEventFields.cost_source,
4808
+ max_applications: lifecycleEventFields.max_applications,
4809
+ min_years_between: lifecycleEventFields.min_years_between,
4810
+ sort_order: lifecycleEventFields.sort_order,
4811
+ }, async ({ id, ...rest }) => {
4812
+ try {
4813
+ const result = await client.update('infrastructure-lifecycle-events', id, buildBody(rest));
4814
+ return formatResult(result);
4815
+ }
4816
+ catch (err) {
4817
+ return formatError(err);
4818
+ }
4819
+ });
4820
+ server.tool('delete_infrastructure_lifecycle_event', 'Delete a lifecycle strategy event by ID. Projections recompute immediately; consider update with is_active=false to disable instead. Requires infrastructure_lifecycle_events:write scope.', { id: z.string().uuid().describe('Lifecycle event ID') }, async ({ id }) => {
4821
+ try {
4822
+ const result = await client.remove('infrastructure-lifecycle-events', id);
4823
+ return formatResult(result);
4824
+ }
4825
+ catch (err) {
4826
+ return formatError(err);
4827
+ }
4828
+ });
4829
+ // ============================================================
4699
4830
  // Infrastructure Networks (scope: infrastructure_networks)
4700
4831
  // ============================================================
4701
4832
  server.tool('create_infrastructure_network', 'Create an infrastructure network — a named grouping of features bound to one feature class. Requires infrastructure_networks:write scope.', {
@@ -4952,6 +5083,122 @@ export function registerWriteTools(server, client) {
4952
5083
  }
4953
5084
  });
4954
5085
  // ============================================================
5086
+ // Asset Lifecycle Events (scope: asset_lifecycle_events)
5087
+ // ============================================================
5088
+ const assetLifecycleEventFields = {
5089
+ event_class: z
5090
+ .enum(['preventive', 'rehabilitation'])
5091
+ .describe('Event type — preventative maintenance or rehabilitation'),
5092
+ trigger_condition_max: z
5093
+ .number()
5094
+ .int()
5095
+ .min(1)
5096
+ .max(99)
5097
+ .describe('Upper bound of the trigger window — the event fires when projected condition falls to this'),
5098
+ trigger_condition_min: z
5099
+ .number()
5100
+ .int()
5101
+ .min(0)
5102
+ .max(98)
5103
+ .optional()
5104
+ .describe('Lower bound of the trigger window (default 0); an asset already below it has missed the event'),
5105
+ impact_method: z
5106
+ .enum(['add_years', 'reset_condition'])
5107
+ .describe('Effect: add years of life, or reset condition to a value'),
5108
+ impact_add_years: z
5109
+ .number()
5110
+ .positive()
5111
+ .max(100)
5112
+ .optional()
5113
+ .describe('Years added (required when impact_method is add_years)'),
5114
+ impact_reset_to: z
5115
+ .number()
5116
+ .int()
5117
+ .min(1)
5118
+ .max(100)
5119
+ .optional()
5120
+ .describe('Condition after the event (required when impact_method is reset_condition)'),
5121
+ fixed_cost: z
5122
+ .number()
5123
+ .min(0)
5124
+ .optional()
5125
+ .describe('Fixed cost per application (current dollars, never indexed)'),
5126
+ cost_source: z
5127
+ .string()
5128
+ .max(200)
5129
+ .optional()
5130
+ .describe('Provenance of the cost ("Engineering 2026", a tender reference)'),
5131
+ max_applications: z
5132
+ .number()
5133
+ .int()
5134
+ .min(1)
5135
+ .max(10)
5136
+ .optional()
5137
+ .describe("How many times the event may fire over an asset's life (default 1)"),
5138
+ min_years_between: z
5139
+ .number()
5140
+ .min(1)
5141
+ .max(100)
5142
+ .optional()
5143
+ .describe('Minimum years between firings of a recurring event (default 1)'),
5144
+ sort_order: z.number().int().min(0).optional().describe('Evaluation order within the strategy'),
5145
+ };
5146
+ server.tool('create_asset_lifecycle_event', "Create a facility lifecycle strategy event. Events attach to an asset-type SCOPE — exactly ONE of asset_type_id or asset_type_group_id — never to individual assets; an asset resolves its type's own strategy first, else its type group's. Replacement is NOT an event (it stays the renewal forecast + replacement value) — model the interventions BEFORE replacement: roof recoats, boiler retubes, overhauls. Resolve type ids with list_asset_types / list_asset_type_groups, and list existing events first to reuse a scope. Requires asset_lifecycle_events:write scope.", {
5147
+ name: z.string().max(200).describe('Event name (required, e.g. "Roof recoat")'),
5148
+ asset_type_id: z
5149
+ .string()
5150
+ .uuid()
5151
+ .optional()
5152
+ .describe('Asset type the strategy scope applies to (exactly one of the two scope ids)'),
5153
+ asset_type_group_id: z
5154
+ .string()
5155
+ .uuid()
5156
+ .optional()
5157
+ .describe('Asset type group the scope applies to (exactly one of the two scope ids)'),
5158
+ ...assetLifecycleEventFields,
5159
+ }, async (params) => {
5160
+ try {
5161
+ const result = await client.create('asset-lifecycle-events', buildBody(params));
5162
+ return formatResult(result);
5163
+ }
5164
+ catch (err) {
5165
+ return formatError(err);
5166
+ }
5167
+ });
5168
+ server.tool('update_asset_lifecycle_event', 'Update a facility lifecycle strategy event by ID. Editing re-confirms the cost (cost_reviewed_on is server-set). Set is_active false to disable an event without deleting it — projections recompute immediately. Requires asset_lifecycle_events:write scope.', {
5169
+ id: z.string().uuid().describe('Lifecycle event ID'),
5170
+ name: z.string().max(200).optional().describe('Event name'),
5171
+ is_active: z.boolean().optional().describe('Disable/enable the event'),
5172
+ event_class: assetLifecycleEventFields.event_class.optional(),
5173
+ trigger_condition_max: assetLifecycleEventFields.trigger_condition_max.optional(),
5174
+ trigger_condition_min: assetLifecycleEventFields.trigger_condition_min,
5175
+ impact_method: assetLifecycleEventFields.impact_method.optional(),
5176
+ impact_add_years: assetLifecycleEventFields.impact_add_years,
5177
+ impact_reset_to: assetLifecycleEventFields.impact_reset_to,
5178
+ fixed_cost: assetLifecycleEventFields.fixed_cost,
5179
+ cost_source: assetLifecycleEventFields.cost_source,
5180
+ max_applications: assetLifecycleEventFields.max_applications,
5181
+ min_years_between: assetLifecycleEventFields.min_years_between,
5182
+ sort_order: assetLifecycleEventFields.sort_order,
5183
+ }, async ({ id, ...rest }) => {
5184
+ try {
5185
+ const result = await client.update('asset-lifecycle-events', id, buildBody(rest));
5186
+ return formatResult(result);
5187
+ }
5188
+ catch (err) {
5189
+ return formatError(err);
5190
+ }
5191
+ });
5192
+ server.tool('delete_asset_lifecycle_event', 'Delete a facility lifecycle strategy event by ID. Projections recompute immediately; consider update with is_active=false to disable instead. Requires asset_lifecycle_events:write scope.', { id: z.string().uuid().describe('Lifecycle event ID') }, async ({ id }) => {
5193
+ try {
5194
+ const result = await client.remove('asset-lifecycle-events', id);
5195
+ return formatResult(result);
5196
+ }
5197
+ catch (err) {
5198
+ return formatError(err);
5199
+ }
5200
+ });
5201
+ // ============================================================
4955
5202
  // Asset Condition Assessments (scope: asset_condition_assessments)
4956
5203
  // ============================================================
4957
5204
  server.tool('create_asset_condition_assessment', 'Record a point-in-time condition assessment against an asset. Requires asset_condition_assessments:write scope. Call list_assets first to resolve asset_id.', {