Platform model metadata
Once your model is 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 Deploying to the Timefold Platform for why you’d want to do this in the first place, and Platform concepts for how this metadata relates to your model’s identifier, registration key, and visibility on the platform.
|
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, get in touch with the Timefold team to discuss access. |
1. 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 Getting started: building a service, since they are required for every model, service or not.
| Property | Purpose |
|---|---|
|
Description of the model, included in the generated OpenAPI specification and shown on the platform. |
|
Maturity level of the model. Defaults to |
Use timefold.model.maturity-level to tell consumers how stable a given model is, using the same maturity levels Timefold uses for its own models (Example, Experimental, Preview, Stable, Deprecated).
This is especially useful once you’re 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.
1.1. 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:
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.
2. 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):idis the stable identifier of the constraint,nameis the human-readable name shown in the UI,descriptionexplains the constraint’s goal, andconstraintGroupoptionally assigns the constraint to aConstraintGroupInfo. -
ConstraintGroupInfo(id, name, description, icon, tags): groups related constraints under a shared category.iconaccepts any icon name from Tabler Icons, andtagsare optional labels you can use to classify or filter groups.
Extending the TimetableConstraintProvider from Adjusting constraint weights with names, descriptions, and a group:
-
Java
-
Kotlin
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
}
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 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.
3. 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.
| Property | Purpose |
|---|---|
|
Name of the default configuration profile. |
|
Description of the default configuration profile. |
|
Default number of threads used for solving. |
|
Default map provider, for models that require map data. |
|
Default map location, for models that require map data. |
|
Default maximum distance from a road, for models that require map data. |
|
Default transport type used when computing distances, for models that require map data. |
|
Whether the default profile takes live traffic into account. Defaults to |
|
The default maximum solving time, in the ISO 8601 duration format. This property is required. |
|
The default unimproved time limit. If not set, 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 constraint weight overrides, thread count, memory, and termination limits are set for a specific use case. See Configuration parameters and profiles for how tenant users manage profiles from the platform side.