> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sensorup.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Field Service Management Guide

> Working with mobile workflows, issue tracking, task management, and data collection forms for field service operations.

# Field Service Management Guide

The Field Service Management (FSM) API enables mobile workflows through issue tracking, task management, and dynamic data collection forms. FSM integrates four subgraphs: `su-assets` (form subjects), `su-issues` (issue tracking), `su-workflow` (workflow orchestration), and `su-forms` (data collection).

## Overview

FSM enables field teams to:

* Track **issues** and work items requiring attention
* Execute **workflows** with defined states and transitions
* Complete **tasks** using dynamic XForms
* Reference **asset profiles** as subjects for field work
* Collect structured data through mobile applications
* Manage multi-step inspection campaigns

## Core Concepts

### Asset Profiles (Form Subjects)

Asset profiles (`su-assets`) define the subjects that field teams work on during mobile workflows. These are NOT the same as CAM assets:

* **Asset Profile**: Schema definition for form subjects (e.g., "Well Inspection", "Equipment Survey")
* **Asset**: Instance of a profile used as a form subject
* **Properties**: Attributes collected about the subject
* **Relationships**: Connections between assets and other entities

Asset profiles serve as the foundation for tasks and forms in mobile applications.

### Issues

Issues represent trackable work items requiring attention or resolution:

* **Issue Types**: `ISSUE`, `TASK`, `MULTITASK`, `SUBTASK`
* **Status**: `OPEN`, `IN_PROGRESS`, `RESOLVED`, `CLOSED`, `CANCELLED`
* **Subject**: The entity the issue relates to (e.g., an asset profile instance)
* **Trigger**: What caused the issue (e.g., a detection, event, or manual creation)

### Tasks

Tasks are specific work to be performed by field personnel:

* **Form-based Tasks**: Tasks with an associated XForm for data collection
* **General Tasks**: Simple work items without form requirements
* **Multi-tasks**: Parent tasks coordinating multiple subtasks
* **Subtasks**: Individual tasks within a multi-task campaign

### XForms

Dynamic data collection forms based on the ODK XForms standard:

* **Controls**: Input fields (text, select, date, location, etc.)
* **Groups**: Logical sections organizing related questions
* **Repeats**: Repeating sections for multiple observations
* **Constraints**: Validation rules and calculations
* **Relevance**: Conditional display logic

### Workflows

State machines defining the lifecycle of issues and tasks:

* **States**: Valid statuses an issue can have
* **Transitions**: Allowed movements between states
* **Guards**: Conditions required for transitions
* **Actions**: Side effects triggered during transitions

## Asset Profiles for Field Work

### Query Available Profiles

```graphql theme={null}
query GetAssetProfiles {
  assetProfiles {
    all(first: 20) {
      edges {
        node {
          profile
          title
          displayNameProperty
          descriptionProperty
          properties {
            id
            name
            dataType
            required
          }
          relationships {
            relationship
            type
            profile
            referenceProperty
          }
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
}
```

### Get Specific Profile

```graphql theme={null}
query GetAssetProfile($profile: ID!) {
  assetProfiles {
    byId(profile: $profile) {
      profile
      title
      displayNameProperty
      views {
        view
        title
        fields
      }
      filters {
        filter
        displayName
        filterString
      }
    }
  }
}
```

**Variables:**

```json theme={null}
{
  "profile": "well-inspection"
}
```

### Query Assets (Form Subjects)

```graphql theme={null}
query GetAssets($profile: ID!, $first: Int!) {
  assets(profile: $profile) {
    all(first: $first) {
      edges {
        node {
          id
          displayName
          properties
          geometry {
            type
            coordinates
          }
          relationships {
            ... on AssetRelationshipOne {
              asset {
                id
                displayName
              }
            }
          }
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
}
```

**Variables:**

```json theme={null}
{
  "profile": "well-inspection",
  "first": 50
}
```

### Create Asset (Form Subject)

```graphql theme={null}
mutation CreateAsset($input: CreateAssetInput!) {
  createAsset(input: $input) {
    asset {
      id
      displayName
      properties
    }
    correlationId
    errors {
      message
      type
    }
  }
}
```

**Variables:**

```json theme={null}
{
  "input": {
    "profile": "well-inspection",
    "id": "well-abc-123",
    "displayName": "Well ABC-123",
    "properties": {
      "operator": "ACME Energy",
      "wellType": "gas",
      "status": "active"
    },
    "geometry": {
      "type": "Point",
      "coordinates": [-114.0719, 51.0447]
    }
  }
}
```

## Issue Tracking

### List All Issues

```graphql theme={null}
query GetIssues($first: Int!, $filter: IssueFilter) {
  issues {
    all(first: $first, filter: $filter) {
      edges {
        node {
          id
          type
          title
          description
          status
          severity
          openedAt
          closedAt
          dueAt
          pastDue
          assignee {
            username
          }
          subject {
            type
            reference
          }
          trigger {
            type
            reference
          }
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
      totalCount
    }
  }
}
```

**Variables (Filter Open Issues):**

```json theme={null}
{
  "first": 20,
  "filter": {
    "status": ["OPEN", "IN_PROGRESS"]
  }
}
```

### Get Specific Issue

```graphql theme={null}
query GetIssue($id: ID!) {
  issues {
    byId(id: $id) {
      id
      type
      taskType
      title
      description
      status
      severity
      openedAt
      closedAt
      dueAt
      subject {
        type
        reference
      }
      assignee {
        userId
        username
      }
      annotations {
        id
        text
        createdAt
        createdBy {
          username
        }
        attachments {
          url
          filename
          contentType
        }
      }
      xform {
        formId
        responseId
        status
      }
    }
  }
}
```

### Create Issue

```graphql theme={null}
mutation CreateIssue($input: CreateIssueInput!) {
  createIssue(input: $input) {
    issue {
      id
      title
      status
      type
    }
    correlationId
    errors {
      message
      type
    }
  }
}
```

**Variables:**

```json theme={null}
{
  "input": {
    "title": "Quarterly Well Inspection - ABC-123",
    "description": "Routine quarterly inspection required",
    "type": "TASK",
    "severity": "MEDIUM",
    "dueAt": "2025-12-31T23:59:59Z",
    "subject": {
      "type": "ASSET",
      "reference": "well-abc-123"
    },
    "assignee": {
      "userId": "user-123"
    }
  }
}
```

### Update Issue Status

```graphql theme={null}
mutation UpdateIssue($input: UpdateIssueInput!) {
  updateIssue(input: $input) {
    issue {
      id
      status
      closedAt
    }
    correlationId
    errors {
      message
      type
    }
  }
}
```

**Variables:**

```json theme={null}
{
  "input": {
    "id": "issue-123",
    "status": "RESOLVED"
  }
}
```

### Add Annotation to Issue

```graphql theme={null}
mutation AddIssueAnnotation($input: AddIssueAnnotationInput!) {
  addIssueAnnotation(input: $input) {
    annotation {
      id
      text
      createdAt
      createdBy {
        username
      }
    }
    correlationId
    errors {
      message
      type
    }
  }
}
```

**Variables:**

```json theme={null}
{
  "input": {
    "issueId": "issue-123",
    "text": "Inspection completed. No issues found. Equipment operating normally.",
    "attachments": [
      {
        "url": "https://storage.example.com/photos/inspection-photo.jpg",
        "filename": "inspection-photo.jpg",
        "contentType": "image/jpeg"
      }
    ]
  }
}
```

### Perform Follow-Up Action

Create or update a follow-up issue linked to an existing issue. The mutation finds an original issue using filters and creates a follow-up with the specified form data. Subsequent calls with the same `triggerContextReference` update the existing follow-up rather than creating duplicates.

```graphql theme={null}
mutation PerformIssueFollowUpAction($input: PerformIssueFollowUpActionInput!) {
  performIssueFollowUpAction(input: $input) {
    issue {
      id
      title
      status
    }
    correlationId
  }
}
```

**Variables:**

```json theme={null}
{
  "input": {
    "subjectFormReference": "leak",
    "triggerContextReference": "external-id-123",
    "filters": [
      {
        "field": "work_order_number",
        "operator": "EQUAL",
        "value": "external-id-123"
      }
    ],
    "followUpFormReference": "leak-repair",
    "followUpTaskTitle": "Leak Repair Required",
    "followUpTaskDescription": "Repair leak identified in work order external-id-123",
    "followUpFormData": {
      "technician_name": "John Smith",
      "repairDate": "2025-01-01T00:00:00.000Z"
    }
  }
}
```

### Delete Issue

Deletes an issue. Assets and statuses associated with the issue are not affected.

```graphql theme={null}
mutation DeleteIssue($input: DeleteIssueInput!) {
  deleteIssue(input: $input) {
    issue {
      id
      title
    }
    correlationId
    errors {
      message
      type
    }
  }
}
```

**Variables:**

```json theme={null}
{
  "input": {
    "issueId": "issue-123"
  }
}
```

## Multi-Task Management

Multi-tasks enable coordinated campaigns across multiple locations or assets.

### Create Multi-Task

```graphql theme={null}
mutation CreateMultiTask($input: CreateIssueInput!) {
  createIssue(input: $input) {
    issue {
      id
      title
      type
      status
    }
    correlationId
    errors {
      message
      type
    }
  }
}
```

**Variables:**

```json theme={null}
{
  "input": {
    "title": "Q4 2025 Site-Wide Inspection",
    "description": "Quarterly inspection of all wells at North Field",
    "type": "MULTITASK",
    "severity": "HIGH",
    "dueAt": "2025-12-31T23:59:59Z"
  }
}
```

### Create Subtasks

```graphql theme={null}
mutation CreateSubtask($input: CreateIssueInput!) {
  createIssue(input: $input) {
    issue {
      id
      title
      type
      parentTask {
        id
        title
      }
    }
    correlationId
    errors {
      message
      type
    }
  }
}
```

**Variables:**

```json theme={null}
{
  "input": {
    "title": "Inspect Well ABC-123",
    "type": "SUBTASK",
    "parentTaskId": "multitask-456",
    "subject": {
      "type": "ASSET",
      "reference": "well-abc-123"
    },
    "assignee": {
      "userId": "field-tech-001"
    }
  }
}
```

### Query Multi-Task with Subtasks

```graphql theme={null}
query GetMultiTaskWithSubtasks($id: ID!) {
  issues {
    byId(id: $id) {
      id
      title
      type
      status
      subtasks {
        id
        title
        status
        assignee {
          username
        }
        subject {
          type
          reference
        }
      }
    }
  }
}
```

## XForms for Data Collection

### Query Available Forms

```graphql theme={null}
query GetForms {
  forms {
    all(first: 100) {
      edges {
        node {
          formId
          title
          version
          description
          xformDetails {
            model
            binds {
              nodeset
              type
              required
              readonly
            }
            body {
              ... on XFormBodyControlNode {
                id
                control
                label
                hint
                ref
              }
              ... on XFormBodyGroupNode {
                id
                label
                childNodeIds
              }
            }
          }
        }
      }
    }
  }
}
```

### Get Specific Form

```graphql theme={null}
query GetForm($formId: ID!) {
  forms {
    byId(id: $formId) {
      formId
      title
      version
      description
      xformDetails {
        model
        binds {
          nodeset
          type
          required
          relevant
          constraint
          calculate
        }
        body {
          __typename
          ... on XFormBodyControlNode {
            id
            control
            label
            hint
            items {
              label
              value
            }
          }
          ... on XFormBodyGroupNode {
            id
            label
            childNodeIds
          }
          ... on XFormBodyRepeatNode {
            id
            label
            ref
            childNodeIds
          }
        }
      }
    }
  }
}
```

### Submit Form Response

```graphql theme={null}
mutation SubmitFormResponse($input: CreateFormResponseInput!) {
  createFormResponse(input: $input) {
    formResponse {
      responseId
      formId
      submittedAt
      submittedBy {
        username
      }
      data
    }
    correlationId
    errors {
      message
      type
    }
  }
}
```

**Variables:**

```json theme={null}
{
  "input": {
    "formId": "well-inspection-form",
    "issueId": "issue-123",
    "data": {
      "inspector": "John Smith",
      "inspectionDate": "2025-11-05",
      "wellCondition": "good",
      "pressureReading": "850",
      "temperatureReading": "72",
      "notes": "No issues observed",
      "photos": [
        "https://storage.example.com/photo1.jpg",
        "https://storage.example.com/photo2.jpg"
      ]
    }
  }
}
```

### Link Form to Task

```graphql theme={null}
mutation LinkFormToTask($input: LinkFormToIssueInput!) {
  linkFormToIssue(input: $input) {
    issue {
      id
      xform {
        formId
        responseId
        status
      }
    }
    correlationId
    errors {
      message
      type
    }
  }
}
```

**Variables:**

```json theme={null}
{
  "input": {
    "issueId": "issue-123",
    "formId": "well-inspection-form"
  }
}
```

## Workflows

### Query Workflow Definition

```graphql theme={null}
query GetWorkflow($workflowId: ID!) {
  workflow(id: $workflowId) {
    id
    name
    description
    states {
      id
      name
      isInitial
      isFinal
    }
    transitions {
      from
      to
      event
      guards {
        condition
        message
      }
    }
  }
}
```

### Trigger Workflow Transition

```graphql theme={null}
mutation TransitionIssue($input: TransitionIssueInput!) {
  transitionIssue(input: $input) {
    issue {
      id
      status
      workflow {
        currentState
        availableTransitions {
          event
          to
        }
      }
    }
    correlationId
    errors {
      message
      type
    }
  }
}
```

**Variables:**

```json theme={null}
{
  "input": {
    "issueId": "issue-123",
    "event": "complete_inspection"
  }
}
```

## Common Workflows

### Mobile Inspection Workflow

1. **Create Task** with asset subject and XForm
2. **Assign** to field technician
3. **Technician opens** task in mobile app
4. **Complete XForm** with inspection data
5. **Submit** form response
6. **Update task** status to RESOLVED
7. **Supervisor reviews** and CLOSES task

```javascript theme={null}
// Example mobile inspection flow
async function mobileInspectionWorkflow(assetId, formId, technicianId) {
  // 1. Create task
  const { data: taskData } = await createIssue({
    title: `Inspect Asset ${assetId}`,
    type: 'TASK',
    subject: { type: 'ASSET', reference: assetId },
    assignee: { userId: technicianId }
  })

  const taskId = taskData.createIssue.issue.id

  // 2. Link form to task
  await linkFormToIssue({
    issueId: taskId,
    formId: formId
  })

  // 3. Technician completes form
  const formData = await collectDataInMobileApp()

  // 4. Submit form response
  await createFormResponse({
    formId: formId,
    issueId: taskId,
    data: formData
  })

  // 5. Update task status
  await updateIssue({
    id: taskId,
    status: 'RESOLVED'
  })

  return taskId
}
```

### Multi-Task Campaign Workflow

1. **Create multi-task** for campaign
2. **Generate subtasks** for each location
3. **Assign subtasks** to field teams
4. **Track completion** of individual subtasks
5. **Multi-task auto-completes** when all subtasks done

```javascript theme={null}
// Example multi-task campaign
async function createInspectionCampaign(assets, formId) {
  // 1. Create multi-task
  const { data: multiTaskData } = await createIssue({
    title: 'Q4 Inspection Campaign',
    type: 'MULTITASK',
    dueAt: '2025-12-31T23:59:59Z'
  })

  const multiTaskId = multiTaskData.createIssue.issue.id

  // 2. Create subtask for each asset
  for (const asset of assets) {
    await createIssue({
      title: `Inspect ${asset.displayName}`,
      type: 'SUBTASK',
      parentTaskId: multiTaskId,
      subject: { type: 'ASSET', reference: asset.id },
      assignee: { userId: asset.assignedTech }
    })
  }

  return multiTaskId
}
```

## Issue Templates

### Create Issue Template

```graphql theme={null}
mutation CreateIssueTemplate($input: CreateIssueTemplateInput!) {
  createIssueTemplate(input: $input) {
    template {
      id
      name
      title
      description
      type
      defaultSeverity
      defaultFormId
    }
    correlationId
    errors {
      message
      type
    }
  }
}
```

**Variables:**

```json theme={null}
{
  "input": {
    "name": "quarterly-well-inspection",
    "title": "Quarterly Well Inspection",
    "description": "Standard quarterly inspection procedure",
    "type": "TASK",
    "defaultSeverity": "MEDIUM",
    "defaultFormId": "well-inspection-form"
  }
}
```

### Create Issue from Template

```graphql theme={null}
mutation CreateIssueFromTemplate($input: CreateIssueFromTemplateInput!) {
  createIssueFromTemplate(input: $input) {
    issue {
      id
      title
      type
      xform {
        formId
      }
    }
    correlationId
    errors {
      message
      type
    }
  }
}
```

**Variables:**

```json theme={null}
{
  "input": {
    "templateId": "quarterly-well-inspection",
    "subject": {
      "type": "ASSET",
      "reference": "well-abc-123"
    },
    "assignee": {
      "userId": "tech-001"
    },
    "dueAt": "2025-12-15T23:59:59Z"
  }
}
```

## Asset Filters

Create reusable filters for finding form subjects:

### Create Asset Filter

```graphql theme={null}
mutation CreateAssetFilter($input: CreateAssetFilterInput!) {
  createAssetFilter(input: $input) {
    assetFilter {
      id
      displayName
      profile
      filterString
    }
    correlationId
    errors {
      message
      type
    }
  }
}
```

**Variables:**

```json theme={null}
{
  "input": {
    "id": "active-wells-north-field",
    "displayName": "Active Wells - North Field",
    "profile": "well-inspection",
    "subProfiles": [],
    "filterString": "properties.status = 'active' AND properties.field = 'north'"
  }
}
```

### Query Assets with Filter

```graphql theme={null}
query GetFilteredAssets($profile: ID!, $filterId: ID!) {
  assets(profile: $profile) {
    filtered(filter: $filterId, first: 100) {
      edges {
        node {
          id
          displayName
          properties
        }
      }
    }
  }
}
```

## Best Practices

### 1. Use Asset Profiles for Form Subjects

Define asset profiles for entities that field teams will work on:

```graphql theme={null}
# Good - dedicated profile for inspection subjects
assetProfiles { byId(profile: "equipment-inspection") }

# Avoid - mixing operational assets with form subjects
# Use CAM assets for operational data, FSM asset profiles for forms
```

### 2. Link Forms to Tasks

Always link XForms to tasks for proper tracking:

```graphql theme={null}
# Good - form linked to task
linkFormToIssue(input: { issueId: "task-123", formId: "inspection-form" })

# Incomplete - orphaned form response
createFormResponse(input: { formId: "inspection-form", data: {...} })
```

### 3. Use Multi-Tasks for Campaigns

For coordinated work across multiple locations, use multi-tasks:

```graphql theme={null}
# Good - multi-task with subtasks
createIssue(type: "MULTITASK") → createIssue(type: "SUBTASK", parentTaskId: "...")

# Less organized - individual unrelated tasks
createIssue(type: "TASK") + createIssue(type: "TASK") + ...
```

### 4. Leverage Templates

Create templates for recurring workflows:

```graphql theme={null}
# Good - consistent task creation
createIssueFromTemplate(templateId: "monthly-inspection")

# More work - manual task creation each time
createIssue(title: "...", description: "...", formId: "...")
```

### 5. Filter by Status for Work Queues

Query issues by status to create work queues:

```graphql theme={null}
# Active work queue
issues { all(filter: { status: ["OPEN", "IN_PROGRESS"] }) }

# Completed work
issues { all(filter: { status: ["RESOLVED", "CLOSED"] }) }
```

## Error Handling

FSM mutations return errors in the standard format:

```json theme={null}
{
  "data": {
    "createIssue": {
      "issue": null,
      "correlationId": "abc-123",
      "errors": [
        {
          "message": "Subject asset 'well-xyz' not found",
          "type": "INVALID_SUBJECT"
        }
      ]
    }
  }
}
```

Common error types:

* `INVALID_SUBJECT`: Referenced subject does not exist
* `INVALID_ASSIGNEE`: Assignee user not found
* `INVALID_FORM`: Form ID does not exist
* `INVALID_STATUS_TRANSITION`: Status change not allowed
* `MISSING_REQUIRED`: Required field not provided
* `DUPLICATE_ID`: ID already exists

## Related Resources

* **[Issues Subgraph Reference](../subgraphs/issues)** - Complete issue schema
* **[Workflow Subgraph Reference](../subgraphs/workflow)** - Workflow definitions
* **[Forms Subgraph Reference](../subgraphs/forms)** - XForms schema
* **[Assets Subgraph Reference](../subgraphs/assets)** - Asset profiles schema
* **[CAM Guide](./cam-assets)** - Connected asset management (different from FSM assets)
* **[Common Patterns](../common-patterns)** - Pagination and filtering
