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

# Migrating InField

> Step-by-step guide to migrate asset-centric InField configuration and data to InField on the Cognite Core Data Model (CDM).

<Warning>
  The migration plugin is an experimental feature. Updates to the Cognite Toolkit are likely to introduce changes to the described commands and processes.
</Warning>

This guide walks data engineers through migrating asset-centric **Cognite InField** configuration and application data to InField on the Cognite Core Data Model (CDM). Use the [migration overview](#migration-overview) table to navigate each phase, and complete every phase in a development or staging Cognite Data Fusion (CDF) project before you run commands in production.

## Prerequisites

Before migrating InField, complete the following:

1. [Migrating assets](/cdf/deploy/cdf_toolkit/guides/plugins/migration_plugin/assets)
2. [Migrating files and time series](/cdf/deploy/cdf_toolkit/guides/plugins/migration_plugin/files_timeseries)

<Note>
  If you have not completed some prerequisites, see [Manually populate the CogniteMigration data model](/cdf/deploy/cdf_toolkit/guides/plugins/migration_plugin/prepare#manual-population-of-the-cognitemigration-data-model).
</Note>

## Required capabilities

The following capabilities are required to run `cdf migrate infield-configs` and `cdf migrate infield-data`:

| Capability type      | Action                          | Scope                                          | Description                                          |
| -------------------- | ------------------------------- | ---------------------------------------------- | ---------------------------------------------------- |
| Data model instances | `datamodelinstances:read,write` | The InField source and target spaces           | The spaces InField are reading and writing to.       |
| Data model           | `dataModels:read`               | The `cdf_apm`, `cdf_infield`, `cdf_cdm` spaces | To spaces with the legacy and new InField data model |

<a id="migration-overview" />

## Migration overview

InField migration has two required phases and two optional phases. Test each phase in a development or staging project before you run it in production.

| Phase                             | Section                                                             | Command                           |
| --------------------------------- | ------------------------------------------------------------------- | --------------------------------- |
| 1. Configurations                 | [Migrate InField configurations](#migrate-infield-configurations)   | `cdf migrate infield-configs`     |
| 2. App data                       | [Migrate InField data](#migrate-infield-data)                       | `cdf migrate infield-data`        |
| 3. Custom observations (optional) | [Migrate to custom observations](#migrate-to-custom-observations)   | Data plugin + custom script       |
| 4. Source data (optional)         | [Migrate InField source data](#migrate-infield-source-data)         | `cdf migrate infield-source-data` |
| 5. Production                     | [Run the migration in production](#run-the-migration-in-production) | Same commands against production  |

<a id="enable-infield-alpha-flags" />

### Enable InField alpha flags

To migrate InField, set the `infield` alpha flag in addition to `migrate`:

```toml cdf.toml theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
# ...

[alpha_flags]
migrate = true
infield = true
extend-download = true  # Only needed to test the migration in a development or staging environment.

# ...
```

Before you start, confirm that assets and time series used by InField are migrated. You also need views for assets, notifications, maintenance orders, operations, and files.

<a id="migrate-infield-configurations" />

## Migrate InField configurations

<Steps>
  <Step title="Run cdf migrate infield-configs">
    ```bash theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
    cdf migrate infield-configs
    ```

    This retrieves the legacy InField configuration from the `APM_Config` model
    and generates a configuration template (`<location_name>.InfieldCDMLocationConfig.yaml`) and space definitions YAML files (`<space_name>.Space.yaml`) for each location, written to the
    `tmp/` folder. It also generates location filter per location that you can
    optionally deploy to enable location filtering in the Search UI.

    This step **only reads** existing InField configurations and writes templates — it makes no configuration changes to the CDF project or InField itself.
  </Step>

  <Step title="Review and edit the generated configuration">
    The two most important sections in the newly generated configuration YAML files are `dataStorage` and `viewMappings`.

    `dataStorage` controls which space InField writes its data to:

    ```yaml fragment theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
    dataStorage:
      rootLocation:
        space: <instance space for assets>
        externalId: <root asset>
      appInstanceSpace: <InField app instance space>
    ```

    The `rootLocation` is the root asset for the given location, and `appInstanceSpace`
    is the space InField will write its data to.

    <Warning>
      Set `appInstanceSpace` to a different space than the legacy
      `appDataInstanceSpace`. If they are the same, you risk cross-contamination of data in
      legacy InField and CDM InField which could lead to unexpected behavior in the legacy InField application.
    </Warning>

    `viewMappings` defines which views InField should use for each resource type. By
    default these point to CogniteCore (CDM) and Cognite IDM views — these can however be updated
    to use custom views extending from these instead:

    ```yaml fragment theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
    viewMappings:
      asset:
        space: cdf_cdm
        externalId: CogniteAsset
        version: v1
      operation:
        space: cdf_idm
        externalId: CogniteOperation
        version: v1
      notification:
        space: cdf_idm
        externalId: CogniteNotification
        version: v1
      maintenanceOrder:
        space: cdf_idm
        externalId: CogniteMaintenanceOrder
        version: v1
      file:
        space: cdf_cdm
        externalId: CogniteFile
        version: v1
    ```

    If you use a custom observation view instead of the default `cdf_infield/FieldObservation` view — for example to support
    [SAP writeback](#sap-writeback-for-custom-observation-views) — add an `observation` entry that points to that view:

    ```yaml fragment theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
    viewMappings:
      observation:
        - view:
            space: sp_customer_idm
            externalId: ObservationView
            version: v1
          writeBack:
            notificationsEndpointExternalId: sap_notifications_endpoint
            attachmentsEndpointExternalId: sap_attachments_endpoint
    ```

    `writeBack` is only required (and should only be used) if your custom observation view supports sending observations to SAP. `notificationsEndpointExternalId`
    and `attachmentsEndpointExternalId` reference the SAP endpoints InField sends to — they don't affect data migration, but must be set
    for SAP writeback to work once the location is deployed. See [Set up the SAP writeback service](/cdf/integration/guides/interfaces/sap_writeback_setup)
    for how to configure these endpoints.
  </Step>

  <Step title="Move the files to your modules/ folder">
    After having reviewed and edited the generated files, you need to move them from `tmp/` into a custom-defined module inside your `modules/`
    directory in the repository you manage with the Cognite Toolkit. It is up to you to decide how to structure your modules.
    As an example, you could create a module called `infield/` and move all your InField-related config YAML files there.
  </Step>

  <Step title="Deploy the configuration">
    First build the module as usual to make sure there are no errors in the configuration(s):

    ```bash theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
    cdf build -m modules/<your_infield_module>/
    ```

    Next, deploy the configuration(s) with the Cognite Toolkit:

    ```bash theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
    cdf deploy
    ```
  </Step>
</Steps>

After you deploy, open each InField location in the target project and confirm that `dataStorage` and `viewMappings` point to the correct spaces and views.

<a id="migrate-infield-data" />

## Migrate InField data

<Warning>Read the next sections **carefully** before you run any of the mentioned commands, as they make changes to your data.</Warning>

Before you migrate InField data in a production CDF project, Cognite **strongly recommends** copying a snapshot to a development or staging project and running the migration there first. That lets you find mapping or data issues before you touch production.

### Copy legacy InField data to a development project

Use the Cognite Toolkit [data plugin](/cdf/deploy/cdf_toolkit/guides/plugins/data_plugin) to download from the legacy InField model `cdf_apm:ApmAppData(version=v13)` in production and upload to development or staging. To include edges, enable `extend-download = true` in `cdf.toml` as shown [above](#enable-infield-alpha-flags).

Download all InField-related data:

```bash theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
cdf --env-path my_production_credentials.env data download instances \
  --schema-space cdf_apm \
  --view Observation/v5 \
  --view Checklist/v7 \
  --view Template/v8 \
  --view ConditionalAction/v1 \
  --view MeasurementReading/v4 \
  --view Action/v1 \
  --view Condition/v1 \
  --view ChecklistItem/v7 \
  --view TemplateItem/v7 \
  --view Schedule/v4 \
  --include-edges \
  --limit -1
```

Use `--env-path` here because these commands target different environments — downloading from
production and uploading to dev/staging — while your active `cdf.toml` points to only one of them.
Alternatively, you can manually replace the credentials in your `.env` file with the production credentials,
but take care not to mix up credentials for different environments.

After downloading the data, upload it to the development or staging environment:

```bash theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
cdf --env-path my_dev_credentials.env data upload dir data/ApmAppData
```

Before running the migration, deploy the InField configuration from the previous section to the development or staging project. The `cdf migrate infield-data` command requires that configuration in the target environment.

### Run `cdf migrate infield-data`

After you copy the data and deploy the configuration, run `cdf migrate infield-data` to write legacy InField data to the new model:

```bash theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
cdf migrate infield-data
```

The command prompts you interactively to select the legacy InField app space and the new InField app space.

By default, `cdf migrate infield-data` migrates observations to the view you configured under `viewMappings.observation`
in the InField location(s) that target your `appInstanceSpace` (see [Migrate InField configurations](#migrate-infield-configurations)), falling back to the default
`cdf_infield/FieldObservation` view if you did not configure a custom one.

If you use a custom observation view, this is a **best-effort** migration. It works well when your custom view is a simple extension of `FieldObservation`, i.e.
you have all the same properties as `FieldObservation` and you only added the `sapStatus` and `notificationIdInSap` fields to support [SAP writeback for custom observation views](#sap-writeback-for-custom-observation-views).

If you renamed property identifiers or changed their types, migrate observations manually using the
`--skip-observations` flag and follow [Migrate to custom observations](#migrate-to-custom-observations). If you are unsure whether your view can
be migrated automatically, run the command without `--skip-observations` first and inspect the migration logs.
See [Migrate to custom observations](#migrate-to-custom-observations) for example warnings to look for.

```bash theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
cdf migrate infield-data --skip-observations
```

<Warning>
  If multiple InField locations target the same `appInstanceSpace` but you have configured **different** observation views for them, `cdf migrate infield-data`
  fails with an error, since it can't unambiguously determine which view to migrate to. Use `--skip-observations` in this case, or update your location
  configurations so they all reference the same observation view before migrating.
</Warning>

### Verify the migration

In the development or staging project, confirm that observations, checklists, templates, and related data appear in the expected spaces and CDM views. Review the migration logs for errors before you proceed to production or optional phases.

<a id="sap-writeback-for-custom-observation-views" />

## SAP writeback for custom observation views

<Note>
  This section is **only** relevant if you use InField's SAP writeback functionality. If you don't send observations to SAP, skip this section.
</Note>

Classic InField's Observation `status` field served two purposes: business workflow state (for example, `Draft` or `Completed`)
and SAP send-state (for example, `Sent` or `Not sent`). The new InField observation types split this into two dedicated fields:
a dedicated business `status` field, and — if you need SAP writeback — two additional fields that mirror the legacy SAP-specific behavior:

| Field                 | Purpose                                                                    |
| --------------------- | -------------------------------------------------------------------------- |
| `sapStatus`           | The SAP send-state, equivalent to the SAP-specific legacy `status` values. |
| `notificationIdInSap` | The SAP notification ID, equivalent to the legacy `sourceId`.              |

Add both fields to your custom observation view if you want InField to support sending observations to SAP. `cdf migrate infield-data`
detects these fields on your configured view and automatically splits the legacy `status` value into the new `status` and `sapStatus`
fields:

| Legacy `status` value                                | New `status`   | New `sapStatus` | `notificationIdInSap`          |
| ---------------------------------------------------- | -------------- | --------------- | ------------------------------ |
| `Sent`                                               | `Completed`    | `sent`          | Copied from legacy `sourceId`. |
| `Not sent`                                           | `Completed`    | `notSent`       | Not set.                       |
| `File not sent`                                      | `Completed`    | `fileNotSent`   | Copied from legacy `sourceId`. |
| `Pending`                                            | `Draft`        | `pending`       | Not set.                       |
| `Failed`                                             | `Completed`    | `notSent`       | Not set.                       |
| Any other value (for example `Draft` or `Completed`) | Migrated as-is | Not set         | Not set.                       |

If your configured observation view doesn't have both the `sapStatus` and `notificationIdInSap` properties, `cdf migrate infield-data`
still migrates the business `status` (as `Completed` or `Draft`, per the table above), but reports an error for every affected observation
because the SAP send-state can't be preserved. If this happens, make sure to add the two properties to your view and run the migration again.

An example container that defines the two SAP writeback properties could look like this. `pending` is included because the migration
itself can now write it (for legacy `Pending` observations), and it's also the state InField sets on `sapStatus` while a live writeback
request is in flight.

```yaml title="containers/observation_sap_writeback.container.yaml" theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
space: sp_customer_idm
externalId: ObservationSapWriteback
name: Observation SAP writeback
usedFor: node
properties:
  sapStatus:
    type:
      type: enum
      values:
        pending:
          name: Pending
        sent:
          name: Sent
        notSent:
          name: Not sent
        fileNotSent:
          name: File not sent
    immutable: false
    nullable: true
    autoIncrement: false
    name: SAP Status
    description: SAP writeback send-state of the notification
  notificationIdInSap:
    type:
      list: false
      collation: ucs_basic
      type: text
    immutable: false
    nullable: true
    autoIncrement: false
    name: Notification ID in SAP
```

Your custom observation view then includes these properties, alongside whatever other properties it maps or extends from `FieldObservation`:

```yaml title="views/observation.view.yaml" theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
space: sp_custom_space
externalId: ObservationView
version: v1
name: Observation
implements: # Note: Using implements is not required
  - space: cdf_infield
    externalId: FieldObservation
    version: v1
    type: view
properties:
  sapStatus:
    container:
      space: sp_custom_space
      externalId: ObservationSapWriteback
      type: container
    containerPropertyIdentifier: sapStatus
    name: SAP Status
  notificationIdInSap:
    container:
      space: sp_custom_space
      externalId: ObservationSapWriteback
      type: container
    containerPropertyIdentifier: notificationIdInSap
    name: Notification ID in SAP
```

<a id="migrate-to-custom-observations" />

## Migrate to custom observations (optional)

<Note>
  Perform this step only if your custom observation view has changes that the best-effort migration in
  [Migrate InField data](#migrate-infield-data) cannot handle, such as renamed, removed, or restructured properties.
</Note>

If your legacy InField setup uses a custom observation view with property changes beyond what `cdf migrate infield-data` can
automatically map, migrate those observations outside the standard process. Skip this section if you use the default `FieldObservation` view, or if you see no warnings or errors when migrating observations.

You'll typically discover you need this section by running `cdf migrate infield-data` without `--skip-observations` first, and finding
one or more of these warnings in the migration logs:

| Log message                                                                                                                                                                                                        | What it means                                                                                                                                                    | What to do                                                                                                        |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `Destination instance is missing property 'name'.`                                                                                                                                                                 | A legacy property maps to a property (here, `name`) that doesn't exist on your custom view, typically because you renamed or removed it.                         | Follow the steps below to migrate observations manually instead.                                                  |
| `Failed to convert property 'status' with value 'Draft': Value 'draft' is not a valid enum value. Available values: ...`                                                                                           | Your view's enum property (here, `status`) doesn't define one of the legacy values.                                                                              | Either add the missing enum value to your view and re-run the migration, or follow the steps below.               |
| `Failed to convert property 'priority' with value 'High': Cannot convert High to int32.`                                                                                                                           | Your view's property (here, `priority`) has a different, incompatible type than the legacy value, for example an `int32` property when the legacy value is text. | Change the property to a compatible type and re-run the migration, or follow the steps below.                     |
| `Observation has SAP writeback status 'Sent' but the configured observation view ... does not have the required target 'sapStatus' and/or 'notificationIdInSap' properties required to migrate this status value.` | Your view is missing one or both of the [SAP writeback](#sap-writeback-for-custom-observation-views) fields.                                                     | Add `sapStatus` and `notificationIdInSap` to your view before migrating if you need to retain the SAP send-state. |

These warnings don't stop the migration — the observation is still written with whatever properties **did** convert successfully. If you
fix your view afterwards, you can safely rerun `cdf migrate infield-data`: it upserts observations by external ID, so re-migrating
updates the existing instances instead of duplicating them.

With the exception of the SAP writeback warning, if you see any of these errors or similar, it means your defined view is not supported by
the migration plugin, and will need to be handled by custom scripting. You might only need to migrate a few of the observations or some of
the properties manually however, use the generated log messages to help you identify the gap.

Here is a suggested step-by-step process to migrate the observations manually:

<Steps>
  <Step title="Download the legacy observations data">
    ```bash theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
    cdf data download instances
    ```

    When you run the command, select the `cdf_apm:ApmAppData (version=v13)` data model, then the
    `cdf_apm:Observation(version=v5)` view. You can choose to include only certain instance spaces if you want. You
    do not need to include edges for this view. Finally, make sure you select `csv` (or `parquet`) as the output format.
  </Step>

  <Step title="Run your custom migration script">
    You can now run your custom migration script to read the downloaded data, transform it and write it to a file that
    you can upload to the new observations view. See example script below for inspiration on how to do this.
  </Step>

  <Step title="Create a manifest file for the new observation view">
    This manifest tells the Cognite Toolkit where to upload the transformed data. See below for an example.
  </Step>

  <Step title="Upload your custom observations data">
    You can now upload the transformed data to the new observations view using the [data plugin](/cdf/deploy/cdf_toolkit/guides/plugins/data_plugin) in the Cognite Toolkit:

    ```bash theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
    cdf --env-path my_dev_credentials.env data upload dir data/infield_custom_observations
    ```
  </Step>
</Steps>

The following example shows a custom migration script. Tweak it to fit your specific use case and mapping logic:

```python title="custom_migration_script.py" theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
import pandas as pd
from pathlib import Path

observation_df = pd.read_csv("data/ApmAppData/Observation_v5_node-part-0000.Instances.csv")

# Example: Selecting the legacy columns that should be brought over into your custom view (removing unnecessary columns).
# The legacy Observation view includes (among others): sourceId, source, title, description, labels, visbility, etc.
new_observation_df = observation_df[
    ["sourceId", "source", "title", "description", "labels", "visibility", "status"]
].copy()

# Example: If your custom view uses enum values whose values differ from how they were represented in the legacy view, map them here.
# E.g. if your view uses "active"/"resolved" instead of "open"/"closed":
status_mapping = {"open": "active", "closed": "resolved"}
new_observation_df["status"] = (
    new_observation_df["status"].map(status_mapping).fillna(new_observation_df["status"])
)

# Example: It is likely your custom view uses different property names than the legacy Observation view, 
# so we rename some of the columns here from the property name in the legacy view to the property name in the new view.
new_observation_df = new_observation_df.rename(columns={
    "assignedTo": "assignee",
    "troubleshooting": "troubleshootingDescription",
    # add more mappings as needed
})

# Write to the new space — must differ from the legacy appDataInstanceSpace.
new_observation_df["space"] = "new_infield_write_space"

# Write to upload folder
destination_path = Path("data/infield_custom_observations/custom_observation1.csv")
destination_path.parent.mkdir(parents=True, exist_ok=True)
new_observation_df.to_csv(destination_path, index=False)

```

An example of a manifest file for the new observation view could look like this:

```yaml title="data/infield_custom_observations/custom_observation1.Manifest.yaml" theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
kind: Instances
type: instanceView
instanceType: node
view:
    space: <destination space for the new observation view>
    externalId: <externalId of the new observation view>
    version: <version of the new observation view>
```

<a id="migrate-infield-source-data" />

## Migrate InField source data (optional)

<Note>
  This step is **optional** and only relevant if your InField project uses work orders (activities), operations, and notifications stored in the `APM_SourceData` data model and you have not already populated your IDM model.
</Note>

`cdf migrate infield-source-data` migrates the work order data stored in the legacy `APM_SourceData` data model — specifically maintenance orders (activities), operations, and notifications — into the `cdf_idm` views used by InField on CDM.

**Prerequisites:** Assets must be migrated and InField configurations must be deployed before running this command. The command reads destination spaces and views from the deployed InField CDM location configs, and resolves asset cross-references through the `CogniteMigration` model.

### Required capabilities

| Capability type      | Action                     | Scope                                     | Description                                                                              |
| -------------------- | -------------------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------- |
| Data model instances | `datamodelinstances:read`  | The APM\_SourceData source space(s)       | Read APM\_Activity, APM\_Operation, and APM\_Notification nodes to migrate.              |
| Data model instances | `datamodelinstances:read`  | `APM_Config`                              | Read the APMConfig node to determine source views and instance spaces.                   |
| Data model instances | `datamodelinstances:read`  | The InField CDM location config space     | Read deployed InField CDM location configs to determine target spaces and views.         |
| Data model instances | `datamodelinstances:write` | The target instance space(s)              | Write migrated CogniteMaintenanceOrder, CogniteOperation, and CogniteNotification nodes. |
| Data model instances | `datamodelinstances:read`  | `CogniteMigration` instance space         | Resolve asset cross-references via the `InstanceSource` view.                            |
| Data models          | `dataModels:read`          | `APM_SourceData`, `APM_Config`, `cdf_idm` | Read the source and destination data model definitions.                                  |

### How source and target spaces are determined

The command prompts you interactively to select a source space and a target space. The available choices are derived automatically from deployed config — **you cannot specify arbitrary spaces**.

**Source space candidates** are collected from your `APM_Config` node.

**Target space candidates** are any space that appears as the entry in `dataFilters.maintenanceOrders.instanceSpaces`, `dataFilters.operations.instanceSpaces`, or `dataFilters.notifications.instanceSpaces` (these are expected to use the same space for a location) on any deployed InField CDM location config.

<Note>
  InField only uses the **first** entry in each `instanceSpaces` array at runtime, so only that space qualifies as a candidate target.
</Note>

### How destination views are determined

For each entity type, the command checks `viewMappings.<type>` on every deployed InField CDM location config
whose `dataFilters.<type>s.instanceSpaces[0]` matches the selected **target space**:

| Among those locations…                         | Destination view used                                                                                     |
| ---------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| None specify a custom view for this type       | Default `cdf_idm` view — `CogniteMaintenanceOrder/v1`, `CogniteOperation/v1`, or `CogniteNotification/v1` |
| Those that do all specify the same custom view | That custom view                                                                                          |
| Those that do specify different custom views   | Falls back to the default `cdf_idm` view                                                                  |

Locations without a `viewMappings` entry for a given type are skipped and do not affect conflict detection.

<Warning>
  If multiple InField locations target the same instance space but configure **different** custom views for the same entity type, `cdf migrate infield-source-data` will log a warning and fall back to the default `cdf_idm` view for that entity type. Align all affected locations to the same view before migrating to use a custom view.
</Warning>

### Property mapping

These are the mappings applied out of the box. If you use custom views with different property identifiers, some of these mappings may fail to apply.
Similarly, some properties are intentionally ignored out of the box, as they have no equivalent in the `cdf_idm` view(s). You may create a custom view to replace the `cdf_idm` views
if you want to bring these properties over, but for the migration script to be able to handle mapping these, the target property needs to use the exact same identifier and type as the source property.

**APM\_Activity → CogniteMaintenanceOrder**

| APM\_Activity          | CogniteMaintenanceOrder | Notes                                                          |
| ---------------------- | ----------------------- | -------------------------------------------------------------- |
| `id`                   | `sourceId`              |                                                                |
| `title`                | `name`                  |                                                                |
| `startTime`            | `scheduledStartTime`    |                                                                |
| `endTime`              | `scheduledEndTime`      |                                                                |
| `actualStartTime`      | `startTime`             |                                                                |
| `actualEndTime`        | `endTime`               |                                                                |
| `assetExternalId`      | `mainAsset`             | Resolved via `InstanceSource` in the `CogniteMigration` model. |
| `assetExternalIds`     | `assets`                | Resolved via `InstanceSource` in the `CogniteMigration` model. |
| `node.createdTime`     | `sourceCreatedTime`     |                                                                |
| `node.lastUpdatedTime` | `sourceUpdatedTime`     |                                                                |
| `createdBy`            | `sourceCreatedUser`     |                                                                |
| `mutatedBy`            | `sourceUpdatedUser`     |                                                                |

These properties are ignored by default as they have no equivalent in `CogniteMaintenanceOrder`: `assignedTo`, `subActivities`, `functionalLocation`, `isInApp`, `resource`, `rootLocation`, `source`, `cloneOf`, `isMutated`, `mutatedFields`, `createdAt`, `lastUpdatedDate`, `isArchived`.

**APM\_Operation → CogniteOperation**

| APM\_Operation         | CogniteOperation         | Notes                                                                                                                              |
| ---------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
| `id`                   | `sourceId`               |                                                                                                                                    |
| `title`                | `name`                   |                                                                                                                                    |
| `order`                | `sequence`               |                                                                                                                                    |
| `mainResource`         | `mainDiscipline`         |                                                                                                                                    |
| `numberOfMainResource` | `numberOfMainDiscipline` |                                                                                                                                    |
| `parentActivityId`     | `maintenanceOrder`       | Converted to `NodeId(space=target_space, external_id=parentActivityId)` referencing the equivalent `CogniteMaintenanceOrder` node. |
| `labels`               | `tags`                   |                                                                                                                                    |
| `assetExternalId`      | `mainAsset`              | Resolved via `InstanceSource`.                                                                                                     |
| `startTime`            | `scheduledStartTime`     |                                                                                                                                    |
| `endTime`              | `scheduledEndTime`       |                                                                                                                                    |
| `node.createdTime`     | `sourceCreatedTime`      |                                                                                                                                    |
| `node.lastUpdatedTime` | `sourceUpdatedTime`      |                                                                                                                                    |
| `createdBy`            | `sourceCreatedUser`      |                                                                                                                                    |
| `mutatedBy`            | `sourceUpdatedUser`      |                                                                                                                                    |

Ignored by default: `cloneOf`, `source`, `type`, `isInApp`, `createdAt`, `isMutated`, `files`, `lastUpdatedDate`, `subOrder`.

**APM\_Notification → CogniteNotification**

| APM\_Notification      | CogniteNotification | Notes                          |
| ---------------------- | ------------------- | ------------------------------ |
| `id`                   | `sourceId`          |                                |
| `title`                | `name`              |                                |
| `assetExternalId`      | `asset`             | Resolved via `InstanceSource`. |
| `createdDate`          | `sourceCreatedTime` |                                |
| `node.lastUpdatedTime` | `sourceUpdatedTime` |                                |

Ignored by default: `rootLocationExternalId`, `system`, `Country`, `subSystem`, `source`.

### Caveats and known limitations

* **Asset references must be migrated first.** If an asset has not been migrated, the asset reference is omitted from the output node and an error is written to the migration log.
* **Source view variation between projects.** Projects configured with `featureConfiguration.viewMappings` read from different source views than the default, which may mean your configuration is not reflective of what is described in this documentation.
* **Idempotent upserts.** Re-running the migration after fixing errors is safe — existing nodes are updated rather than duplicated.

### Migrate to a development or staging environment first

As with [Migrate InField data](#migrate-infield-data), we **recommend** copying a snapshot of the source data to a
development or staging project and run `cdf migrate infield-source-data` there before you run it in production.
Use the Cognite Toolkit [data plugin](/cdf/deploy/cdf_toolkit/guides/plugins/data_plugin) to download the source data from
production and upload it to the development or staging environment.

<Note>
  The `--env-path` flag specifies which credentials file to use. It is needed
  here because these commands target different environments — downloading from
  production and uploading to dev/staging — while your active `cdf.toml`
  points to only one of them.
</Note>

Download the InField source data (if you use a different model from `APM_SourceData`, adjust the values accordingly):

```bash theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
cdf --env-path my_production_credentials.env data download instances \
  --schema-space APM_SourceData \
  --view MaintenanceOrder/v1 \
  --view Operation/v1 \
  --view Notification/v1 \
  --limit -1
```

Then upload it to the development or staging environment:

```bash theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
cdf --env-path my_dev_credentials.env data upload dir data/APM_SourceData
```

`cdf migrate infield-source-data` also needs to read the `APM_Config` node to determine the source space candidates, so populate
that too:

```bash theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
cdf --env-path my_production_credentials.env data download instances \
  --schema-space APM_Config \
  --view APM_Config/1 \
  --limit -1
```

```bash theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
cdf --env-path my_dev_credentials.env data upload dir data/APM_Config
```

Before running the migration, make sure you have also deployed the InField configuration to the development or staging
project — `cdf migrate infield-source-data` relies on the deployed InField CDM location configs to determine target
spaces and views.

### Running the migration

```bash theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
cdf migrate infield-source-data
```

To skip the interactive prompts:

```bash theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
cdf migrate infield-source-data --source-space APM_SourceData --target-space my_infield_instance_space
```

<Note>
  You must provide both `--source-space` and `--target-space` together, or neither.
</Note>

<a id="run-the-migration-in-production" />

## Run the migration in production

Once you have verified the migration in development or staging — with no unexpected data issues or mapping errors — run the same steps in production:

1. Deploy the InField configurations you reviewed and tested (if you deployed them only to development or staging earlier).
2. Run `cdf migrate infield-data` (with `--skip-observations` if applicable).
3. Optionally run `cdf migrate infield-source-data`.

Confirm that InField locations open as expected and that observations, checklists, and source data appear in the configured CDM views.

## What `cdf migrate infield-configs` does

When you run `cdf migrate infield-configs`, the Cognite Toolkit performs the following steps:

<Steps>
  <Step title="Download the legacy InField configuration">
    The Cognite Toolkit downloads the nodes in the `APM_Config:APM_Config(version=1)` data model.
  </Step>

  <Step title="Create new InField configuration templates">
    For each location, the Cognite Toolkit creates a new InField configuration template based on the legacy configuration
    and writes it to the `tmp/` folder by default. The template lookup reads the root asset from the `InstanceSource` view
    in the `CogniteMigration` data model, so assets must be migrated and the root asset must be set up in `InstanceSource`
    before you run this command.
  </Step>

  <Step title="Create location filters">
    For each location, the Cognite Toolkit creates a location filter that you can optionally deploy to
    filter on location in the Search UI.
  </Step>

  <Step title="Write the new configuration templates to files">
    The Cognite Toolkit writes the new configuration templates to the `tmp/` folder by default. Move these files
    to your `modules/` folder and deploy them — the Cognite Toolkit does not deploy templates automatically
    because you are expected to review and edit them first.
  </Step>
</Steps>

## What `cdf migrate infield-data` does

When you run `cdf migrate infield-data`, the Cognite Toolkit performs the following steps in batches:

<Steps>
  <Step title="Download nodes and edges from the legacy InField model">
    The Cognite Toolkit downloads the nodes and edges in the `cdf_apm:ApmAppData(version=v13)` data model that have
    properties in the `ConditionalAction`, `Observation`, `Checklist`, `MeasurementReading`, `Template`,
    `Action`, `Condition`, `ChecklistItem`, `TemplateItem`, and `Schedule` views.
  </Step>

  <Step title="Convert legacy InField nodes to the new InField model">
    For each downloaded node, the Cognite Toolkit converts it to the new InField model using the mapping rules defined by the InField team.
    It also looks up the `InstanceSource` view in the `CogniteMigration` data model to find the corresponding assets,
    time series, and files that the InField data references, and updates those references to point to the nodes in the new model.
  </Step>

  <Step title="Write the new InField data to CDF">
    The Cognite Toolkit writes the new InField data to CDF, to the system model `cdf_infield:InFieldOnCMD(version=v1)`.
  </Step>
</Steps>

## Further reading

* [Migrating assets](/cdf/deploy/cdf_toolkit/guides/plugins/migration_plugin/assets)
* [Migrating files and time series](/cdf/deploy/cdf_toolkit/guides/plugins/migration_plugin/files_timeseries)
* [Migration overview](/cdf/deploy/cdf_toolkit/guides/plugins/migration_plugin/steps)
