Product Customization as Linked Data:
Connecting to Renault's range description.

Renault publishes the description of its commercial range as Linked Data. This page explains how to connect to this data, and how to use it. As an example, a configurator over the Renault range is implemented in javascript, using these data.

Note: this tutorial and the configurator require a modern, CORS (Cross-Origin Resource Sharing) compliant browser. It has been tested (2012-11) with up-to-date Firefox. It doesn't work with Internet Explorer 7 and 8 (which doesn't seem to handle correctly some HTTP headers in CORS situations)

Feedback, questions, etc.: francois-paul.servant or edouard.chevalier at renault dot com

Background: Configuration as Linked Data

The configuration process, which helps a customer to make her choice among a range of customizable products, one step at a time, feature after feature, can be modeled as the traversal of a graph of "Partially Defined Products" (PDP), or "Configurations", each configuration linking to those that refine it.

Each Configuration, (= each step of the configuration process = each "Partially Defined Product") is identified by a URI. When dereferencing (HTTP GETting) that URI, we get the data that describe the Configuration, in particular the links that allow to make the choices that remain possible.

This is formalized in the "Configuration as Linked Data Ontology". This ontology is generic and can be applied to many product customization use-cases. It is domain independent - by no way limited to the automotive product - and it doesn't depend on the vocabulary used to identify the specifications (= features) of the product in question. It is published under a liberal Creative Commons license so you can use it to describe the configuration of your own products.

These ideas have been presented at the ESWC 2012:

Connecting to the data

Starting point

The data published by Renault allows to choose (to configure) a car among its range. As Renault's commercial offer is slightly different between countries, there is a different starting point for each country where the service is deployed. We'll use the following one:

Getting the data

Let's fetch this URI. As we want data, and more specifically RDF data, we'll set the HTTP accept header of the query to "application/rdf+xml". We could use curl from a terminal window:

curl -X GET -L -i -H "Accept: application/rdf+xml" 

but as we'll be working with javascript here, let's use jQuery:

jQuery.ajax({
	url: "",
	cache: true,
	type: "GET",
	dataType: "xml",
	accepts: {xml: "application/rdf+xml"},
	success: function(response) {...},
	error: function(jqXHR, textStatus, errorThrown) {...}
});

Here is the raw data that we get:

RDF Serialization format:

Several RDF serializations are available: RDF-XML ("application/rdf+xml"), turtle ("text/turtle"), Talis RDF/JSON format ("application/rdf+json"), JSON-LD ("application/ld+json"). They all represent exactly the same data, so you can use any of them. Another JSON serialization is available ("application/json") but that does not correspond to the configuration ontology (legacy format used by Renault applications).

Note: no HTML representation available yet

When accessing the URI of a Configuration using a browser, we should get HTML: we should be redirected to the Renault web site, to the corresponding page of the online configurator application (which, BTW, uses the data provided by this service) But the required mechanism is not yet implemented by the web site. For now therefore, we'll have to be satisfied browsing the data only, without switching to the GUI (in short, we'll have to behave like machines, not like human users).

Manipulating the data in Javascript

RDF data can be used with a lot of tools in various languages. In Javascript, we'll use here rdfQuery. It is a jQuery plugin that allows to create, store and query RDF triples in a jQuery-like way. It can import data in RDFa, RDF-XML and Talis JSON/RDF. To load data about a Configuration into a rdfQuery "databank", we use the following code:

cold.loadRDF = function (confUri, success, failure) {
	jQuery.ajax({
		url: confUri,
		cache: true,
		type: "GET",
		dataType: "xml",
		accepts: {xml: "application/rdf+xml"},
		success: function(response){
			success(jQuery.rdf().load(response, {}), uri);
		},
		error: function(jqXHR, textStatus, errorThrown) {...}
	});
};

where success is a callback function that is invoked once the RDF is loaded, with the following arguments: an instance of jQuery.rdf (the downloaded data), and confUri.

Making choices

The configuration process is all about making choices of specifications, one choice at a time: at any step of this process, which is materialized by one "Configuration" instance, we get the data about that Configuration, and these data list the choices that are possible, given the previously selected specifications. Incompatibilities between choices (the fact that some specifications are incompatible with previous selections) are handled by the configuration server, that performs all the required reasonning.

But for now, we haven't made any choice yet: let's look at the choices we are presented with.

The empty Configuration. Possible choices and the cold:ConfigurationLink class

Let's look again at the data that are returned when connecting to the starting point of the service. For lisibility, we display them here in turtle syntax (omitting the prefix declarations). We're using the primary URI of the starting point as the default namespace, so ":this" identifies here the starting point of the configuration process.

As we see, :this is a Configuration:

that is, following the definition of the Configuration Ontology, a state of the Configuration process. It is the "empty Configuration": a configuration without any choice made yet (besides "Renault" of course, but this doesn't show up in the data).

Its description lists the choices that can be made at this first step of the configuration process. This list is given by the triples involving the "cold:possible" property, a property whose domain is the cold:Configuration class, and whose range is the cold:ConfigurationLink class. Here is one example:

The object of the cold:possible property (the cold:ConfigurationLink) is an anonymous resource. Its properties, defined between the brackets, modelize this possible choice, labelled . This corresponds to the selection of the cold:Specification (= characteristic of a car) identified by the value of the cold:specToBeAdded property. This cold:Specification identifies a car model.

Last, but not least, the value of the cold:linkedConf property: , is the URI of the cold:Configuration that matches this choice: making the choice of this model is just replacing the current (empty) configuration by this new one.

A Configuration with the model chosen

So, let's choose the model in question. We fetch the URI given by the cold:linkedConf above, and we get the following data:

Well, that's quite a lot of data. Once we have chosen the model, the set of the Specifications (car features) that are relevant for the Configuration is defined, (each model has its own set), and the Renault configuration engine allows to choose them in a completely free order: from now on, a lot of specifications can be chosen at any step of the configuration process, and all these possibilities are listed here, using the cold:possible property that we've already encountered.

These data are not really friendly at first sight, because they don't contain labels. All the URIs here, in particular the URIs of the Specifications are dereferenceable, and we can get this information over there, but we can get all the labels at once, and other valuable information, thanks to the value of the cold:lexicon property.

The Lexicon

The cold:lexicon property links the configuration to the dictionary of relevant specifications.

In Renault's data model, the set of the Specifications that are relevant for a given Configuration is completely defined once the model is chosen: it doesn't change as long as the model doesn't change. We can therefore dereference the lexicon once and keep it as long as the choice of the model is not modified. This explains why the labels of the specifications are not included in the RDF describing each configuration: better to acces the lexicon once, and to keep the RDF describing each configuration light.

Here's what we get when dereferencing the URI of the Lexicon, in our case

There are two kinds of (related) entities in the Lexicon: Specifications and ConfigurationVariables.

A cold:ConfigurationVariable is a characteristic of a car that a user can choose, and a cold:Specification is one of the possible values of a given cold:ConfigurationVariable.

For instance, in the case of cars, the gearbox type is a cold:ConfigurationVariable. The lexicon contains its definition, and the list of corresponding specifications:

Note: the published ontology suggests to use a "cold:hasValue" to link the ConfigurationVariable to its corresponding specifications. In the data returned (as of 2013-05), the cold:ConfigurationVariable is also defined as an OWL enumerated class, whose instances are the corresponding Specifications (eg. the "manual gearbox" specification is an instance of "gearbox type"). The data also contain rdf:type statements linking each specification to its corresponding variable. That's probably too many redundant statements. The declarations of the enumerated classes will probably be removed in the future.

The configuration process is completed when all the variables cold:ConfigurationVariable listed in the Lexicon are defined. In a completely defined product, any of the ConfigurationVariable has one and only one value assigned to (for instance, there is one and only one gearbox type assigned). This is quite a general modeling for products, even if it doesn't seem to be the case at first sight. (One might think that to be very general, one variable should be allowed to take several different values in one completely defined product, but this is just a matter of changing the definition of the set of variables. An easy way to get convinced is to note that we could just use one Boolean variable for each possible Specification: a variable that is true if the specification in question is included in the product, and false otherwise).

Choosing a specification given by its label

Let's say we want an automatic gearbox. The URI of this specification was given above but should we want to find it by ourselves, it would just be a matter of finding the corresponding label in the Lexicon.

A simple SPARQL query can do it (leaving out the prefix definition here):

SELECT ?spec WHERE {
	?spec a cold:Specification.
	?spec rdfs:label ?label.
	FILTER regex(?label, "automatic gearbox", "i") 
}

In javascript using RDFQuery, (assuming that lexRDF is a variable containing lexicon's RDF and, again, omitting prefix declaration), this translates to:

var query = lexRDF
	.where('?spec a cold:Specification')
	.where('?spec rdfs:label ?label')
	.filter('label', /automatic gearbox/i);

whose result as turtle is:

RDFQuery gives the URI of the specification this way:

var specUri = query.get(0).spec.value.toString();

To choose this specification, we now have to find the corresponding ConfigurationLink in the RDF that describes the Configuration. Assuming we stored that RDF in the "confRDF" variable, (and again omitting prefix declaration):

var query = confRDF
	.where('?link cold:specToBeAdded <' + specUri + '>')
	.where('<' + confUri + '> cold:possible ?link')
	.where('?link cold:linkedConf ?linkedConf');

which reads: select from confRDF the possible ?link (cf. line 2: cold:possible) whose effect is to select specUri (line 1: cold:specToBeAdded) and take note of the corresponding modified configuration (line 3: cold:linkedConf).

There could be no such link: if the specification in question is not available on our configuration, but as we only chose the model for now, this should'nt be the case.

This can be checked with RDFQuery, and if it's OK, we can easily get the URI of the linked configuration:

var linkedConfUri;
if (query.length > 0) linkedConfUri = query.get(0).linkedConf.value.toString();

Linked Conf is:

Note: it isn't the case with the data as it is returned by now (2012-10), but we could get several links matching the query we made. The ontology indeed leaves open the possibility to have several cold:specToBeAdded within one ConfigurationLink: one ConfigurationLink can be used to select several specifications at once. So, we should make some extra verification to check that the found ConfigurationLink really means "adding specUri and nothing else". Problem is, this extra verification is not really easy to be written (and cannot actually be done within the Open World Assumption). To avoid this difficulty, the returned data should probably be changed slightly. One way would be to define and use a subclass of ConfigurationLink that allows one and only one value for the cold:specToBeAdded property. // TODO

Anatomy of a cold:Configuration

Let us choose the automatic gearbox, and let us have a deeper look at the corresponding data

Properties that define the Configuration: the choices that have been made.

The cold:Configuration is defined by the choices that we have made, and this information is of course returned in the data. The Specifications that we've chosen are given by the cold:chosenSpec property:

you can check that our two choices (the model and the automatic gearbox) are listed.

The cold:choiceSeq property also gives the list of chosen specifications, but as a rdf:Seq. It keeps track of the order the choices were made in. This order doesn't have any impact on the characteristics of the Configuration in terms of specifications, but this information can be interesting in some cases (for instance in defining a strategy to resolve conflicts, when the user wants to choose a Specification that is not possible given the previous choices, and some of them must therefore be changed.)

Other kind of choices can be made to define a Configuration (eg. setting a max price). When it is the case, dedicated properties are used (see the ontology for details. All properties used to define the Configuration, such as cold:chosenSpec and cold:maxPriceChoice are subProperties of cold:definingChoice).

Description of the Configuration

The other properties describe the Configuration. Let's review them

gr:hasPriceSpecification

gives the start price of the Configuration (the price of the cheapest car that matches the Configuration). This property and the format of its value is defined by the GoodRelations ontology. Prices are those from the official Renault price list.

cold:impliedSpec

lists the specifications that are implied by the choices: these specifications are included in any car that matches the configuration. Quite a lot:

cold:possible

We already discussed it. It links to a cold:ConfigurationLink, that models the possibility to choose a given cold:Specification, and provides the URI of the modified configuration (that is, the configuration defined by the choices of the current configuration plus the specification in question).

cold:alternative

the "automatic gearbox" specification that we chose was one among several gearbox types (see above the discussion about the Lexicon). We could change our mind regarding this choice and decide to choose a manual gearbox. This is modeled with the cold:alternative property. Just as the cold:possible property, its range is a cold:ConfigurationLink:

Note that a specification, that has been chosen among several, can later turn to be implied, because of some other choices made afterwards. In this case, there is no alternative involving that specification.

cold:impossible

lists specifications that are impossible given the previous choices: they cannot be chosen anymore, unless some of the previous choices get changed.

The range of this property is nevertheless a cold:ConfigurationLink, because the configuration server may implement a conflict resolution functionnality, - a way to say: you cannot select that specification, unless you change this and/or that previous choice - and the cold:ConfigurationLink is adequate to model it:

  • the cold:specToBeAdded property being used to define the specification that requires some changes to the previous choices
  • the cold:specToBeRemoved property to list the previous choices that must be canceled,
  • cold:linkedConf to link to the configuration with the conflict resolved.

It must be noted however that the computation of the data about a conflict resolution is a bit expensive. Computing them for all the impossible specifications would be too time consuming, and it won't be done by Renault. Instead, we will include in the RDF a link to that data, that is: instead of including all such cold:ConfigurationLink(s) directly in the RDF as anonymous resources, we'll provide a URI for them, that return their computed description when dereferenced.

cold:defaultSpec

defined in the ontolofy, but not used in the data (2012-10). Supposed to list the specifications included by default (other than implied): what you would get if you don't make more choices. Problem is, this is not always defined (eg. if you didn't choose the color, there is no color defined by default. Or if the default value of a variable is impossible, because of some of the choices, but there are still several possible values for that variable) While it is true that traditionally, there is a list of default values for variables once the variable called "version" is set, we could not think of several different ways to complete a configuration. It seems therefore that it is better not to attempt to list "default specifications", but to provide a link to a completed configuration - this leaving open the possibility to have several different ways to complete the configuration. It also requires less in terms of computing time (as the list is computed only when it is requested by the client)

cold:completed, cold:completedAtSamePrice

properties that link to a completed configuration (a completely defined product) that matches this configuration. Renault data always include at least one that is completed at the same price.

There is something particular with the values of these properties: the URI that is given is not a the "canonic" URI of the linked configuration. This is because the computation of the canonic URI of the linked configuration is expensive (it happens to be as expensive as the computing of the description of the linked configuration). When getting the "non-canonic" URI that is given in this triple, the returned RDF contains the description of the configuration using the canonic URI. The client must therefore be prepared for that (the data returned will evolve a little bit to make all of this more explicit: in a next release, it will include an owl:sameAs statement between the non-canonic and the canonic URIs)

Configuration service

Choosing several specifications at once?

The linked data description of the range allows one to crawl the data, choosing one specification at a time. The user is ensured that she cannot make impossible choices (well, as long as she only choose specifications among the "possible" ones).

Would it be possible to choose several specifications at once? A service allows it. If the requested combination of specifications is not possible, the service returns a 404. Otherwise, it redirects to the canonic URI of the corresponding configuration. This service is available once the model is chosen, as query parameters added to the end of the URI of the corresponding configuration.

The API of the service will probably change slightly, but here how it looks like for now (confURI being the canonic URI of a configuration):

confURI?schoice=specCode1&schoice=specCode2&...

where specCode1, specCode2, etc. are the short codes of the specifications that we want to choose (each Specification is linked to its short code by the property cold:specId, so we can easily get them from lexicon'RDF, using something like this in RDFQuery:

specCode = lexRDF
	.where('<' + specUri + '> cold:specId ?specCode')
	.get(0)
	.specCode
	.value;

For instance lets say we want to add the following specifications to our previous configuration:

the service call is

and the linked Conf is:

Let's try with more specifications:

Service call is:

Result is:

Setting a max price

One valid choice in a configuration is to set a max price. As there's no well established way to include forms in linked data, this doesn't appear in the data returned about a configuration. The way to set a max price is to add a "maxprice" param at the end of the URI of a configuration. For instance, using the same configuration as above, to set the max price to , the call to the service is:

which returns the description of the following canonical URI: