@nexalink/coding-contracts 0.3.0 → 0.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.
package/dist/index.js CHANGED
@@ -41,6 +41,7 @@ export const RCM_MODEL_POLICY_ID = "carecompass-rcm-review-v1";
41
41
  export const RCM_HUMAN_REVIEW_POLICY_ID = "qualified-coder-review-v1";
42
42
  export const RCM_OUTPUT_SCHEMA_NAME = "carecompass_rcm_grounded_review";
43
43
  export const RCM_HUMAN_REVIEW_INSTRUCTION = "Qualified human coder review is required before billing, coding, or claim submission.";
44
+ export const PIN_TIME_WORKFLOW_ID = "monthly-pin-time-aggregation";
44
45
  // Schema wire versions (source: schemas/*.schema.json).
45
46
  export const CODING_CASE_CONTEXT_SCHEMA_VERSION = "coding-case-context-v1";
46
47
  export const CODING_CASE_PAYER_SCHEMA_VERSION = "coding-case-payer-v1";
@@ -59,6 +60,7 @@ export const RCM_INPUT_SCHEMA_VERSION = "rcm-coding-input-v1";
59
60
  export const RCM_OUTPUT_SCHEMA_VERSION = "rcm-grounded-human-review-v2";
60
61
  export const RCM_MODEL_PROMPT_SCHEMA_VERSION = "rcm-model-prompt-v1";
61
62
  export const RCM_RETRIEVAL_SCHEMA_VERSION = "rcm-retrieval-metadata-v1";
63
+ export const RCM_REVIEWER_EVENT_SCHEMA_VERSION = "rcm-reviewer-event-v1";
62
64
  export const RCM_RUNTIME_EVENT_SCHEMA_VERSION = "rcm-runtime-event-v1";
63
65
  export const RCM_WORKFLOW_CATALOG_SCHEMA_VERSION = "rcm-workflow-catalog-v1";
64
66
  // --------------------------------------------------------------------
@@ -3166,7 +3168,7 @@ export const CodingRuleResultSchemaJson = {
3166
3168
  },
3167
3169
  };
3168
3170
  // --------------------------------------------------------------------
3169
- // CodingReviewerAction (schema 1.0.0, wire coding-reviewer-action-v1)
3171
+ // CodingReviewerAction (schema 1.1.0, wire coding-reviewer-action-v1)
3170
3172
  // --------------------------------------------------------------------
3171
3173
  export const codingReviewerActionSchema = z
3172
3174
  .object({
@@ -3179,6 +3181,15 @@ export const codingReviewerActionSchema = z
3179
3181
  "escalated",
3180
3182
  ]),
3181
3183
  resolutionCode: z.string().min(1).max(80).optional(),
3184
+ reviewOutcomeCategory: z
3185
+ .enum([
3186
+ "unsupported_or_excessive",
3187
+ "missed_supported",
3188
+ "unchanged_supported",
3189
+ "missing_evidence",
3190
+ "escalation",
3191
+ ])
3192
+ .optional(),
3182
3193
  notes: z
3183
3194
  .string()
3184
3195
  .min(1)
@@ -3202,6 +3213,7 @@ export function codingReviewerActionValidationError(value) {
3202
3213
  "reviewerRole",
3203
3214
  "action",
3204
3215
  "resolutionCode",
3216
+ "reviewOutcomeCategory",
3205
3217
  "notes",
3206
3218
  "occurredAt",
3207
3219
  ].includes(key)))
@@ -3229,6 +3241,18 @@ export function codingReviewerActionValidationError(value) {
3229
3241
  value_resolutionCode.length > 80)
3230
3242
  return "reviewer_action_resolution_code";
3231
3243
  }
3244
+ if (value["reviewOutcomeCategory"] !== undefined) {
3245
+ const value_reviewOutcomeCategory = value["reviewOutcomeCategory"];
3246
+ if (typeof value_reviewOutcomeCategory !== "string" ||
3247
+ ![
3248
+ "unsupported_or_excessive",
3249
+ "missed_supported",
3250
+ "unchanged_supported",
3251
+ "missing_evidence",
3252
+ "escalation",
3253
+ ].includes(value_reviewOutcomeCategory))
3254
+ return "reviewer_action_outcome_category";
3255
+ }
3232
3256
  if (value["notes"] !== undefined) {
3233
3257
  const value_notes = value["notes"];
3234
3258
  if (typeof value_notes !== "string" ||
@@ -3250,8 +3274,8 @@ export const CodingReviewerActionSchemaJson = {
3250
3274
  $schema: "https://json-schema.org/draft/2020-12/schema",
3251
3275
  $id: "https://nexalink.care/contracts/coding-reviewer-action.schema.json",
3252
3276
  title: "CodingReviewerAction",
3253
- description: "Attributable human-review disposition appended to an otherwise immutable decision. A decision plus its reviewer actions remains replayable: original and reviewer versions are both preserved.",
3254
- version: "1.0.0",
3277
+ description: "Attributable human-review disposition appended to an otherwise immutable decision. A decision plus its reviewer actions remains replayable: original and reviewer versions are both preserved. Review outcome categories are reviewer-adjudicated observations and never inferred from payment, code, or reimbursement changes.",
3278
+ version: "1.1.0",
3255
3279
  type: "object",
3256
3280
  additionalProperties: false,
3257
3281
  required: ["reviewerId", "action", "occurredAt"],
@@ -3275,6 +3299,16 @@ export const CodingReviewerActionSchemaJson = {
3275
3299
  minLength: 1,
3276
3300
  maxLength: 80,
3277
3301
  },
3302
+ reviewOutcomeCategory: {
3303
+ type: "string",
3304
+ enum: [
3305
+ "unsupported_or_excessive",
3306
+ "missed_supported",
3307
+ "unchanged_supported",
3308
+ "missing_evidence",
3309
+ "escalation",
3310
+ ],
3311
+ },
3278
3312
  notes: {
3279
3313
  type: "string",
3280
3314
  minLength: 1,
@@ -3466,7 +3500,7 @@ export const codingDecisionSchema = z
3466
3500
  });
3467
3501
  }
3468
3502
  });
3469
- export function codingDecisionValidationError(value, options) {
3503
+ export function codingDecisionValidationError(value) {
3470
3504
  if (!isRecord(value))
3471
3505
  return "coding_decision";
3472
3506
  if (![
@@ -5203,6 +5237,341 @@ export const RcmRetrievalMetadataSchemaJson = {
5203
5237
  },
5204
5238
  };
5205
5239
  // --------------------------------------------------------------------
5240
+ // RcmReviewerEvent (schema 1.0.0, wire rcm-reviewer-event-v1)
5241
+ // --------------------------------------------------------------------
5242
+ export const rcmReviewerEventSchema = z
5243
+ .object({
5244
+ eventName: z.enum([
5245
+ "review_opened",
5246
+ "decision_shown",
5247
+ "evidence_opened",
5248
+ "evidence_copied",
5249
+ "review_active_time",
5250
+ ]),
5251
+ occurredAt: z
5252
+ .string()
5253
+ .regex(new RegExp("^\\d{4}-\\d{2}-\\d{2}T(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|[+-](?:[01]\\d|2[0-3]):[0-5]\\d)$"))
5254
+ .refine(isRfc3339DateTime)
5255
+ .max(64),
5256
+ tenantRef: z
5257
+ .string()
5258
+ .regex(new RegExp("^[A-Za-z0-9_.:-]+$"))
5259
+ .min(1)
5260
+ .max(128),
5261
+ caseRef: z.string().regex(new RegExp("^[A-Za-z0-9_.:-]+$")).min(1).max(128),
5262
+ reviewId: z
5263
+ .string()
5264
+ .regex(new RegExp("^[A-Za-z0-9_.:-]+$"))
5265
+ .min(1)
5266
+ .max(128)
5267
+ .optional(),
5268
+ workflowId: z.enum([
5269
+ "rcm-coding-assistance",
5270
+ "monthly-pin-time-aggregation",
5271
+ ]),
5272
+ packId: z
5273
+ .string()
5274
+ .regex(new RegExp("^[A-Za-z0-9_.:-]+$"))
5275
+ .min(1)
5276
+ .max(80)
5277
+ .optional(),
5278
+ packVersion: z
5279
+ .string()
5280
+ .regex(new RegExp("^[A-Za-z0-9_.:-]+$"))
5281
+ .min(1)
5282
+ .max(80)
5283
+ .optional(),
5284
+ environment: z.enum(["development", "staging", "production"]).optional(),
5285
+ source: z.enum(["portal", "embed"]),
5286
+ evidenceId: z
5287
+ .string()
5288
+ .regex(new RegExp("^[A-Za-z0-9_.:-]{1,64}$"))
5289
+ .min(1)
5290
+ .max(64)
5291
+ .optional(),
5292
+ activeSeconds: z.number().int().min(0).max(86400).optional(),
5293
+ idlePaused: z.boolean().optional(),
5294
+ })
5295
+ .strict()
5296
+ .superRefine((value, ctx) => {
5297
+ const isEvidenceEvent = value["eventName"] === "evidence_opened" ||
5298
+ value["eventName"] === "evidence_copied";
5299
+ const isActiveTimeEvent = value["eventName"] === "review_active_time";
5300
+ if (isEvidenceEvent !== (value.evidenceId !== undefined) ||
5301
+ isActiveTimeEvent !== (value.activeSeconds !== undefined) ||
5302
+ (!isActiveTimeEvent && value.idlePaused !== undefined)) {
5303
+ ctx.addIssue({
5304
+ code: "custom",
5305
+ path: ["eventName"],
5306
+ message: "reviewer_event_shape",
5307
+ });
5308
+ }
5309
+ });
5310
+ export function rcmReviewerEventValidationError(value) {
5311
+ if (!isRecord(value))
5312
+ return "reviewer_event";
5313
+ if (![
5314
+ "eventName",
5315
+ "occurredAt",
5316
+ "tenantRef",
5317
+ "caseRef",
5318
+ "workflowId",
5319
+ "source",
5320
+ ].every((key) => key in value))
5321
+ return "reviewer_event_keys";
5322
+ if (!Object.keys(value).every((key) => [
5323
+ "eventName",
5324
+ "occurredAt",
5325
+ "tenantRef",
5326
+ "caseRef",
5327
+ "reviewId",
5328
+ "workflowId",
5329
+ "packId",
5330
+ "packVersion",
5331
+ "environment",
5332
+ "source",
5333
+ "evidenceId",
5334
+ "activeSeconds",
5335
+ "idlePaused",
5336
+ ].includes(key)))
5337
+ return "reviewer_event_keys";
5338
+ const value_eventName = value["eventName"];
5339
+ if (typeof value_eventName !== "string" ||
5340
+ ![
5341
+ "review_opened",
5342
+ "decision_shown",
5343
+ "evidence_opened",
5344
+ "evidence_copied",
5345
+ "review_active_time",
5346
+ ].includes(value_eventName))
5347
+ return "reviewer_event_name";
5348
+ const value_occurredAt = value["occurredAt"];
5349
+ if (typeof value_occurredAt !== "string" ||
5350
+ value_occurredAt.length > 64 ||
5351
+ !new RegExp("^\\d{4}-\\d{2}-\\d{2}T(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|[+-](?:[01]\\d|2[0-3]):[0-5]\\d)$").test(value_occurredAt) ||
5352
+ !isRfc3339DateTime(value_occurredAt))
5353
+ return "reviewer_event_occurred_at";
5354
+ const value_tenantRef = value["tenantRef"];
5355
+ if (typeof value_tenantRef !== "string" ||
5356
+ value_tenantRef.length < 1 ||
5357
+ value_tenantRef.length > 128 ||
5358
+ !new RegExp("^[A-Za-z0-9_.:-]+$").test(value_tenantRef))
5359
+ return "reviewer_event_tenant_ref";
5360
+ const value_caseRef = value["caseRef"];
5361
+ if (typeof value_caseRef !== "string" ||
5362
+ value_caseRef.length < 1 ||
5363
+ value_caseRef.length > 128 ||
5364
+ !new RegExp("^[A-Za-z0-9_.:-]+$").test(value_caseRef))
5365
+ return "reviewer_event_case_ref";
5366
+ if (value["reviewId"] !== undefined) {
5367
+ const value_reviewId = value["reviewId"];
5368
+ if (typeof value_reviewId !== "string" ||
5369
+ value_reviewId.length < 1 ||
5370
+ value_reviewId.length > 128 ||
5371
+ !new RegExp("^[A-Za-z0-9_.:-]+$").test(value_reviewId))
5372
+ return "reviewer_event_review_id";
5373
+ }
5374
+ const value_workflowId = value["workflowId"];
5375
+ if (typeof value_workflowId !== "string" ||
5376
+ !["rcm-coding-assistance", "monthly-pin-time-aggregation"].includes(value_workflowId))
5377
+ return "reviewer_event_workflow_id";
5378
+ if (value["packId"] !== undefined) {
5379
+ const value_packId = value["packId"];
5380
+ if (typeof value_packId !== "string" ||
5381
+ value_packId.length < 1 ||
5382
+ value_packId.length > 80 ||
5383
+ !new RegExp("^[A-Za-z0-9_.:-]+$").test(value_packId))
5384
+ return "reviewer_event_pack_id";
5385
+ }
5386
+ if (value["packVersion"] !== undefined) {
5387
+ const value_packVersion = value["packVersion"];
5388
+ if (typeof value_packVersion !== "string" ||
5389
+ value_packVersion.length < 1 ||
5390
+ value_packVersion.length > 80 ||
5391
+ !new RegExp("^[A-Za-z0-9_.:-]+$").test(value_packVersion))
5392
+ return "reviewer_event_pack_version";
5393
+ }
5394
+ if (value["environment"] !== undefined) {
5395
+ const value_environment = value["environment"];
5396
+ if (typeof value_environment !== "string" ||
5397
+ !["development", "staging", "production"].includes(value_environment))
5398
+ return "reviewer_event_environment";
5399
+ }
5400
+ const value_source = value["source"];
5401
+ if (typeof value_source !== "string" ||
5402
+ !["portal", "embed"].includes(value_source))
5403
+ return "reviewer_event_source";
5404
+ if (value["evidenceId"] !== undefined) {
5405
+ const value_evidenceId = value["evidenceId"];
5406
+ if (typeof value_evidenceId !== "string" ||
5407
+ value_evidenceId.length < 1 ||
5408
+ value_evidenceId.length > 64 ||
5409
+ !new RegExp("^[A-Za-z0-9_.:-]{1,64}$").test(value_evidenceId))
5410
+ return "reviewer_event_evidence_id";
5411
+ }
5412
+ if (value["activeSeconds"] !== undefined) {
5413
+ const value_activeSeconds = value["activeSeconds"];
5414
+ if (typeof value_activeSeconds !== "number" ||
5415
+ Number.isNaN(value_activeSeconds) ||
5416
+ !Number.isInteger(value_activeSeconds) ||
5417
+ value_activeSeconds < 0 ||
5418
+ value_activeSeconds > 86400)
5419
+ return "reviewer_event_active_seconds";
5420
+ }
5421
+ if (value["idlePaused"] !== undefined) {
5422
+ const value_idlePaused = value["idlePaused"];
5423
+ if (typeof value_idlePaused !== "boolean")
5424
+ return "reviewer_event_idle_paused";
5425
+ }
5426
+ const eventName = value["eventName"];
5427
+ const isEvidenceEvent = eventName === "evidence_opened" || eventName === "evidence_copied";
5428
+ if (isEvidenceEvent !== (value["evidenceId"] !== undefined))
5429
+ return "reviewer_event_shape";
5430
+ const isActiveTimeEvent = eventName === "review_active_time";
5431
+ if (isActiveTimeEvent !== (value["activeSeconds"] !== undefined))
5432
+ return "reviewer_event_shape";
5433
+ if (!isActiveTimeEvent && value["idlePaused"] !== undefined)
5434
+ return "reviewer_event_shape";
5435
+ return null;
5436
+ }
5437
+ export const validRcmReviewerEvent = (value) => rcmReviewerEventValidationError(value) === null;
5438
+ export const RcmReviewerEventSchemaJson = {
5439
+ $schema: "https://json-schema.org/draft/2020-12/schema",
5440
+ $id: "https://nexalink.care/contracts/rcm-reviewer-event.schema.json",
5441
+ title: "RcmReviewerEvent",
5442
+ description: "Tenant-scoped, PHI-free reviewer engagement event for CareCompass RCM workflows. Events carry only an opaque case reference, categorical engagement facts, and aggregate durations. They never carry prompts, model responses, document contents, patient or member identifiers, claim identifiers, or free text.",
5443
+ version: "1.0.0",
5444
+ type: "object",
5445
+ additionalProperties: false,
5446
+ required: [
5447
+ "eventName",
5448
+ "occurredAt",
5449
+ "tenantRef",
5450
+ "caseRef",
5451
+ "workflowId",
5452
+ "source",
5453
+ ],
5454
+ properties: {
5455
+ eventName: {
5456
+ type: "string",
5457
+ enum: [
5458
+ "review_opened",
5459
+ "decision_shown",
5460
+ "evidence_opened",
5461
+ "evidence_copied",
5462
+ "review_active_time",
5463
+ ],
5464
+ },
5465
+ occurredAt: {
5466
+ type: "string",
5467
+ pattern: "^\\d{4}-\\d{2}-\\d{2}T(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|[+-](?:[01]\\d|2[0-3]):[0-5]\\d)$",
5468
+ format: "date-time",
5469
+ maxLength: 64,
5470
+ },
5471
+ tenantRef: {
5472
+ type: "string",
5473
+ minLength: 1,
5474
+ maxLength: 128,
5475
+ pattern: "^[A-Za-z0-9_.:-]+$",
5476
+ },
5477
+ caseRef: {
5478
+ type: "string",
5479
+ minLength: 1,
5480
+ maxLength: 128,
5481
+ pattern: "^[A-Za-z0-9_.:-]+$",
5482
+ },
5483
+ reviewId: {
5484
+ type: "string",
5485
+ minLength: 1,
5486
+ maxLength: 128,
5487
+ pattern: "^[A-Za-z0-9_.:-]+$",
5488
+ },
5489
+ workflowId: {
5490
+ type: "string",
5491
+ enum: ["rcm-coding-assistance", "monthly-pin-time-aggregation"],
5492
+ },
5493
+ packId: {
5494
+ type: "string",
5495
+ minLength: 1,
5496
+ maxLength: 80,
5497
+ pattern: "^[A-Za-z0-9_.:-]+$",
5498
+ },
5499
+ packVersion: {
5500
+ type: "string",
5501
+ minLength: 1,
5502
+ maxLength: 80,
5503
+ pattern: "^[A-Za-z0-9_.:-]+$",
5504
+ },
5505
+ environment: {
5506
+ type: "string",
5507
+ enum: ["development", "staging", "production"],
5508
+ },
5509
+ source: {
5510
+ type: "string",
5511
+ enum: ["portal", "embed"],
5512
+ },
5513
+ evidenceId: {
5514
+ type: "string",
5515
+ minLength: 1,
5516
+ pattern: "^[A-Za-z0-9_.:-]{1,64}$",
5517
+ maxLength: 64,
5518
+ },
5519
+ activeSeconds: {
5520
+ type: "integer",
5521
+ minimum: 0,
5522
+ maximum: 86400,
5523
+ },
5524
+ idlePaused: {
5525
+ type: "boolean",
5526
+ },
5527
+ },
5528
+ allOf: [
5529
+ {
5530
+ if: {
5531
+ properties: {
5532
+ eventName: {
5533
+ enum: ["evidence_opened", "evidence_copied"],
5534
+ },
5535
+ },
5536
+ required: ["eventName"],
5537
+ },
5538
+ then: {
5539
+ required: ["evidenceId"],
5540
+ },
5541
+ else: {
5542
+ not: {
5543
+ required: ["evidenceId"],
5544
+ },
5545
+ },
5546
+ },
5547
+ {
5548
+ if: {
5549
+ properties: {
5550
+ eventName: {
5551
+ const: "review_active_time",
5552
+ },
5553
+ },
5554
+ required: ["eventName"],
5555
+ },
5556
+ then: {
5557
+ required: ["activeSeconds"],
5558
+ },
5559
+ else: {
5560
+ not: {
5561
+ anyOf: [
5562
+ {
5563
+ required: ["activeSeconds"],
5564
+ },
5565
+ {
5566
+ required: ["idlePaused"],
5567
+ },
5568
+ ],
5569
+ },
5570
+ },
5571
+ },
5572
+ ],
5573
+ };
5574
+ // --------------------------------------------------------------------
5206
5575
  // RcmRuntimeEvent (schema 1.0.0, wire rcm-runtime-event-v1)
5207
5576
  // --------------------------------------------------------------------
5208
5577
  export const rcmRuntimeEventSchema = z
@@ -5560,17 +5929,20 @@ export const RcmRuntimeEventSchemaJson = {
5560
5929
  },
5561
5930
  };
5562
5931
  // --------------------------------------------------------------------
5563
- // RcmWorkflowCatalog (schema 1.0.0, wire rcm-workflow-catalog-v1)
5932
+ // RcmWorkflowCatalog (schema 1.1.0, wire rcm-workflow-catalog-v1)
5564
5933
  // --------------------------------------------------------------------
5565
5934
  export const rcmWorkflowCatalogSchema = z
5566
5935
  .object({
5567
- id: z.enum(["rcm-coding-assistance"]),
5936
+ id: z.enum(["rcm-coding-assistance", "monthly-pin-time-aggregation"]),
5568
5937
  name: z.string().min(1).max(120),
5569
5938
  description: z.string().min(1).max(400),
5570
5939
  environments: z.array(z.enum(["development", "production"])).min(1),
5571
5940
  modelPolicy: z
5572
5941
  .object({
5573
- policyId: z.literal("carecompass-rcm-review-v1"),
5942
+ policyId: z.enum([
5943
+ "carecompass-rcm-review-v1",
5944
+ "carecompass-pin-time-policy-v1",
5945
+ ]),
5574
5946
  inferencePath: z.string().max(64),
5575
5947
  maxNewTokens: z.number().int().min(1).max(2048),
5576
5948
  temperature: z.number().min(0).max(2),
@@ -5586,7 +5958,21 @@ export const rcmWorkflowCatalogSchema = z
5586
5958
  .strict(),
5587
5959
  outputSchemaVersion: z.literal("rcm-grounded-human-review-v2"),
5588
5960
  })
5589
- .strict();
5961
+ .strict()
5962
+ .superRefine((value, ctx) => {
5963
+ const expectedPolicy = value.id === "rcm-coding-assistance"
5964
+ ? "carecompass-rcm-review-v1"
5965
+ : "carecompass-pin-time-policy-v1";
5966
+ if (value.modelPolicy.policyId !== expectedPolicy ||
5967
+ (value.id === "monthly-pin-time-aggregation" &&
5968
+ value.environments.some((environment) => environment !== "development"))) {
5969
+ ctx.addIssue({
5970
+ code: "custom",
5971
+ path: ["modelPolicy"],
5972
+ message: "workflow_policy_binding",
5973
+ });
5974
+ }
5975
+ });
5590
5976
  export function rcmWorkflowCatalogValidationError(value) {
5591
5977
  if (!isRecord(value))
5592
5978
  return "workflow_catalog";
@@ -5612,7 +5998,7 @@ export function rcmWorkflowCatalogValidationError(value) {
5612
5998
  return "workflow_catalog_keys";
5613
5999
  const value_id = value["id"];
5614
6000
  if (typeof value_id !== "string" ||
5615
- !["rcm-coding-assistance"].includes(value_id))
6001
+ !["rcm-coding-assistance", "monthly-pin-time-aggregation"].includes(value_id))
5616
6002
  return "workflow_id";
5617
6003
  const value_name = value["name"];
5618
6004
  if (typeof value_name !== "string" ||
@@ -5655,7 +6041,7 @@ export function rcmWorkflowCatalogValidationError(value) {
5655
6041
  return "model_policy_keys";
5656
6042
  const value_modelPolicy_policyId = value_modelPolicy["policyId"];
5657
6043
  if (typeof value_modelPolicy_policyId !== "string" ||
5658
- value_modelPolicy_policyId !== "carecompass-rcm-review-v1")
6044
+ !["carecompass-rcm-review-v1", "carecompass-pin-time-policy-v1"].includes(value_modelPolicy_policyId))
5659
6045
  return "model_policy_id";
5660
6046
  const value_modelPolicy_inferencePath = value_modelPolicy["inferencePath"];
5661
6047
  if (typeof value_modelPolicy_inferencePath !== "string" ||
@@ -5701,6 +6087,15 @@ export function rcmWorkflowCatalogValidationError(value) {
5701
6087
  if (typeof value_outputSchemaVersion !== "string" ||
5702
6088
  value_outputSchemaVersion !== "rcm-grounded-human-review-v2")
5703
6089
  return "workflow_output_schema_version";
6090
+ const policy = isRecord(value["modelPolicy"]) ? value["modelPolicy"] : {};
6091
+ const expectedPolicy = value["id"] === "rcm-coding-assistance"
6092
+ ? "carecompass-rcm-review-v1"
6093
+ : "carecompass-pin-time-policy-v1";
6094
+ if (policy["policyId"] !== expectedPolicy ||
6095
+ (value["id"] === "monthly-pin-time-aggregation" &&
6096
+ Array.isArray(value["environments"]) &&
6097
+ value["environments"].some((environment) => environment !== "development")))
6098
+ return "workflow_policy_binding";
5704
6099
  return null;
5705
6100
  }
5706
6101
  export const validRcmWorkflowCatalog = (value) => rcmWorkflowCatalogValidationError(value) === null;
@@ -5708,8 +6103,8 @@ export const RcmWorkflowCatalogSchemaJson = {
5708
6103
  $schema: "https://json-schema.org/draft/2020-12/schema",
5709
6104
  $id: "https://nexalink.care/contracts/rcm-workflow-catalog.schema.json",
5710
6105
  title: "RcmWorkflowCatalog",
5711
- description: "A single authorized workflow entry. The catalog pins the workflow identifier, model policy, human-review policy, and output schema version that the runtime and portal may offer for the RCM coding-assistance workflow. Customer configuration can never redefine these immutable server-side values.",
5712
- version: "1.0.0",
6106
+ description: "A single authorized workflow entry. The catalog pins the workflow identifier, model policy, human-review policy, and output schema version that the runtime and portal may offer for registered coding workflows (currently rcm-coding-assistance and monthly-pin-time-aggregation). Customer configuration can never redefine these immutable server-side values.",
6107
+ version: "1.1.0",
5713
6108
  type: "object",
5714
6109
  additionalProperties: false,
5715
6110
  required: [
@@ -5724,7 +6119,7 @@ export const RcmWorkflowCatalogSchemaJson = {
5724
6119
  properties: {
5725
6120
  id: {
5726
6121
  type: "string",
5727
- enum: ["rcm-coding-assistance"],
6122
+ enum: ["rcm-coding-assistance", "monthly-pin-time-aggregation"],
5728
6123
  },
5729
6124
  name: {
5730
6125
  type: "string",
@@ -5758,7 +6153,7 @@ export const RcmWorkflowCatalogSchemaJson = {
5758
6153
  properties: {
5759
6154
  policyId: {
5760
6155
  type: "string",
5761
- const: "carecompass-rcm-review-v1",
6156
+ enum: ["carecompass-rcm-review-v1", "carecompass-pin-time-policy-v1"],
5762
6157
  },
5763
6158
  inferencePath: {
5764
6159
  type: "string",
@@ -5804,4 +6199,51 @@ export const RcmWorkflowCatalogSchemaJson = {
5804
6199
  const: "rcm-grounded-human-review-v2",
5805
6200
  },
5806
6201
  },
6202
+ allOf: [
6203
+ {
6204
+ if: {
6205
+ properties: {
6206
+ id: {
6207
+ const: "rcm-coding-assistance",
6208
+ },
6209
+ },
6210
+ },
6211
+ then: {
6212
+ properties: {
6213
+ modelPolicy: {
6214
+ properties: {
6215
+ policyId: {
6216
+ const: "carecompass-rcm-review-v1",
6217
+ },
6218
+ },
6219
+ },
6220
+ },
6221
+ },
6222
+ },
6223
+ {
6224
+ if: {
6225
+ properties: {
6226
+ id: {
6227
+ const: "monthly-pin-time-aggregation",
6228
+ },
6229
+ },
6230
+ },
6231
+ then: {
6232
+ properties: {
6233
+ modelPolicy: {
6234
+ properties: {
6235
+ policyId: {
6236
+ const: "carecompass-pin-time-policy-v1",
6237
+ },
6238
+ },
6239
+ },
6240
+ environments: {
6241
+ items: {
6242
+ const: "development",
6243
+ },
6244
+ },
6245
+ },
6246
+ },
6247
+ },
6248
+ ],
5807
6249
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexalink/coding-contracts",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Versioned TypeScript types, validators, constants, and JSON Schemas for CareCompass coding workflows.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -32,8 +32,10 @@
32
32
  "contracts:check": "bun scripts/codegen.ts --check",
33
33
  "format": "bunx prettier --write --ignore-unknown .",
34
34
  "format:check": "bunx prettier --check --ignore-unknown .",
35
+ "lint": "bunx eslint .",
35
36
  "test": "bun test",
36
37
  "test:python": "PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=python .venv/bin/python -m pytest -p no:cacheprovider python/tests",
38
+ "tidy": "bun run format && bun run lint",
37
39
  "audit": "bun audit",
38
40
  "verify:package": "bun scripts/verify-package.mjs",
39
41
  "pack:dry-run": "bun run build && bun run verify:package && bun pm pack --dry-run --ignore-scripts",
@@ -43,8 +45,12 @@
43
45
  "zod": "^4.4.3"
44
46
  },
45
47
  "devDependencies": {
48
+ "@eslint/js": "^10.0.1",
49
+ "eslint": "^10.12.0",
50
+ "globals": "^17.13.0",
46
51
  "prettier": "3.8.1",
47
- "typescript": "^5.9.2"
52
+ "typescript": "^5.9.2",
53
+ "typescript-eslint": "^8.71.0"
48
54
  },
49
55
  "publishConfig": {
50
56
  "access": "public",
@@ -2,8 +2,8 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://nexalink.care/contracts/coding-reviewer-action.schema.json",
4
4
  "title": "CodingReviewerAction",
5
- "description": "Attributable human-review disposition appended to an otherwise immutable decision. A decision plus its reviewer actions remains replayable: original and reviewer versions are both preserved.",
6
- "version": "1.0.0",
5
+ "description": "Attributable human-review disposition appended to an otherwise immutable decision. A decision plus its reviewer actions remains replayable: original and reviewer versions are both preserved. Review outcome categories are reviewer-adjudicated observations and never inferred from payment, code, or reimbursement changes.",
6
+ "version": "1.1.0",
7
7
  "x-wire-version": "coding-reviewer-action-v1",
8
8
  "x-wire-constant": "CODING_REVIEWER_ACTION_SCHEMA_VERSION",
9
9
  "x-code": "reviewer_action",
@@ -35,6 +35,17 @@
35
35
  "maxLength": 80,
36
36
  "x-code": "reviewer_action_resolution_code"
37
37
  },
38
+ "reviewOutcomeCategory": {
39
+ "type": "string",
40
+ "enum": [
41
+ "unsupported_or_excessive",
42
+ "missed_supported",
43
+ "unchanged_supported",
44
+ "missing_evidence",
45
+ "escalation"
46
+ ],
47
+ "x-code": "reviewer_action_outcome_category"
48
+ },
38
49
  "notes": {
39
50
  "type": "string",
40
51
  "minLength": 1,