Appendix A. Appendix

1. Capacity Planning JDL Samples

This section describes JDL samples of a Capacity Planning application, using either a standard single JDL file or a composite data model with multiple JDL files.

1.1. Capacity Planning Application JDL Sample

In this JDL sample for a simple Data Model application, all entities and relationships are indicated in one single file, entities.jdl.

application {
    // DOM [java.collectorClass] : [CapacityPlanning]
}

entity Country {
    // DOM [primary.keys] : [id]
    id String required,
    name String
}

entity Plant {
    // DOM [primary.keys] : [plantId]
    plantId String required,
    lat Double,
    lng Double,

    @Formula("(SELECT SUM(a.duration_in_hours) FROM activity a WHERE a.plant_id = db_gene_internal_id AND a.db_gene_internal_scenario_id = db_gene_internal_scenario_id)")
    durationInHours Double
}

entity Parameter {
    // DOM  [single.row] : [true]
    // DOM  [java.collectionName] : [Parameter]
    currentTime Instant
}

entity Resource {
    // DOM  [primary.keys] : [id]
    id String required,
    name String
}

entity ResourceCapacity {
    quantity Integer min(1)
}

entity Activity {
    // DOM  [primary.keys] : [id]
    id String required,
    name String,
    durationInHours Duration,
    dueDate Instant
}

entity Precedence {
}

entity Requirement {
    @DefaultValue("1")
    quantity Integer min(1)
}

entity Schedule {
    start Instant,
    end Instant
}

entity SolutionSummary {
    // DOM  [single.row] : [true]
    start Instant,
    end Instant,
    timespan Integer
}

entity ResourceUsagePerDay {
    // DOM  [primary.keys] : [day]
    day Integer,
    numberOfHours Integer
}

relationship ManyToOne {
    // DOM  [affects.primary.key] : [true]
    Requirement{activity} to Activity{requirements},
    // DOM  [affects.primary.key] : [true]
    Requirement{resource} to Resource{requirements},
    // DOM  [affects.primary.key] : [true]
    Precedence{first} to Activity{successors},
    // DOM  [affects.primary.key] : [true]
    Precedence{second} to Activity{predecessors},
    // DOM  [affects.primary.key] : [true]
    Schedule{resource} to Resource{schedules}
    // DOM  [affects.primary.key] : [true]
    Schedule{activity} to Activity{schedules}
    // DOM  [affects.primary.key] : [true]
    ResourceUsagePerDay{resource} to Resource{usagePerDay}
    // DOM  [affects.primary.key] : [true]
    Plant{country} to Country{plants}
    // DOM  [affects.primary.key] : [true]
    Activity{plant} to Plant{activities}
}

relationship OneToOne {
    // DOM  [affects.primary.key] : [true]
    ResourceCapacity{resource} to Resource{capacity}
}

1.2. Capacity Planning Application JDL Samples using a Composite Data Model

In these JDL samples for a Composite Data Model application, capacity_planning.jdl is the main JDL file. It includes all the JDL files of the model, where each one represents a scenario type. For more details, refer to Section Defining a Composite Data Model (CDM).

1.2.1. capacity_planning.jdl

application {
  // DOM [java.collectorClass] : [CapacityPlanning]

  include "master_data.jdl"
  include "delivery_data.jdl"
  include "transactional_data.jdl"
  include "plan_data.jdl"
}

1.2.2. master_data.jdl

@Description("Master data other scenario types")
scenarioType MasterData {
}

@Description("Define a country by an ID and a name")
entity Country {
  // DOM [primary.keys] : [id]
  @Description("ISO 3166-1 alpha-3 codes")
  id String required length(3),
  @Description("Country full name")
  name String
}

entity Plant {
  // DOM [primary.keys] : [plantId]
  plantId String required,
  latitude Double,
  longitude Double

  @DefaultValue("true")
  online Boolean
}

relationship ManyToOne {
  // DOM  [affects.primary.key] : [true]
  Plant{country} to Country{plants}
}

1.2.3. delivery_data.jdl

@Description("This is a scenario type used to store data associated to the delivery")
scenarioType DeliveryData {
}

// Trucks handle the deliveries; they start and end their routes at the depot
entity Truck {
    // DOM [primary.keys] : [id]
    id String,
    depotLongitude Double,
    depotLatitude Double
}

entity TruckRoute {
    // DOM [primary.keys] : [id]
    id String,
    start LocalDateTime,
    end LocalDateTime
}


relationship ManyToOne {
  // DOM  [affects.primary.key] : [true]
  TruckRoute{truck} to Truck{routes}
}

1.2.4. transactional_data.jdl

@Description("Scenario type used to store data for a plan")
scenarioType TransactionalData {
   import MasterData
}

entity Activity {
  // DOM  [primary.keys] : [id]
  id String required,
  name String,
  durationInHours Double min(0) max(4096)
}

entity Precedence {
}

entity Resource {
  // DOM  [primary.keys] : [id]
  id String required,
  name String,
  active Boolean
}

entity ResourceCapacity {
  quantity Integer min(0) max(512)
}

entity Requirement {
  quantity Integer min(1)
}

relationship OneToOne {
  // DOM  [affects.primary.key] : [true]
  ResourceCapacity{resource} to Resource{capacity}
}

relationship ManyToOne {
  // DOM  [affects.primary.key] : [true]
  Requirement{activity} to Activity{requirements},

  // DOM  [affects.primary.key] : [true]
  Requirement{resource} to Resource{requirements},

  // DOM  [affects.primary.key] : [true]
  Precedence{first} to Activity{successors},

  // DOM  [affects.primary.key] : [true]
  Precedence{second} to Activity{predecessors},

  // DOM  [affects.primary.key] : [true]
  Activity{plant} to Plant{activities}
}

1.2.5. plan_data.jdl

@Description("Scenario type used to store data for a plan")
scenarioType PlanData {
   import TransactionalData
   import MasterData
   import DeliveryData
}

entity Schedule {
  // DOM  [primary.keys] : [start, end]
  start Instant required,
  end Instant required
}

entity ResourceUsagePerDay {
  // DOM  [primary.keys] : [day]
  day Integer,
  numberOfHours Integer,
}

entity SolutionSummary {
  // DOM  [single.row] : [true]
  start Instant,
  end Instant,
  timespan Integer
}

// Plant are sending resources to other plants
entity ResourceDelivery {
    start LocalDateTime,
    end LocalDateTime
}

@Description("Calendar with cycle")
entity Calendar {
  // DOM  [java.collectionName] : [Calendars]
  // DOM  [primary.keys] : [id]
  id String required
}


relationship ManyToOne {
  // DOM  [affects.primary.key] : [true]
  ResourceDelivery{resource} to Resource{deliveries}

  // DOM  [affects.primary.key] : [true]
  ResourceDelivery{from} to Plant{shippings}

  // DOM  [affects.primary.key] : [true]
  ResourceDelivery{to} to Plant{deliveries}

  // DOM  [affects.primary.key] : [true]
  ResourceDelivery{route} to TruckRoute{deliveries}

  // DOM  [affects.primary.key] : [true]
  Schedule{resource} to Resource{schedules}

  // DOM  [affects.primary.key] : [true]
  Schedule{activity} to Activity{schedules}

  // DOM  [affects.primary.key] : [true]
  ResourceUsagePerDay{resource} to Resource{usagePerDay}


  Calendar{parentCalendar} to Calendar{subCalendars},

  // DOM  [affects.primary.key] : [true]
  Calendar{schedule} to Schedule{schedules}
}

2. Capacity Planning Data Model Types

This section provides user with details on the entities and relationships generated using the above JDL files of the Capacity Planning sample.

It can be used to fill in the scenario template that corresponds to the application data model, which is available for download from Topbar menu Settings > Application Configuration > Excel Template.

2.1. Calendar

[Note]

In the Capacity Planning Application JDL Samples using a Composite Data Model, this entity relies on the following entities:

  • Schedule

The Calendar entity has the following attributes, which detail the hierarchical structure and scheduling of calendar activities:

  • id - String, required: The unique identifier for the calendar.

  • parentCalendar.id - String: The identifier of the parent calendar.

  • parentCalendar.schedule.end - DateTime: The end date and time of the schedule in the parent calendar.

  • parentCalendar.schedule.start - DateTime: The start date and time of the schedule in the parent calendar.

  • parentCalendar.schedule.activity.id - String: The identifier of the activity in the parent calendar schedule.

  • parentCalendar.schedule.activity.plant.plantId - String: The plant identifier associated with the activity in the parent calendar schedule.

  • parentCalendar.schedule.activity.plant.country.id - String: The country identifier of the plant associated with the activity in the parent calendar schedule.

  • parentCalendar.schedule.resource.id - String: The resource identifier associated with the parent calendar schedule.

  • schedule.end - DateTime: The end date and time of the schedule.

  • schedule.start - DateTime: The start date and time of the schedule.

  • schedule.activity.id - String: The identifier of the activity in the schedule.

  • schedule.activity.plant.plantId - String: The plant identifier associated with the activity in the schedule.

  • schedule.activity.plant.country.id - String: The country identifier of the plant associated with the activity in the schedule.

  • schedule.resource.id - String: The resource identifier associated with the schedule.

Here are some sample values it can contain:

Table A.1. Calendar Attributes
idparentCalendar.idparentCalendar.schedule.endparentCalendar.schedule.startparentCalendar.schedule.activity.idparentCalendar.schedule.activity.plant.plantIdparentCalendar.schedule.activity.plant.country.idparentCalendar.schedule.resource.idschedule.endschedule.startschedule.activity.idschedule.activity.plant.plantIdschedule.activity.plant.country.idschedule.resource.id
CAL001PCAL0012024-11-15T17:00:002024-11-15T09:00:00ACT001ParisFRRES0012024-11-15T18:00:002024-11-15T10:00:00ACT002PLANT002UKRES002
CAL002PCAL0022024-12-01T12:00:002024-12-01T08:00:00ACT003LondonUKRES0032024-12-01T16:00:002024-12-01T09:00:00ACT004PLANT004UKRES004

2.2. ResourceDelivery

[Note]

In the Capacity Planning Application JDL Samples using a Composite Data Model, this entity relies on the following entities:

  • Resource

  • Plant

  • TruckRoute

The ResourceDelivery entity has the following attributes, detailing the delivery schedule and route information for resources:

  • end - LocalDateTime: The end date and time of the delivery.

  • start - LocalDateTime: The start date and time of the delivery.

  • from.plantId - String: The plant identifier for the origin of the delivery.

  • from.country.id - String: The country identifier for the origin of the delivery.

  • resource.id - String, required: The unique identifier of the resource being delivered.

  • route.id - String: The identifier of the delivery route.

  • route.truck.id - String: The identifier of the truck used for the delivery route.

  • to.plantId - String: The plant identifier for the destination of the delivery.

  • to.country.id - String: The country identifier for the destination of the delivery.

Here are some sample values it can contain:

Table A.2. ResourceDelivery Attributes
endstartfrom.plantIdfrom.country.idresource.idroute.idroute.truck.idto.plantIdto.country.id
2024-11-15T18:00:002024-11-15T10:00:00NiceFRARES001ROUTE001TRUCK001ParisFRA
2024-12-01T16:00:002024-12-01T08:00:00LondonUKRES002ROUTE002TRUCK002ParisFRA

2.3. ResourceUsagePerDay

[Note]

In the Capacity Planning Application JDL Samples using a Composite Data Model, this entity relies on the following entities:

  • Resource

The ResourceUsagePerDay entity has the following attributes, detailing the daily usage of resources in terms of hours:

  • day - Integer: The specific day for which the resource usage is recorded.

  • numberOfHours - Integer: The number of hours the resource is used on the specified day.

  • resource.id - String: The unique identifier of the resource being used.

Here are some sample values it can contain:

Table A.3. ResourceUsagePerDay Attributes
daynumberOfHoursresource.id
2024-11-138.0RES001
2024-11-146.5RES002
2024-11-157.0RES003
2024-11-165.5RES001

2.4. Schedule

[Note]

In the Capacity Planning Application JDL Samples using a Composite Data Model, this entity relies on the following entities:

  • Activity

  • Resource

The Schedule entity has the following attributes, detailing the scheduling of activities and associated resources:

  • end - Instant: The end date and time of the scheduled activity.

  • start - Instant: The start date and time of the scheduled activity.

  • activity.id - String: The unique identifier for the activity.

  • activity.plant.plantId - String: The plant identifier associated with the activity.

  • activity.plant.country.id - String: The country identifier of the plant associated with the activity.

  • resource.id - String: The unique identifier of the resource used for the activity.

Here are some sample values it can contain:

Table A.4. Schedule Attributes
endstartactivity.idactivity.plant.plantIdactivity.plant.country.idresource.id
2024-11-15T18:00:002024-11-15T10:00:00ACT001ParisFRRES001
2024-12-01T16:00:002024-12-01T08:00:00ACT002PLANT002USARES002
2024-12-15T15:00:002024-12-15T09:00:00ACT003LondonUKRES003

2.5. SolutionSummary

The SolutionSummary entity has the following attributes, which summarize the solution time span:

  • end - Instant: The end date and time of the solution time span.

  • start - Instant: The start date and time of the solution time span.

  • timespan - Integer: The total duration of the solution time span.

Here are some sample values it can contain:

Table A.5. SolutionSummary Attributes
endstarttimespan
2024-11-15T18:00:002024-11-15T08:00:0030

2.6. Truck

The Truck entity has the following attributes, detailing the depot location and identifier for each truck:

  • depotLatitude - Double: The latitude coordinate of the truck's depot.

  • depotLongitude - Double: The longitude coordinate of the truck's depot.

  • id - String: The unique identifier of the truck.

Here are some sample values it can contain:

Table A.6. Truck Attributes
depotLatitudedepotLongitudeid
48.85662.3522TRUCK001
51.5074-0.1278TRUCK002
40.7128-74.0060TRUCK003

2.7. TruckRoute

[Note]

In the Capacity Planning Application JDL Samples using a Composite Data Model, this entity relies on the following entities:

  • Truck

The TruckRoute entity has the following attributes, detailing the route information for each truck:

  • end - LocalDateTime: The end date and time of the truck route.

  • id - String: The unique identifier of the truck route.

  • start - LocalDateTime: The start date and time of the truck route.

  • truck.id - String: The unique identifier of the truck assigned to this route.

Here are some sample values it can contain:

Table A.7. TruckRoute Attributes
endidstarttruck.id
2024-11-15T18:00:00ROUTE0012024-11-15T10:00:00TRUCK001
2024-12-01T17:00:00ROUTE0022024-12-01T09:00:00TRUCK002
2024-12-15T20:00:00ROUTE0032024-12-15T11:00:00TRUCK003

2.8. Activity

[Note]

In the Capacity Planning Application JDL Samples using a Composite Data Model, this entity relies on the following entities:

  • Plant

The Activity entity has the following attributes:

  • creationDate - LocalDate: The date the activity was created.

  • creationDateTime - LocalDateTime: The date and time the activity was created.

  • durationInHours - Double: The duration of the activity in hours.

  • id - String, required: The unique identifier of the activity.

  • name - String: The name of the activity.

  • plant.plantId - String: The identifier of the plant associated with the activity.

  • plant.country.id - String: The country identifier of the plant associated with the activity.

Here are some sample values it can contain:

Table A.8. Activity Attributes
creationDatecreationDateTimedurationInHoursidnameplant.plantIdplant.country.id
2024-11-132024-11-13T09:00:003.5ACT001Activity AlphaParisFR
2024-12-012024-12-01T14:30:002.0ACT002Activity BetaNYCUSA
2024-12-152024-12-15T11:15:005.0ACT003Activity GammaLondonUK

2.9. Precedence

[Note]

In the Capacity Planning Application JDL Samples using a Composite Data Model, this entity relies on the following entities:

  • Activity

The Precedence entity has the following attributes, detailing the precedence relationships between activities, including associated plants and countries:

  • first.id - String, required: The unique identifier of the first activity in the precedence relationship.

  • first.plant.plantId - String: The plant identifier associated with the first activity.

  • first.plant.country.id - String: The country identifier of the plant associated with the first activity.

  • second.id - String, required: The unique identifier of the second activity in the precedence relationship.

  • second.plant.plantId - String: The plant identifier associated with the second activity.

  • second.plant.country.id - String: The country identifier of the plant associated with the second activity.

Here are some sample values it can contain:

Table A.9. Precedence Attributes
first.idfirst.plant.plantIdfirst.plant.country.idsecond.idsecond.plant.plantIdsecond.plant.country.id
ACT001ParisFRAACT002NYCUSA
ACT002NYCUSAACT003LondonUK

2.10. Requirement

[Note]

In the Capacity Planning Application JDL Samples using a Composite Data Model, this entity relies on the following entities:

  • Activity

  • Resource

The Requirement entity has the following attributes, detailing the resource requirements for specific activities, including associated plants and countries:

  • quantity - Integer, required: The quantity of the resource required for the activity.

  • activity.id - String: The unique identifier of the activity associated with the requirement.

  • activity.plant.plantId - String: The plant identifier associated with the activity.

  • activity.plant.country.id - String: The country identifier of the plant associated with the activity.

  • resource.id - String, required: The unique identifier of the resource needed for the activity.

Here are some sample values it can contain:

Table A.10. Requirement Attributes
quantityactivity.idactivity.plant.plantIdactivity.plant.country.idresource.id
50ACT001ParisFRARES001
30ACT002NYCUSARES002
75ACT003LondonUKRES003

2.11. Resource

The Resource entity has the following attributes:

  • active - Boolean: Indicates whether the resource is active.

  • id - String, required: The unique identifier of the resource.

  • name - String: The name of the resource.

Here are some sample values it can contain:

Table A.11. Resource Attributes
activeidname
trueRES001Resource Alpha
falseRES002Resource Beta
trueRES003Resource Gamma
falseRES004Resource Delta

2.12. Resource Capacity

[Note]

In the Capacity Planning Application JDL Samples using a Composite Data Model, this entity relies on the following entities:

  • Resource

The Resource Capacity entity has the following attributes:

  • quantity - Integer: The quantity available for the resource.

  • resource.id - String, required: The unique identifier of the resource.

Here are some sample values it can contain:

Table A.12. Resource Capacity Attributes
quantityresource.id
100RES123
250RES456
50RES789
75RES012

2.13. Country

The Country entity has the following attributes:

  • id - String, required: The country code identifier.

  • name - String: The full name of the country.

Here are some values it can contain:

Table A.13. Country Attributes
idname
FRAFrance
UKUnited Kingdom
USAUnited States of America
SNGSingapore

2.14. Plant

[Note]

In the Capacity Planning Application JDL Samples using a Composite Data Model, this entity relies on the following entities:

  • Country

The Plant entity has the following attributes:

  • geojson - String: The GeoJSON area of the plant location.

  • latitude - Double: The latitude of the plant location.

  • longitude - Double: The longitude of the plant location.

  • online - Boolean: The online status of the plant.

  • plantId - String, required: The identifier of the plant.

  • country.id - String: The id of the country where the plant is located.

Here are some values it can contain:

Table A.14. Plant Locations
geojsonlatitudelongitudeonlineplantIdcountry.id
{ "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "name": "Paris", "country": "France", "population": 2148000 }, "geometry": { "type": "Polygon", "coordinates": [ [ [2.2241, 48.8156], [2.4699, 48.8156], [2.4699, 48.9021], [2.2241, 48.9021], [2.2241, 48.8156] ] ] } } ] }48.85662.3522trueParisFRA
{ "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "name": "New York City", "state": "New York", "country": "United States", "population": 8419600 }, "geometry": { "type": "Polygon", "coordinates": [ [ [-74.2591, 40.4774], [-73.7004, 40.4774], [-73.7004, 40.9176], [-74.2591, 40.9176], [-74.2591, 40.4774] ] ] } } ] }40.7128-74.0060trueNYCUSA
{ "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "name": "London", "country": "United Kingdom", "population": 8982000 }, "geometry": { "type": "Polygon", "coordinates": [ [ [-0.5103, 51.2868], [0.3340, 51.2868], [0.3340, 51.6919], [-0.5103, 51.6919], [-0.5103, 51.2868] ] ] } } ] }51.5074-0.1278trueLondonUK
{ "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "name": "Singapore", "country": "Singapore", "population": 5639000 }, "geometry": { "type": "Polygon", "coordinates": [ [ [103.6050, 1.2098], [104.0327, 1.2098], [104.0327, 1.4713], [103.6050, 1.4713], [103.6050, 1.2098] ] ] } } ] }1.3521103.8198trueSingaporeSNG
{ "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "name": "Nice", "region": "Provence-Alpes-Côte d'Azur", "country": "France", "population": 340000 }, "geometry": { "type": "Polygon", "coordinates": [ [ [7.2174, 43.6650], [7.3050, 43.6650], [7.3050, 43.7450], [7.2174, 43.7450], [7.2174, 43.6650] ] ] } } ] }43.71027.2620trueNiceFRA

3. Task Samples

Here we provide some sample scripted tasks and screenshots of the web client.

3.1. Basic and User-Defined Routine Task Samples

Basic tasks are tasks that do not require running a routine on the Backend Service, nor on an optimization server. User-defined routine tasks are described in Section Understanding User-Defined Routine Statements.

3.1.1. Greetings

This task launches a routine on the Backend Service to log a 'Hello' message..

    @Bean
    public ScriptedTaskDescription helloWorldTask() {
        ScriptedTaskDescription task = new ScriptedTaskDescription("HelloWorldTask", "*Greetings (a sample)");
        task.setDescription("Launch a backend routine to log a 'Hello' message.");
        task.getScript()
            .addStatement(AskInputStatement.of("name", true, ParameterTypes.TEXT, "The name of the person to greet."))
            .addStatement(ExecuteRoutineStatement.of(StringExpression.of("HelloWorld"))
                .withInput(VariableAccessExpression.of("name")));
        return task;
    }

3.1.2. Check Scenario Schema

This task runs a checker on the selected scenario schema by calling CheckSchemaStatement.forScenario. Note that this statement returns exit code 1 when the check ends with errors. For that, we check in the following statement the return code of the check statement IfStatement.of(BooleanExpression.LAST_STATEMENT_EXIT_CODE_NOT_OK.... If the return code is not OK (not 0), we interrupt the task with ExitTaskStatement.failing. The following statement is an example of how to check the data status of a scenario directly ScenarioStatusExpression.dataStatusIs (or ScenarioStatusExpression.dataStatusIs). In this sample task, we interrupt the execution with ExitTaskStatement.alerting if the data status is ScenarioStatusExpression.WARNING.

We could have replaced the condition, following which we do ExitTaskStatement.failing(StringExpression.of("Scenario Schema Check ended with ERRORS")), with:

IfStatement.of(ScenarioStatusExpression.dataStatusIs(VariableAccessExpression.of("scenario"), ScenarioStatusExpression.ERROR), ExitTaskStatement.failing(StringExpression.of("Scenario Schema Check ended with ERRORS")))

Note that the possible values for schema or data statuses are: UNCHECKED, CHECKING, VALID, WARNING, and ERROR

public class Tasks {
    @Bean
    public ScriptedTaskDescription checkScenarioSchemaTask() {
        ScriptedTaskDescription task = new ScriptedTaskDescription("checkScenarioSchema", "Test - Check Scenario Schema using CheckSchemaStatement");
        task.setDescription("Check Scenario Schema, using CheckSchemaStatement");
        task.getScript()
            .addStatement(AskInputStatement.of("scenario", true, JobInputType.scenarioId(WRITABLE)))
            .addStatement(CheckSchemaStatement.forScenario(VariableAccessExpression.of("scenario")))
            .addStatement(IfStatement.of(BooleanExpression.LAST_STATEMENT_EXIT_CODE_NOT_OK,
                ExitTaskStatement.failing(StringExpression.of("Scenario Schema Check ended with ERRORS"))
            ))
            .addStatement(ExitTaskStatement.alerting(StringExpression.of("Scenario Schema Check ended with WARNINGS"))
                .when(ScenarioStatusExpression.dataStatusIs(VariableAccessExpression.of("scenario"), ScenarioStatusExpression.WARNING))
            );
        return task;
    }
}

3.1.3. Create an Empty Scenario

This task creates an empty Scenario with no data.

@Bean
    public ScriptedTaskDescription createEmptyScenarioTask() {
        ScriptedTaskDescription task = new ScriptedTaskDescription("CreateEmptyScenarioTask", "Create an empty Scenario");
        task.setDescription("Creates an empty Scenario with no data");
        setI18nKeys(task, "CREATE_EMPTY_SCENARIO");

        var scenarioId = VariableAccessExpression.of("scenarioId");
        var folder = VariableAccessExpression.ofFolder();
        var scenarioCreationParameters = VariableAccessExpression.ofScenarioCreationExpression();
        var createdScenarios = VariableAccessExpression.of("createdScenarios");

        Supplier<Statement> deleteAllCreatedScenariosAndFail = () -> Block.of(
            ForeachStatement.of(scenarioId.getVariableName(), createdScenarios,
                DeleteScenarioStatement.of(scenarioId).moveToTrash(BooleanExpression.FALSE)),
            ExitTaskStatement.alerting(StringExpression.of("Scenario could not be imported due to errors."))
        );

3.1.4. Create Scenario from File

This task imports a file content into a new scenario, supporting several formats.

    @Bean
    public ScriptedTaskDescription scenarioImportTask() {
        ScriptedTaskDescription task = new ScriptedTaskDescription("ScenarioImportTask", "Create scenario from file");
        task.setDescription("Import a file content into a new scenario, supporting several formats");
        setI18nKeys(task, "SCENARIO_IMPORT");

        var folder = VariableAccessExpression.ofFolder();
        var scenarioFile = VariableAccessExpression.ofFile();
        var scenarioCreationParameters = VariableAccessExpression.ofScenarioCreationExpression();

        var createdScenarios = VariableAccessExpression.of("createdScenarios");
        var issues = VariableAccessExpression.of("issues");

        task.getScript()
            .addStatement(AskInputStatement.ofVariable(folder, true, JobInputType.FOLDER_IN_WORKSPACE, "The Folder in a Workspace where to import the new Scenario "))
            .addStatement(AskInputStatement.ofVariable(scenarioFile, true, ParameterTypes.file("xlsx", "xcsv", "dbrf", "gz", "zip"), "The Scenario file containing the data (.xlsx, .xcsv, .dbrf, .gz, .zip)"))
            .addStatement(AskInputStatement.ofVariable(scenarioCreationParameters, true, JobInputType.SCENARIO_CREATION_PARAMETERS, "Parameters to create the Scenario"))

            // Create the new scenario using metadata if exists.
            .addStatement(SetVariableStatement.of(
                createdScenarios.getVariableName(),
                ScenarioCreationExpression.of(
                        scenarioCreationParameters,
                        folder
                    )
                    .withFileForProperties(scenarioFile)
            ))

            // Import the scenarios or delete them if an error occurs
            .addStatement(DoStatementOnLockedScenariosOrDeleteThem.of(
                createdScenarios,
                Block.of(
                    // Save the file content in the data service
                    ImportScenarioStatement.of(createdScenarios, scenarioFile).storingIssuesInto(issues.getVariableName()),
                    // Save the issues to output
                    SetTaskOutputStatement.of("issues", issues)
                )
            ));

        return task;
    }

3.1.5. Create Scenarios from Files

This task imports a list of files into new scenarios, supporting several formats.

    @Bean
    public ScriptedTaskDescription scenariosImportTask() {
        ScriptedTaskDescription task = new ScriptedTaskDescription("ScenariosImportTask", "Create scenarios from files");
        task.setDescription("Import a list of files into new scenarios, supporting several formats");
        setI18nKeys(task, "SCENARIOS_IMPORT");

        var folder = VariableAccessExpression.ofFolder();
        var scenarioFiles = VariableAccessExpression.ofFile();
        var scenarioCreationParameters = VariableAccessExpression.ofScenarioCreationExpression();

        // Variables for ForeachStatement
        var scenarioFile = VariableAccessExpression.of("scenarioFile");
        var createdScenarios = VariableAccessExpression.of("createdScenarios");

        task.getScript()
            .addStatement(AskInputStatement.ofVariable(folder, true, JobInputType.FOLDER_IN_WORKSPACE, "The Folder in a Workspace where to import the new Scenarios"))
            .addStatement(AskInputStatement.ofVariable(scenarioFiles, true, JobInputType.files(1, null, "xlsx", "dbrf", "gz", "zip"), "The Scenario files containing the data (.xlsx, .dbrf, .gz, .zip)"))
            .addStatement(AskInputStatement.ofVariable(scenarioCreationParameters, true, JobInputType.SCENARIO_CREATION_PARAMETERS, "Parameters to create the Scenario"))
            .addStatement(ForeachStatement.of(
                scenarioFile.getVariableName(),
                scenarioFiles,
                // Create the new scenario using metadata if exists.
                SetVariableStatement.of(
                    createdScenarios.getVariableName(),
                    ScenarioCreationExpression.of(
                            scenarioCreationParameters,
                            folder
                        )
                        .withFileForProperties(scenarioFile)
                        .withScenarioNamePrefix(
                            StringExpression.concat(
                                StringExpression.ofFileName(scenarioFile),
                                StringExpression.of(" - ")
                            ))
                ),

                // Import the scenarios or delete them if an error occurs
                DoStatementOnLockedScenariosOrDeleteThem.of(
                    createdScenarios,
                    // Save the file content in the data service
                    ImportScenarioStatement.of(createdScenarios, scenarioFile)
                )
            ));

        return task;
    }

3.1.6. Duplicate Scenario

This task duplicates the selected scenario.

    @Bean
    public ScriptedTaskDescription duplicateScenarioTask() {
        ScriptedTaskDescription task = new ScriptedTaskDescription("DuplicateScenarioTask", "Duplicate Scenario");
        task.setDescription("Duplicate the selected scenario");
        setI18nKeys(task, "DUPLICATE_SCENARIO");
        var scenario = VariableAccessExpression.ofScenario();
        var newScenarioName = VariableAccessExpression.of("New scenario name");
        var duplicateVisibleScenarios = VariableAccessExpression.of("Duplicate visible scenarios");
        var newScenarioIds = VariableAccessExpression.of("Created scenario IDs");

        task.getScript()
            .addStatement(AskInputStatement.ofVariable(scenario, true, JobInputType.SCENARIO_ID))
            .addStatement(AskInputStatement.ofVariable(newScenarioName,false, ParameterTypes.TEXT))
            .addStatement(AskInputStatement.ofVariable(duplicateVisibleScenarios,false, ParameterTypes.BOOLEAN))
            .addStatement(ExecuteRoutineStatement
                .of(StringExpression.of("com.decisionbrain.gene.DuplicateScenarioRoutine"))
                .withInput(scenario)
                .withInput(newScenarioName)
                .withInput(duplicateVisibleScenarios)
                .withOutput("new-scenario-ids", newScenarioIds.getVariableName())
            )
            .addStatement(SetTaskOutputStatement.of("New Scenario IDs", newScenarioIds));
        return task;
    }

3.1.7. Duplicate View or Dashboard

This task duplicates a custom view or dashboard under the same parent as the original.

    @Bean
    public ScriptedTaskDescription duplicateViewDashboard() {
        ScriptedTaskDescription task = new ScriptedTaskDescription("DuplicateViewDashboard", "Duplicate View or Dashboard");
        task.setDescription("Copies Custom View or Custom Dashboard under the same parent as the original.");
        setI18nKeys(task, "DUPLICATE_VIEW_DASHBOARD");
        var pathUuid = VariableAccessExpression.of("pathUuid");
        task.getScript()
            .addStatement(AskInputStatement.ofVariable(pathUuid, true, ParameterTypes.TEXT, "Id of the Custom View/Dashboard to copy"))
            .addStatement(ExecuteRoutineStatement
                .of(StringExpression.of("com.decisionbrain.gene.DuplicateViewDashboard"))
                .withInput(pathUuid));
        return task;
    }

3.1.8. Execute a Ruleset on a Scenario

This task executes a ruleset on a scenario.

    @Bean
    public ScriptedTaskDescription executeRulesTask() {
        ScriptedTaskDescription task = new ScriptedTaskDescription(
            "ExecuteRulesetOnScenarioTask",
            "Execute Ruleset On Scenario Task");
        task.setDescription("Execute the given ruleset on the given scenario");
        setI18nKeys(task, "EXECUTE_RULES");



        var scenario = VariableAccessExpression.ofScenario();
        var scriptParameterName = VariableAccessExpression.of("scriptParameterName");
        var executionLogs = VariableAccessExpression.of("executionLogs");
        var exitCode = VariableAccessExpression.of("exitCode");

        task.getScript()
            .addStatement(AskInputStatement.ofVariable(scenario, true, JobInputType.SCENARIO_ID))
            .addStatement(AskInputStatement.ofVariable(scriptParameterName, true, JobInputType.TEXT))
            .addStatement(ExecuteRoutineStatement
                // For the given routine
                .of(EXECUTE_RULESET_ROUTINE_NAME)
                // Pass the input parameters
                .withInput(ScenarioDataExpression.of(scenario))
                .withInput(scriptParameterName)
                // And retrieve the logs of the routine execution in a variable
                .withOutput(executionLogs.getVariableName(), executionLogs.getVariableName()))
            // Keep the exit code of the routine in a variable
            .addStatement(SetVariableStatement.of(exitCode.getVariableName(), NumericExpression.LAST_STATEMENT_EXIT_CODE))
            // Set the output of the task to the routine execution logs
            .addStatement(SetTaskOutputStatement.of(executionLogs.getVariableName(), executionLogs))
            // Process routine errors exit codes
            .addStatement(alertingTheTaskWithMessageWhen(exitCode, "An error occurred during the processing the routine input parameters", ROUTINE_PARAMETER_ERROR_EXIT_CODE))
            .addStatement(alertingTheTaskWithMessageWhen(exitCode, "An error occurred during the compilation of the ruleset", RULESET_COMPILATION_ERROR_EXIT_CODE))
            .addStatement(alertingTheTaskWithMessageWhen(exitCode, "An error occurred during the execution of the ruleset", RULESET_EXECUTION_ERROR_EXIT_CODE))
            .addStatement(alertingTheTaskWithMessageWhen(exitCode, "An unexpected error occurred during the routine execution", UNKNOWN_ERROR_EXIT_CODE));

        return task;
    }

3.1.9. Export Scenario to DBRF

This task exports the contents of the selected scenario to a GZIP archive, in DBRF format.

    @Bean
    public ScriptedTaskDescription dbrfExportTask() {
        ScriptedTaskDescription task = new ScriptedTaskDescription("DbrfExportTask", "Export scenario to DBRF");
        task.setDescription("Export the contents of the selected scenario to a GZIP archive, in DBRF format");
        setI18nKeys(task, "DBPF_EXPORT");

        var scenario = VariableAccessExpression.ofScenario();
        var baseFileName = VariableAccessExpression.of("base file name");
        var filter = VariableAccessExpression.ofFilter();

        task.getScript()
            .addStatement(AskInputStatement.ofVariable(scenario, true, JobInputType.SCENARIO_ID))
            .addStatement(AskInputStatement.ofVariable(baseFileName, true, ParameterTypes.TEXT, "Name of the file to export to. Warning: a '.gz' extension will be appended."))
            .addStatement(AskInputStatement.ofVariable(filter, false, JobInputType.ENTITIES, "Select the tables to export"))

            .addStatement(SetTaskOutputStatement.of("DBRF file",
                FileExpression.of(
                    StringExpression.concat(baseFileName, StringExpression.of(".gz")),
                    BlobExpression.of(
                        ScenarioDataExpression.of(scenario)
                            .withFormat(ScenarioDataFormat.DBRF)
                            .onlyTables(filter)),
                    StringExpression.of("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
                )));

        return task;
    }

3.1.10. Export Scenario to Excel

This task exports the selected scenario to an Excel file.

    @Bean
    public ScriptedTaskDescription excelExportTask() {
        ScriptedTaskDescription task = new ScriptedTaskDescription("ExcelExportTask", "Export scenario to Excel");
        task.setDescription("Exports the selected scenario to an Excel file");
        setI18nKeys(task, "EXCEL_EXPORT");

        var scenario = VariableAccessExpression.ofScenario();
        var baseFileName = VariableAccessExpression.of("base file name");
        var filter = VariableAccessExpression.ofFilter();

        VariableAccessExpression scenarioData = VariableAccessExpression.of("scenario-data");

        task.getScript()
            .addStatement(AskInputStatement.ofVariable(scenario, true, JobInputType.SCENARIO_ID))
            .addStatement(AskInputStatement.ofVariable(baseFileName, true, ParameterTypes.TEXT, "Name of the file to export to. Warning: a '.xlsx' extension will be appended."))
            .addStatement(AskInputStatement.ofVariable(filter, false, JobInputType.ENTITIES, "Select the tables to export"))

            // Load the scenario data into a variable
            .addStatement(SetVariableStatement.of(
                    scenarioData.getVariableName(),
                    ScenarioDataExpression.of(scenario).withFormat(ScenarioDataFormat.EXCEL).onlyTables(filter)
            ))

            // Set the Excel file as task output
            .addStatement(SetTaskOutputStatement.of("Excel file",
                FileExpression.of(
                    StringExpression.concat(baseFileName, StringExpression.of(".xlsx")),
                    BlobExpression.of(scenarioData),
                    StringExpression.of("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
                )))

            // If there are errors or warnings, we exit the task with an alert.
            // The only known case is when the scenario content does not fit into an Excel file.
            .addStatement(ExitTaskStatement.alerting(StringExpression.of("Exporting the scenario data was not completed successfully"))
                .when(ScenarioDataExpression.hasErrorsOrWarnings(scenarioData))
            );

        return task;
    }

3.1.11. Export Scenario to XCSV

This task exports the contents of the selected scenario to an XCSV archive file (ZIP with CSV).

    @Bean
    public ScriptedTaskDescription xcsvExportTask() {
        ScriptedTaskDescription task = new ScriptedTaskDescription("XCSVExportTask", "Export scenario to XCSV");
        task.setDescription("Export the contents of the selected scenario to a XCSV archive file (ZIP with CSV)");
        setI18nKeys(task, "XCSV_EXPORT");

        task.getScript()

            .addStatement(AskInputStatement.of("scenario", true, JobInputType.SCENARIO_ID))
            .addStatement(AskInputStatement.of("base file name", true, ParameterTypes.TEXT, "Name of the file to export to. Warning: a '.xcsv' extension will be appended."))
            .addStatement(AskInputStatement.of("filter", false, JobInputType.ENTITIES, "Select the tables to export"))

            .addStatement(SetTaskOutputStatement.of("XCSV file",
                FileExpression.of(
                    StringExpression.concat(VariableAccessExpression.of("base file name"), StringExpression.of(".xcsv")),
                    BlobExpression.of(ScenarioDataExpression
                        .of(VariableAccessExpression.of("scenario"))
                        .withFormat(ScenarioDataFormat.XCSV)
                        .onlyTables(VariableAccessExpression.of("filter"))),
                    StringExpression.of("application/octet-stream")
                )));

        return task;
    }

3.1.12. Export Scenario to ZIP

This task exports the contents of the selected scenario to a ZIP archive, in CSV format.

   @Bean
    public ScriptedTaskDescription zipExportTask() {
        ScriptedTaskDescription task = new ScriptedTaskDescription("ZipExportTask", "Export scenario to ZIP");
        task.setDescription("Export the contents of the selected scenario to a ZIP archive, in CSV format");
        setI18nKeys(task, "ZIP_EXPORT");

        var scenario = VariableAccessExpression.ofScenario();
        var baseFileName = VariableAccessExpression.of("base file name");
        var filter = VariableAccessExpression.ofFilter();

        task.getScript()

            .addStatement(AskInputStatement.ofVariable(scenario, true, JobInputType.SCENARIO_ID))
            .addStatement(AskInputStatement.ofVariable(baseFileName, true, ParameterTypes.TEXT, "Name of the file to export to. Warning: a '.zip' extension will be appended."))
            .addStatement(AskInputStatement.ofVariable(filter, false, JobInputType.ENTITIES, "Select the tables to export"))

            .addStatement(SetTaskOutputStatement.of("ZIP file",
                FileExpression.of(
                    StringExpression.concat(baseFileName, StringExpression.of(".zip")),
                    BlobExpression.of(ScenarioDataExpression.of(scenario).onlyTables(filter).withFormat(CSV)),
                    StringExpression.of("application/octet-stream")
                )));

        return task;
    }

3.1.13. Set Scenario Properties

This task sets a scenario property (key and value pair) for the specified scenario. It takes as input:

  • a scenario Id

  • a property key

  • a property value

@Configuration
public class Tasks {
    @Bean
    public ScriptedTaskDescription setScenarioPropertyTask() {
        ScriptedTaskDescription task = new ScriptedTaskDescription("SetScenarioProperty", "Set Scenario Property");
        task.setDescription("Set a property for the selected scenario");
        task.getScript()
            .addStatement(AskInputStatement.of("scenario", true, JobInputType.scenarioId(WRITABLE)))
            .addStatement(AskInputStatement.of("property name", true, JobInputType.TEXT))
            .addStatement(AskInputStatement.of("property value", true, JobInputType.TEXT))
            //set property (key and value) from input variables
            .addStatement(SetScenarioPropertyStatement.of(VariableAccessExpression.of("scenario"), VariableAccessExpression.of("property name"), VariableAccessExpression.of("property value")))
            //Make the task return a result, which is a string representing the new property, having as key the value entered in "property name" and as value what is entered in "property value"
            //using the formatting "Scenario property[{property name}] new value is: {property value}"
            .addStatement(SetTaskOutputStatement.of("result",
                StringExpression.concat(
                    StringExpression.of("Scenario property["),
                    VariableAccessExpression.of("property name"),
                    StringExpression.of("] new value is: "),
                    ScenarioPropertyExpression.of(VariableAccessExpression.of("scenario"), VariableAccessExpression.of("property name")))
                ));
        return task;
    }
}

The generated web client component for this task is the following:

Figure A.1. Configuring a TaskSetProperty Task
Configuring a TaskSetProperty Task

Note that it is possible to create a scripted task to fetch the value of a scenario property by using ScenarioPropertyExpression. In this case, we are getting the property that was just created, so we use the key that was entered in "property name" by using VariableAccessExpression.of("property name"). We can get the value of a property that we know is already defined in the scenario. For instance, to get the value of the scenario property "property1", we can use the following expression: ScenarioPropertyExpression.of(VariableAccessExpression.of("scenario"), StringExpression.of("property1"))

3.1.14. Share Scenario

This task shares the selected scenario with a different workspace.

    @Bean
    public ScriptedTaskDescription shareScenarioTask() {
        ScriptedTaskDescription task = new ScriptedTaskDescription("ShareScenarioTask", "Share Scenario");
        task.setDescription("Share the selected scenario to a different workspace");
        setI18nKeys(task, "SHARE_SCENARIO");

        var scenario = VariableAccessExpression.ofScenario();
        var workspace = VariableAccessExpression.of("Share to");
        var newScenarioId = VariableAccessExpression.of("newScenarioId");

        task.getScript()
            .addStatement(AskInputStatement.ofVariable(scenario, true, JobInputType.SCENARIO_ID))
            .addStatement(AskInputStatement.ofVariable(workspace, true, JobInputType.workspaceId(JobInputType.WorkspaceOrScenarioFilter.UNIQUE), "The Uuid of the new workspace to share the scenario to"))
            .addStatement(ExecuteRoutineStatement
                .of(StringExpression.of("com.decisionbrain.gene.ShareScenarioRoutine"))
                .withInput(scenario)
                .withInput(workspace)
                .withOutput("new-scenario-id", newScenarioId.getVariableName()))
            .addStatement(SetTaskOutputStatement.of("New Scenario ID", newScenarioId));

        return task;
    }

3.2. Optimization Server Task Samples

DOC allows launching worker tasks using the Optimization Server. For more details, refer to Sections Understanding Optimization Server Statements and Implementing Worker Tasks.

3.2.1. Optimization Server - Run Add Issue Task

This task takes scenarioId and a text message as input, adds the message to the scenario issues collection, then returns an output collector of the modified scenario as a result.

@Configuration
public class Tasks {
    @Bean
    public ScriptedTaskDescription addIssueWithOptimizationServerTask() {
        ScriptedTaskDescription task = new ScriptedTaskDescription("addIssueWithOptimizationServer", "Add Issue with OptimizationServer");
        task.setDescription("Add an issue to the selected scenario, using an OptimizationServer task");
        task.getScript()
            .addStatement(AskInputStatement.of("scenario", true, JobInputType.scenarioId(WRITABLE)))
            .addStatement(AskInputStatement.of("Issue Message", true, JobInputType.TEXT, "The text of the issue to add"))
            .addStatement(ExecuteOptimizationServerTaskStatement
                .forTaskId(StringExpression.of("Add Issue with OptimizationServer"))
                .withInput("Issue Message", VariableAccessExpression.of("Issue Message"))
                .withInput("inputCollector", ScenarioDataExpression.of(VariableAccessExpression.of("scenario")))
                .withOutputScenario("outputCollector", VariableAccessExpression.of("scenario"), StringExpression.of("GeneIssue"), StringExpression.of("Activity")));
        return task;
    }
}

Note that the table names mentioned in the arguments of withOutputScenario can include list expressions. That is, the corresponding line in the example above could have been written as follows:

.withOutputScenario("outputCollector", VariableAccessExpression.of("scenario"), ListExpression.of(StringExpression.of("GeneIssue"), StringExpression.of("Activity")))

A slightly different version of this task is a task that creates a new scenario (with a name given as input) and adds an issue message to the newly created scenario. To create a new scenario, we use StringExpression.idOfNewScenario() as in the following task:

@Configuration
public class Tasks {
    @Bean
    public ScriptedTaskDescription addIssueToNewScenarioWithOptimizationServerTask() {
        ScriptedTaskDescription task = new ScriptedTaskDescription("createScenarioAndAddIssueWithOptimizationServer", "Add Issue to new scenario");
        task.setDescription("Add an issue to a new scenario, using an OptimizationServer task");
        task.getScript()
            .addStatement(AskInputStatement.of("Issue Message", true, JobInputType.TEXT, "The text of the issue to add"))
            .addStatement(AskInputStatement.of("Scenario Name", true, JobInputType.TEXT, "The name of the scenario to create"))
            .addStatement(AskInputStatement.of("workspace", true, JobInputType.WORKSPACE_ID, "The Workspace where to create the new Scenario"))
            .addStatement(SetVariableStatement.of("scenarioId", StringExpression.idOfNewScenario(VariableAccessExpression.of("Scenario Name"), VariableAccessExpression.of("workspace"))))
            .addStatement(ExecuteOptimizationServerTaskStatement
                .forTaskId(StringExpression.of("Add Issue with OptimizationServer"))
                .withInput("Issue Message", VariableAccessExpression.of("Issue Message"))
                .withInput("inputCollector", ScenarioDataExpression.of(VariableAccessExpression.of("scenarioId")))
                .withOutputScenario("outputCollector", VariableAccessExpression.of("scenarioId"), StringExpression.of("GeneIssue")));
        return task;
    }
}

The OptimizationServer task with id "Add Issue with OptimizationServer" (for both versions above) is defined in OptimizationServer tasks configuration in worker.yml as follows:

tasks:
  - id: Add Issue with OptimizationServer
    implementationClassName: com.decisionbrain.gene.execution.worker.AddIssueTask
    description: This task adds an issue to the provided scenario
    inputs:
      - name: Issue Message
        type: TEXT
        description: The text of the issue to add
        required: true
      - name: inputCollector
        type: BINARY
        description: Collector with initial data
        required: true
    outputs:
      - name: outputCollector
        type: BINARY
        description: Collector with initial data plus added issue
        required: true

With the following implementation class:

import com.decisionbrain.optimserver.common.parameter.Parameter;
import com.decisionbrain.optimserver.worker.api.ExecutionContext;
import com.decisionbrain.optimserver.worker.api.Task;
import com.decisionbrain.sample.model.Activity;
import com.decisionbrain.sample.model.CapacityPlanning;
import com.decisionbrain.sample.model.GeneIssue;
import com.decisionbrain.sample.model.impl.CapacityPlanningFactoryImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Arrays;

public class AddIssueTask implements Task {
    private static final Logger LOGGER = LoggerFactory.getLogger("AddIssueTask");

    @Override
    public void execute(Parameter input, Parameter output, ExecutionContext context) {
        CapacityPlanning collector = getInputCollector(input);

        LOGGER.info("Seeing {} existing issues", collector.getIssues().size());

        GeneIssue issue = collector.createGeneIssue();

        issue.setSeverity("INFO");
        issue.setMessage(new String(input.get("Issue Message")));

        emitOutputCollector(output, collector);
    }

    private CapacityPlanning getInputCollector(Parameter input) {
        try {
            CapacityPlanning collector = new CapacityPlanningFactoryImpl().createCollector();
            byte[] inputData = input.get("inputCollector");
            collector.loadSnapshot(inputData);

            return collector;
        } catch (Exception e) {
            throw new RuntimeException("Error while preparing data", e);
        }
    }

    private void emitOutputCollector(Parameter output, CapacityPlanning collector) {
        try {
            output.emit("outputCollector", collector.saveSnapshot(Arrays.asList(Activity.class, GeneIssue.class)));
        } catch (IOException e) {
            throw new RuntimeException("Error while saving data", e);
        }
    }
}

The generated web client component for this task is the following:

Figure A.2. Configuring a AddIssueWithOptimizationServer Task
Configuring a AddIssueWithOptimizationServer Task

3.2.2. Optimization Server - Run Add Issue Task Using a Scenario URI

When using ScenarioDataExpression, you can choose the URI format. This brings some special features:

  • Only an URI to fetch the scenario data is sent to the worker. The input scenario data are not loaded and transferred as bytes through the OptimizationServer, but they are directly streamed between the Data Service and the worker upon worker demand. This leads to better performance and reduced memory usage.

  • The input scenario data modifications can be persisted directly by the worker (the data are streamed directly to the Data Service), which means that you will not need to declare an output scenario in your task.

The task does the same logic as the previous task example except it uses a Scenario URI to read and update the scenario:

@Configuration
public class Tasks {
    @Bean
    public ScriptedTaskDescription addIssueWithOptimizationServerWithUriTask() {
        ScriptedTaskDescription task = new ScriptedTaskDescription("addIssueWithOptimizationServerWithUri", "Add Issue with OptimizationServer using URI");
        task.setDescription("Add an issue to the selected scenario, using an OptimizationServer task using URI");
        task.getScript()
            .addStatement(AskInputStatement.of("scenario", true, JobInputType.scenarioId(WRITABLE)))
            .addStatement(AskInputStatement.of("Issue Message", true, JobInputType.TEXT, "The text of the issue to add"))
            .addStatement(ExecuteOptimizationServerTaskStatement
                .forTaskId(StringExpression.of("Add Issue with OptimizationServer using URI"))
                .withInput("Issue Message", VariableAccessExpression.of("Issue Message"))
                .withInput("inputCollector", ScenarioDataExpression.of(VariableAccessExpression.of("scenario")).withFormat(ScenarioDataFormat.URI)));
        return task;
    }
}

Compared to the previous task example, note that the call to withFormat(ScenarioDataFormat.URI) has been added and the call to withOutputScenario(...) has been removed.

The OptimizationServer task with id "Add Issue with OptimizationServer using URI" is defined in OptimizationServer tasks configuration in worker.yml as follows:

tasks:
  - id: Add Issue with OptimizationServer using URI
    implementationClassName: com.decisionbrain.gene.execution.worker.AddIssueUsingUriTask
    description: This task adds an issue to the provided scenario
    inputs:
      - name: Issue Message
        type: TEXT
        description: The text of the issue to add
        required: true
      - name: inputCollector
        type: BINARY
        description: Collector with initial data
        required: true

Compared to the previous task example, note that the outputCollector output has been removed.

With the following implementation class:

import com.decisionbrain.gene.worker.component.DbDomCollectorDAO;
import com.decisionbrain.gene.worker.task.GeneTask;
import com.decisionbrain.optimserver.common.parameter.Parameter;
import com.decisionbrain.optimserver.worker.api.ExecutionContext;
import com.example.caplan.model.CapacityPlanning;
import com.example.caplan.model.GeneIssue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

public class AddIssueUsingUriTask extends GeneTask<CapacityPlanning> {
    private static final Logger LOGGER = LoggerFactory.getLogger("AddIssueTask");

    @Override
    public void execute(Parameter input, Parameter output, ExecutionContext context) {

        DbDomCollectorDAO<CapacityPlanning> collectorDAO = createDbDomCollectorDAO(context, "inputCollector");

        // Load only issues
        CapacityPlanning collector = collectorDAO.loadScenario(List.of(GeneIssue.class));

        LOGGER.info("Seeing {} existing issues", collector.getGeneIssues().size());

        GeneIssue issue = collector.createGeneIssue();
        issue.setSeverity("INFO");
        issue.setMessage(context.getInputData("Issue Message").loadAsString());

        // Save only issues
        collectorDAO.saveScenario(collector, List.of(GeneIssue.class));
    }
}

3.2.3. Optimization Server - Run Checker Task

This task runs the checker task in the Optimization server.

    @Bean
    public ScriptedTaskDescription checker() {
        ScriptedTaskDescription task = new ScriptedTaskDescription("CheckerTask", "Optimization server - Run Checker task");
        task.setDescription("Run the checker task in the Optimization server");

        VariableAccessExpression scenario = VariableAccessExpression.of(SCENARIO);

        task.getScript()
            .addStatement(AskInputStatement.of(scenario.getVariableName(), true, JobInputType.scenarioId(WRITABLE)))
            .addStatement(ExecuteOptimizationServerTaskStatement
                .forTaskId(StringExpression.of("CheckerTask"))
                .usingOnDemandContext(StringExpression.of("small"))
                .withInput(INPUT_COLLECTOR, ScenarioDataExpression.of(scenario))
                .withOutputScenario(OUTPUT_COLLECTOR, scenario, StringExpression.of("GeneIssue"))
            );
        return task;
    }

3.2.4. Optimization Server - Run Engine Task

This task runs the engine task in the Optimization server.

    @Bean
    public ScriptedTaskDescription engineTask() {
        ScriptedTaskDescription task = new ScriptedTaskDescription("EngineTask", "Optimization server - Run Engine task");
        task.setDescription("Run the engine task in the Optimization server");

        VariableAccessExpression scenario = VariableAccessExpression.of(SCENARIO);

        task.getScript()
            .addStatement(AskInputStatement.of(scenario.getVariableName(), true, JobInputType.scenarioId(WRITABLE)))
            .addStatement(ExecuteOptimizationServerTaskStatement
                .forTaskId(StringExpression.of("EngineTask"))
                .withInput(INPUT_COLLECTOR, ScenarioDataExpression.of(scenario))
                .withOutputScenario(OUTPUT_COLLECTOR, scenario)
            );
        return task;
    }

3.2.5. Optimization Server - Run Python Engine Task using a Collector

This task runs the Python engine task using a DOM collector in the Optimization server.

    @Bean
    public ScriptedTaskDescription pythonEngineCollectorTask() {
        ScriptedTaskDescription task = new ScriptedTaskDescription("PythonEngineCollectorTask", "Optimization server - Run Python Engine task using collector");
        task.setDescription("Run the Python engine task using collector in the Optimization server");

        VariableAccessExpression scenario = VariableAccessExpression.of(SCENARIO);

        task.getScript()
            .addStatement(AskInputStatement.of(scenario.getVariableName(), true, JobInputType.scenarioId(WRITABLE)))
            .addStatement(ExecuteOptimizationServerTaskStatement
                .forTaskId(StringExpression.of("PythonEngineCollectorTask"))
                .withInput(INPUT_COLLECTOR, ScenarioDataExpression.of(scenario))
                .withOutputScenario(OUTPUT_COLLECTOR, scenario)
            );
        return task;
    }

3.2.6. Optimization Server - Run Python Engine Task using a Dataframe

This task runs the Python engine task using a dataframe in the Optimization server.

    @Bean
    public ScriptedTaskDescription pythonEngineDataframeTask() {
        ScriptedTaskDescription task = new ScriptedTaskDescription("PythonEngineDataframeTask", "Optimization server - Run Python Engine task using dataframe");
        task.setDescription("Run the Python engine task using dataframes in the Optimization server");

        VariableAccessExpression scenario = VariableAccessExpression.of(SCENARIO);

        task.getScript()
            .addStatement(AskInputStatement.of(scenario.getVariableName(), true, JobInputType.scenarioId(WRITABLE)))
            .addStatement(ExecuteOptimizationServerTaskStatement
                .forTaskId(StringExpression.of("PythonEngineDataframeTask"))
                .withInput(INPUT_COLLECTOR, ScenarioDataExpression.of(scenario))
                .withOutputScenario(OUTPUT_COLLECTOR, scenario,
                    StringExpression.of("GeneIssue"))
            );
        return task;
    }