# MachineMetrics Entity Keys Reference

This document provides a definitive guide to the primary keys and foreign key relationships for MachineMetrics (MM) entities. This information is extracted from the SDK codebase and represents the actual implementation used for entity identification and referential integrity.

## Source Code References

- **Primary Keys**: `src/services/mm-api-service/types/entity-transformer.ts` - `EntityTransformer` class
- **Foreign Key Relationships**: `src/utils/standard-process-drivers/standard-process-drivers.ts` - `writeWorkOrderBatchToMM()` method
- **Entity Types / Endpoints**: `src/types/erp-types.ts` (`ERPObjType`) and `src/services/mm-api-service/mm-api-service.ts` (`resourceURLs`)
- **Dispatch priorities (GraphQL-only)**: `src/services/mm-api-service/dispatch-priorities.ts` — `dispatchPriorityIdentityKey()` builds the key MM's unique constraint compares

---

## Entity Types Delivered to MM

These are the `ERPObjType` members writable via `writeEntitiesToMM` / `MMEntityProcessor.writeEntities`, with the send type and MM endpoint each maps to:

| `ERPObjType` | Send type | MM endpoint |
|--------------|-----------|-------------|
| `PERSONS` | `MMSendPerson` | `/erp/v1/persons` |
| `RESOURCES` | `MMSendResource` | `/erp/v1/resources` |
| `PARTS` | `MMSendPart` | `/erp/v1/parts` |
| `PART_OPERATION` | `MMSendPartOperation` | `/erp/v1/part-operations` |
| `WORK_ORDERS` | `MMSendWorkOrder` | `/erp/v1/production-orders` |
| `WORK_ORDER_OPERATIONS` | `MMSendWorkOrderOperationPayload` (`MMSendWorkOrderOperation` or `MMSendWorkOrderOperationLaborTicketSync`) | `/erp/v1/production-order-operations` |
| `REASONS` | `MMSendReason` | `/erp/v1/reasons` |
| `LABOR_TICKETS` | `MMSendLaborTicket` | `/erp/v1/labor-tickets` |

> **Note**: Timesheets (employee clock in/out) are **not** a deliverable entity type — there is no `TIMESHEETS` member of `ERPObjType` and no send type. Timesheets flow only MM → ERP (`/erp/v1/timesheets` via `fetchTimesheetUpdates` / `syncTimesheetsToERP`).

> **Note**: Dispatch priorities (`MMSendDispatchPriority`) are **not** an `ERPObjType` either. They are written with scope-level replace semantics through the MM GraphQL proxy (`MMApiClient.replaceDispatchPriorityScope`), not through `/erp/v1/...`. Their key is documented under **Dispatch priorities (GraphQL-only)** below.

---

## Primary Keys

### Simple Entities (Single-Field Keys)

| Entity Type | Primary Key Field | Description |
|-------------|------------------|-------------|
| **PERSONS** | `personId` | Unique identifier for a person/employee |
| **RESOURCES** | `resourceId` | Unique identifier for a machine/resource |
| **REASONS** | `reasonId` | Unique identifier for a reason code |

### Composite Keys

#### PARTS

| Field | Required | Description |
|-------|----------|-------------|
| `partNumber` | ✅ Yes | The primary part identifier |
| `partRevision` | ❌ No | Part revision (can be empty) |
| `method` | ❌ No | Manufacturing method (can be empty) |

**Combined Key Format**: `partNumber|partRevision|method`

---

#### PART_OPERATION

| Field | Required | Description |
|-------|----------|-------------|
| `partNumber` | ✅ Yes | The part identifier (FK to PARTS) |
| `partRevision` | ❌ No | Part revision (can be empty) |
| `method` | ❌ No | Manufacturing method (can be empty) |
| `sequenceNumber` | ✅ Yes | Operation sequence number |

**Combined Key Format**: `partNumber|partRevision|method|sequenceNumber`

---

#### WORK_ORDERS

| Field | Required | Description |
|-------|----------|-------------|
| `workOrderId` | ✅ Yes | The primary work order identifier |
| `lot` | ❌ No | Lot number (can be empty) |
| `split` | ❌ No | Split number (can be empty) |
| `sub` | ❌ No | Sub number (can be empty) |

**Combined Key Format**: `workOrderId|lot|split|sub`

---

#### WORK_ORDER_OPERATIONS

| Field | Required | Description |
|-------|----------|-------------|
| `workOrderId` | ✅ Yes | The work order identifier (FK to WORK_ORDERS) |
| `lot` | ❌ No | Lot number (can be empty) |
| `split` | ❌ No | Split number (can be empty) |
| `sub` | ❌ No | Sub number (can be empty) |
| `sequenceNumber` | ✅ Yes | Operation sequence number |

**Combined Key Format**: `workOrderId|lot|split|sub|sequenceNumber`

---

#### Dispatch priorities (GraphQL-only)

MM table `erpWorkOrderOperationDispatchPriorities` (verified against the live schema, 2026-09-14).

**Primary Key**: `dispatchPriorityRef` (surrogate bigint, generated by MM)

**Unique constraint** `work_order_operation_dispatch_priority_uq` — the identity the SDK dedupes on before a scope replace:

| Field | Required | Description |
|-------|----------|-------------|
| `companyId` | (session) | Preset from the token; not sent |
| `scopeResourceId` | ✅ Yes | The scope (ERP resource id of a machine or group) the ordering is valid within |
| `workOrderId` | ✅ Yes | Work order identifier (cf. WORK_ORDERS; **no FK**) |
| `sequenceNumber` | ✅ Yes | Operation sequence number, a `real` column; the SDK bounds it to 10,000,000, far above any routing step |
| `lot` | NOT NULL, default `''` | Must be MM's value for the operation to resolve |
| `split` | NOT NULL, default `''` | Must be MM's value for the operation to resolve |
| `sub` | NOT NULL, default `''` | Must be MM's value for the operation to resolve |

`authorityOperationId` (the scheduler's own operation id) is **not** part of the key and is nullable in MM; the SDK requires it on write regardless.

**Combined Key Format** (`dispatchPriorityIdentityKey`, per scope): `workOrderId|sequenceNumber|lot|split|sub`, the sequence number rendered as MM's float32 column stores it so key equality matches the index's equality exactly (treat as opaque), with `\` and `|` escaped inside each part so a literal separator in an ERP value cannot make two different operations share a key

**Relationships**: `workOrderOperation` (joins on `workOrderId + sequenceNumber + lot + split + sub`) and `scopeResource` (joins on `resourceId`) are manual Hasura relationships with **no foreign key enforcement** — rows for operations or resources MM does not have are accepted and resolve to null.

---

#### LABOR_TICKETS

**Primary Key**: `laborTicketRef` (auto-generated by MM API)

The primary key for labor tickets is `laborTicketRef`, which is automatically generated by the MachineMetrics API when a new labor ticket record is created.

**ERP Identifier**: `laborTicketId` (optional, but recommended when available)

| Field | Required | Description |
|-------|----------|-------------|
| `laborTicketId` | ❌ No | Unique labor ticket identifier from the ERP system |

When `laborTicketId` is known (i.e., the labor ticket has been created in the ERP), it should be provided to create or update the corresponding record in MM. This field will NOT be known when:
- The labor ticket has been created in MM but not yet synchronized to the ERP
- The ERP does not assign labor ticket IDs

**Required Foreign Key Fields**

Labor tickets have several required fields that reference other entities:

| Field | Required | Foreign Key To | Description |
|-------|----------|----------------|-------------|
| `workOrderId` | ✅ Yes | WORK_ORDERS | Work order identifier |
| `lot` | ❌ No | WORK_ORDERS | Lot number (must match work order) |
| `split` | ❌ No | WORK_ORDERS | Split number (must match work order) |
| `sub` | ❌ No | WORK_ORDERS | Sub number (must match work order) |
| `sequenceNumber` | ✅ Yes | WORK_ORDER_OPERATIONS | Operation sequence number |
| `resourceId` | ✅ Yes | RESOURCES | Machine/resource identifier |
| `personId` | ✅ Yes | PERSONS | Person/employee identifier |

These fields are NOT part of the primary key, but must match their corresponding foreign key relationships in the referenced tables.

**Combined Key Format** (for SDK internal tracking): `laborTicketId` OR `workOrderId|lot|split|sub|sequenceNumber|resourceId|personId`

---

## Foreign Key Relationships

### Dependency Graph

```
PARTS (base entity)                    PERSONS (independent)      RESOURCES (independent)
  ↓                                           ↓                         ↓
  ├─→ PART_OPERATION (depends on PARTS)       │                         │
  │     ↓                                     │                         │
  └─→ WORK_ORDERS (depends on PARTS)          │                         │
        ↓                                     │                         │
        WORK_ORDER_OPERATIONS ────────────────┼─────────────────────────┤
        (depends on WO + PART_OP)             │                         │
                ↓                             │                         │
                LABOR_TICKETS ────────────────┴─────────────────────────┘
                (depends on WO_OP + PERSONS + RESOURCES)
```

### Detailed Relationships

| Child Entity | Parent Entity(ies) | Foreign Key Fields | Notes |
|--------------|-------------------|-------------------|-------|
| **PART_OPERATION** | **PARTS** | `partNumber`<br/>`partRevision`<br/>`method` | References the part this operation belongs to |
| **WORK_ORDERS** | **PARTS** | Part fields in work order | References the part being produced |
| **WORK_ORDER_OPERATIONS** | **WORK_ORDERS**<br/>**PART_OPERATION** | Work order fields<br/>+<br/>Part + sequence fields | Dual dependency: must reference both a work order AND a part operation |
| **LABOR_TICKETS** | **WORK_ORDER_OPERATIONS**<br/>**PERSONS**<br/>**RESOURCES** | `workOrderId`, `lot`, `split`, `sub`, `sequenceNumber`<br/>+<br/>`personId`<br/>+<br/>`resourceId` | Triple dependency: must reference a work order operation, a person, and a resource |

---

## Entity Processing Order

**To maintain referential integrity**, entities must be processed in this specific order when syncing to MachineMetrics:

| Order | Entity Type | Dependencies | Rationale |
|-------|-------------|--------------|-----------|
| 0️⃣ | **PERSONS**<br/>**RESOURCES**<br/>**REASONS** | None | Independent entities (but PERSONS and RESOURCES are referenced by LABOR_TICKETS) |
| 1️⃣ | **PARTS** | None | Base entity for work order hierarchy |
| 2️⃣ | **PART_OPERATION** | PARTS | Requires parent part to exist |
| 3️⃣ | **WORK_ORDERS** | PARTS | Requires the part being produced to exist |
| 4️⃣ | **WORK_ORDER_OPERATIONS** | WORK_ORDERS + PART_OPERATION | Requires both parent entities to exist |
| 5️⃣ | **LABOR_TICKETS** | WORK_ORDER_OPERATIONS + PERSONS + RESOURCES | Requires work order operation, person, and resource to exist |

> **Note**: PERSONS, RESOURCES, and REASONS have no dependencies and can be processed first or in parallel. However, PERSONS and RESOURCES must be synchronized before LABOR_TICKETS that reference them.

---

## Implementation Notes

### Key Extraction Logic

The `EntityTransformer.extractPrimaryKey()` method handles primary key extraction for all entity types. Keys are constructed by joining field values with the pipe (`|`) delimiter.

**Special Case: Labor Tickets**
- Labor tickets have an auto-generated primary key (`laborTicketRef`) assigned by the MM API
- For SDK tracking purposes, the SDK uses `laborTicketId` (when available) or a composite of work order operation + person + resource fields
- The actual database primary key (`laborTicketRef`) is managed entirely by the MM API

### Required vs Optional Fields

- **Required fields** must be non-null, non-undefined, and non-empty strings
- **Optional fields** can be empty strings (represented as empty string in the combined key)
- The key extraction will throw an error if any required field is missing

### Referential Integrity

The `StandardProcessDrivers.writeWorkOrderBatchToMM()` method ensures referential integrity by:
1. Processing entities in dependency order
2. Preventing foreign key constraint violations
3. Allowing partial success (some entities may succeed even if others fail)

### Example Combined Keys

```typescript
// PARTS
"ABC123||"                    // partNumber only
"ABC123|Rev A|CNC"           // all fields populated

// PART_OPERATION
"ABC123|Rev A|CNC|10"        // part fields + sequence

// WORK_ORDERS
"WO-2024-001|||"             // workOrderId only
"WO-2024-001|LOT-A|1|2"     // all fields populated

// WORK_ORDER_OPERATIONS
"WO-2024-001|LOT-A|1|2|10"  // work order fields + sequence

// LABOR_TICKETS (SDK tracking only - actual primary key is auto-generated laborTicketRef)
"LT-ERP-12345"                                           // laborTicketId from ERP (preferred)
"WO-2024-001|LOT-A|1|2|10|MACHINE-001|EMPLOYEE-123"     // composite when laborTicketId unknown
```

---

## Version Information

- **Last Updated**: 2025-12-01
- **SDK Version**: Based on current codebase
- **Maintained By**: MachineMetrics ERP SDK Team

---

## See Also

- `src/services/mm-api-service/types/entity-transformer.ts` - Primary key extraction implementation
- `src/services/mm-api-service/types/send-types.ts` - Entity type definitions
- `src/utils/standard-process-drivers/standard-process-drivers.ts` - Batch processing with referential integrity




