From 36b093c5f16246497fce619a2e44c4b7352cfd45 Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Mon, 6 Jul 2026 20:47:22 +0200 Subject: [PATCH 01/28] docs: deploying to the platform --- docs/src/modules/ROOT/nav.adoc | 6 + .../deploying-to-platform/_preview-note.adoc | 7 + .../getting-started.adoc | 110 ++++++++++ .../deploying-to-platform/introduction.adoc | 103 +++++++++ .../pages/deploying-to-platform/metrics.adoc | 29 +++ .../deploying-to-platform/model-metadata.adoc | 199 ++++++++++++++++++ .../quickstart/service/getting-started.adoc | 4 +- 7 files changed, 457 insertions(+), 1 deletion(-) create mode 100644 docs/src/modules/ROOT/pages/deploying-to-platform/_preview-note.adoc create mode 100644 docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc create mode 100644 docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc create mode 100644 docs/src/modules/ROOT/pages/deploying-to-platform/metrics.adoc create mode 100644 docs/src/modules/ROOT/pages/deploying-to-platform/model-metadata.adoc diff --git a/docs/src/modules/ROOT/nav.adoc b/docs/src/modules/ROOT/nav.adoc index 4357f24700..0f5de8a2a6 100644 --- a/docs/src/modules/ROOT/nav.adoc +++ b/docs/src/modules/ROOT/nav.adoc @@ -38,6 +38,12 @@ ** xref:running-timefold-solver/benchmarking-and-tweaking.adoc[leveloffset=+1] ** xref:running-timefold-solver/solver-diagnostics.adoc[leveloffset=+1] +* Deploying to the Timefold Platform +** xref:deploying-to-platform/introduction.adoc[Overview] +** xref:deploying-to-platform/getting-started.adoc[Getting started] +** xref:deploying-to-platform/model-metadata.adoc[leveloffset=+1] +** xref:deploying-to-platform/metrics.adoc[leveloffset=+1] + * Optimization algorithms ** xref:optimization-algorithms/overview.adoc[Overview] ** xref:optimization-algorithms/construction-heuristics.adoc[leveloffset=+1] diff --git a/docs/src/modules/ROOT/pages/deploying-to-platform/_preview-note.adoc b/docs/src/modules/ROOT/pages/deploying-to-platform/_preview-note.adoc new file mode 100644 index 0000000000..c014451d1c --- /dev/null +++ b/docs/src/modules/ROOT/pages/deploying-to-platform/_preview-note.adoc @@ -0,0 +1,7 @@ +[IMPORTANT] +==== +Deploying custom models to the platform is in preview. + +This is currently only available to a limited set of partners. +If you're interested in joining this preview program, mailto:info@timefold.ai[get in touch with the Timefold team] to discuss access. +==== diff --git a/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc b/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc new file mode 100644 index 0000000000..ce80190128 --- /dev/null +++ b/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc @@ -0,0 +1,110 @@ += Getting started: deploying to the platform (Preview) +:page-aliases: quickstart/service/deploy-to-platform.adoc +:description: Deploy the service you built to Timefold Platform +:doctype: book +:sectnums: +:icons: font + +include::../_attributes.adoc[] + +This guide continues from xref:quickstart/service/getting-started.adoc[Getting started: building a service], where you built the School Timetabling model as a service. +It walks you through deploying that same model to Timefold Platform, so it can be reached through a managed, multi-tenant REST API instead of `mvn quarkus:dev` on your own machine. + +For the reasoning behind why you'd want to do this, see xref:deploying-to-platform/introduction.adoc[Deploying to the Timefold Platform]. + +include::_preview-note.adoc[] + +[sectnums!] +== Prerequisites + +To complete this guide, you need: + +* The School Timetabling service built in xref:quickstart/service/getting-started.adoc[Getting started: building a service]. +* A Timefold Platform account with tenant-admin access to the tenant you want to register the model in. +* A Personal Access Token (PAT), scoped to the tenant you want to register the model in, with *Create*, *Read*, *Update*, and *Delete* permissions on the *Registered Model* resource. +See xref:#_generate_a_personal_access_token[Generate a Personal Access Token] below. +* Push access to Timefold's container registry, since the `configure` goal builds and pushes your model's container image there. +This access needs to be requested; mailto:info@timefold.ai[get in touch with the Timefold team] before you try to deploy. + +[#_add_the_maven_plugin] +== Add the Maven plugin + +The `timefold-maven-plugin` connects your build to the platform's container registry and handles registering your model. +Add it to your `pom.xml`, and configure it with your `platformUrl`, a registration `key` for your model, and the tenant(s) to register it under. + +Your `platformUrl` will typically be `https://app.timefold.ai`. +See https://docs.timefold.ai/timefold-platform/latest/api/integration-scenarios/integration-scenarios#_our_recommendation[Integration scenarios] if you're deploying to a different installation, for example a self-hosted platform or a different data residency region. + +Also set `handleSubscription` to `true`. +Registering a model does not automatically subscribe your tenant to it; without this setting, your tenant won't see the model on the platform after you deploy it. + +Follow the setup instructions in the https://github.com/TimefoldAI/timefold-solver/blob/main/service/tools/maven-plugin/README.adoc[Maven plugin documentation] to add and configure it. + +[#_authenticate] +== Authenticate + +[#_generate_a_personal_access_token] +=== Generate a Personal Access Token + +Generate a PAT from your user menu on the platform (*User menu → Access tokens → Generate new token*). +When selecting its permissions, scope the token to the tenant you want to register the model in, and grant *Create*, *Read*, *Update*, and *Delete* on the *Registered Model* resource, since the plugin needs all four to register, update, and later undeploy your model. + +See https://docs.timefold.ai/timefold-platform/latest/api/platform-api#_authentication_with_personal_access_tokens[Authentication with Personal Access Tokens] for the full walkthrough, including screenshots of the token generation dialog. + +=== Export the token + +The plugin reads your PAT from the `TIMEFOLD_PAT` environment variable. +Export it before building: + +[source,bash,options="nowrap"] +---- +export TIMEFOLD_PAT= +---- + +[#_build_and_deploy] +== Build and deploy + +Build and deploy your model in a single command: + +[source,bash,options="nowrap"] +---- +mvn clean package timefold:deploy +---- + +=== Preview without pushing container +To preview what would happen without actually pushing the container image or registering the model, add `-Dtimefold.dryRun=true`. +This logs the requests that would have been made instead of sending them. + +[#_what_happens_on_deploy] +=== What happens on deploy + +During `package`, the build generates a model descriptor (`target/timefold/timefold-model-descriptor.json`) and packages it together with the OpenAPI specification, JSON schemas, and default configuration profile into `target/model-descriptor.zip`. + +The `timefold:deploy` goal uploads that archive and registers it against the platform's model registration endpoint (`POST /api/platform/v1/models`), using the `key` and `tenants` from the plugin configuration. + +If a model with the same `key` is already registered, the deploy fails with a conflict (HTTP 409) unless `overwrite` is enabled, in which case the existing registration is updated instead (`PATCH /api/platform/v1/models/`). +You can also set this from the command line with `-Dtimefold.model.overwrite=true`. + +[#_verify_on_the_platform] +== Verify on the platform + +Log in to the platform UI at your `platformUrl` and find the model under your tenant. +Before considering the model ready for review, confirm the following: + +* *The model shows up in your tenant*: go to *Manage tenant → Models* and confirm your model is listed, with the version details (major API version, and the current minor version's build time, commit, and solver version) matching what you just deployed. +If it's missing, double check that `handleSubscription` is set to `true` in the Maven plugin configuration. +* *It solves*: submit your xref:running-timefold-solver/service/demo-data.adoc[demo dataset] and confirm the solve completes with a reasonable score. +* *The output looks right*: inspect the resulting schedule and check that lessons are assigned sensible timeslots and rooms. +* *Constraints show up by name in score analysis*: the score analysis breakdown should show your constraints under human-readable names, not raw identifiers. +This requires the richer constraint metadata described in xref:deploying-to-platform/model-metadata.adoc#_constraint_descriptions_and_groups[Constraint descriptions and groups]. +* *Input and output metrics show up*: the dataset view should display the metrics you exposed, as described in xref:deploying-to-platform/metrics.adoc[Using metrics]. +* *You can add a configuration profile*: add a new configuration profile in the UI and override one of your xref:running-timefold-solver/service/constraint-overrides.adoc[constraint weights], then confirm a re-solve picks up the new weight. +See xref:deploying-to-platform/model-metadata.adoc#_configuration_profiles[Configuration profiles] for the properties that define the default profile shipped with your model. + +[sectnums!] +== Next + +* Learn about the platform-specific metadata that shapes your model's UI experience: xref:deploying-to-platform/model-metadata.adoc[Platform model metadata]. +* Make solve graphs, comparison, and Insights more useful by adding xref:deploying-to-platform/metrics.adoc[input and output metrics]. +* Read more about xref:running-timefold-solver/service/overview.adoc[Building a service]. +* See the https://github.com/TimefoldAI/timefold-solver/blob/main/service/tools/maven-plugin/README.adoc[Maven plugin documentation] for the full set of goals and parameters, including how to undeploy a model. diff --git a/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc b/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc new file mode 100644 index 0000000000..97c98f0877 --- /dev/null +++ b/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc @@ -0,0 +1,103 @@ +[#_deploying_to_platform_introduction] += Deploying to the Timefold Platform (Preview) +:description: Why deploy your service model to Timefold Platform. +:doctype: book +:icons: font + +include::../_attributes.adoc[] + +Building a model is only half of getting a planning problem solved in production. +The other half is operational: hosting it, scaling it, exposing it to consumers through an API, and making its decisions explainable. + +Timefold Platform takes care of that other half, which is why deploying your model there is the natural next step once it's built. + +include::_preview-note.adoc[] + +NOTE: Using Timefold Platform, including deploying and running your own custom model, is subject to usage fees under your plan. + +== Platform layers + +The platform is organized in layers, each building on the one beneath it, per the platform's own https://docs.timefold.ai/timefold-platform/latest/introduction[introduction]. +From a model developer's perspective, each layer is plumbing you'd otherwise have to build yourself. + +[#_enterprise_edition_features] +=== Enterprise Edition features + +Deploying to the platform gives your model access to Timefold Solver Enterprise Edition capabilities, without you having to license, wire up, or tune them yourself: + +* *Explainability*: xref:constraints-and-score/understanding-the-score.adoc[Score analysis] breaks a solution down constraint-by-constraint, so consumers can see why the solver made the decisions it did, and what the impact would be of overriding one. +* *Recommendations*: the xref:responding-to-change/recommendation-api.adoc#assignmentRecommendationAPI[Recommendation API] suggests the best fit for a new assignment without a full re-solve. +* *Performance*: xref:running-timefold-solver/multithreaded-solving.adoc#multithreadedIncrementalSolving[multithreaded solving], xref:optimization-algorithms/move-selector-reference.adoc#nearbySelection[nearby selection], and xref:constraints-and-score/performance.adoc#constraintProfiling[constraint profiling] let the solver scale to larger datasets. + +[#_hosting_and_orchestration] +=== Hosting and orchestration + +* *Hosting and scaling*: the platform builds, stores, and runs the container image, scaling it to the number of concurrent solve requests, so you're not managing a Kubernetes deployment or a fleet of containers. +* *Compliance*: the platform is ISO and SOC certified, so you don't have to pursue and maintain those certifications yourself for your model's hosting. +See https://trust.timefold.ai/[trust.timefold.ai] for details. +* *A multi-tenant REST gateway*: consumers of your model authenticate with API keys scoped to a tenant, so authentication, rate limiting, and per-customer isolation come built in. +* *Role-based access control*: https://docs.timefold.ai/timefold-platform/latest/how-tos/member-management-and-roles[member management and roles] gives tenants a built-in User/Administrator permission model, distinguishing who can view datasets from who can manage your model's configuration profiles, API keys, and members, so you don't have to build your own authorization layer. +* *Solve queue*: when concurrent solve requests exceed provisioned resources, the https://docs.timefold.ai/timefold-platform/latest/how-tos/solve-queue[solve queue] automatically queues and runs them as capacity frees up, so you don't have to implement your own request throttling or backpressure. +* *Audit logging*: configuration changes to your model, such as updates to a configuration profile, are automatically recorded in the https://docs.timefold.ai/timefold-platform/latest/how-tos/audit-log[audit log], with who made the change and when, without you building your own change-tracking system. + +[#_integrations] +=== Integrations + +* *Result delivery*: consumers receive results via https://docs.timefold.ai/timefold-platform/latest/api/receiving-model-api-results/receiving-model-api-results[webhooks, server-sent events, or polling], so you don't have to design your own asynchronous job-notification mechanism. +* *Self-service configuration profiles*: consumers can add their own configuration profiles (termination limits, thread counts, constraint weight overrides) without opening a pull request against your model. +* *Parallel versions and staggered releases*: because a model's identifier includes its API version, you can run multiple major versions of your model side by side and let consumers migrate at their own pace. +Within a version, you can stagger a release the same way https://docs.timefold.ai/timefold-platform/latest/models/versioning-and-maturity#_staggered_model_releases[Timefold does for its own models]: register a new implementation under its own registration key, restricted to a subset of tenants (for example a staging tenant), while other tenants keep running the previous implementation. +See xref:#_registration_key[Registration key] for how registration keys make this possible. +* *Incremental replanning*: the https://docs.timefold.ai/timefold-platform/latest/how-tos/from-patch-endpoint[`/from-patch` endpoint] lets consumers apply small, targeted changes to an existing dataset instead of resubmitting the full input, with built-in traceability and versioning between revisions, so you don't have to design your own patch protocol or dataset lineage tracking. +* *Secrets management*: if your model calls out to external systems, for example through webhooks, https://docs.timefold.ai/timefold-platform/latest/how-tos/secrets-management[secrets management] lets consumers store credentials encrypted and write-only, instead of you building your own credential storage. + +[#_explainability_and_trust] +=== Explainability and trust + +* *A UI for your consumers*: the platform UI lets consumers submit datasets, inspect the resulting plan, and read a constraint-by-constraint score analysis, without you building any of that tooling yourself. +* *Dataset organization*: consumers can name, tag, search, and filter their datasets out of the box, as described in https://docs.timefold.ai/timefold-platform/latest/how-tos/categorizing-runs[categorizing datasets], instead of you building dataset management UI. +* *Dataset comparison*: https://docs.timefold.ai/timefold-platform/latest/how-tos/comparing-runs[comparing datasets] lets consumers compare metrics, scores, and constraint breakdowns across multiple datasets side by side, including radar charts and saved comparisons, without you building any comparison tooling. + +[#_intelligence] +=== Intelligence + +* *Insights*: https://docs.timefold.ai/timefold-platform/latest/how-tos/insights[Insights] tracks metric trends across datasets over time, so consumers can spot whether operational planning is improving or degrading, without you building your own analytics dashboard. +* *Experiments*: https://docs.timefold.ai/timefold-platform/latest/how-tos/experiments[experiments] let consumers run many dataset and configuration-profile combinations in one batch and compare the results, including benchmarking different model versions against each other, instead of you building your own scenario-testing or benchmarking harness. + +In short, deploying to the platform turns your model from "a service I have to run" into "a product other people can safely self-serve." + +[#_platform_concepts] +== Platform concepts + +A few terms come up throughout this section of the documentation: + +Custom model:: The model you build and deploy yourself, as opposed to one of Timefold's https://docs.timefold.ai/[pre-built, off-the-shelf models]. +This is what this section of the documentation is about. + +Model registration:: The act of publishing your model's descriptor and container image to the platform under a registration key, using `timefold:deploy`. +See xref:deploying-to-platform/getting-started.adoc[Getting started: deploying to the platform] for the full walkthrough. + +[#_model_identifier] +Model identifier:: Made up of your model's bare id and its API version, combined as `_` (for example `schooltimetabling_v1`). +This is what appears in the consumer-facing API path (`/api/models//`). +The bare model id comes from your Quarkus application name, which defaults to your Maven artifact id unless you set `quarkus.application.name` explicitly. +The version comes from `timefold.application.version`. +Neither comes from `timefold.application.name` (which is only the human-readable display name shown in the UI) or from `timefold.application.description`. + +[#_registration_key] +Registration key:: The `key` you set through the Maven plugin, as described in xref:deploying-to-platform/getting-started.adoc#_add_the_maven_plugin[Add the Maven plugin]. +This is an administrative identifier, used only for managing the deployment itself (updating, overwriting, or undeploying it) and isn't part of the consumer-facing API. +Keeping it distinct from the model identifier matters once you deploy more than one implementation of the same model, for example a stable version and a private beta of the next version: both can share the same model identifier and API path, while each has its own registration key for independent management. + +[#_model_privacy] +Model privacy:: Every registered model has a visibility scope that determines which tenants can see and use it: *Private* (a single tenant) or *Shared* (a selected set of tenants). +The Maven plugin's `deploy` goal infers one of these automatically from how many tenants you list, as described in xref:deploying-to-platform/getting-started.adoc#_add_the_maven_plugin[Add the Maven plugin]. +A third scope, *Public* (visible to every tenant on the platform), also exists, but registering a model as Public is restricted to global admins of the platform, so it isn't a self-service option for model developers. + +Model consumers:: The tenant users, and the systems they build, that call your model's REST API: submitting datasets, reading results and score analysis, and managing configuration profiles. +As the model developer, you typically aren't the consumer yourself; you're building something others on your tenant (or on tenants you share the model with) will use. + +Model subscription:: Tenants subscribe to a model by its model identifier (not its registration key) to get access to it. +The Maven plugin can automate this for you: its `handleSubscription` setting subscribes your tenant when you register the model, and unsubscribes it when you undeploy. + +To get started, see xref:deploying-to-platform/getting-started.adoc[Getting started: deploying to the platform]. diff --git a/docs/src/modules/ROOT/pages/deploying-to-platform/metrics.adoc b/docs/src/modules/ROOT/pages/deploying-to-platform/metrics.adoc new file mode 100644 index 0000000000..dc5a69f21b --- /dev/null +++ b/docs/src/modules/ROOT/pages/deploying-to-platform/metrics.adoc @@ -0,0 +1,29 @@ +[#_using_metrics] += Using metrics +:description: Input and output metrics make platform features like solve graphs, comparison, and Insights more useful. +:doctype: book +:icons: font + +include::../_attributes.adoc[] + +include::_preview-note.adoc[] + +Metrics aren't just extra fields in the API response. +Once your model is xref:deploying-to-platform/getting-started.adoc[deployed to Timefold Platform], input and output metrics feed several platform features that would otherwise only show consumers a raw score and a solved dataset: + +* *Solve graphs*: output metrics can be plotted alongside the hard, medium, and soft score as the solver runs, so consumers see how a metric evolves over time, not just its final value. +See https://docs.timefold.ai/timefold-platform/latest/how-tos/interpreting-model-run-results#_solve_graphs[Solve graphs] for details. +* *Dataset comparison*: input and output metrics are available as columns when comparing multiple datasets side by side, making it possible to spot how problem size or solution quality differs between them. +See https://docs.timefold.ai/timefold-platform/latest/how-tos/comparing-runs[Comparing datasets] for details. +* *Insights*: both metric types can be tracked over time across many datasets, so consumers can tell whether operational planning is improving or degrading. +See https://docs.timefold.ai/timefold-platform/latest/how-tos/insights[Insights] for details. + +Without input and output metrics, none of these views have anything model-specific to show beyond the score. + +See xref:running-timefold-solver/service/exposing-metrics.adoc[Exposing metrics] for how to define and implement input and output metrics on your model. + +While you're at it, give each metric a proper OpenAPI `title`, `description`, and `format` in its `@Schema` annotation. +These surface directly in the platform UI: in solve graphs, the comparison view, and Insights, consumers see the `title` as the metric's label and the `description` as its explanation, not the raw field name. +A metric without a clear title and description is much harder for a consumer to interpret at a glance. + +Redeploy your model, as described in xref:deploying-to-platform/getting-started.adoc[Getting started: deploying to the platform], then submit a dataset and check that your metrics show up in the solve graphs, comparison view, and Insights. diff --git a/docs/src/modules/ROOT/pages/deploying-to-platform/model-metadata.adoc b/docs/src/modules/ROOT/pages/deploying-to-platform/model-metadata.adoc new file mode 100644 index 0000000000..eff80a5368 --- /dev/null +++ b/docs/src/modules/ROOT/pages/deploying-to-platform/model-metadata.adoc @@ -0,0 +1,199 @@ +[#_platform_model_metadata] += Platform model metadata +:page-aliases: running-timefold-solver/service/platform-model-metadata.adoc +:description: Platform-specific model metadata that shapes the score analysis, configuration profile, and model identity UI. +:doctype: book +:sectnums: +:icons: font + +Once your model is xref:deploying-to-platform/getting-started.adoc[deployed to Timefold Platform], the platform UI surfaces several pieces of your model's code and configuration directly. + +This page documents that platform-specific metadata: which identity properties describe your model on the platform, how to give your constraints human-readable names and groups, and how to ship a default configuration profile. + +See xref:deploying-to-platform/introduction.adoc[Deploying to the Timefold Platform] for why you'd want to do this in the first place, and xref:deploying-to-platform/introduction.adoc#_platform_concepts[Platform concepts] for how this metadata relates to your model's identifier, registration key, and visibility on the platform. + +include::_preview-note.adoc[] + +[#_model_identity_properties] +== Model identity properties + +The following `application.properties` describe your model's identity on the platform. +`timefold.application.name`, `timefold.application.version`, and the `timefold.application.contact.*` properties are covered in xref:quickstart/service/getting-started.adoc[Getting started: building a service], since they are required for every model, service or not. + +[cols="2,3", options="header"] +|=== +| Property | Purpose + +| `timefold.application.description` +| Description of the model, included in the generated OpenAPI specification and shown on the platform. + +| `timefold.model.maturity-level` +| Maturity level of the model. Defaults to `Experimental` if not set. +|=== + +Use `timefold.model.maturity-level` to tell consumers how stable a given model is, using the same https://docs.timefold.ai/timefold-platform/latest/models/versioning-and-maturity[maturity levels] Timefold uses for its own models (`Example`, `Experimental`, `Preview`, `Stable`, `Deprecated`). +This is especially useful once you're xref:deploying-to-platform/introduction.adoc#_integrations[running multiple versions of your model in parallel]: consumers can tell at a glance which version is safe to build on and which is still being validated, instead of having to ask you. + +=== Logo + +To give your model a custom logo on the platform, add a square `logo.png` file to a `model-images` folder at the root of your project, next to `pom.xml`: + +[source,options="nowrap"] +---- +my-model/ +├── model-images/ +│ └── logo.png +├── src/ +└── pom.xml +---- + +The build picks up any file in `model-images` automatically; `logo.png` specifically is used as the model's logo, and any other file is added as an additional model image. + +[#_constraint_descriptions_and_groups] +== Constraint descriptions and groups + +By default, a constraint is identified only by the string you pass to `asConstraint(String id)`, which is also used as its display name. +To give consumers a better score analysis and configuration profile experience, attach a `ConstraintInfo` to the constraint instead. + +`ConstraintInfo` and `ConstraintGroupInfo` live in `ai.timefold.solver.service.definition.api.description`: + +* `ConstraintInfo(id, name, description, constraintGroup)`: `id` is the stable identifier of the constraint, `name` is the human-readable name shown in the UI, `description` explains the constraint's goal, and `constraintGroup` optionally assigns the constraint to a `ConstraintGroupInfo`. +* `ConstraintGroupInfo(id, name, description, icon, tags)`: groups related constraints under a shared category. `icon` accepts any icon name from https://tabler.io/icons[Tabler Icons], and `tags` are optional labels you can use to classify or filter groups. + +Extending the `TimetableConstraintProvider` from xref:running-timefold-solver/service/constraint-overrides.adoc[Adjusting constraint weights] with names, descriptions, and a group: + +.The ConstraintProvider class, with ConstraintInfo attached. +[tabs] +==== +Java:: ++ +-- +[source,java,options="nowrap"] +---- +public class TimetableConstraintProvider implements ConstraintProvider { + + public static final String TEACHER_CONFLICT = "Teacher conflict"; + public static final String ROOM_CONFLICT = "Room conflict"; + + private static final ConstraintGroupInfo CONFLICT_GROUP = new ConstraintGroupInfo( + "conflicts", + "Conflicts", + "Constraints that prevent double-booking of teachers and rooms.", + "alert-triangle", + new String[] { "hard-constraints" }); + + Constraint roomConflict(ConstraintFactory constraintFactory) { + return constraintFactory + // constraint implementation excluded + .asConstraint(new ConstraintInfo(ROOM_CONFLICT, "Room conflict", + "A room can be used for at most one lesson at the same time.", CONFLICT_GROUP)); + } + + Constraint teacherConflict(ConstraintFactory constraintFactory) { + return constraintFactory + // constraint implementation excluded + .asConstraint(new ConstraintInfo(TEACHER_CONFLICT, "Teacher conflict", + "A teacher can teach at most one lesson at the same time.", CONFLICT_GROUP)); + } + + // other constraints excluded +} +---- +-- + +Kotlin:: ++ +-- +[source,kotlin,options="nowrap"] +---- +class TimetableConstraintProvider : ConstraintProvider { + + companion object { + const val TEACHER_CONFLICT = "Teacher conflict" + const val ROOM_CONFLICT = "Room conflict" + + private val CONFLICT_GROUP = ConstraintGroupInfo( + "conflicts", + "Conflicts", + "Constraints that prevent double-booking of teachers and rooms.", + "alert-triangle", + arrayOf("hard-constraints") + ) + } + + fun roomConflict(constraintFactory: ConstraintFactory): Constraint { + return constraintFactory + // constraint implementation excluded + .asConstraint(ConstraintInfo(ROOM_CONFLICT, "Room conflict", + "A room can be used for at most one lesson at the same time.", CONFLICT_GROUP)) + } + + fun teacherConflict(constraintFactory: ConstraintFactory): Constraint { + return constraintFactory + // constraint implementation excluded + .asConstraint(ConstraintInfo(TEACHER_CONFLICT, "Teacher conflict", + "A teacher can teach at most one lesson at the same time.", CONFLICT_GROUP)) + } + + // other constraints excluded +} +---- +-- +==== + +The `id` you pass into `ConstraintInfo` is still the identifier used by `@ConstraintReference` in your `ModelConfigOverrides`, as described in xref:running-timefold-solver/service/constraint-overrides.adoc[Adjusting constraint weights]. +The `name`, `description`, and `constraintGroup` are purely presentational: they are what the platform's score analysis view and configuration profile editor use to show your constraints to consumers, instead of falling back to the raw id. + +The UI groups constraints that don't specify a `constraintGroup` under a default group. + +[#_configuration_profiles] +== Configuration profiles + +Every model ships with a default configuration profile, defined through `ai.timefold.model.default-config.*` properties in `application.properties`. +This profile is what consumers get out of the box, before they add any profile of their own. + +[cols="2,3", options="header"] +|=== +| Property | Purpose + +| `ai.timefold.model.default-config.name` +| Name of the default configuration profile. + +| `ai.timefold.model.default-config.description` +| Description of the default configuration profile. + +| `ai.timefold.model.default-config.max-thread-count` +| Default number of threads used for solving. + +| `ai.timefold.model.default-config.map.provider` +| Default map provider, for models that require map data. + +| `ai.timefold.model.default-config.map.location` +| Default map location, for models that require map data. + +| `ai.timefold.model.default-config.map.max-distance-from-road` +| Default maximum distance from a road, for models that require map data. + +| `ai.timefold.model.default-config.map.transport-type` +| Default transport type used when computing distances, for models that require map data. + +| `ai.timefold.model.default-config.map.use-traffic` +| Whether the default profile takes live traffic into account. Defaults to `false`. + +| `ai.timefold.model.default-config.termination.spent-limit` +| The default maximum solving time, in the https://www.digi.com/resources/documentation/digidocs/90001488-13/reference/r_iso_8601_duration_format.htm[ISO 8601 duration format]. This property is *required*. + +| `ai.timefold.model.default-config.termination.unimproved-spent-limit` +| The default unimproved time limit. If not set, https://docs.timefold.ai/timefold-solver/latest/optimization-algorithms/overview#diminishedReturnsTermination[Diminished Returns termination] is used instead. +|=== + +Once your model is deployed, tenant users can add additional configuration profiles on top of this default directly in the platform UI, without you making any code changes. +A configuration profile is where per-request xref:running-timefold-solver/service/constraint-overrides.adoc[constraint weight overrides], thread count, memory, and termination limits are set for a specific use case. +See https://docs.timefold.ai/timefold-platform/latest/how-tos/configuration-parameters-and-profiles[Configuration parameters and profiles] for how tenant users manage profiles from the platform side. + +[#_visualization] +== Visualization + +There is currently no dedicated mechanism for adding a custom visualization UI for your model's solution on the platform. +Consumers see the raw solution data and the generic score analysis view. +This is an open area of the platform, so expect it to evolve in future releases. diff --git a/docs/src/modules/ROOT/pages/quickstart/service/getting-started.adoc b/docs/src/modules/ROOT/pages/quickstart/service/getting-started.adoc index da43c65083..7487fb801d 100644 --- a/docs/src/modules/ROOT/pages/quickstart/service/getting-started.adoc +++ b/docs/src/modules/ROOT/pages/quickstart/service/getting-started.adoc @@ -477,4 +477,6 @@ Since this is a "Getting Started" guide, not everything is covered yet. * Learn about improvements you can make to your model: ** How to enrich your model with xref:running-timefold-solver/service/modeling-changes.adoc[Model Enrichment]. -** How to configure your xref:running-timefold-solver/service/rest-api.adoc[REST API] with validations, custom endpoints, etc. \ No newline at end of file +** How to configure your xref:running-timefold-solver/service/rest-api.adoc[REST API] with validations, custom endpoints, etc. +** How to xref:deploying-to-platform/getting-started.adoc[deploy this model to Timefold Platform]. +** How to xref:deploying-to-platform/metrics.adoc[use input and output metrics] to make platform features like solve graphs, comparison, and Insights more useful. From 2b90919689339a826790a963548765e12c61d205 Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Tue, 7 Jul 2026 09:47:37 +0200 Subject: [PATCH 02/28] chore: cleanup aliases, these are new pages, no need for them --- .../ROOT/pages/deploying-to-platform/getting-started.adoc | 1 - .../modules/ROOT/pages/deploying-to-platform/model-metadata.adoc | 1 - 2 files changed, 2 deletions(-) diff --git a/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc b/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc index ce80190128..724e2675c4 100644 --- a/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc +++ b/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc @@ -1,5 +1,4 @@ = Getting started: deploying to the platform (Preview) -:page-aliases: quickstart/service/deploy-to-platform.adoc :description: Deploy the service you built to Timefold Platform :doctype: book :sectnums: diff --git a/docs/src/modules/ROOT/pages/deploying-to-platform/model-metadata.adoc b/docs/src/modules/ROOT/pages/deploying-to-platform/model-metadata.adoc index eff80a5368..eab0a9f6c9 100644 --- a/docs/src/modules/ROOT/pages/deploying-to-platform/model-metadata.adoc +++ b/docs/src/modules/ROOT/pages/deploying-to-platform/model-metadata.adoc @@ -1,6 +1,5 @@ [#_platform_model_metadata] = Platform model metadata -:page-aliases: running-timefold-solver/service/platform-model-metadata.adoc :description: Platform-specific model metadata that shapes the score analysis, configuration profile, and model identity UI. :doctype: book :sectnums: From 137c65f4532d872da90b74d50b10bb32d72e7a6b Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Tue, 7 Jul 2026 09:47:47 +0200 Subject: [PATCH 03/28] chore: no plan details yet --- .../modules/ROOT/pages/deploying-to-platform/introduction.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc b/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc index 97c98f0877..b142f6f5d2 100644 --- a/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc +++ b/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc @@ -13,7 +13,7 @@ Timefold Platform takes care of that other half, which is why deploying your mod include::_preview-note.adoc[] -NOTE: Using Timefold Platform, including deploying and running your own custom model, is subject to usage fees under your plan. +NOTE: Using Timefold Platform, including deploying and running your own custom model, is subject to usage fees. == Platform layers From ef9039535aa5caaa2a3ed3e52c0653c391c8f6b5 Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Tue, 7 Jul 2026 09:47:57 +0200 Subject: [PATCH 04/28] docs: mention maps service as benefit --- .../modules/ROOT/pages/deploying-to-platform/introduction.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc b/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc index b142f6f5d2..cab3c6b60f 100644 --- a/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc +++ b/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc @@ -50,6 +50,8 @@ Within a version, you can stagger a release the same way https://docs.timefold.a See xref:#_registration_key[Registration key] for how registration keys make this possible. * *Incremental replanning*: the https://docs.timefold.ai/timefold-platform/latest/how-tos/from-patch-endpoint[`/from-patch` endpoint] lets consumers apply small, targeted changes to an existing dataset instead of resubmitting the full input, with built-in traceability and versioning between revisions, so you don't have to design your own patch protocol or dataset lineage tracking. * *Secrets management*: if your model calls out to external systems, for example through webhooks, https://docs.timefold.ai/timefold-platform/latest/how-tos/secrets-management[secrets management] lets consumers store credentials encrypted and write-only, instead of you building your own credential storage. +* *Maps service*: for routing models, the https://docs.timefold.ai/timefold-platform/latest/how-tos/maps-service[maps service] calculates and caches distance and travel matrices for you, with incremental updates, throttling, and concurrency guarding against the map provider, instead of you integrating and managing a map provider yourself. +It supports multiple providers out of the box, and consumers can configure which one to use through your model's xref:deploying-to-platform/model-metadata.adoc#_configuration_profiles[configuration profile]. [#_explainability_and_trust] === Explainability and trust From 5d12b622748753f625a5a803defdce554ff4503b Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Tue, 7 Jul 2026 09:48:03 +0200 Subject: [PATCH 05/28] docs: better wording --- .../modules/ROOT/pages/deploying-to-platform/introduction.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc b/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc index cab3c6b60f..9300c9f382 100644 --- a/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc +++ b/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc @@ -56,7 +56,7 @@ It supports multiple providers out of the box, and consumers can configure which [#_explainability_and_trust] === Explainability and trust -* *A UI for your consumers*: the platform UI lets consumers submit datasets, inspect the resulting plan, and read a constraint-by-constraint score analysis, without you building any of that tooling yourself. +* *A UI for your Model API consumers*: the platform UI lets consumers submit datasets, inspect the resulting plan, and read a constraint-by-constraint score analysis, without you building any of that tooling yourself. * *Dataset organization*: consumers can name, tag, search, and filter their datasets out of the box, as described in https://docs.timefold.ai/timefold-platform/latest/how-tos/categorizing-runs[categorizing datasets], instead of you building dataset management UI. * *Dataset comparison*: https://docs.timefold.ai/timefold-platform/latest/how-tos/comparing-runs[comparing datasets] lets consumers compare metrics, scores, and constraint breakdowns across multiple datasets side by side, including radar charts and saved comparisons, without you building any comparison tooling. From f9af4ec308011697214daa547c32bf14a4cfb19a Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Tue, 7 Jul 2026 09:48:36 +0200 Subject: [PATCH 06/28] chore: add note on names & letters --- .../modules/ROOT/pages/deploying-to-platform/introduction.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc b/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc index 9300c9f382..43761eb583 100644 --- a/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc +++ b/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc @@ -86,6 +86,8 @@ The bare model id comes from your Quarkus application name, which defaults to yo The version comes from `timefold.application.version`. Neither comes from `timefold.application.name` (which is only the human-readable display name shown in the UI) or from `timefold.application.description`. +The bare id may only contain letters, numbers, and hyphens, and can't itself contain an underscore, since the version is appended with one. + [#_registration_key] Registration key:: The `key` you set through the Maven plugin, as described in xref:deploying-to-platform/getting-started.adoc#_add_the_maven_plugin[Add the Maven plugin]. This is an administrative identifier, used only for managing the deployment itself (updating, overwriting, or undeploying it) and isn't part of the consumer-facing API. From ae1850dd7af138bc7438258a6245b6158fc0fbe4 Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Tue, 7 Jul 2026 09:49:01 +0200 Subject: [PATCH 07/28] docs: simplify and leave out licensing --- .../modules/ROOT/pages/deploying-to-platform/introduction.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc b/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc index 43761eb583..7f3d96d7b8 100644 --- a/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc +++ b/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc @@ -23,7 +23,7 @@ From a model developer's perspective, each layer is plumbing you'd otherwise hav [#_enterprise_edition_features] === Enterprise Edition features -Deploying to the platform gives your model access to Timefold Solver Enterprise Edition capabilities, without you having to license, wire up, or tune them yourself: +Deploying to the platform will give your model access to Timefold Solver Enterprise Edition capabilities: * *Explainability*: xref:constraints-and-score/understanding-the-score.adoc[Score analysis] breaks a solution down constraint-by-constraint, so consumers can see why the solver made the decisions it did, and what the impact would be of overriding one. * *Recommendations*: the xref:responding-to-change/recommendation-api.adoc#assignmentRecommendationAPI[Recommendation API] suggests the best fit for a new assignment without a full re-solve. From 71df06f430cce36338a0bbbaf40b0000b9abf232 Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Tue, 7 Jul 2026 14:54:25 +0200 Subject: [PATCH 08/28] chore: process feedback --- .../getting-started.adoc | 6 ++ .../deploying-to-platform/model-metadata.adoc | 12 ++++ .../quickstart/service/getting-started.adoc | 8 +++ .../service/constraint-overrides.adoc | 40 ++++++++++-- .../service/demo-data.adoc | 42 ++++++++++--- .../service/exposing-metrics.adoc | 38 ++++++++++++ .../service/modeling-changes.adoc | 26 ++++++++ .../service/rest-api.adoc | 61 +++++++++++++++++-- 8 files changed, 217 insertions(+), 16 deletions(-) diff --git a/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc b/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc index 724e2675c4..0887d7c817 100644 --- a/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc +++ b/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc @@ -70,6 +70,12 @@ Build and deploy your model in a single command: mvn clean package timefold:deploy ---- +[NOTE] +==== +If this is the first time you're running a full build (`mvn clean package` or `mvn install`) rather than `mvn quarkus:dev`, and you haven't generated an OpenAPI baseline yet, the build fails by comparing against a `src/build/openapi.json` file that doesn't exist yet. +Run `mvn clean package -Dupdate-api` once first to generate it, as described in xref:running-timefold-solver/service/rest-api.adoc#deliberateAPIChanges[Deliberate API changes], then retry the command above. +==== + === Preview without pushing container To preview what would happen without actually pushing the container image or registering the model, add `-Dtimefold.dryRun=true`. This logs the requests that would have been made instead of sending them. diff --git a/docs/src/modules/ROOT/pages/deploying-to-platform/model-metadata.adoc b/docs/src/modules/ROOT/pages/deploying-to-platform/model-metadata.adoc index eab0a9f6c9..8375ae875d 100644 --- a/docs/src/modules/ROOT/pages/deploying-to-platform/model-metadata.adoc +++ b/docs/src/modules/ROOT/pages/deploying-to-platform/model-metadata.adoc @@ -69,6 +69,12 @@ Java:: -- [source,java,options="nowrap"] ---- +import ai.timefold.solver.core.api.score.stream.Constraint; +import ai.timefold.solver.core.api.score.stream.ConstraintFactory; +import ai.timefold.solver.core.api.score.stream.ConstraintProvider; +import ai.timefold.solver.service.definition.api.description.ConstraintGroupInfo; +import ai.timefold.solver.service.definition.api.description.ConstraintInfo; + public class TimetableConstraintProvider implements ConstraintProvider { public static final String TEACHER_CONFLICT = "Teacher conflict"; @@ -105,6 +111,12 @@ Kotlin:: -- [source,kotlin,options="nowrap"] ---- +import ai.timefold.solver.core.api.score.stream.Constraint +import ai.timefold.solver.core.api.score.stream.ConstraintFactory +import ai.timefold.solver.core.api.score.stream.ConstraintProvider +import ai.timefold.solver.service.definition.api.description.ConstraintGroupInfo +import ai.timefold.solver.service.definition.api.description.ConstraintInfo + class TimetableConstraintProvider : ConstraintProvider { companion object { diff --git a/docs/src/modules/ROOT/pages/quickstart/service/getting-started.adoc b/docs/src/modules/ROOT/pages/quickstart/service/getting-started.adoc index 7487fb801d..2c69cbeeba 100644 --- a/docs/src/modules/ROOT/pages/quickstart/service/getting-started.adoc +++ b/docs/src/modules/ROOT/pages/quickstart/service/getting-started.adoc @@ -86,10 +86,18 @@ Extending `AbstractSimpleModel`: - Configures this class to be (de-)serialized as part of the `ModelInput` and `ModelOutput` of the xref:running-timefold-solver/service/rest-api.adoc[REST API]. - Implements the `SolverModel` interface using the default `HardMediumSoftScore`. +- Already declares the `@PlanningScore` field for you. [NOTE] It's ok if you are not familiar with these terms yet. We'll get to them later. +[IMPORTANT] +==== +Don't declare your own `@PlanningScore` field when extending `AbstractSimpleModel`: it already has one. +A `@PlanningSolution` class must have exactly one `@PlanningScore` field, so adding a second one fails when the solution descriptor is built, for example with an opaque `ExceptionInInitializerError` if you build a `ConstraintVerifier` in a static test field. +If you need a different score type than `HardMediumSoftScore`, don't extend `AbstractSimpleModel`; implement `ModelInput`, `ModelOutput`, and `SolverModel` directly instead, as described in xref:running-timefold-solver/service/exposing-metrics.adoc[Exposing metrics]. +==== + [tabs] ==== Java:: diff --git a/docs/src/modules/ROOT/pages/running-timefold-solver/service/constraint-overrides.adoc b/docs/src/modules/ROOT/pages/running-timefold-solver/service/constraint-overrides.adoc index 5737aa4219..bb716a0264 100644 --- a/docs/src/modules/ROOT/pages/running-timefold-solver/service/constraint-overrides.adoc +++ b/docs/src/modules/ROOT/pages/running-timefold-solver/service/constraint-overrides.adoc @@ -35,6 +35,10 @@ Java:: -- [source,java,options="nowrap"] ---- +import ai.timefold.solver.core.api.score.stream.Constraint; +import ai.timefold.solver.core.api.score.stream.ConstraintFactory; +import ai.timefold.solver.core.api.score.stream.ConstraintProvider; + public class TimetableConstraintProvider implements ConstraintProvider { public static final String TEACHER_CONFLICT = "Teacher conflict"; @@ -62,6 +66,10 @@ Kotlin:: -- [source,kotlin,options="nowrap"] ---- +import ai.timefold.solver.core.api.score.stream.Constraint +import ai.timefold.solver.core.api.score.stream.ConstraintFactory +import ai.timefold.solver.core.api.score.stream.ConstraintProvider + class TimetableConstraintProvider : ConstraintProvider { companion object { @@ -95,6 +103,9 @@ Java:: -- [source,java,options="nowrap"] ---- +import ai.timefold.solver.service.definition.api.ModelConfigOverrides; +import ai.timefold.solver.service.definition.api.domain.ConstraintReference; + public final class TimetableConfigOverrides implements ModelConfigOverrides { public static final long DEFAULT_WEIGHT_ZERO = 0L; @@ -117,6 +128,9 @@ Kotlin:: -- [source,kotlin,options="nowrap"] ---- +import ai.timefold.solver.service.definition.api.ModelConfigOverrides +import ai.timefold.solver.service.definition.api.domain.ConstraintReference + data class TimetableConfigOverrides( @ConstraintReference(TimetableConstraintProvider.TEACHER_CONFLICT) val teacherConflictWeight: Long = DEFAULT_WEIGHT_ONE, @@ -161,6 +175,14 @@ Next, in the xref:running-timefold-solver/service/rest-api.adoc#modelConverter[m to a `ConstraintWeightOverrides` object and set it on the `@PlanningSolution` class xref:#enablingWeightOverrides[as described above]: +[IMPORTANT] +==== +This requires a custom `ModelConvertor`. +If your `SolverModel` also serves as `ModelInput` and `ModelOutput`, for example because you extend `AbstractSimpleModel`, the framework uses a trivial `ModelConvertor` by default, which ignores `modelConfig` entirely. +With only the trivial convertor in place, constraint weight overrides submitted in a request are silently dropped: the request succeeds, but the override never reaches the solver. +Provide your own `ModelConvertor` implementation, as shown below, to actually apply them. +==== + .As part of the ModelConverter. [tabs] ==== @@ -169,14 +191,19 @@ Java:: -- [source,java,options="nowrap"] ---- +import java.util.Map; + +import ai.timefold.solver.core.api.domain.solution.ConstraintWeightOverrides; +import ai.timefold.solver.core.api.score.HardMediumSoftScore; + TimetableConfigOverrides modelConfigOverrides = modelConfig.overrides(); -ConstraintWeightOverrides constraintWeightOverrides = ConstraintWeightOverrides.of( +ConstraintWeightOverrides constraintWeightOverrides = ConstraintWeightOverrides.of( Map.ofEntries( Map.entry(TimetableConstraintProvider.TEACHER_CONFLICT, - HardMediumSoftLongScore.ofHard(modelConfigOverrides.getTeacherConflictWeight())), + HardMediumSoftScore.ofHard(modelConfigOverrides.getTeacherConflictWeight())), Map.entry(TimetableConstraintProvider.ROOM_CONFLICT, - HardMediumSoftLongScore.ofSoft(modelConfigOverrides.getRoomConflictWeight())) + HardMediumSoftScore.ofSoft(modelConfigOverrides.getRoomConflictWeight())) ) ); @@ -189,14 +216,17 @@ Kotlin:: -- [source,kotlin,options="nowrap"] ---- +import ai.timefold.solver.core.api.domain.solution.ConstraintWeightOverrides +import ai.timefold.solver.core.api.score.HardMediumSoftScore + val modelConfigOverrides = modelConfig.overrides() val constraintWeightOverrides = ConstraintWeightOverrides.of( mapOf( TimetableConstraintProvider.TEACHER_CONFLICT to - HardMediumSoftLongScore.ofHard(modelConfigOverrides.teacherConflictWeight), + HardMediumSoftScore.ofHard(modelConfigOverrides.teacherConflictWeight), TimetableConstraintProvider.ROOM_CONFLICT to - HardMediumSoftLongScore.ofSoft(modelConfigOverrides.roomConflictWeight) + HardMediumSoftScore.ofSoft(modelConfigOverrides.roomConflictWeight) ) ) diff --git a/docs/src/modules/ROOT/pages/running-timefold-solver/service/demo-data.adoc b/docs/src/modules/ROOT/pages/running-timefold-solver/service/demo-data.adoc index 8d915ce2fd..697872ea08 100644 --- a/docs/src/modules/ROOT/pages/running-timefold-solver/service/demo-data.adoc +++ b/docs/src/modules/ROOT/pages/running-timefold-solver/service/demo-data.adoc @@ -31,6 +31,18 @@ Java:: -- [source,java,options="nowrap"] ---- +import java.util.List; +import java.util.function.Function; +import java.util.stream.Stream; + +import ai.timefold.solver.service.definition.api.data.DemoData; +import ai.timefold.solver.service.definition.api.data.DemoDataGenerator; +import ai.timefold.solver.service.definition.api.data.DemoMetaData; +import ai.timefold.solver.service.definition.api.domain.Configuration; +import ai.timefold.solver.service.definition.api.domain.ModelRequest; + +import jakarta.enterprise.context.ApplicationScoped; + @ApplicationScoped public class TimetableDemoDataGenerator implements DemoDataGenerator { @@ -64,11 +76,14 @@ public class TimetableDemoDataGenerator implements DemoDataGenerator { } public ModelRequest generateBasicDemoData() { - // Generate basic request. + TimetableInput input = // build a small, hardcoded dataset + return new ModelRequest<>(input); } public ModelRequest generateComplexSet() { - // Generate complex request. + TimetableInput input = // build a larger, hardcoded dataset + Configuration configuration = // optionally, a non-default configuration + return new ModelRequest<>(configuration, input); } } @@ -92,6 +107,14 @@ Kotlin:: -- [source,kotlin,options="nowrap"] ---- +import ai.timefold.solver.service.definition.api.data.DemoData +import ai.timefold.solver.service.definition.api.data.DemoDataGenerator +import ai.timefold.solver.service.definition.api.data.DemoMetaData +import ai.timefold.solver.service.definition.api.domain.Configuration +import ai.timefold.solver.service.definition.api.domain.ModelRequest + +import jakarta.enterprise.context.ApplicationScoped + @ApplicationScoped class TimetableDemoDataGenerator : DemoDataGenerator { @@ -113,11 +136,14 @@ class TimetableDemoDataGenerator : DemoDataGenerator { fun getDemoData(): DemoData = DemoData(metaData, requestFunction(this)) fun generateBasicDemoData(): ModelRequest { - return TODO("Generate basic request.") + val input: TimetableInput = TODO("build a small, hardcoded dataset") + return ModelRequest(input) } fun generateComplexSet(): ModelRequest { - return TODO("Generate complex request.") + val input: TimetableInput = TODO("build a larger, hardcoded dataset") + val configuration: Configuration = TODO("optionally, a non-default configuration") + return ModelRequest(configuration, input) } } @@ -133,7 +159,9 @@ class TimetableDemoDataGenerator : DemoDataGenerator { -- ==== -With this interface implemented, Timefold Solver will automatically expose these methods as REST endpoints: +`ModelRequest` has two constructors: `ModelRequest(ModelInput_ modelInput)`, which defaults the configuration to null, and `ModelRequest(Configuration configuration, ModelInput_ modelInput)`, if you also want to set a non-default configuration. +There is no constructor that takes the model config overrides directly; overrides are wrapped in a `Configuration` object first. -- `GET /<root>/demo-data`: Retrieve all available demo dataset ids. -- `GET /<root>/demo-data/\{demoDataId\}`: Retrieve the demo dataset with the given identifier +With this interface implemented, Timefold Solver will automatically expose these methods as REST endpoints. +These endpoints are *not* nested under your `ModelRest` resource path: they only share its version prefix. +See xref:./rest-api.adoc#generatedEndpoints[Generated endpoints] for the exact path. diff --git a/docs/src/modules/ROOT/pages/running-timefold-solver/service/exposing-metrics.adoc b/docs/src/modules/ROOT/pages/running-timefold-solver/service/exposing-metrics.adoc index 634049b731..2dbe93c8a5 100644 --- a/docs/src/modules/ROOT/pages/running-timefold-solver/service/exposing-metrics.adoc +++ b/docs/src/modules/ROOT/pages/running-timefold-solver/service/exposing-metrics.adoc @@ -30,6 +30,14 @@ Java:: -- [source,java,options="nowrap"] ---- +import ai.timefold.solver.service.definition.api.domain.DataFormat; +import ai.timefold.solver.service.definition.api.metrics.ModelInputMetrics; + +import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +import com.fasterxml.jackson.annotation.JsonFormat; + public record TimetableInputMetrics( @JsonFormat(shape = JsonFormat.Shape.NUMBER_INT) @Schema(name = "lessons", title = "Lessons", format = DataFormat.Values.NUMBER, description = "The number of lessons submitted in the input dataset.", @@ -46,6 +54,14 @@ Kotlin:: -- [source,kotlin,options="nowrap"] ---- +import ai.timefold.solver.service.definition.api.domain.DataFormat +import ai.timefold.solver.service.definition.api.metrics.ModelInputMetrics + +import org.eclipse.microprofile.openapi.annotations.enums.SchemaType +import org.eclipse.microprofile.openapi.annotations.media.Schema + +import com.fasterxml.jackson.annotation.JsonFormat + data class TimetableInputMetrics( @JsonFormat(shape = JsonFormat.Shape.NUMBER_INT) @Schema(name = "lessons", title = "Lessons", format = DataFormat.Values.NUMBER, description = "The number of lessons submitted in the input dataset.", @@ -141,6 +157,12 @@ Like input metrics, they are exposed through the xref:./rest-api.adoc[REST API] It is therefore necessary to add xref:./rest-api.adoc#openAPISpecification[OpenAPI Specification] annotations to the fields. ==== +[WARNING] +==== +If a metric is a timestamp, use `java.time.OffsetDateTime` or `java.time.Instant`, not `java.time.LocalDateTime`. +The platform's `date-time` format validation expects a timezone offset (for example `2027-02-02T09:00:00Z`); `LocalDateTime` serializes without one and fails validation. +==== + .Example for School Timetabling [tabs] ==== @@ -149,6 +171,14 @@ Java:: -- [source,java,options="nowrap"] ---- +import ai.timefold.solver.service.definition.api.domain.DataFormat; +import ai.timefold.solver.service.definition.api.metrics.ModelOutputMetrics; + +import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +import com.fasterxml.jackson.annotation.JsonFormat; + public record TimetableOutputMetrics( @JsonFormat(shape = JsonFormat.Shape.NUMBER_INT) @Schema(name = "unassignedLessons", title = "Unassigned lessons", format = DataFormat.Values.NUMBER, description = "The number of lessons that could not be assigned a timeslot or room.", @@ -165,6 +195,14 @@ Kotlin:: -- [source,kotlin,options="nowrap"] ---- +import ai.timefold.solver.service.definition.api.domain.DataFormat +import ai.timefold.solver.service.definition.api.metrics.ModelOutputMetrics + +import org.eclipse.microprofile.openapi.annotations.enums.SchemaType +import org.eclipse.microprofile.openapi.annotations.media.Schema + +import com.fasterxml.jackson.annotation.JsonFormat + data class TimetableOutputMetrics( @JsonFormat(shape = JsonFormat.Shape.NUMBER_INT) @Schema(name = "unassignedLessons", title = "Unassigned lessons", format = DataFormat.Values.NUMBER, description = "The number of lessons that could not be assigned a timeslot or room.", diff --git a/docs/src/modules/ROOT/pages/running-timefold-solver/service/modeling-changes.adoc b/docs/src/modules/ROOT/pages/running-timefold-solver/service/modeling-changes.adoc index a447f3f67a..7286bf931e 100644 --- a/docs/src/modules/ROOT/pages/running-timefold-solver/service/modeling-changes.adoc +++ b/docs/src/modules/ROOT/pages/running-timefold-solver/service/modeling-changes.adoc @@ -27,6 +27,8 @@ Java:: -- [source,java,options="nowrap"] ---- +import java.time.LocalDateTime; + public class Timeslot { private LocalDateTime startTime; @@ -48,6 +50,8 @@ Kotlin:: -- [source,kotlin,options="nowrap"] ---- +import java.time.LocalDateTime + data class Timeslot( val startTime: LocalDateTime? = null, val endTime: LocalDateTime? = null @@ -68,6 +72,12 @@ Java:: -- [source,java,options="nowrap"] ---- +import java.time.LocalDateTime; + +import ai.timefold.solver.service.definition.api.enrichment.SolverModelEnricher; + +import jakarta.enterprise.context.ApplicationScoped; + @ApplicationScoped public class TimeslotHolidayEnricher implements SolverModelEnricher { @@ -94,6 +104,12 @@ Kotlin:: -- [source,kotlin,options="nowrap"] ---- +import java.time.LocalDateTime + +import ai.timefold.solver.service.definition.api.enrichment.SolverModelEnricher + +import jakarta.enterprise.context.ApplicationScoped + @ApplicationScoped class TimeslotHolidayEnricher : SolverModelEnricher { @@ -127,6 +143,11 @@ Java:: -- [source,java,options="nowrap"] ---- +import ai.timefold.solver.service.definition.api.enrichment.SolverModelEnrichmentDirector; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + @ApplicationScoped public class TimetableEnrichmentDirector implements SolverModelEnrichmentDirector { @@ -152,6 +173,11 @@ Kotlin:: -- [source,kotlin,options="nowrap"] ---- +import ai.timefold.solver.service.definition.api.enrichment.SolverModelEnrichmentDirector + +import jakarta.enterprise.context.ApplicationScoped +import jakarta.inject.Inject + @ApplicationScoped class TimetableEnrichmentDirector @Inject constructor( private val timeslotEnricher: TimeslotHolidayEnricher diff --git a/docs/src/modules/ROOT/pages/running-timefold-solver/service/rest-api.adoc b/docs/src/modules/ROOT/pages/running-timefold-solver/service/rest-api.adoc index 39827767be..1c333775de 100644 --- a/docs/src/modules/ROOT/pages/running-timefold-solver/service/rest-api.adoc +++ b/docs/src/modules/ROOT/pages/running-timefold-solver/service/rest-api.adoc @@ -24,6 +24,17 @@ Java:: -- [source,java,options="nowrap"] ---- +import java.util.List; + +import ai.timefold.solver.service.definition.api.ModelInput; +import ai.timefold.solver.service.definition.api.ModelOutput; +import ai.timefold.solver.service.rest.api.ModelRest; + +import org.eclipse.microprofile.openapi.annotations.media.Schema; +import org.eclipse.microprofile.openapi.annotations.tags.Tag; + +import jakarta.ws.rs.Path; + // TimetableDto.java public class TimetableDto implements ModelInput, ModelOutput { @@ -51,6 +62,15 @@ Kotlin:: -- [source,kotlin,options="nowrap"] ---- +import ai.timefold.solver.service.definition.api.ModelInput +import ai.timefold.solver.service.definition.api.ModelOutput +import ai.timefold.solver.service.rest.api.ModelRest + +import org.eclipse.microprofile.openapi.annotations.media.Schema +import org.eclipse.microprofile.openapi.annotations.tags.Tag + +import jakarta.ws.rs.Path + // TimetableDto.kt class TimetableDto( @Schema(required = true, description = "The unique identifier of timetable") @@ -87,9 +107,12 @@ The `` is determined by the @Path annotation on the interface extending Mo - `DELETE //\{id\}`: Terminate a dataset optimization run If your model provides xref:./demo-data.adoc[demo data], the following endpoints are also created. +These are *not* nested under ``: they only share its version prefix (for example `/v1`), not the rest of the path. +So for a `@Path("/v1/timetables")` resource, the demo data endpoints are at `/v1/demo-data`, not `/v1/timetables/demo-data`. +If your resource path has no version prefix, the demo data endpoints are served at the root, without a version. -- `GET //demo-data`: Retrieve all available demo dataset ids. -- `GET //demo-data/\{demoDataId\}`: Retrieve the demo dataset with the given identifier +- `GET /v/demo-data`: Retrieve all available demo dataset ids. +- `GET /v/demo-data/\{demoDataId\}`: Retrieve the demo dataset with the given identifier // TODO See the xref:consumer-guide.adoc[service consumer guide] for information on how consumers of the service should interact with these API endpoints. // TODO https://github.com/TimefoldAI/timefold-solver/issues/2349 @@ -166,6 +189,13 @@ Additionally, more fields might be added for specific model implementations: - `inputMetrics`: metrics about the input of the planning problem. See: xref:./exposing-metrics.adoc#modelInputMetrics[Input Metrics] - `kpis`: metrics about the output of the planning problem. See: xref:./exposing-metrics.adoc#modelOutputMetrics[Output Metrics] +[NOTE] +==== +This envelope is represented in Java/Kotlin by the `ModelResponse` record (`ai.timefold.solver.service.definition.api.domain.ModelResponse`). +Its accessor is named `outputMetrics()`, not `kpis()`; only the JSON serialization uses the `kpis` key (via `@JsonProperty("kpis")`). +The score itself is not a top-level field: it's nested inside `metadata.score`, not `metadata.run.score` or a top-level `score`. +==== + .Example Response [source,json] ---- @@ -235,8 +265,17 @@ Java:: -- [source,java,options="nowrap"] ---- +import java.util.Optional; + +import ai.timefold.solver.core.api.score.HardSoftScore; +import ai.timefold.solver.service.definition.api.ModelConvertor; +import ai.timefold.solver.service.definition.api.domain.ModelConfig; +import ai.timefold.solver.service.quarkus.deployment.defaults.EmptyModelConfigOverrides; + +import jakarta.enterprise.context.ApplicationScoped; + @ApplicationScoped -public class TimetableConvertor implements ModelConvertor { +public class TimetableConvertor implements ModelConvertor { @Override public Timetable toSolverModel(TimetableDto modelInput, ModelConfig modelConfig, @@ -262,8 +301,17 @@ Kotlin:: -- [source,kotlin,options="nowrap"] ---- +import java.util.Optional + +import ai.timefold.solver.core.api.score.HardSoftScore +import ai.timefold.solver.service.definition.api.ModelConvertor +import ai.timefold.solver.service.definition.api.domain.ModelConfig +import ai.timefold.solver.service.quarkus.deployment.defaults.EmptyModelConfigOverrides + +import jakarta.enterprise.context.ApplicationScoped + @ApplicationScoped -class TimetableConvertor : ModelConvertor { +class TimetableConvertor : ModelConvertor { override fun toSolverModel(modelInput: TimetableDto, modelConfig: ModelConfig, lastModelOutput: Optional): Timetable { @@ -294,6 +342,11 @@ For enhancing the SolverModel, use the xref:./modeling-changes.adoc#solverModelE By default, validation on the input is based on the xref:./rest-api.adoc#openAPISpecification[OpenAPI Specification annotations]. Additional validations can be added by implementing the `ModelValidator` interface. +// TODO: verify against the current ValidationBuilder/IssueType/Issue API. As of this writing, ValidationBuilder.addIssue() +// takes a single `Issue` argument, not `addIssue(IssueType, IssueDetail)` as shown below, and `IssueDetail` does not exist +// anywhere in the codebase. The example below reflects an older API shape and needs a rewrite against the current +// ai.timefold.solver.service.definition.api.validation package before it can be trusted. + [#timetableValidatorExample] [tabs] ==== From d330fd6f2e479ec1ab6e3124560d22eecf7bef65 Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Tue, 7 Jul 2026 20:29:36 +0200 Subject: [PATCH 09/28] docs: document to use enterprise edition --- .../getting-started.adoc | 28 +++++++++++++++++-- .../deploying-to-platform/introduction.adoc | 3 +- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc b/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc index 0887d7c817..b08760755e 100644 --- a/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc +++ b/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc @@ -39,6 +39,27 @@ Registering a model does not automatically subscribe your tenant to it; without Follow the setup instructions in the https://github.com/TimefoldAI/timefold-solver/blob/main/service/tools/maven-plugin/README.adoc[Maven plugin documentation] to add and configure it. +[#_enterprise_edition] +== Build with the Enterprise Edition + +By default, your build uses the Community Edition of Timefold Solver. +The xref:deploying-to-platform/introduction.adoc#_enterprise_edition_features[Enterprise Edition features] surfaced by the platform, such as score analysis, are not included unless you opt in. + +Build with `-Denterprise=true` to activate the `enterprise` Maven profile defined in `timefold-solver-service-parent`, which pulls in the Enterprise Edition dependencies: + +[source,bash,options="nowrap"] +---- +mvn clean package -Denterprise=true +---- + +Verify it worked by checking that `target/timefold/_v/jsonschema/calculateScoreAnalysis.json` exists. +If it's missing, the build used the Community Edition, and registering the model will fail with an error such as `Model descriptor is missing entry ^.+/jsonschema/calculateScoreAnalysis.json$`. + +You'll need to pass `-Denterprise=true` on every build you intend to deploy, including the `timefold:deploy` command in the next step. + +NOTE: The Enterprise Edition also requires a license key to function at runtime, as described in xref:commercial-editions/installation.adoc[Installation]. +Check with the Timefold team on whether this is already configured for you in the sandbox environment. + [#_authenticate] == Authenticate @@ -63,17 +84,18 @@ export TIMEFOLD_PAT= [#_build_and_deploy] == Build and deploy -Build and deploy your model in a single command: +Build and deploy your model in a single command, including `-Denterprise=true` if you want the xref:deploying-to-platform/introduction.adoc#_enterprise_edition_features[Enterprise Edition features] to work on the platform: [source,bash,options="nowrap"] ---- -mvn clean package timefold:deploy +mvn clean package -Denterprise=true timefold:deploy ---- [NOTE] ==== If this is the first time you're running a full build (`mvn clean package` or `mvn install`) rather than `mvn quarkus:dev`, and you haven't generated an OpenAPI baseline yet, the build fails by comparing against a `src/build/openapi.json` file that doesn't exist yet. -Run `mvn clean package -Dupdate-api` once first to generate it, as described in xref:running-timefold-solver/service/rest-api.adoc#deliberateAPIChanges[Deliberate API changes], then retry the command above. +Run `mvn clean package -Denterprise=true -Dupdate-api` once first to generate it, as described in xref:running-timefold-solver/service/rest-api.adoc#deliberateAPIChanges[Deliberate API changes], then retry the command above. +Generate the baseline with the same `-Denterprise` value you intend to deploy with, since the generated API can differ between editions. ==== === Preview without pushing container diff --git a/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc b/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc index 7f3d96d7b8..5b8e2af27d 100644 --- a/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc +++ b/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc @@ -23,7 +23,8 @@ From a model developer's perspective, each layer is plumbing you'd otherwise hav [#_enterprise_edition_features] === Enterprise Edition features -Deploying to the platform will give your model access to Timefold Solver Enterprise Edition capabilities: +These Timefold Solver Enterprise Edition capabilities are especially valuable once your model is deployed, since consumers interact with them directly through the platform UI and API. +Using them requires building your model with the Enterprise Edition of the solver, as described in xref:deploying-to-platform/getting-started.adoc#_enterprise_edition[Build with the Enterprise Edition]; deploying to the platform by itself doesn't enable them. * *Explainability*: xref:constraints-and-score/understanding-the-score.adoc[Score analysis] breaks a solution down constraint-by-constraint, so consumers can see why the solver made the decisions it did, and what the impact would be of overriding one. * *Recommendations*: the xref:responding-to-change/recommendation-api.adoc#assignmentRecommendationAPI[Recommendation API] suggests the best fit for a new assignment without a full re-solve. From b0ef8b5341d626f0df7168978c05b67e08d97065 Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Tue, 7 Jul 2026 20:36:36 +0200 Subject: [PATCH 10/28] docs: add details on how to request a license --- .../ROOT/pages/deploying-to-platform/getting-started.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc b/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc index b08760755e..5c3798fd73 100644 --- a/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc +++ b/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc @@ -60,6 +60,8 @@ You'll need to pass `-Denterprise=true` on every build you intend to deploy, inc NOTE: The Enterprise Edition also requires a license key to function at runtime, as described in xref:commercial-editions/installation.adoc[Installation]. Check with the Timefold team on whether this is already configured for you in the sandbox environment. +If you need your own license, sign up at the xref:commercial-editions/license-management.adoc[Timefold License Manager]: every new account starts with a 30-day free Enterprise trial, so you can get a working license without contacting sales first. + [#_authenticate] == Authenticate From 46d6db707ea9ab5fb2c6829b37c713ba1c5114b3 Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Tue, 7 Jul 2026 20:38:27 +0200 Subject: [PATCH 11/28] chore: remove "Plus" edition from solver docs closes https://github.com/TimefoldAI/timefold-solver-enterprise/issues/753 --- .../_only-plus-and-enterprise.adoc | 1 - .../commercial-editions.adoc | 54 ++++++------------- .../commercial-editions/installation.adoc | 5 +- .../score-calculation.adoc | 2 +- .../understanding-the-score.adoc | 2 +- .../pages/frequently-asked-questions.adoc | 6 +-- .../recommendation-api.adoc | 2 +- .../upgrade-from-v1.adoc | 4 +- 8 files changed, 27 insertions(+), 49 deletions(-) delete mode 100644 docs/src/modules/ROOT/pages/commercial-editions/_only-plus-and-enterprise.adoc diff --git a/docs/src/modules/ROOT/pages/commercial-editions/_only-plus-and-enterprise.adoc b/docs/src/modules/ROOT/pages/commercial-editions/_only-plus-and-enterprise.adoc deleted file mode 100644 index a92afcb2de..0000000000 --- a/docs/src/modules/ROOT/pages/commercial-editions/_only-plus-and-enterprise.adoc +++ /dev/null @@ -1 +0,0 @@ -NOTE: This feature is exclusive to xref:commercial-editions/commercial-editions.adoc[Timefold Solver Plus and Enterprise Editions.] \ No newline at end of file diff --git a/docs/src/modules/ROOT/pages/commercial-editions/commercial-editions.adoc b/docs/src/modules/ROOT/pages/commercial-editions/commercial-editions.adoc index 961fdd1f70..e6efa83d92 100644 --- a/docs/src/modules/ROOT/pages/commercial-editions/commercial-editions.adoc +++ b/docs/src/modules/ROOT/pages/commercial-editions/commercial-editions.adoc @@ -1,55 +1,35 @@ -= Plus/Enterprise Editions += Enterprise Edition :page-aliases: partitioned-search/partitioned-search.adoc, \ enterprise-edition/enterprise-edition.adoc :doctype: book :icons: font -_Timefold Solver Plus_ and _Timefold Solver Enterprise_ are commercial products which offer additional features, +_Timefold Solver Enterprise_ is a commercial product which offers additional features, such as xref:constraints-and-score/understanding-the-score.adoc[score analysis], xref:optimization-algorithms/move-selector-reference.adoc#nearbySelection[nearby selection] and xref:running-timefold-solver/multithreaded-solving.adoc#multithreadedIncrementalSolving[multithreaded solving]. These features are essential to scale out to large datasets and build trust with your users. -Unlike _Timefold Solver Community Edition_, these commercial editions are not open-source. +Unlike _Timefold Solver Community Edition_, Enterprise Edition is not open-source. TIP: Looking for quicker time-to-value? Timefold offers https://docs.timefold.ai/[pre-built, fully tuned optimization models], no constraint building required. Just plug into our API and start optimizing immediately. +[#_free_trials_and_licenses] == Free trials and licenses -We offer free trials to everyone as well as free licenses to non-profit organizations and for academic research. -See our https://licenses.timefold.ai/[license portal] to get your license, then follow the xref:commercial-editions/installation.adoc[installation guide] to get started. +Every new account on the https://licenses.timefold.ai/[Timefold License Manager] starts with a 30-day free Enterprise trial, so you can create and download fully functional Enterprise Edition license files without talking to sales first. +We also offer free licenses to non-profit organizations and for academic research. -== Feature Comparison +See xref:commercial-editions/license-management.adoc[License management] for how to sign up, create and download license files, and manage your team's access, then follow the xref:commercial-editions/installation.adoc[installation guide] to get started. -[cols="4,^1,^1", options="header"] -|=== -| Feature | Plus | Enterprise +== Features -| xref:constraints-and-score/understanding-the-score.adoc[Score Analysis] -| ✓ | ✓ - -| xref:responding-to-change/recommendation-api.adoc#assignmentRecommendationAPI[Recommendation API] -| ✓ | ✓ - -| xref:commercial-editions/performance-improvements.adoc[Performance improvements] -| | ✓ - -| xref:optimization-algorithms/move-selector-reference.adoc#nearbySelection[Nearby selection] -| | ✓ - -| xref:running-timefold-solver/multithreaded-solving.adoc#multithreadedIncrementalSolving[Multithreaded solving] -| | ✓ - -| xref:running-timefold-solver/multithreaded-solving.adoc#partitionedSearch[Partitioned search] -| | ✓ - -| xref:constraints-and-score/performance.adoc#constraintProfiling[Constraint profiling] -| | ✓ - -| xref:commercial-editions/multistage-moves.adoc[Multistage moves] -| | ✓ - -| xref:running-timefold-solver/library/library-integration.adoc#throttlingBestSolutionEvents[Throttling best solution events] -| | ✓ - -|=== \ No newline at end of file +* xref:constraints-and-score/understanding-the-score.adoc[Score analysis] +* xref:responding-to-change/recommendation-api.adoc#assignmentRecommendationAPI[Recommendation API] +* xref:commercial-editions/performance-improvements.adoc[Performance improvements] +* xref:optimization-algorithms/move-selector-reference.adoc#nearbySelection[Nearby selection] +* xref:running-timefold-solver/multithreaded-solving.adoc#multithreadedIncrementalSolving[Multithreaded solving] +* xref:running-timefold-solver/multithreaded-solving.adoc#partitionedSearch[Partitioned search] +* xref:constraints-and-score/performance.adoc#constraintProfiling[Constraint profiling] +* xref:commercial-editions/multistage-moves.adoc[Multistage moves] +* xref:running-timefold-solver/library/library-integration.adoc#throttlingBestSolutionEvents[Throttling best solution events] diff --git a/docs/src/modules/ROOT/pages/commercial-editions/installation.adoc b/docs/src/modules/ROOT/pages/commercial-editions/installation.adoc index bc6eb69127..80c328ed8f 100644 --- a/docs/src/modules/ROOT/pages/commercial-editions/installation.adoc +++ b/docs/src/modules/ROOT/pages/commercial-editions/installation.adoc @@ -1,6 +1,6 @@ -= Installing Timefold Solver Plus & Enterprise += Installing Timefold Solver Enterprise Edition -In order to work with the commercial editions of Timefold Solver, 2 actions need to be taken: +In order to work with the commercial edition of Timefold Solver, 2 actions need to be taken: - <>; - <>; @@ -38,7 +38,6 @@ These artifacts are available from Maven Central, so no additional repositories [NOTE] ==== -To avoid operational complexity, both Plus and Enterprise editions of Timefold Solver use the `enterprise` artifacts. Features for your licensed version are enabled based on the provided license key. ==== diff --git a/docs/src/modules/ROOT/pages/constraints-and-score/score-calculation.adoc b/docs/src/modules/ROOT/pages/constraints-and-score/score-calculation.adoc index 1f0b58287d..5c4dfee202 100644 --- a/docs/src/modules/ROOT/pages/constraints-and-score/score-calculation.adoc +++ b/docs/src/modules/ROOT/pages/constraints-and-score/score-calculation.adoc @@ -2304,7 +2304,7 @@ add the `incrementalScoreCalculatorCustomProperties` element and use xref:runnin [#analyzableIncrementalScoreCalculator] ==== Incremental score calculator and score analysis -include::../commercial-editions/_only-plus-and-enterprise.adoc[] +include::../commercial-editions/_only-enterprise.adoc[] To add support for xref:constraints-and-score/understanding-the-score.adoc[score analysis], implement the `AnalyzableIncrementalScoreCalculator` interface instead of `IncrementalScoreCalculator`: diff --git a/docs/src/modules/ROOT/pages/constraints-and-score/understanding-the-score.adoc b/docs/src/modules/ROOT/pages/constraints-and-score/understanding-the-score.adoc index 5f9947ab59..0e87ec040c 100644 --- a/docs/src/modules/ROOT/pages/constraints-and-score/understanding-the-score.adoc +++ b/docs/src/modules/ROOT/pages/constraints-and-score/understanding-the-score.adoc @@ -8,7 +8,7 @@ The score in its pure form is just a number, and does not help us understand the It doesn't say which constraints are broken, or what caused them to break. To understand the score, it needs to be broken down. -include::../commercial-editions/_only-plus-and-enterprise.adoc[] +include::../commercial-editions/_only-enterprise.adoc[] The easiest way to break down the score during development is to print the score summary: diff --git a/docs/src/modules/ROOT/pages/frequently-asked-questions.adoc b/docs/src/modules/ROOT/pages/frequently-asked-questions.adoc index 1ec2cfcf72..37b52ade4b 100644 --- a/docs/src/modules/ROOT/pages/frequently-asked-questions.adoc +++ b/docs/src/modules/ROOT/pages/frequently-asked-questions.adoc @@ -9,10 +9,10 @@ released under http://www.apache.org/licenses/LICENSE-2.0.html[the Apache Licens This license is very liberal and allows reuse for commercial purposes. Read http://www.apache.org/foundation/licence-FAQ.html#WhatDoesItMEAN[the layman's explanation]. -_Timefold Solver Plus and Enterprise editions_ are commercial products -that offer xref:commercial-editions/commercial-editions.adoc[additional features] +_Timefold Solver Enterprise Edition_ is a commercial product +that offers xref:commercial-editions/commercial-editions.adoc[additional features] to scale out to very large datasets. -To find out more, see xref:commercial-editions/commercial-editions.adoc[Plus / Enterprise Edition section] of this documentation. +To find out more, see the xref:commercial-editions/commercial-editions.adoc[Enterprise Edition section] of this documentation. == Does Timefold offer pre-built models? diff --git a/docs/src/modules/ROOT/pages/responding-to-change/recommendation-api.adoc b/docs/src/modules/ROOT/pages/responding-to-change/recommendation-api.adoc index 16b1df728f..f016ec6731 100644 --- a/docs/src/modules/ROOT/pages/responding-to-change/recommendation-api.adoc +++ b/docs/src/modules/ROOT/pages/responding-to-change/recommendation-api.adoc @@ -5,7 +5,7 @@ :sectnums: :icons: font -include::../commercial-editions/_only-plus-and-enterprise.adoc[] +include::../commercial-editions/_only-enterprise.adoc[] With xref:responding-to-change/real-time-planning.adoc[real-time planning], we can respond to a continuous stream of external changes. However, it is often necessary to respond to ad hoc changes too, diff --git a/docs/src/modules/ROOT/pages/upgrading-timefold-solver/upgrade-from-v1.adoc b/docs/src/modules/ROOT/pages/upgrading-timefold-solver/upgrade-from-v1.adoc index a5479ec835..d2206f6498 100644 --- a/docs/src/modules/ROOT/pages/upgrading-timefold-solver/upgrade-from-v1.adoc +++ b/docs/src/modules/ROOT/pages/upgrading-timefold-solver/upgrade-from-v1.adoc @@ -301,7 +301,7 @@ We invite you to read the xref:upgrading-timefold-solver/migration-guides/variab [%collapsible%open] ==== `ScoreExplanation` has been removed. -Use `ScoreAnalysis` instead, which is now provided exclusively by _Timefold Solver Plus and Enterprise Editions_. +Use `ScoreAnalysis` instead, which is now provided exclusively by _Timefold Solver Enterprise Edition_. `ScoreAnalysis` provides the following methods: @@ -321,7 +321,7 @@ The following `ScoreExplanation` methods have no direct replacement and must be `IncrementalScoreCalculator` has also seen changes in this area. `ConstraintMatchAwareIncrementalScoreCalculator` was removed. -The replacement `AnalyzableIncrementalScoreCalculator` is provided by the Plus and Enterprise editions. +The replacement `AnalyzableIncrementalScoreCalculator` is provided by the Enterprise Edition. Instead of overriding methods to return constraint matches, implement the `enableConstraintMatch(ConstraintMatchRegistry)` method. Use `ConstraintMatchRegistry.registerConstraintMatch(ConstraintRef, score, justification)` to register each match; From 4698804ce9975932eb8cc9c8570dbf87857f43ff Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Tue, 7 Jul 2026 20:39:49 +0200 Subject: [PATCH 12/28] docs: add docs on license manager --- docs/src/modules/ROOT/nav.adoc | 1 + .../commercial-editions/installation.adoc | 3 +- .../license-management.adoc | 100 ++++++++++++++++++ 3 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 docs/src/modules/ROOT/pages/commercial-editions/license-management.adoc diff --git a/docs/src/modules/ROOT/nav.adoc b/docs/src/modules/ROOT/nav.adoc index 4357f24700..807bd301ec 100644 --- a/docs/src/modules/ROOT/nav.adoc +++ b/docs/src/modules/ROOT/nav.adoc @@ -81,3 +81,4 @@ ** xref:constraints-and-score/performance.adoc#constraintProfiling[Constraint profiling] ** xref:commercial-editions/multistage-moves.adoc[leveloffset=+1] ** xref:running-timefold-solver/library/library-integration.adoc#throttlingBestSolutionEvents[Throttling best solution events] +** xref:commercial-editions/license-management.adoc[License management] diff --git a/docs/src/modules/ROOT/pages/commercial-editions/installation.adoc b/docs/src/modules/ROOT/pages/commercial-editions/installation.adoc index 80c328ed8f..56968e4c2e 100644 --- a/docs/src/modules/ROOT/pages/commercial-editions/installation.adoc +++ b/docs/src/modules/ROOT/pages/commercial-editions/installation.adoc @@ -13,7 +13,8 @@ A correctly configured and active license key is required in order for our comme [#solverObtainLicenseKey] === Obtaining a license key -Generate your license key using https://licenses.timefold.ai/[Timefold License Manager]. +Generate your license key using the xref:commercial-editions/license-management.adoc[Timefold License Manager]. +Every new account starts with a 30-day free Enterprise trial, so you don't need to talk to sales to get started. Take care not to leak the license file, for example by committing it to a public repository, logging the license file content in logs, or sharing it with unauthorized parties. diff --git a/docs/src/modules/ROOT/pages/commercial-editions/license-management.adoc b/docs/src/modules/ROOT/pages/commercial-editions/license-management.adoc new file mode 100644 index 0000000000..52113800c6 --- /dev/null +++ b/docs/src/modules/ROOT/pages/commercial-editions/license-management.adoc @@ -0,0 +1,100 @@ +[#licenseManagement] += License management +:doctype: book +:sectnums: +:icons: font + +The https://licenses.timefold.ai/[Timefold License Manager] is where you sign up for a free trial, create and download license files, and manage your team's access to Timefold Solver Enterprise Edition. + +== Sign up and free trial + +. Go to https://licenses.timefold.ai/[licenses.timefold.ai] and click *Sign up*. +. Sign in with your company email address. +Public email domains (Gmail, Hotmail, Yahoo, and similar) are not accepted; you must use a work email. +. Fill in the registration form: company name and website URL, the scheduling problem you're solving, the scale of your problem, and how you currently solve it. +. Accept the terms of service and click *Register*. + +Your account and company are created immediately, and you're given a 30-day free Enterprise trial: you can create and download fully functional Enterprise Edition license files right away, without talking to sales first. + +The first user to register for a company automatically becomes that company's *admin*. + +When the trial ends, you need a paid subscription to keep creating new licenses. +Contact mailto:sales@timefold.ai[sales@timefold.ai] to convert your trial to a paid plan. + +== Managing licenses + +Creating licenses requires the *admin* role within your company. +Regular users can view and download their own licenses only. + +=== View your licenses + +Click *Licenses* in the sidebar to see your active licenses, or switch to the *Expired* tab to see past ones. +Each license card shows its type, expiration date, and a download button. + +=== Create a license + +. Click *Licenses* → *Create license*. +. Select an expiration date. It can't exceed your company's contract end date. +. Optionally, assign the license to a specific user in your company. +. Click *Create*. + +The license type is determined by your company's plan; you don't need to select it manually. + +=== Download a license file + +Click *Download* next to a license to get its `.pem` file. +Introduce it to your project as described in xref:commercial-editions/installation.adoc#solverSetupLicenseKey[Set up your license key]. + +[IMPORTANT] +==== +Treat your license file as a secret. +Don't commit it to source control; inject it at runtime using your secret manager instead (for example Kubernetes Secrets, AWS Secrets Manager, HashiCorp Vault, or a CI/CD secret store). +==== + +=== Expired licenses + +Expired licenses remain visible under the *Expired* tab, and you can still download them to review, but they're no longer accepted by the solver. + +IMPORTANT: Once a license expires, Timefold Solver refuses to start. There is no grace period. Renew your license before it expires to avoid downtime. + +== Notifications + +=== Email reminders + +The license manager emails the license owner before a license expires: 7 days before, and again 1 day before. +If you're not receiving these emails, check your spam folder or ask your company admin to verify the license owner's address. + +=== Solver warnings + +Timefold Solver also logs warnings as a license approaches expiry, with increasing urgency at less than 30 days, less than 14 days, and less than 1 day remaining. +Once expired, the solver refuses to start. +Monitor your application logs for these warnings so you have time to renew. + +== Managing your team + +Inviting and managing team members requires the *admin* role within your company. + +=== Invite team members + +Open your company from the sidebar, go to the *Users* tab, and click *Invite user*. +The invitee receives an email with an acceptance link valid for 14 days; once they accept, they join your company with the *user* role. + +If an invitation expires or wasn't received, find it in the *Users* tab and click *Resend* to send a fresh 14-day link. + +=== Change a user's role or remove a user + +From the *Users* tab, click *Edit* next to a user to change their role between *admin* and *user*, or click *Delete* to remove them. + +NOTE: At least one admin must remain in a company at all times; you can't demote or delete the last admin. + +== Account and company settings + +Update your name from your avatar menu → *Settings*. +Your email address is managed by your identity provider and can't be changed there. + +Company-level settings, such as your plan and contract dates, can only be changed by Timefold. +Contact mailto:support@timefold.ai[support@timefold.ai] if you need your plan updated. + +== Pricing + +Visit https://licenses.timefold.ai/pricing[the pricing page] for current Enterprise pricing, or mailto:sales@timefold.ai[contact sales]. From 463be70e6a6e81da758da5fed48521560bfe17cf Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Tue, 7 Jul 2026 21:02:29 +0200 Subject: [PATCH 13/28] chore: put back original message --- .../ROOT/pages/commercial-editions/commercial-editions.adoc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/src/modules/ROOT/pages/commercial-editions/commercial-editions.adoc b/docs/src/modules/ROOT/pages/commercial-editions/commercial-editions.adoc index e6efa83d92..e377b34e02 100644 --- a/docs/src/modules/ROOT/pages/commercial-editions/commercial-editions.adoc +++ b/docs/src/modules/ROOT/pages/commercial-editions/commercial-editions.adoc @@ -17,10 +17,8 @@ Just plug into our API and start optimizing immediately. [#_free_trials_and_licenses] == Free trials and licenses -Every new account on the https://licenses.timefold.ai/[Timefold License Manager] starts with a 30-day free Enterprise trial, so you can create and download fully functional Enterprise Edition license files without talking to sales first. -We also offer free licenses to non-profit organizations and for academic research. - -See xref:commercial-editions/license-management.adoc[License management] for how to sign up, create and download license files, and manage your team's access, then follow the xref:commercial-editions/installation.adoc[installation guide] to get started. +We offer free trials to everyone as well as free licenses to non-profit organizations and for academic research. +See our https://licenses.timefold.ai/[license portal] to get your license, then follow the xref:commercial-editions/installation.adoc[installation guide] to get started. == Features From 249daa67203377d01c880586554a92d2766d9741 Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Tue, 7 Jul 2026 21:03:29 +0200 Subject: [PATCH 14/28] chore: add back link to license management page --- .../ROOT/pages/commercial-editions/commercial-editions.adoc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/src/modules/ROOT/pages/commercial-editions/commercial-editions.adoc b/docs/src/modules/ROOT/pages/commercial-editions/commercial-editions.adoc index e377b34e02..e81dad4a6e 100644 --- a/docs/src/modules/ROOT/pages/commercial-editions/commercial-editions.adoc +++ b/docs/src/modules/ROOT/pages/commercial-editions/commercial-editions.adoc @@ -17,8 +17,10 @@ Just plug into our API and start optimizing immediately. [#_free_trials_and_licenses] == Free trials and licenses -We offer free trials to everyone as well as free licenses to non-profit organizations and for academic research. -See our https://licenses.timefold.ai/[license portal] to get your license, then follow the xref:commercial-editions/installation.adoc[installation guide] to get started. +Every new account on the https://licenses.timefold.ai/[Timefold License Manager] starts with a 30-day free Enterprise trial, so you can create and download fully functional Enterprise Edition license files without talking to sales first. +We also offer free licenses to non-profit organizations and for academic research. + +See https://licenses.timefold.ai/[license portal] for how to sign up, create and download license files, and manage your team's access, then follow the xref:commercial-editions/installation.adoc[installation guide] to get started. == Features From bd3a7b73146fb3edd0aa3604aa75998fe5e1de2b Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Tue, 7 Jul 2026 21:08:17 +0200 Subject: [PATCH 15/28] chore: cleanup contact details (leave that to pricing page) --- .../ROOT/pages/commercial-editions/license-management.adoc | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/src/modules/ROOT/pages/commercial-editions/license-management.adoc b/docs/src/modules/ROOT/pages/commercial-editions/license-management.adoc index 52113800c6..968754393b 100644 --- a/docs/src/modules/ROOT/pages/commercial-editions/license-management.adoc +++ b/docs/src/modules/ROOT/pages/commercial-editions/license-management.adoc @@ -1,7 +1,5 @@ -[#licenseManagement] = License management :doctype: book -:sectnums: :icons: font The https://licenses.timefold.ai/[Timefold License Manager] is where you sign up for a free trial, create and download license files, and manage your team's access to Timefold Solver Enterprise Edition. @@ -19,7 +17,6 @@ Your account and company are created immediately, and you're given a 30-day free The first user to register for a company automatically becomes that company's *admin*. When the trial ends, you need a paid subscription to keep creating new licenses. -Contact mailto:sales@timefold.ai[sales@timefold.ai] to convert your trial to a paid plan. == Managing licenses @@ -97,4 +94,4 @@ Contact mailto:support@timefold.ai[support@timefold.ai] if you need your plan up == Pricing -Visit https://licenses.timefold.ai/pricing[the pricing page] for current Enterprise pricing, or mailto:sales@timefold.ai[contact sales]. +Visit https://licenses.timefold.ai/pricing[the pricing page] for current Enterprise pricing and contact details. From c333a8d7e673332a4eb0107097863dc38af8b121 Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Wed, 8 Jul 2026 13:01:20 +0200 Subject: [PATCH 16/28] chore: process feedback timefold-docs-audit --- .../license-management.adoc | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/docs/src/modules/ROOT/pages/commercial-editions/license-management.adoc b/docs/src/modules/ROOT/pages/commercial-editions/license-management.adoc index 968754393b..9073be5583 100644 --- a/docs/src/modules/ROOT/pages/commercial-editions/license-management.adoc +++ b/docs/src/modules/ROOT/pages/commercial-editions/license-management.adoc @@ -8,7 +8,8 @@ The https://licenses.timefold.ai/[Timefold License Manager] is where you sign up . Go to https://licenses.timefold.ai/[licenses.timefold.ai] and click *Sign up*. . Sign in with your company email address. -Public email domains (Gmail, Hotmail, Yahoo, and similar) are not accepted; you must use a work email. +Public email domains (Gmail, Hotmail, Yahoo, and similar) aren't accepted. +You must use a work email. . Fill in the registration form: company name and website URL, the scheduling problem you're solving, the scale of your problem, and how you currently solve it. . Accept the terms of service and click *Register*. @@ -31,11 +32,12 @@ Each license card shows its type, expiration date, and a download button. === Create a license . Click *Licenses* → *Create license*. -. Select an expiration date. It can't exceed your company's contract end date. +. Select an expiration date. +It can't exceed your company's contract end date. . Optionally, assign the license to a specific user in your company. . Click *Create*. -The license type is determined by your company's plan; you don't need to select it manually. +Your company's plan determines the license type; you don't need to select it manually. === Download a license file @@ -52,18 +54,23 @@ Don't commit it to source control; inject it at runtime using your secret manage Expired licenses remain visible under the *Expired* tab, and you can still download them to review, but they're no longer accepted by the solver. -IMPORTANT: Once a license expires, Timefold Solver refuses to start. There is no grace period. Renew your license before it expires to avoid downtime. +[IMPORTANT] +==== +Once a license expires, Timefold Solver refuses to start. +There is no grace period. +Renew your license before it expires to avoid downtime. +==== == Notifications === Email reminders -The license manager emails the license owner before a license expires: 7 days before, and again 1 day before. +The license manager emails the license owner before a license expires: seven days before, and again one day before. If you're not receiving these emails, check your spam folder or ask your company admin to verify the license owner's address. === Solver warnings -Timefold Solver also logs warnings as a license approaches expiry, with increasing urgency at less than 30 days, less than 14 days, and less than 1 day remaining. +Timefold Solver also logs warnings as a license approaches expiry, with increasing urgency at less than 30 days, less than 14 days, and less than one day remaining. Once expired, the solver refuses to start. Monitor your application logs for these warnings so you have time to renew. @@ -74,7 +81,8 @@ Inviting and managing team members requires the *admin* role within your company === Invite team members Open your company from the sidebar, go to the *Users* tab, and click *Invite user*. -The invitee receives an email with an acceptance link valid for 14 days; once they accept, they join your company with the *user* role. +The invitee receives an email with an acceptance link valid for 14 days. +Once they accept, they join your company with the *user* role. If an invitation expires or wasn't received, find it in the *Users* tab and click *Resend* to send a fresh 14-day link. @@ -87,9 +95,9 @@ NOTE: At least one admin must remain in a company at all times; you can't demote == Account and company settings Update your name from your avatar menu → *Settings*. -Your email address is managed by your identity provider and can't be changed there. +Your identity provider manages your email address, so you can't change it there. -Company-level settings, such as your plan and contract dates, can only be changed by Timefold. +Only Timefold can change company-level settings, such as your plan and contract dates. Contact mailto:support@timefold.ai[support@timefold.ai] if you need your plan updated. == Pricing From c6e7ca806a87d7cd2811b6c099c63821aaf92f37 Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Wed, 8 Jul 2026 13:17:59 +0200 Subject: [PATCH 17/28] docs: license management docs updates (screenshots & slight updates) --- .../license-manager-view-licenses.png | Bin 0 -> 131248 bytes .../commercial-editions.adoc | 2 +- .../commercial-editions/installation.adoc | 2 +- .../license-management.adoc | 25 ++++++++++-------- 4 files changed, 16 insertions(+), 13 deletions(-) create mode 100644 docs/src/modules/ROOT/images/commercial-editions/license-manager-view-licenses.png diff --git a/docs/src/modules/ROOT/images/commercial-editions/license-manager-view-licenses.png b/docs/src/modules/ROOT/images/commercial-editions/license-manager-view-licenses.png new file mode 100644 index 0000000000000000000000000000000000000000..9513e5ecd00985765682ee976c36643ef4e61bd5 GIT binary patch literal 131248 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 I=PU#NABf;XHUIzs literal 0 HcmV?d00001 diff --git a/docs/src/modules/ROOT/pages/commercial-editions/commercial-editions.adoc b/docs/src/modules/ROOT/pages/commercial-editions/commercial-editions.adoc index e81dad4a6e..40005f2296 100644 --- a/docs/src/modules/ROOT/pages/commercial-editions/commercial-editions.adoc +++ b/docs/src/modules/ROOT/pages/commercial-editions/commercial-editions.adoc @@ -17,7 +17,7 @@ Just plug into our API and start optimizing immediately. [#_free_trials_and_licenses] == Free trials and licenses -Every new account on the https://licenses.timefold.ai/[Timefold License Manager] starts with a 30-day free Enterprise trial, so you can create and download fully functional Enterprise Edition license files without talking to sales first. +Every new account on the https://licenses.timefold.ai/[Timefold License Manager] starts with a free, time-limited Enterprise trial, so you can create and download fully functional Enterprise Edition license files without talking to sales first. We also offer free licenses to non-profit organizations and for academic research. See https://licenses.timefold.ai/[license portal] for how to sign up, create and download license files, and manage your team's access, then follow the xref:commercial-editions/installation.adoc[installation guide] to get started. diff --git a/docs/src/modules/ROOT/pages/commercial-editions/installation.adoc b/docs/src/modules/ROOT/pages/commercial-editions/installation.adoc index 56968e4c2e..b680cf803b 100644 --- a/docs/src/modules/ROOT/pages/commercial-editions/installation.adoc +++ b/docs/src/modules/ROOT/pages/commercial-editions/installation.adoc @@ -14,7 +14,7 @@ A correctly configured and active license key is required in order for our comme === Obtaining a license key Generate your license key using the xref:commercial-editions/license-management.adoc[Timefold License Manager]. -Every new account starts with a 30-day free Enterprise trial, so you don't need to talk to sales to get started. +Every new account starts with a free, time-limited Enterprise trial, so you don't need to talk to sales to get started. Take care not to leak the license file, for example by committing it to a public repository, logging the license file content in logs, or sharing it with unauthorized parties. diff --git a/docs/src/modules/ROOT/pages/commercial-editions/license-management.adoc b/docs/src/modules/ROOT/pages/commercial-editions/license-management.adoc index 9073be5583..960fc83fe4 100644 --- a/docs/src/modules/ROOT/pages/commercial-editions/license-management.adoc +++ b/docs/src/modules/ROOT/pages/commercial-editions/license-management.adoc @@ -2,18 +2,18 @@ :doctype: book :icons: font -The https://licenses.timefold.ai/[Timefold License Manager] is where you sign up for a free trial, create and download license files, and manage your team's access to Timefold Solver Enterprise Edition. +The https://licenses.timefold.ai/[Timefold License Manager] is where you register for a free trial, add and download license files, and manage your team's access to Timefold Solver Enterprise Edition. -== Sign up and free trial +== Register and free trial -. Go to https://licenses.timefold.ai/[licenses.timefold.ai] and click *Sign up*. -. Sign in with your company email address. +. Go to https://licenses.timefold.ai/[licenses.timefold.ai] and click *Register*. +. Log in with your company email address. Public email domains (Gmail, Hotmail, Yahoo, and similar) aren't accepted. You must use a work email. . Fill in the registration form: company name and website URL, the scheduling problem you're solving, the scale of your problem, and how you currently solve it. . Accept the terms of service and click *Register*. -Your account and company are created immediately, and you're given a 30-day free Enterprise trial: you can create and download fully functional Enterprise Edition license files right away, without talking to sales first. +Your account and company are created immediately, and you're given a free, time-limited Enterprise trial: you can create and download fully functional Enterprise Edition license files right away, without talking to sales first. The first user to register for a company automatically becomes that company's *admin*. @@ -29,24 +29,27 @@ Regular users can view and download their own licenses only. Click *Licenses* in the sidebar to see your active licenses, or switch to the *Expired* tab to see past ones. Each license card shows its type, expiration date, and a download button. -=== Create a license +.The Licenses list in the Timefold License Manager +image::commercial-editions/license-manager-view-licenses.png[The Licenses list in the Timefold License Manager, showing active license cards with type, expiration date, and a download button] -. Click *Licenses* → *Create license*. +=== Add a license + +. Click *Licenses* → *Add license key*. . Select an expiration date. It can't exceed your company's contract end date. . Optionally, assign the license to a specific user in your company. -. Click *Create*. +. Click *Add license key*. Your company's plan determines the license type; you don't need to select it manually. -=== Download a license file +=== Download a license key -Click *Download* next to a license to get its `.pem` file. +Click *Download license key* next to a license to get its `.pem` file. Introduce it to your project as described in xref:commercial-editions/installation.adoc#solverSetupLicenseKey[Set up your license key]. [IMPORTANT] ==== -Treat your license file as a secret. +Treat your license key as a secret. Don't commit it to source control; inject it at runtime using your secret manager instead (for example Kubernetes Secrets, AWS Secrets Manager, HashiCorp Vault, or a CI/CD secret store). ==== From 42572619fc043bb54dfed2822b02cc6c7ec0d70c Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Wed, 8 Jul 2026 13:54:50 +0200 Subject: [PATCH 18/28] chore: updates --- .../getting-started.adoc | 29 ++++++++++--------- .../deploying-to-platform/introduction.adoc | 9 ++---- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc b/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc index 5c3798fd73..dd94ee4ab2 100644 --- a/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc +++ b/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc @@ -24,6 +24,9 @@ To complete this guide, you need: See xref:#_generate_a_personal_access_token[Generate a Personal Access Token] below. * Push access to Timefold's container registry, since the `configure` goal builds and pushes your model's container image there. This access needs to be requested; mailto:info@timefold.ai[get in touch with the Timefold team] before you try to deploy. +* An Enterprise Edition license, since Timefold Platform requires your model to be built with the Enterprise Edition of Timefold Solver. +A free, time-limited trial is available if you don't have one yet. +See xref:#_enterprise_edition[Build with the Enterprise Edition] below. [#_add_the_maven_plugin] == Add the Maven plugin @@ -32,18 +35,18 @@ The `timefold-maven-plugin` connects your build to the platform's container regi Add it to your `pom.xml`, and configure it with your `platformUrl`, a registration `key` for your model, and the tenant(s) to register it under. Your `platformUrl` will typically be `https://app.timefold.ai`. -See https://docs.timefold.ai/timefold-platform/latest/api/integration-scenarios/integration-scenarios#_our_recommendation[Integration scenarios] if you're deploying to a different installation, for example a self-hosted platform or a different data residency region. +(See https://docs.timefold.ai/timefold-platform/latest/api/integration-scenarios/integration-scenarios#_our_recommendation[Integration scenarios] if you're deploying to a different installation, for example a self-hosted platform or a different data residency region.) -Also set `handleSubscription` to `true`. -Registering a model does not automatically subscribe your tenant to it; without this setting, your tenant won't see the model on the platform after you deploy it. +Also set `handleSubscription` to `true`, so your tenant gets subscribed to the model when you deploy it; see xref:#_what_happens_on_deploy[What happens on deploy] below. Follow the setup instructions in the https://github.com/TimefoldAI/timefold-solver/blob/main/service/tools/maven-plugin/README.adoc[Maven plugin documentation] to add and configure it. [#_enterprise_edition] == Build with the Enterprise Edition -By default, your build uses the Community Edition of Timefold Solver. -The xref:deploying-to-platform/introduction.adoc#_enterprise_edition_features[Enterprise Edition features] surfaced by the platform, such as score analysis, are not included unless you opt in. +Timefold Platform requires your model to be built with the Enterprise Edition of Timefold Solver; a Community Edition build cannot be deployed. +By default, your build uses the Community Edition, so you need to opt in explicitly. +This also gets you the xref:deploying-to-platform/introduction.adoc#_enterprise_edition_features[Enterprise Edition features] surfaced by the platform, such as score analysis. Build with `-Denterprise=true` to activate the `enterprise` Maven profile defined in `timefold-solver-service-parent`, which pulls in the Enterprise Edition dependencies: @@ -52,15 +55,9 @@ Build with `-Denterprise=true` to activate the `enterprise` Maven profile define mvn clean package -Denterprise=true ---- -Verify it worked by checking that `target/timefold/_v/jsonschema/calculateScoreAnalysis.json` exists. -If it's missing, the build used the Community Edition, and registering the model will fail with an error such as `Model descriptor is missing entry ^.+/jsonschema/calculateScoreAnalysis.json$`. +The Enterprise Edition requires a license key to function at runtime, as described in xref:commercial-editions/installation.adoc[Installation]. -You'll need to pass `-Denterprise=true` on every build you intend to deploy, including the `timefold:deploy` command in the next step. - -NOTE: The Enterprise Edition also requires a license key to function at runtime, as described in xref:commercial-editions/installation.adoc[Installation]. -Check with the Timefold team on whether this is already configured for you in the sandbox environment. - -If you need your own license, sign up at the xref:commercial-editions/license-management.adoc[Timefold License Manager]: every new account starts with a 30-day free Enterprise trial, so you can get a working license without contacting sales first. +If you need your own license, sign up at the xref:commercial-editions/license-management.adoc[Timefold License Manager]: every new account starts with a free, time-limited Enterprise trial, so you can get a working license without contacting sales first. [#_authenticate] == Authenticate @@ -86,7 +83,8 @@ export TIMEFOLD_PAT= [#_build_and_deploy] == Build and deploy -Build and deploy your model in a single command, including `-Denterprise=true` if you want the xref:deploying-to-platform/introduction.adoc#_enterprise_edition_features[Enterprise Edition features] to work on the platform: +Build and deploy your model in a single command. +`-Denterprise=true` is required, since the platform only accepts models built with the Enterprise Edition; see xref:#_enterprise_edition[Build with the Enterprise Edition] above. [source,bash,options="nowrap"] ---- @@ -114,6 +112,9 @@ The `timefold:deploy` goal uploads that archive and registers it against the pla If a model with the same `key` is already registered, the deploy fails with a conflict (HTTP 409) unless `overwrite` is enabled, in which case the existing registration is updated instead (`PATCH /api/platform/v1/models/`). You can also set this from the command line with `-Dtimefold.model.overwrite=true`. +Registering a model does not automatically subscribe your tenant to it; with `handleSubscription` set to `true`, the plugin also subscribes your tenant when it registers the model. +Without it, your tenant won't see the model on the platform after you deploy it. + [#_verify_on_the_platform] == Verify on the platform diff --git a/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc b/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc index 5b8e2af27d..b013211d76 100644 --- a/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc +++ b/docs/src/modules/ROOT/pages/deploying-to-platform/introduction.adoc @@ -24,7 +24,7 @@ From a model developer's perspective, each layer is plumbing you'd otherwise hav === Enterprise Edition features These Timefold Solver Enterprise Edition capabilities are especially valuable once your model is deployed, since consumers interact with them directly through the platform UI and API. -Using them requires building your model with the Enterprise Edition of the solver, as described in xref:deploying-to-platform/getting-started.adoc#_enterprise_edition[Build with the Enterprise Edition]; deploying to the platform by itself doesn't enable them. +Since deploying to the platform requires building your model with the Enterprise Edition, as described in xref:deploying-to-platform/getting-started.adoc#_enterprise_edition[Build with the Enterprise Edition], you get these for free. * *Explainability*: xref:constraints-and-score/understanding-the-score.adoc[Score analysis] breaks a solution down constraint-by-constraint, so consumers can see why the solver made the decisions it did, and what the impact would be of overriding one. * *Recommendations*: the xref:responding-to-change/recommendation-api.adoc#assignmentRecommendationAPI[Recommendation API] suggests the best fit for a new assignment without a full re-solve. @@ -82,12 +82,7 @@ See xref:deploying-to-platform/getting-started.adoc[Getting started: deploying t [#_model_identifier] Model identifier:: Made up of your model's bare id and its API version, combined as `_` (for example `schooltimetabling_v1`). -This is what appears in the consumer-facing API path (`/api/models//`). -The bare model id comes from your Quarkus application name, which defaults to your Maven artifact id unless you set `quarkus.application.name` explicitly. -The version comes from `timefold.application.version`. -Neither comes from `timefold.application.name` (which is only the human-readable display name shown in the UI) or from `timefold.application.description`. - -The bare id may only contain letters, numbers, and hyphens, and can't itself contain an underscore, since the version is appended with one. +This is what appears in the consumer-facing API path (`/api/models//`). The version comes from `timefold.application.version`. [#_registration_key] Registration key:: The `key` you set through the Maven plugin, as described in xref:deploying-to-platform/getting-started.adoc#_add_the_maven_plugin[Add the Maven plugin]. From 80f224f066a1f0a982b14064d322b651a4fe7ec0 Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Wed, 8 Jul 2026 13:59:40 +0200 Subject: [PATCH 19/28] chore: link to new license management page --- .../ROOT/pages/deploying-to-platform/getting-started.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc b/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc index dd94ee4ab2..d360c59f1e 100644 --- a/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc +++ b/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc @@ -57,7 +57,8 @@ mvn clean package -Denterprise=true The Enterprise Edition requires a license key to function at runtime, as described in xref:commercial-editions/installation.adoc[Installation]. -If you need your own license, sign up at the xref:commercial-editions/license-management.adoc[Timefold License Manager]: every new account starts with a free, time-limited Enterprise trial, so you can get a working license without contacting sales first. +Every new account on the xref:commercial-editions/license-management.adoc[Timefold License Manager] starts with a free, time-limited Enterprise trial, so you can create and download fully functional Enterprise Edition license files without talking to sales first. +We also offer free licenses to non-profit organizations and for academic research. [#_authenticate] == Authenticate From f7978da7245d986160369d2081a9d28a591db81a Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Wed, 8 Jul 2026 14:00:03 +0200 Subject: [PATCH 20/28] chore: cleanup message will be handled by https://github.com/TimefoldAI/timefold-solver-enterprise/issues/754 --- .../ROOT/pages/deploying-to-platform/getting-started.adoc | 7 ------- 1 file changed, 7 deletions(-) diff --git a/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc b/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc index d360c59f1e..72b001e088 100644 --- a/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc +++ b/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc @@ -92,13 +92,6 @@ Build and deploy your model in a single command. mvn clean package -Denterprise=true timefold:deploy ---- -[NOTE] -==== -If this is the first time you're running a full build (`mvn clean package` or `mvn install`) rather than `mvn quarkus:dev`, and you haven't generated an OpenAPI baseline yet, the build fails by comparing against a `src/build/openapi.json` file that doesn't exist yet. -Run `mvn clean package -Denterprise=true -Dupdate-api` once first to generate it, as described in xref:running-timefold-solver/service/rest-api.adoc#deliberateAPIChanges[Deliberate API changes], then retry the command above. -Generate the baseline with the same `-Denterprise` value you intend to deploy with, since the generated API can differ between editions. -==== - === Preview without pushing container To preview what would happen without actually pushing the container image or registering the model, add `-Dtimefold.dryRun=true`. This logs the requests that would have been made instead of sending them. From b9d8c819fd33717299480af0ba974c8eea667be4 Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Wed, 8 Jul 2026 14:00:14 +0200 Subject: [PATCH 21/28] chore: cleanup import statements --- .../ROOT/pages/deploying-to-platform/model-metadata.adoc | 6 ------ .../vehicle-routing-constraints.adoc | 8 -------- .../_school-timetabling-constraints.adoc | 7 ------- .../service/constraint-overrides.adoc | 4 ---- 4 files changed, 25 deletions(-) diff --git a/docs/src/modules/ROOT/pages/deploying-to-platform/model-metadata.adoc b/docs/src/modules/ROOT/pages/deploying-to-platform/model-metadata.adoc index 8375ae875d..4c99eb1e2b 100644 --- a/docs/src/modules/ROOT/pages/deploying-to-platform/model-metadata.adoc +++ b/docs/src/modules/ROOT/pages/deploying-to-platform/model-metadata.adoc @@ -69,12 +69,6 @@ Java:: -- [source,java,options="nowrap"] ---- -import ai.timefold.solver.core.api.score.stream.Constraint; -import ai.timefold.solver.core.api.score.stream.ConstraintFactory; -import ai.timefold.solver.core.api.score.stream.ConstraintProvider; -import ai.timefold.solver.service.definition.api.description.ConstraintGroupInfo; -import ai.timefold.solver.service.definition.api.description.ConstraintInfo; - public class TimetableConstraintProvider implements ConstraintProvider { public static final String TEACHER_CONFLICT = "Teacher conflict"; diff --git a/docs/src/modules/ROOT/pages/quickstart/quarkus-vehicle-routing/vehicle-routing-constraints.adoc b/docs/src/modules/ROOT/pages/quickstart/quarkus-vehicle-routing/vehicle-routing-constraints.adoc index 9549061b6e..6553808f80 100644 --- a/docs/src/modules/ROOT/pages/quickstart/quarkus-vehicle-routing/vehicle-routing-constraints.adoc +++ b/docs/src/modules/ROOT/pages/quickstart/quarkus-vehicle-routing/vehicle-routing-constraints.adoc @@ -34,14 +34,6 @@ Create a `src/main/java/org/acme/vehiclerouting/solver/VehicleRoutingConstraintP ---- package org.acme.vehiclerouting.solver; -import ai.timefold.solver.core.api.score.HardSoftScore; -import ai.timefold.solver.core.api.score.stream.Constraint; -import ai.timefold.solver.core.api.score.stream.ConstraintFactory; -import ai.timefold.solver.core.api.score.stream.ConstraintProvider; - -import org.acme.vehiclerouting.domain.Visit; -import org.acme.vehiclerouting.domain.Vehicle; - public class VehicleRoutingConstraintProvider implements ConstraintProvider { public static final String VEHICLE_CAPACITY = "vehicleCapacity"; diff --git a/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-constraints.adoc b/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-constraints.adoc index 9018381429..c75985f009 100644 --- a/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-constraints.adoc +++ b/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-constraints.adoc @@ -33,13 +33,6 @@ Create a `src/main/java/org/acme/schooltimetabling/solver/TimetableConstraintPro ---- package org.acme.schooltimetabling.solver; -import org.acme.schooltimetabling.domain.Lesson; -import ai.timefold.solver.core.api.score.HardSoftScore; -import ai.timefold.solver.core.api.score.stream.Constraint; -import ai.timefold.solver.core.api.score.stream.ConstraintFactory; -import ai.timefold.solver.core.api.score.stream.ConstraintProvider; -import ai.timefold.solver.core.api.score.stream.Joiners; - public class TimetableConstraintProvider implements ConstraintProvider { @Override diff --git a/docs/src/modules/ROOT/pages/running-timefold-solver/service/constraint-overrides.adoc b/docs/src/modules/ROOT/pages/running-timefold-solver/service/constraint-overrides.adoc index bb716a0264..7d1a021281 100644 --- a/docs/src/modules/ROOT/pages/running-timefold-solver/service/constraint-overrides.adoc +++ b/docs/src/modules/ROOT/pages/running-timefold-solver/service/constraint-overrides.adoc @@ -35,10 +35,6 @@ Java:: -- [source,java,options="nowrap"] ---- -import ai.timefold.solver.core.api.score.stream.Constraint; -import ai.timefold.solver.core.api.score.stream.ConstraintFactory; -import ai.timefold.solver.core.api.score.stream.ConstraintProvider; - public class TimetableConstraintProvider implements ConstraintProvider { public static final String TEACHER_CONFLICT = "Teacher conflict"; From d9823773d75e651bb49ee5598f8877cfc9dbf12e Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Wed, 8 Jul 2026 14:53:30 +0200 Subject: [PATCH 22/28] chore: import cleanups --- .../vehicle-routing-constraints.adoc | 7 +++ .../_school-timetabling-constraints.adoc | 7 +++ .../service/modeling-changes.adoc | 21 --------- .../service/rest-api.adoc | 43 ------------------- 4 files changed, 14 insertions(+), 64 deletions(-) diff --git a/docs/src/modules/ROOT/pages/quickstart/quarkus-vehicle-routing/vehicle-routing-constraints.adoc b/docs/src/modules/ROOT/pages/quickstart/quarkus-vehicle-routing/vehicle-routing-constraints.adoc index 6553808f80..54854b0260 100644 --- a/docs/src/modules/ROOT/pages/quickstart/quarkus-vehicle-routing/vehicle-routing-constraints.adoc +++ b/docs/src/modules/ROOT/pages/quickstart/quarkus-vehicle-routing/vehicle-routing-constraints.adoc @@ -34,6 +34,13 @@ Create a `src/main/java/org/acme/vehiclerouting/solver/VehicleRoutingConstraintP ---- package org.acme.vehiclerouting.solver; +import ai.timefold.solver.core.api.score.HardSoftScore; +import ai.timefold.solver.core.api.score.stream.Constraint; +import ai.timefold.solver.core.api.score.stream.ConstraintFactory; +import ai.timefold.solver.core.api.score.stream.ConstraintProvider; +import org.acme.vehiclerouting.domain.Visit; +import org.acme.vehiclerouting.domain.Vehicle; + public class VehicleRoutingConstraintProvider implements ConstraintProvider { public static final String VEHICLE_CAPACITY = "vehicleCapacity"; diff --git a/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-constraints.adoc b/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-constraints.adoc index c75985f009..9018381429 100644 --- a/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-constraints.adoc +++ b/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-constraints.adoc @@ -33,6 +33,13 @@ Create a `src/main/java/org/acme/schooltimetabling/solver/TimetableConstraintPro ---- package org.acme.schooltimetabling.solver; +import org.acme.schooltimetabling.domain.Lesson; +import ai.timefold.solver.core.api.score.HardSoftScore; +import ai.timefold.solver.core.api.score.stream.Constraint; +import ai.timefold.solver.core.api.score.stream.ConstraintFactory; +import ai.timefold.solver.core.api.score.stream.ConstraintProvider; +import ai.timefold.solver.core.api.score.stream.Joiners; + public class TimetableConstraintProvider implements ConstraintProvider { @Override diff --git a/docs/src/modules/ROOT/pages/running-timefold-solver/service/modeling-changes.adoc b/docs/src/modules/ROOT/pages/running-timefold-solver/service/modeling-changes.adoc index 7286bf931e..8488b0a423 100644 --- a/docs/src/modules/ROOT/pages/running-timefold-solver/service/modeling-changes.adoc +++ b/docs/src/modules/ROOT/pages/running-timefold-solver/service/modeling-changes.adoc @@ -27,8 +27,6 @@ Java:: -- [source,java,options="nowrap"] ---- -import java.time.LocalDateTime; - public class Timeslot { private LocalDateTime startTime; @@ -50,8 +48,6 @@ Kotlin:: -- [source,kotlin,options="nowrap"] ---- -import java.time.LocalDateTime - data class Timeslot( val startTime: LocalDateTime? = null, val endTime: LocalDateTime? = null @@ -72,12 +68,6 @@ Java:: -- [source,java,options="nowrap"] ---- -import java.time.LocalDateTime; - -import ai.timefold.solver.service.definition.api.enrichment.SolverModelEnricher; - -import jakarta.enterprise.context.ApplicationScoped; - @ApplicationScoped public class TimeslotHolidayEnricher implements SolverModelEnricher { @@ -104,12 +94,6 @@ Kotlin:: -- [source,kotlin,options="nowrap"] ---- -import java.time.LocalDateTime - -import ai.timefold.solver.service.definition.api.enrichment.SolverModelEnricher - -import jakarta.enterprise.context.ApplicationScoped - @ApplicationScoped class TimeslotHolidayEnricher : SolverModelEnricher { @@ -143,11 +127,6 @@ Java:: -- [source,java,options="nowrap"] ---- -import ai.timefold.solver.service.definition.api.enrichment.SolverModelEnrichmentDirector; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; - @ApplicationScoped public class TimetableEnrichmentDirector implements SolverModelEnrichmentDirector { diff --git a/docs/src/modules/ROOT/pages/running-timefold-solver/service/rest-api.adoc b/docs/src/modules/ROOT/pages/running-timefold-solver/service/rest-api.adoc index 1c333775de..7ee13460ad 100644 --- a/docs/src/modules/ROOT/pages/running-timefold-solver/service/rest-api.adoc +++ b/docs/src/modules/ROOT/pages/running-timefold-solver/service/rest-api.adoc @@ -24,17 +24,6 @@ Java:: -- [source,java,options="nowrap"] ---- -import java.util.List; - -import ai.timefold.solver.service.definition.api.ModelInput; -import ai.timefold.solver.service.definition.api.ModelOutput; -import ai.timefold.solver.service.rest.api.ModelRest; - -import org.eclipse.microprofile.openapi.annotations.media.Schema; -import org.eclipse.microprofile.openapi.annotations.tags.Tag; - -import jakarta.ws.rs.Path; - // TimetableDto.java public class TimetableDto implements ModelInput, ModelOutput { @@ -62,15 +51,6 @@ Kotlin:: -- [source,kotlin,options="nowrap"] ---- -import ai.timefold.solver.service.definition.api.ModelInput -import ai.timefold.solver.service.definition.api.ModelOutput -import ai.timefold.solver.service.rest.api.ModelRest - -import org.eclipse.microprofile.openapi.annotations.media.Schema -import org.eclipse.microprofile.openapi.annotations.tags.Tag - -import jakarta.ws.rs.Path - // TimetableDto.kt class TimetableDto( @Schema(required = true, description = "The unique identifier of timetable") @@ -265,15 +245,6 @@ Java:: -- [source,java,options="nowrap"] ---- -import java.util.Optional; - -import ai.timefold.solver.core.api.score.HardSoftScore; -import ai.timefold.solver.service.definition.api.ModelConvertor; -import ai.timefold.solver.service.definition.api.domain.ModelConfig; -import ai.timefold.solver.service.quarkus.deployment.defaults.EmptyModelConfigOverrides; - -import jakarta.enterprise.context.ApplicationScoped; - @ApplicationScoped public class TimetableConvertor implements ModelConvertor { @@ -301,15 +272,6 @@ Kotlin:: -- [source,kotlin,options="nowrap"] ---- -import java.util.Optional - -import ai.timefold.solver.core.api.score.HardSoftScore -import ai.timefold.solver.service.definition.api.ModelConvertor -import ai.timefold.solver.service.definition.api.domain.ModelConfig -import ai.timefold.solver.service.quarkus.deployment.defaults.EmptyModelConfigOverrides - -import jakarta.enterprise.context.ApplicationScoped - @ApplicationScoped class TimetableConvertor : ModelConvertor { @@ -342,11 +304,6 @@ For enhancing the SolverModel, use the xref:./modeling-changes.adoc#solverModelE By default, validation on the input is based on the xref:./rest-api.adoc#openAPISpecification[OpenAPI Specification annotations]. Additional validations can be added by implementing the `ModelValidator` interface. -// TODO: verify against the current ValidationBuilder/IssueType/Issue API. As of this writing, ValidationBuilder.addIssue() -// takes a single `Issue` argument, not `addIssue(IssueType, IssueDetail)` as shown below, and `IssueDetail` does not exist -// anywhere in the codebase. The example below reflects an older API shape and needs a rewrite against the current -// ai.timefold.solver.service.definition.api.validation package before it can be trusted. - [#timetableValidatorExample] [tabs] ==== From f4935a3f4410c69b8425942bf0b4ce583fdb9125 Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Wed, 8 Jul 2026 14:58:37 +0200 Subject: [PATCH 23/28] chore: cleanup imports --- .../vehicle-routing-constraints.adoc | 1 + .../quickstart/service/getting-started.adoc | 8 ----- .../service/constraint-overrides.adoc | 18 ----------- .../service/demo-data.adoc | 27 ++-------------- .../service/exposing-metrics.adoc | 32 ------------------- .../service/modeling-changes.adoc | 5 --- 6 files changed, 3 insertions(+), 88 deletions(-) diff --git a/docs/src/modules/ROOT/pages/quickstart/quarkus-vehicle-routing/vehicle-routing-constraints.adoc b/docs/src/modules/ROOT/pages/quickstart/quarkus-vehicle-routing/vehicle-routing-constraints.adoc index 54854b0260..9549061b6e 100644 --- a/docs/src/modules/ROOT/pages/quickstart/quarkus-vehicle-routing/vehicle-routing-constraints.adoc +++ b/docs/src/modules/ROOT/pages/quickstart/quarkus-vehicle-routing/vehicle-routing-constraints.adoc @@ -38,6 +38,7 @@ import ai.timefold.solver.core.api.score.HardSoftScore; import ai.timefold.solver.core.api.score.stream.Constraint; import ai.timefold.solver.core.api.score.stream.ConstraintFactory; import ai.timefold.solver.core.api.score.stream.ConstraintProvider; + import org.acme.vehiclerouting.domain.Visit; import org.acme.vehiclerouting.domain.Vehicle; diff --git a/docs/src/modules/ROOT/pages/quickstart/service/getting-started.adoc b/docs/src/modules/ROOT/pages/quickstart/service/getting-started.adoc index 2c69cbeeba..7487fb801d 100644 --- a/docs/src/modules/ROOT/pages/quickstart/service/getting-started.adoc +++ b/docs/src/modules/ROOT/pages/quickstart/service/getting-started.adoc @@ -86,18 +86,10 @@ Extending `AbstractSimpleModel`: - Configures this class to be (de-)serialized as part of the `ModelInput` and `ModelOutput` of the xref:running-timefold-solver/service/rest-api.adoc[REST API]. - Implements the `SolverModel` interface using the default `HardMediumSoftScore`. -- Already declares the `@PlanningScore` field for you. [NOTE] It's ok if you are not familiar with these terms yet. We'll get to them later. -[IMPORTANT] -==== -Don't declare your own `@PlanningScore` field when extending `AbstractSimpleModel`: it already has one. -A `@PlanningSolution` class must have exactly one `@PlanningScore` field, so adding a second one fails when the solution descriptor is built, for example with an opaque `ExceptionInInitializerError` if you build a `ConstraintVerifier` in a static test field. -If you need a different score type than `HardMediumSoftScore`, don't extend `AbstractSimpleModel`; implement `ModelInput`, `ModelOutput`, and `SolverModel` directly instead, as described in xref:running-timefold-solver/service/exposing-metrics.adoc[Exposing metrics]. -==== - [tabs] ==== Java:: diff --git a/docs/src/modules/ROOT/pages/running-timefold-solver/service/constraint-overrides.adoc b/docs/src/modules/ROOT/pages/running-timefold-solver/service/constraint-overrides.adoc index 7d1a021281..cf8162f8b9 100644 --- a/docs/src/modules/ROOT/pages/running-timefold-solver/service/constraint-overrides.adoc +++ b/docs/src/modules/ROOT/pages/running-timefold-solver/service/constraint-overrides.adoc @@ -62,10 +62,6 @@ Kotlin:: -- [source,kotlin,options="nowrap"] ---- -import ai.timefold.solver.core.api.score.stream.Constraint -import ai.timefold.solver.core.api.score.stream.ConstraintFactory -import ai.timefold.solver.core.api.score.stream.ConstraintProvider - class TimetableConstraintProvider : ConstraintProvider { companion object { @@ -99,9 +95,6 @@ Java:: -- [source,java,options="nowrap"] ---- -import ai.timefold.solver.service.definition.api.ModelConfigOverrides; -import ai.timefold.solver.service.definition.api.domain.ConstraintReference; - public final class TimetableConfigOverrides implements ModelConfigOverrides { public static final long DEFAULT_WEIGHT_ZERO = 0L; @@ -124,9 +117,6 @@ Kotlin:: -- [source,kotlin,options="nowrap"] ---- -import ai.timefold.solver.service.definition.api.ModelConfigOverrides -import ai.timefold.solver.service.definition.api.domain.ConstraintReference - data class TimetableConfigOverrides( @ConstraintReference(TimetableConstraintProvider.TEACHER_CONFLICT) val teacherConflictWeight: Long = DEFAULT_WEIGHT_ONE, @@ -187,11 +177,6 @@ Java:: -- [source,java,options="nowrap"] ---- -import java.util.Map; - -import ai.timefold.solver.core.api.domain.solution.ConstraintWeightOverrides; -import ai.timefold.solver.core.api.score.HardMediumSoftScore; - TimetableConfigOverrides modelConfigOverrides = modelConfig.overrides(); ConstraintWeightOverrides constraintWeightOverrides = ConstraintWeightOverrides.of( @@ -212,9 +197,6 @@ Kotlin:: -- [source,kotlin,options="nowrap"] ---- -import ai.timefold.solver.core.api.domain.solution.ConstraintWeightOverrides -import ai.timefold.solver.core.api.score.HardMediumSoftScore - val modelConfigOverrides = modelConfig.overrides() val constraintWeightOverrides = ConstraintWeightOverrides.of( diff --git a/docs/src/modules/ROOT/pages/running-timefold-solver/service/demo-data.adoc b/docs/src/modules/ROOT/pages/running-timefold-solver/service/demo-data.adoc index 697872ea08..93b072b559 100644 --- a/docs/src/modules/ROOT/pages/running-timefold-solver/service/demo-data.adoc +++ b/docs/src/modules/ROOT/pages/running-timefold-solver/service/demo-data.adoc @@ -31,18 +31,6 @@ Java:: -- [source,java,options="nowrap"] ---- -import java.util.List; -import java.util.function.Function; -import java.util.stream.Stream; - -import ai.timefold.solver.service.definition.api.data.DemoData; -import ai.timefold.solver.service.definition.api.data.DemoDataGenerator; -import ai.timefold.solver.service.definition.api.data.DemoMetaData; -import ai.timefold.solver.service.definition.api.domain.Configuration; -import ai.timefold.solver.service.definition.api.domain.ModelRequest; - -import jakarta.enterprise.context.ApplicationScoped; - @ApplicationScoped public class TimetableDemoDataGenerator implements DemoDataGenerator { @@ -76,14 +64,11 @@ public class TimetableDemoDataGenerator implements DemoDataGenerator { } public ModelRequest generateBasicDemoData() { - TimetableInput input = // build a small, hardcoded dataset - return new ModelRequest<>(input); + // Generate basic request. } public ModelRequest generateComplexSet() { - TimetableInput input = // build a larger, hardcoded dataset - Configuration configuration = // optionally, a non-default configuration - return new ModelRequest<>(configuration, input); + // Generate complex request. } } @@ -107,14 +92,6 @@ Kotlin:: -- [source,kotlin,options="nowrap"] ---- -import ai.timefold.solver.service.definition.api.data.DemoData -import ai.timefold.solver.service.definition.api.data.DemoDataGenerator -import ai.timefold.solver.service.definition.api.data.DemoMetaData -import ai.timefold.solver.service.definition.api.domain.Configuration -import ai.timefold.solver.service.definition.api.domain.ModelRequest - -import jakarta.enterprise.context.ApplicationScoped - @ApplicationScoped class TimetableDemoDataGenerator : DemoDataGenerator { diff --git a/docs/src/modules/ROOT/pages/running-timefold-solver/service/exposing-metrics.adoc b/docs/src/modules/ROOT/pages/running-timefold-solver/service/exposing-metrics.adoc index 2dbe93c8a5..606419bf19 100644 --- a/docs/src/modules/ROOT/pages/running-timefold-solver/service/exposing-metrics.adoc +++ b/docs/src/modules/ROOT/pages/running-timefold-solver/service/exposing-metrics.adoc @@ -30,14 +30,6 @@ Java:: -- [source,java,options="nowrap"] ---- -import ai.timefold.solver.service.definition.api.domain.DataFormat; -import ai.timefold.solver.service.definition.api.metrics.ModelInputMetrics; - -import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; -import org.eclipse.microprofile.openapi.annotations.media.Schema; - -import com.fasterxml.jackson.annotation.JsonFormat; - public record TimetableInputMetrics( @JsonFormat(shape = JsonFormat.Shape.NUMBER_INT) @Schema(name = "lessons", title = "Lessons", format = DataFormat.Values.NUMBER, description = "The number of lessons submitted in the input dataset.", @@ -54,14 +46,6 @@ Kotlin:: -- [source,kotlin,options="nowrap"] ---- -import ai.timefold.solver.service.definition.api.domain.DataFormat -import ai.timefold.solver.service.definition.api.metrics.ModelInputMetrics - -import org.eclipse.microprofile.openapi.annotations.enums.SchemaType -import org.eclipse.microprofile.openapi.annotations.media.Schema - -import com.fasterxml.jackson.annotation.JsonFormat - data class TimetableInputMetrics( @JsonFormat(shape = JsonFormat.Shape.NUMBER_INT) @Schema(name = "lessons", title = "Lessons", format = DataFormat.Values.NUMBER, description = "The number of lessons submitted in the input dataset.", @@ -171,14 +155,6 @@ Java:: -- [source,java,options="nowrap"] ---- -import ai.timefold.solver.service.definition.api.domain.DataFormat; -import ai.timefold.solver.service.definition.api.metrics.ModelOutputMetrics; - -import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; -import org.eclipse.microprofile.openapi.annotations.media.Schema; - -import com.fasterxml.jackson.annotation.JsonFormat; - public record TimetableOutputMetrics( @JsonFormat(shape = JsonFormat.Shape.NUMBER_INT) @Schema(name = "unassignedLessons", title = "Unassigned lessons", format = DataFormat.Values.NUMBER, description = "The number of lessons that could not be assigned a timeslot or room.", @@ -195,14 +171,6 @@ Kotlin:: -- [source,kotlin,options="nowrap"] ---- -import ai.timefold.solver.service.definition.api.domain.DataFormat -import ai.timefold.solver.service.definition.api.metrics.ModelOutputMetrics - -import org.eclipse.microprofile.openapi.annotations.enums.SchemaType -import org.eclipse.microprofile.openapi.annotations.media.Schema - -import com.fasterxml.jackson.annotation.JsonFormat - data class TimetableOutputMetrics( @JsonFormat(shape = JsonFormat.Shape.NUMBER_INT) @Schema(name = "unassignedLessons", title = "Unassigned lessons", format = DataFormat.Values.NUMBER, description = "The number of lessons that could not be assigned a timeslot or room.", diff --git a/docs/src/modules/ROOT/pages/running-timefold-solver/service/modeling-changes.adoc b/docs/src/modules/ROOT/pages/running-timefold-solver/service/modeling-changes.adoc index 8488b0a423..a447f3f67a 100644 --- a/docs/src/modules/ROOT/pages/running-timefold-solver/service/modeling-changes.adoc +++ b/docs/src/modules/ROOT/pages/running-timefold-solver/service/modeling-changes.adoc @@ -152,11 +152,6 @@ Kotlin:: -- [source,kotlin,options="nowrap"] ---- -import ai.timefold.solver.service.definition.api.enrichment.SolverModelEnrichmentDirector - -import jakarta.enterprise.context.ApplicationScoped -import jakarta.inject.Inject - @ApplicationScoped class TimetableEnrichmentDirector @Inject constructor( private val timeslotEnricher: TimeslotHolidayEnricher From 90108deab16b3419d6d61690209cb43f9b27331b Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Wed, 8 Jul 2026 15:00:00 +0200 Subject: [PATCH 24/28] chore: revert to todos --- .../pages/running-timefold-solver/service/demo-data.adoc | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/src/modules/ROOT/pages/running-timefold-solver/service/demo-data.adoc b/docs/src/modules/ROOT/pages/running-timefold-solver/service/demo-data.adoc index 93b072b559..0bc28e66d3 100644 --- a/docs/src/modules/ROOT/pages/running-timefold-solver/service/demo-data.adoc +++ b/docs/src/modules/ROOT/pages/running-timefold-solver/service/demo-data.adoc @@ -113,14 +113,11 @@ class TimetableDemoDataGenerator : DemoDataGenerator { fun getDemoData(): DemoData = DemoData(metaData, requestFunction(this)) fun generateBasicDemoData(): ModelRequest { - val input: TimetableInput = TODO("build a small, hardcoded dataset") - return ModelRequest(input) + return TODO("Generate basic request.") } fun generateComplexSet(): ModelRequest { - val input: TimetableInput = TODO("build a larger, hardcoded dataset") - val configuration: Configuration = TODO("optionally, a non-default configuration") - return ModelRequest(configuration, input) + return TODO("Generate complex request.") } } From d96cdb13f35defa78ee6dd78206026c4f7affaa7 Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Wed, 8 Jul 2026 15:09:14 +0200 Subject: [PATCH 25/28] chore: cleanup imports --- .../ROOT/pages/deploying-to-platform/model-metadata.adoc | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/src/modules/ROOT/pages/deploying-to-platform/model-metadata.adoc b/docs/src/modules/ROOT/pages/deploying-to-platform/model-metadata.adoc index 4c99eb1e2b..eab0a9f6c9 100644 --- a/docs/src/modules/ROOT/pages/deploying-to-platform/model-metadata.adoc +++ b/docs/src/modules/ROOT/pages/deploying-to-platform/model-metadata.adoc @@ -105,12 +105,6 @@ Kotlin:: -- [source,kotlin,options="nowrap"] ---- -import ai.timefold.solver.core.api.score.stream.Constraint -import ai.timefold.solver.core.api.score.stream.ConstraintFactory -import ai.timefold.solver.core.api.score.stream.ConstraintProvider -import ai.timefold.solver.service.definition.api.description.ConstraintGroupInfo -import ai.timefold.solver.service.definition.api.description.ConstraintInfo - class TimetableConstraintProvider : ConstraintProvider { companion object { From bd09acdd64b5f3bb75d014c073670fbb169b8cf3 Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Thu, 9 Jul 2026 09:50:43 +0200 Subject: [PATCH 26/28] docs: update maven plugin readme to add in executions & "deploy to platform" docs closes https://github.com/TimefoldAI/timefold-solver-enterprise/issues/757 --- service/tools/maven-plugin/README.adoc | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/service/tools/maven-plugin/README.adoc b/service/tools/maven-plugin/README.adoc index 49112f3a63..981c4b75d2 100644 --- a/service/tools/maven-plugin/README.adoc +++ b/service/tools/maven-plugin/README.adoc @@ -85,7 +85,8 @@ The plugin uses the JDK HttpClient API (java.net.http.HttpClient) to perform net == Example usage (pom) -Add plugin configuration in your project's POM (example snippet): +Add plugin configuration in your project's POM (example snippet). +Bind `timefold:configure` to the `initialize` phase via `` so it runs automatically as part of `mvn package`. (It only actually does work when `timefold:deploy` is also among the requested goals, so this binding is safe to keep in place for regular builds.) [source,xml] ---- @@ -95,6 +96,15 @@ Add plugin configuration in your project's POM (example snippet): ai.timefold.solver timefold-maven-plugin PUT_PLUGIN_VERSION_HERE + + + configure + initialize + + configure + + + https://api.timefold.example my-model-key @@ -109,6 +119,8 @@ Add plugin configuration in your project's POM (example snippet): ---- +With this binding in place, `mvn clean package timefold:deploy` runs `configure` during the `initialize` phase (before the container image is built later in `package`). Without it, `configure` never runs unless invoked explicitly, and the container image build won't have the platform's required registry/group configuration. + Or call the plugin directly from CLI with system properties and environment var: [source,bash] From 1658f92c80dc29f5229f465e5c5d54eaf121e52c Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Thu, 9 Jul 2026 14:13:19 +0200 Subject: [PATCH 27/28] Update service/tools/maven-plugin/README.adoc Co-authored-by: Jenne De Bleser --- service/tools/maven-plugin/README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/tools/maven-plugin/README.adoc b/service/tools/maven-plugin/README.adoc index 981c4b75d2..ef69cda831 100644 --- a/service/tools/maven-plugin/README.adoc +++ b/service/tools/maven-plugin/README.adoc @@ -95,7 +95,7 @@ Bind `timefold:configure` to the `initialize` phase via `` so it run ai.timefold.solver timefold-maven-plugin - PUT_PLUGIN_VERSION_HERE + ${project.parent.version} configure From d423241d85879951fc5f341d78dec6c347d292f0 Mon Sep 17 00:00:00 2001 From: Jurriaan Persyn Date: Fri, 10 Jul 2026 13:42:04 +0200 Subject: [PATCH 28/28] chore: clarify that only running enterprise solver requires the license --- .../ROOT/pages/deploying-to-platform/getting-started.adoc | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc b/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc index 72b001e088..111e317ffc 100644 --- a/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc +++ b/docs/src/modules/ROOT/pages/deploying-to-platform/getting-started.adoc @@ -24,9 +24,8 @@ To complete this guide, you need: See xref:#_generate_a_personal_access_token[Generate a Personal Access Token] below. * Push access to Timefold's container registry, since the `configure` goal builds and pushes your model's container image there. This access needs to be requested; mailto:info@timefold.ai[get in touch with the Timefold team] before you try to deploy. -* An Enterprise Edition license, since Timefold Platform requires your model to be built with the Enterprise Edition of Timefold Solver. -A free, time-limited trial is available if you don't have one yet. -See xref:#_enterprise_edition[Build with the Enterprise Edition] below. + +NOTE: Timefold Platform requires your model to be built with the Enterprise Edition of Timefold Solver, but building doesn't require a license, only running it does, and the platform provides that license when your model runs there. See xref:#_enterprise_edition[Build with the Enterprise Edition] below. [#_add_the_maven_plugin] == Add the Maven plugin @@ -56,7 +55,10 @@ mvn clean package -Denterprise=true ---- The Enterprise Edition requires a license key to function at runtime, as described in xref:commercial-editions/installation.adoc[Installation]. +Building with the Enterprise Edition does not require a license; a license is only checked when the model actually runs. +When you deploy to Timefold Platform, the platform provides that license for you, so you don't need one of your own just to follow this guide. +You still may want a license if you want to run your Enterprise Edition build locally, for example to test the model with `mvn quarkus:dev` before deploying it. Every new account on the xref:commercial-editions/license-management.adoc[Timefold License Manager] starts with a free, time-limited Enterprise trial, so you can create and download fully functional Enterprise Edition license files without talking to sales first. We also offer free licenses to non-profit organizations and for academic research.