# Schedules (Beta)



<Callout type="warn">
  This feature is currently in beta and may be subject to change. Before using it in production, please contact your Socotra representative.
</Callout>

Overview [#overview]

Schedules are purpose-built to support insurance products that need to manage very large lists of like-typed items such as fleets of vehicles, property inventories, or collections of valuable articles. These lists can contain tens or even hundreds of thousands of items. Schedules allow insurers to efficiently collect, manage, and rate these items as part of the policy record, without sacrificing performance or clarity.

Schedules are especially relevant for:

* Commercial Auto (e.g., listing 10,000+ fleet vehicles)
* Homeowners (e.g., scheduled personal property)
* Commercial Property (e.g., inventories of buildings or contents)

Each item in a schedule is structured, ratable (if needed), and independently addressable, while being logically grouped under a parent policy element (such as a `Fleet` or `Location`).

Use Cases [#use-cases]

* **Fleet Management**: Commercial Auto policies can include a schedule of hundreds of thousands of vehicles, each individually priced and editable.
* **Property Schedules**: Warehouses or commercial buildings can manage long lists of insured contents with granular data capture.
* **Non-ratable Schedules**: Policies can include schedules of beneficiaries, dependents, or other tracked data that doesn't influence premium, but needs to be persisted and auditable.

<Callout>
  Similar use cases can also be implemented leveraging conventional `elements` for the data structure. However, these are not as performant, and we strongly recommend using `schedules` for lists exceeding 2,000 items. A system-imposed limitation is likely in a future release. Such changes will be announced in advance to allow for migration.
</Callout>

Key Capabilities [#key-capabilities]

* Add schedules to any policy element, of any category and type, and at any level (e.g., per `Fleet` or `Location` as modeled using `policyLines`, `exposureGroups`, or `exposures`)

* Each schedule supports:
  * Arbitrary numbers of schedule items (tested up to 500,000 per policy)
  * Custom structured data per schedule type
  * Multiple like-type and/or unique schedule types per policy
  * Automatic generation of unique item identifiers (locators)
  * Addition, modification, and removal of items, individually or in bulk
  * Persisting and accessing granular rates for multiple charge types on each schedule item

* Schedule item types are fully configurable and can be reused across products

* High-performance API support for pricing, quoting, and data extraction

Configuration [#configuration]

Schedules are associated with an element within the product data model.
To configure a schedule, the element definition includes a `schedule` entry identifying the type of items the schedule will contain:

```json
"Fleet": {
  "charges": ["CollisionPremium", "LiabilityPremium"],
  "schedule": "Vehicle"
}
```

Schedule item types are defined globally in the configuration schema:

```json
"schedules": {
  "Vehicle": {
   "data": {
     "year": {},
     "make": {},
     "model": {}
   }
   "resetOnRenewal" : true // Optional, defaults to false, used to ensure that the schedule is cleared on renewal
  }
}
```

Only one schedule instance per element instance on a quote or policy is supported. As a result, the schedule type will be consistent across all instances of a given element type. Multiple unique schedule types can be defined and leveraged as long as the parent element type is also unique.

Data Structure [#data-structure]

Schedule types leverage extension data, and just like elements can support deeply nested structures and inheritance. However, for performance reasons, the bulk upload of schedule items via CSV is limited to flat data structures with primitive typed fields.

Charges and Pricing [#charges-and-pricing]

Each schedule item can be rated independently. As the rating process executes, the following steps are performed:

* A `RatingRegistry` persists the pre-aggregation rating data for each schedule item for future reference (via method within the rating plugin)
* All schedule item rates aggregated per charge type (via method within the rating plugin)
  * A "whole schedule" rate is computed as the sum of all individual item rates by charge type and from this a single synthetic charge is created (automatically by the system)
  * If both schedule and element-level charges of the same type exist, they are summed into a single synthetic charge (automatically by the system)

Example: If a `Fleet` contains 10,000 scheduled vehicles, each with a rate of 23.5 for the `collisionPremium` charge type, the synthetic charge applied to the `Fleet` post rating will have a rate equal to the sum of the individual scheduled item's rates:

```json
{ "collisionPremium": { "rate": 235000.0 } }
```

Only the synthetic (aggregated) charges are submitted to billing, not each individual scheduled item, ensuring optimal billing performance even for large schedules.

Plugin Interfaces [#plugin-interfaces]

Given the complexity and performance requirements of schedules, the system provides specific plugin interfaces to handle the rating and data management:

**Schedule Stream**: The schedule itself is not passed to the plugin as part of the quote or transaction request. Instead the plugin can access the schedule via the `SchedulesFactory` and generate a stream of items for processing using the `.stream()` method.

<Callout>
  Use the `updateScheduleItems` API to update the schedule items in the schedule and trigger a re-rating of the schedule items that have changed. If no changes are detected, the `SchedulesFactory.getQuoteSchedule()` and `SchedulesFactory.getTransactionSchedule()` will not return schedule items during the rating process.
</Callout>

```java
// Quote Request
// Fetch locator for the fleet element in the quote
ULID fleetElementLocator = request.quote().element().elements().stream()
                    .filter(e -> "FleetQuote".equals(e.type()))
                    .map(Element::staticLocator)
                    .findFirst().orElseThrow();

Schedule schedule = SchedulesFactory.getQuoteSchedule(request.quote(), fleetElementLocator);
Stream<VehicleScheduleItem> vehiclesStream = schedule.stream();

// Transaction Request
// Fetch locator for the fleet element in the transaction
ULID fleetElementLocator = segment.element().elements().stream()
                    .filter(e -> "FleetPolicy".equals(e.type()))
                    .map(Element::staticLocator)
                    .findFirst().orElseThrow();
Schedule schedule = SchedulesFactory.getTransactionSchedule(request.transaction(), fleetElementLocator)
Stream<VehicleScheduleItem> vehiclesStream = schedule.stream();
```

**Rating Registry**: The rating plugin can create a `RatingRegistry` to provide a granular view of the rating items for each charge type, of each item in the schedule;

* The registry is instantiated via the `RatingRegistryFactory` class by passing the quote or transaction and the desired `Chargeable` (the element schedule of interest is associated with) to the `createFor` method.
* Once created, the registry's input parameter is a `RatingItem`, the same as is used for other rating operations.

```java
// Create a rating registry for the quote
RatingRegistry registry = RatingRegistryFactory.createFor(quote, fleet);

// Create a rating registry for the transaction
RatingRegistry registry = RatingRegistryFactory.createFor(request.transaction(), fleet);
```

**Consuming Schedule Stream**: The stream of schedule items can be processed using the standard Java Stream API which allows for efficient processing of large datasets without loading everything into memory at once. Here a lambda expression is used to process each item:

```java
vehiclesStream.map(vehicle -> rateVehicle(vehicle))
        .filter(Objects::nonNull)
        .flatMap(List::stream)
        .forEach(registry::register);
```

Here, the following happens:

1. Each vehicle in the stream is processed by the `rateVehicle` method, which returns a list of `RatingItem` objects.
2. The `filter` operation removes any null results from the rating.
3. The `flatMap` operation flattens the list of lists into a single stream
4. Finally, each `RatingItem` is registered in the `RatingRegistry` via the `register` method.

**Aggregating Schedule Items Rates**: Once rating for schedule items has completed, the rating registry can be used to aggregate the rates for each charge type across all items in the schedule.

```java
// Aggregate rates for each charge type, stream to a list and add to the RatingItems list to be returned from the rating plugin
ratingItems.addAll(registry.aggregate().stream().toList());
```

<span id="CustomScheduleItemProcessing" />

Custom Schedule Item Processing [#custom-schedule-item-processing]

The Deserialization Plugin can be used to define asynchronous processing logic for large lists of schedule items.

When you upload a file to add schedule items to a quote or transaction, you'll receive a job identifier to track progress. The target quote or policy transaction will be locked for modification while the platform is executing the plugin code and deserializing records in the file. The <ApiLink name="DeserializationJob" /> API response allows you to track job progress.

The <ApiLink name="uploadDeserializedScheduleItems">Upload Quote Schedule Items for Deserialization</ApiLink> API endpoint can be used to upload a file for a quote, and the <ApiLink name="uploadDeserializedTransactionSchedule">Upload Transaction Schedule Items for Deserialization</ApiLink> API endpoint can be used to upload a file for a policy transaction.

Here's an example configuration with a `FleetTrip` schedule:

```json
{
	"FleetTrip": {
		"data": {
			"date": {
				"type": "date",
				"displayName": "Start Date"
			},
			"distance": {
				"type": "int"
			},
			"note": {
				"type": "string",
				"displayName": "Note"
			}
		}
	}
}
```

The `DeserializationPlugin` interface defines methods corresponding to each schedule item type defined in your data model. Each method returns a `ScheduleItemData` record corresponding to the schedule item type.

Here's an example implementation:

<Callout>
  The columns of the CSV file must be in the same order (date, distance, note).
</Callout>

```java
public class DeserializationPluginImpl implements DeserializationPlugin {

    private static final Logger log = LoggerFactory.getLogger(DeserializationPluginImpl.class);

    @Override
    public FleetTripScheduleItem.FleetTripScheduleItemData deserializeFleetTripScheduleItemData(DeserializationRequest request) {
        log.info("In DeserializationPluginImpl.deserializeFleetTripScheduleItemData(), with params: {} and {} record: ", request.requestParams(), request.record());
        String[] parts = request.record().split(",");
        return FleetTripScheduleItem.FleetTripScheduleItemData
                .builder()
                .date(LocalDate.parse(parts[0]))
                .distance(Integer.parseInt(parts[1]))
                .note(parts[2])
                .build();
    }
}
```

API Usage [#api-usage]

Schedules are fully supported across all policy lifecycle stages and are subject to the same data governance as other policy elements.

Quotes [#quotes]

<ApiEndpoint name="addScheduleItems" title="Add items to schedule" />

<ApiSchema name="AddScheduleItemRequest" />

API requests to add items to a schedule are limited to 500 items

<ApiEndpoint name="uploadScheduleItems" title="Upload a CSV of schedule items" />

CSV uploads of schedule items only support flat item data structures; i.e., no nested objects in the schedule definition.

<ApiEndpoint name="deleteScheduleItems" title="Delete an item from schedule" />

Delete individual items from a schedule by specifying their locator within the string array of the request.

<ApiEndpoint name="updateScheduleItems" title="Update a schedule item" />

<ApiSchema name="PatchScheduleItemRequest" />

Transactions [#transactions]

<ApiEndpoint name="addTransactionSchedule" title="Add items to schedule" />

<ApiSchema name="AddScheduleItemRequest" />

API requests to add items to a schedule are limited to 500 items

<ApiEndpoint name="uploadTransactionSchedule" title="Upload a CSV of schedule items" />

CSV uploads of schedule items only support flat item data structures; i.e., no nested objects in the schedule definition.

<ApiEndpoint name="deleteTransactionSchedule" title="Delete an item from schedule" />

Delete individual items from a schedule by specifying their locator within the string array of the request.

<ApiEndpoint name="updateTransactionSchedule" title="Update a schedule item" />

<ApiSchema name="PatchScheduleItemRequest" />

Upload a schedule file for asynchronous processing on a quote [#upload-a-schedule-file-for-asynchronous-processing-on-a-quote]

<ApiEndpoint name="uploadDeserializedScheduleItems" title="Upload Quote Schedule Items for Deserialization" />

<ApiSchema name="DeserializationResponse" />

Upload a schedule file for asynchronous processing on a transaction [#upload-a-schedule-file-for-asynchronous-processing-on-a-transaction]

<ApiEndpoint name="uploadDeserializedTransactionSchedule" title="Upload Transaction Schedule Items for Deserialization" />

<ApiSchema name="DeserializationResponse" />

Deserialization jobs can be listed, fetched, terminated, and restarted. See the [deserialization jobs section of the Jobs API](/api/configuration-and-development/jobs#deserialization-jobs) for details.

Data & Reporting [#data--reporting]

API [#api]

Rating registries can be retrieved in CSV format via the API to access the pre-aggregation rating data for each schedule item.

<ApiEndpoint name="getRatingRegistry" title="Get Rating Registry" />

Data Lake [#data-lake]

An upcoming release of the Data Lake will include Schedules and Rating Registries.

See Also [#see-also]

* [Quote Schedule API](/api/quotes/quotes-schedules)
* [Transaction Schedule API](/api/policy-management/policy-transactions-schedules)


## API Reference

PUT /policy/{tenantLocator}/quotes/{locator}/schedules/{staticElementLocator} — addScheduleItems
Tags: quotes-controller
Permissions: write, schedule-add
Parameters:
  tenantLocator (uuid, path, required)
  locator (ulid, path, required)
  staticElementLocator (ulid, path, required)
Request body (AddScheduleItemRequest[]):
Responses:
  200 ValidationResult — OK

POST /policy/{tenantLocator}/quotes/{locator}/schedules/{staticElementLocator} — uploadScheduleItems
Tags: quotes-controller
Permissions: write, schedule-add
Parameters:
  tenantLocator (uuid, path, required)
  locator (ulid, path, required)
  staticElementLocator (ulid, path, required)
Responses:
  200 ValidationResult — OK

DELETE /policy/{tenantLocator}/quotes/{locator}/schedules/{staticElementLocator} — deleteScheduleItems
Tags: quotes-controller
Permissions: write, schedule-delete
Parameters:
  tenantLocator (uuid, path, required)
  locator (ulid, path, required)
  staticElementLocator (ulid, path, required)
Request body (ulid[]):
Responses:
  200 — OK

PATCH /policy/{tenantLocator}/quotes/{locator}/schedules/{staticElementLocator} — updateScheduleItems
Tags: quotes-controller
Permissions: write, schedule-update
Parameters:
  tenantLocator (uuid, path, required)
  locator (ulid, path, required)
  staticElementLocator (ulid, path, required)
Request body (PatchScheduleItemRequest[]):
Responses:
  200 ValidationResult — OK

PUT /policy/{tenantLocator}/transactions/{locator}/schedules/{staticElementLocator} — addTransactionSchedule
Tags: transactions-controller
Permissions: write, schedule-add
Parameters:
  tenantLocator (uuid, path, required)
  locator (ulid, path, required)
  staticElementLocator (ulid, path, required)
Request body (AddScheduleItemRequest[]):
Responses:
  200 ValidationResult — OK

POST /policy/{tenantLocator}/transactions/{locator}/schedules/{staticElementLocator} — uploadTransactionSchedule
Tags: transactions-controller
Permissions: write, schedule-add
Parameters:
  tenantLocator (uuid, path, required)
  locator (ulid, path, required)
  staticElementLocator (ulid, path, required)
Responses:
  200 ValidationResult — OK

DELETE /policy/{tenantLocator}/transactions/{locator}/schedules/{staticElementLocator} — deleteTransactionSchedule
Tags: transactions-controller
Permissions: write, schedule-delete
Parameters:
  tenantLocator (uuid, path, required)
  locator (ulid, path, required)
  staticElementLocator (ulid, path, required)
Request body (ulid[]):
Responses:
  200 — OK

PATCH /policy/{tenantLocator}/transactions/{locator}/schedules/{staticElementLocator} — updateTransactionSchedule
Tags: transactions-controller
Permissions: write, schedule-update
Parameters:
  tenantLocator (uuid, path, required)
  locator (ulid, path, required)
  staticElementLocator (ulid, path, required)
Request body (PatchScheduleItemRequest[]):
Responses:
  200 ValidationResult — OK

POST /policy/{tenantLocator}/quotes/{locator}/schedules/{staticElementLocator}/deserialize — uploadDeserializedScheduleItems
Tags: quotes-controller
Permissions: write, schedule-add
Parameters:
  tenantLocator (uuid, path, required)
  locator (ulid, path, required)
  staticElementLocator (ulid, path, required)
  params (map<string, string>, query, required)
Responses:
  200 DeserializationResponse — OK

POST /policy/{tenantLocator}/transactions/{locator}/schedules/{staticElementLocator}/deserialize — uploadDeserializedTransactionSchedule
Tags: transactions-controller
Permissions: write, schedule-add
Parameters:
  tenantLocator (uuid, path, required)
  locator (ulid, path, required)
  staticElementLocator (ulid, path, required)
  params (map<string, string>, query, required)
Responses:
  200 DeserializationResponse — OK

GET /plugin/{tenantLocator}/ratingregistries/{category}/{locator}/{elementLocator} — getRatingRegistry
Tags: rating-registries-controller
Permissions: read
Parameters:
  tenantLocator (uuid, path, required)
  Accept-Encoding (string, header)
  category (Enum quote | transaction, path, required)
  locator (ulid, path, required)
  elementLocator (ulid, path, required)
Responses:
  200 — OK

AddScheduleItemRequest
Properties:
  data (map<string, object>, required)

PatchScheduleItemRequest
Properties:
  locator (ulid, required)
  setData (map<string, object>, required)
  removeData (map<string, object>, required)

DeserializationResponse
Properties:
  jobLocator (ulid, required)