Description of simplified RDF workflow models for a selection of life events

1. Introduction

We explain how the OSLO-STEPS descriptions and composition methods -- introduced here -- have been applied to and influenced by the concrete use cases of the project. Specifically, how the concrete paths and steps can be translated into the OSLO-STEPS format, which adjustments we made to the Semantic Workflow Processor (SWP) to ensure scalability, and how we added user feedback to the SWP API.

2. Translate Procedures into OSLO-STEPS

In the project we describe the use case “onboarding in a new municipality”. One part of that use case is the declaration of the new address. As showcase, an existing online form: Change address Antwerp is manually translated into OSLO-STEPS. A snippet is found in Listing 1, 2, and 3, where the OSLO-STEPS descriptions are split up in steps, states, and shapes.

                    
                        @prefix​ o-steps​ : <​https://fast.ilabt.imec.be/ns/oslo-steps#>​ .
                        @prefix​ xl​ : <​http://www.w3.org/2008/05/skos-xl#>​ .
                        @prefix​ state​ : <https://fast.ilabt.imec.be/api/v5/states#>​ .
                        @prefix​ step​ : <​https://fast.ilabt.imec.be/api/v5/steps#>​ .

                        # Journey level steps: collect address changed data, and declare it

                        step​ :collectAddressChangeData a ​ o-steps​ :JourneyLevelStep ;
                            o-steps​ :hasDescription [ ​ xl​ :literalForm ​ "Data adreswijziging"​ @nl​ ] ;
                            o-steps​ :requiresState ​ o-steps​ :emptyState ;
                            o-steps​ :producesState ​ state​ :citizenNationalNumber, ​ state​ :streetname,
                                                                   state​ :postcode, ​ state​ :streetNumber, ​ [...] ​ ,
                                                                   state​ :citizenEmailAddress .

                        step​ :declareAddressChange a ​ o-steps​ :JourneyLevelStep ;
                            o-steps​ :hasDescription [ ​ xl​ :literalForm ​ "Aangifte adreswijziging"​ @nl​ ] ;
                            o-steps​ :requiresState ​ state​ :citizenNationalNumber, ​ state​ :streetname,
                                                                 state​ :postcode, ​ state​ :streetNumber, ​ [...] ​ ,
                                                                 state​ :citizenEmailAddress ;
                            o-steps​ :producesState ​ state​ :addressChangeDeclared .

                        # Container level steps: provide personal data, new address, and contact info

                        step​ :providePersonalData a ​ o-steps​ :ContainerLevelStep ;
                            o-steps​ :hasDescription [ ​ xl​ :literalForm ​ "Verstrek persoonlijke gegevens"​ @nl​ ] ;
                            o-steps​ :requiresState ​ o-steps​ :emptyState ;
                            o-steps​ :producesState ​ state​ :citizenNationalNumber .

                            step​ :provideNewAddress a ​ o-steps​ :ContainerLevelStep ;
                            o-steps​ :hasDescription [ ​ xl​ :literalForm ​ "Nieuw adres"​ @nl​ ] ;
                            o-steps​ :requiresState ​ o-steps​ :emptyState ;
                            o-steps​ :producesState ​ state​ :streetname, ​ state​ :postcode, ​ state​ :streetNumber.

                        step​ :provideContactInfo a ​ o-steps​ :ContainerLevelStep ;
                            o-steps​ :hasDescription [ ​ xl​ :literalForm ​ "Contactgegevens"​ @nl​ ] ;
                            o-steps​ :requiresState ​ o-steps​ :emptyState ;
                            o-steps​ :producesState ​ state​ :citizenEmailAddress .

                        # Component level steps: provide national number, street name, etc.

                        step​ :provideCitizenNationalNumberManually a ​ o-steps​ :ComponentLevelStep ;
                            o-steps​ :hasDescription [ ​ xl​ :literalForm ​ "Rijksregister inwoner"​ @nl​ ] ;
                            o-steps​ :requiresState ​ o-steps​ :emptyState ;
                            o-steps​ :producesState ​ state​ :citizenNationalNumber .

                        step​ :provideStreetName a ​ o-steps​ :ComponentLevelStep ;
                            o-steps​ :hasDescription [ ​ xl​ :literalForm ​ "Straat"​ @nl​ ] ;
                            o-steps​ :requiresState ​ o-steps​ :emptyState ;
                            o-steps​ :producesState ​ state​ :streetname .

                        step​ :providePostCode a ​ o-steps​ :ComponentLevelStep ;
                            o-steps​ :hasDescription [ ​ xl​ :literalForm ​ "Postcode"​ @nl​ ] ;
                            o-steps​ :requiresState ​ o-steps​ :emptyState ;
                            o-steps​ :producesState ​ state​ :postcode .

                        step​ :provideStreetNumber a ​ o-steps​ :ComponentLevelStep ;
                            o-steps​ :hasDescription [ ​ xl​ :literalForm ​ "Huisnummer"​ @nl​ ] ;
                            o-steps​ :requiresState ​ o-steps​ :emptyState ;
                            o-steps​ :producesState ​ state​ :streetNumber .

                        step​ :provideEmail a ​ o-steps​ :ComponentLevelStep ;
                            o-steps​ :hasDescription [ ​ xl​ :literalForm ​ "e-mail"​ @nl​ ] ;
                            o-steps​ :requiresState ​ o-steps​ :emptyState ;
                            o-steps​ :producesState ​ state​ :citizenEmailAddress .
                    
                
Listing 1: a snippet of OSLO-STEPS step descriptions for changing your address \
                    
                        @prefix​ o-steps​ : <​https://fast.ilabt.imec.be/ns/oslo-steps#>​ .
                        @prefix​ xl​ : <​http://www.w3.org/2008/05/skos-xl#>​ .
                        @prefix​ state​ : <https://fast.ilabt.imec.be/api/v5/states#>​ .
                        @prefix​ step​ : <​https://fast.ilabt.imec.be/api/v5/steps#>​ .

                        state​ :citizenEmailAddress a ​ o-steps​ :State ;
                            rdfs​ :label ​ "Inwoner email address"​ @nl​ ;
                            o-steps​ :hasStateShape ​ shape​ :emailAddressShape .

                        state​ :streetname a ​ o-steps​ :State ;
                            rdfs​ :label ​ "Straat"​ @nl​ ;
                            o-steps​ :hasStateShape ​ shape​ :citizenNewAddressStreetShape .

                        state​ :postcode a ​ o-steps​ :State ;
                            rdfs​ :label ​ "Postcode"​ @nl​ ;
                            o-steps​ :hasStateShape ​ shape​ :citizenNewAddressPostCodeShape .

                        state​ :streetNumber a ​ o-steps​ :State ;
                            rdfs​ :label ​ "Huisnumer"​ @nl​ ;
                            o-steps​ :hasStateShape ​ shape​ :citizenNewAddressStreetNumberShape .

                        state​ :citizenNationalNumber a ​ o-steps​ :State;
                            rdfs​ :label ​ "Inwoner Rijksregisternummer"​ @nl​ ;
                            o-steps​ :hasStateShape ​ shape​ :nationalNumberBelgiumShape .
                    
                
Listing 2: a snippet of OSLO-STEPS state descriptions for changing your address
                    \
                        @prefix​ o-steps​ : <​https://fast.ilabt.imec.be/ns/oslo-steps#>​ .
                        @prefix rdfs​ : <http://www.w3.org/2000/01/rdf-schema#>​ .
                        @prefix ex​ : <http://example.org/ns/example#>​ .
                        @prefix locn​ : <​http://www.w3.org/ns/locn#>​ .

                        @prefix​ ​ shape​ : <​https://fast.ilabt.imec.be/api/v5/shapes#>​ .

                        # Shapes
                        ## E-mail

                        shape​ :emailAddressShape a ​ o-steps​ :StateShape ;
                            rdfs​ :label ​ "A citizen's email"​ @en​ ;
                            sh​ :targetClass ​ o-persoon​ :Inwoner ;
                            sh​ :property ​ shape​ :emailPattern .

                        shape​ :emailPattern a ​ sh​ :PropertyShape ;
                            rdfs​ :label ​ "Valid email address"​ @en​ ;
                            sh​ :path ​ schema​ :email ;
                            sh​ :datatype ​ xsd​ :string ;
                            sh​ :pattern ​ "^\\w+@[a-zA-Z_]+?\\.[a-zA-Z]{2,3}$"​ ;
                            sh​ :minCount ​ 1 ​ .

                        ## Street name

                        shape​ :citizenNewAddressStreetShape a ​ o-steps​ :StateShape ;
                            rdfs​ :label ​ "Street of new address"​ @en​ ;
                            sh​ :targetClass ​ o-persoon​ :Inwoner ;
                            sh​ :property ​ shape​ :newStreet .

                        shape​ :newStreet a ​ sh​ :PropertyShape ;
                            rdfs​ :label ​ "Street of new address"​ @en​ ;
                            sh​ :path (​ ex​ :newAddress ​ locn​ :thoroughfare) ;
                            sh​ :minLength ​ 2 ​ ; ​ sh​ :maxLength ​ 100​ ;
                            sh​ :datatype ​ xsd​ :string ;
                            sh​ :maxCount ​ 1 ​ ; ​ sh​ :minCount ​ 1 ​ .

                        ## Postcode

                        shape​ :citizenNewAddressPostCodeShape a ​ o-steps​ :StateShape ;
                            rdfs​ :label ​ "Postcode of new address"​ @en​ ;
                            rdfs​ :comment ​ "Postcode of new address"​ @en​ ;
                            sh​ :targetClass ​ o-persoon​ :Inwoner ;
                            sh​ :property ​ shape​ :newPostcode .

                        shape​ :newPostcode a ​ sh​ :PropertyShape ;
                            rdfs​ :label ​ "Postcode of new address"​ @en​ ;
                            rdfs​ :comment ​ "Postcode of new address"​ @en​ ;
                            sh​ :path (​ ex​ :newAddress ​ locn​ :postCode) ;
                            sh​ :minLength ​ 4 ​ ; ​ sh​ :maxLength ​ 4 ​ ;
                            sh​ :datatype ​ xsd​ :string ;
                            sh​ :maxCount ​ 1 ​ ; ​ sh​ :minCount ​ 1 ​ .

                        ## Street number

                        shape​ :citizenNewAddressStreetNumberShape a ​ o-steps​ :StateShape ;
                            rdfs​ :label ​ "Street number of new address"​ @en​ ;
                            rdfs​ :comment ​ "Street number of new address"​ @en​ ;
                            sh​ :targetClass ​ o-persoon​ :Inwoner ;
                            sh​ :property ​ shape​ :newStreetnumber .

                        shape​ :newStreetnumber a ​ sh​ :PropertyShape ;
                            rdfs​ :label ​ "Street number of new address"​ @en​ ;
                            rdfs​ :comment ​ "Street number of new address"​ @en​ ;
                            sh​ :path (​ ex​ :newAddress <​ https​ : ​ //data.vlaanderen.be/ns/adres#huisnummer>​ ) ;
                            sh​ :minLength ​ 1 ​ ; ​ sh​ :maxLength ​ 5 ​ ;
                            sh​ :datatype ​ xsd​ :string ;
                            sh​ :maxCount ​ 1 ​ ; ​ sh​ :minCount ​ 1 ​ .

                        ## National number

                        shape​ :nationalNumberBelgiumShape a ​ o-steps​ :StateShape ;
                            rdfs​ :label ​ "Householder national number"​ @en​ ;
                            rdfs​ :comment ​ "User with valid Belgian national number"​ @en​ ;
                            sh​ :targetClass ​ o-persoon​ :Inwoner ;
                            sh​ :property ​ shape​ :nationalNumberPatternBelgium ;
                            sh​ :property ​ shape​ :nationalNumberObliged .

                        shape​ :nationalNumberObliged a ​ sh​ :PropertyShape ;
                            rdfs​ :label ​ "Obliged national number"​ @en​ ;
                            rdfs​ :comment ​ "There can be exactly one national number"​ @en​ ;
                            sh​ :path ​ o-persoon​ :registratie ;
                            sh​ :datatype ​ xsd​ :string ;
                            sh​ :pattern ​ "^[0-9]{2}[.\\- ]{0,1}[0-9]{2}[.\\- ]{0,1}[0-9]{2}[.\\- ]{0,1}[0-9]{3}[.\\-]{0,1}[0-9]{2}$"​ ;
                            sh​ :minCount ​ 1 ​ ; ​ sh​ :maxCount ​ 1 ​ .
                    
                
Listing 3: a snippet of OSLO-STEPS shape descriptions for changing your address

To summarize: steps are grouped within three levels: journey level, container level, and component level steps. Each step requires a set of states and produces a set of states (Listing 1). Each state describes a piece of data, and is linked to a SHACL shape (Listing 2). Each SHACL shape describes the constraints of a piece of data, to make sure all data can be validated (Listing 3). More details are described in the domain model.

2.1 Step Levels

Steps can be described in different levels of granularity. As a result of our first tests, we introduced three levels: journey level, container level and component level. Journey level steps have subpaths containing container level steps, and container level steps have subpaths containing component level steps. In an initial version we only had very fine grained step descriptions which could then be combined. This solution turned out to be not very practical because of two reasons:

  1. User experience: when dealing with and composing workflows, we want to provide the user with different workflows or at least with the entire workflow the user is about to complete. If this results in very long workflows of very fine grained steps, it is difficult for the user to have an overview.
  2. Performance: workflow composition by the SWP gets too complex to guarantee decent performance, especially when many steps are independent of each other. For example, if we have 6 input steps which all provide information for a seventh step which depends on it, the SWP internally takes maximally 6! = 720 alternative workflows into account. When we group these six input steps, e.g., into two groups of three, the SWP internally takes maximally 2! + 3! + 3! = 14 alternative workflows into account. Although this does not affect the total resulting number of workflows, it significantly affects performance.

2.2 Translation process

Listings 1, 2, and 3 were created manually based on the online form. A snippet of the form is visualized in Figure 1. Below, we show how we created the semantic descriptions based on the information given in the form: (i) identify the journey level steps, (ii) identify the component level steps and associated states, (iii) group accordingly, and (iv) complete the shapes. If the states and their associated shapes are known beforehand, 2.2.2 and 2.2.4 are partially completed.

Figure 1: a snippet of the online form

2.2.1 Identify the journey level steps and goal state

Typically, a single online form can be viewed as a two-step process: filling in all the data, and submitting the data. That is why in Listing 1 there are two journey level steps: step:collectAddressChangeData and step:declareAddressChange. The first journey level step requires an o-steps:emptyState and the final journey level step produces your goal state: state:addressChangeDeclared.


For ease of use by users, make sure you add a descriptive label via the construct o-steps:hasDescription [ xl:literalForm "[ENTER STEP LABEL HERE]"@nl ] to every step (not only the journey level steps) .


If the intermediate data is processed and the online form would contain multiple sections, each section could be viewed as a journey level step. If it is not processed, each section of the form could also be seen as a container level step (see Section 2.2.3)

2.2.2 Identify and potentially reuse all component level steps and states

A component level step can be seen as the lowest level form or interaction element. Each such element is typically used to request data to fill in a specific state. In this section, we assume that the full list of states is not known yet, and thus, component level steps and states are typically identified concurrently. If the full list of states is known, you can proceed to Section 2.2.3 and apply it to component level steps.


A state can be seen as a piece of data that we know about the user. To be future-proof, it is best practice to keep the individual states as fine-grained as possible. For example: split between first name and last name. Which granularity to choose depends on the use case (e.g., is it needed to split up the birth date in day/month/year or not), best practices in data modeling, and which ontologies are reused (Listing 2). In FAST, we have chosen to be maximally compliant with existing OSLO vocabularies, by reusing the OSLO vocabularies where possible. This is verifiable by inspecting the shapes (Listing 3): the sh:path properties for the constraints are mostly based on OSLO vocabularies.


As a currently identified best practice, we identify each data element at its lowest granularity to be a component level step. For example, the “E-mailadres” form element translates into a step:provideEmail step, and produces the state:citizenEmailAddress state. As such, we can go over the entire online form to identify each individual form element and associate a step and which state it produces to it.


Where feasible, existing steps should be reused as much as possible. It is highly likely that the step:provideEmail step and associated state will be reusable across forms. As such, it is beneficial to reuse this step and state.


The aforementioned best practice of identifying each data element at its lowest granularity for steps is not a hard requirement: it is possible to, e.g., group the state:citizenFirstName and state:citizenLastName states into a single step:provideFullName step. More on grouping in Section 2.2.3

2.2.3 Identify and potentially reuse grouped states into container level steps by filling in all required and produced states

In Listing 1, we chose to literally translate the grouping of the online form into the container level steps. For example, the step:provideNewAddress step produces state:streetname, state:postcode, state:streetNumber etc. By producing multiple states, you effectively group multiple states into a single step.


This flexibility of grouping (on all levels) allows you to influence the UX, and e.g., make changes for A/B testing. As such, optimal grouping and nesting of steps is subject to UX expertise.


Similar to component level steps, existing container level steps should be reused as much as possible.

2.2.4 Identify and potentially reuse shapes

Each state gets associated with two types of metadata: a human-readable label, and a link to a SHACL shape. When the full list of states is known, you can associate SHACL shapes to describe the format of the data elements. These SHACL shapes can be used to guide the UI generation, and validate the (submitted) data to make sure the Semantic Web application can correctly interpret and process the submitted data.


In Listing 3, a set of SHACL shapes is described. For example, the shape:emailAddressShape shape specifies that each citizen description that is classified as a o-persoon:Inwoner should have at least one schema:email path of which the string matches a specific pattern. These SHACL shapes could be used to further detail the UX, e.g., by automatically deducting that entering the email address is required, and each entered email address is validated using the pattern.


Similar to steps, existing shapes should be reused as much as possible.

3. SWP Adjustments

By applying the SWP to the use case, we noticed a set of scalability issues which we resolved. Specifically, we introduced step levels (Section 3.1) and increased performance during step calculation (Section 3.2).

3.1 Goal generation for subpaths

Without step levels, the SWP creates paths that start from the initial (typically empty) state and end at the goal state in a single process. As this does not scale well and deteriorates user experience (see Section 2.1), we introduce step levels (journey, container, and component). As such, workflow composition becomes a multi-process system: as long as you are not dealing with component level steps, you first invoke the SWP to calculate the subpaths in a lower level. When starting with a journey level step, first you ask the subpath consisting of container level steps, then you ask the subpath consisting of component level steps.


Calculating a subpath based on a higher-level step has following requirements.

3.1.1 Derive the subgoal for this subpath based on the step

The subgoal needs to be explicitly stated to ensure that the result of the subpath equals the result of the original step.

3.1.2 Derive the input data structure for this specific subpath based on the previously executed steps

The input data structure needs to be derived to ensure that a valid subpath can be found without taking the previous steps into account. For example, if a container level step requires to know the personal information of a citizen before the address change can be requested, the structure of a citizen’s personal information needs to be derived based on the prior relevant container level steps. Without deriving this structure and using it as input for the subpath computation, the SWP cannot calculate any subpath because the required inputs are not discoverable.


To make sure the subpaths are still optimal, the costs of each step are taken into account. Each step has costs of different types: these costs are the limit values for the goal. The SWP’s algorithm ensures that each step’s execution cost is within the described limits. For example, when a container level step has a cost of 200 for a specific cost type such as Duration, it is ensured that the sum of all subpath steps remains lower than 200. By reusing these costs when calculating the subpaths, we make sure each subpath’s execution cost is also within the limits of the original step.


Once we additionally derived the subgoal and input data structure, the SWP can calculate the subpath for each step based on the preselected higher-level steps, the subgoal, the input data structure, and the user-specific profile information. This process is the same for subpath calculation of both journey and container level steps.

3.2 Performance

Due to use case testing, we introduced some general improvements for the performance: taking only relevant steps into account when calculating paths (Section 3.2.1), ensuring each step only needs to be completed once (Section 3.2.2), ignoring permutations of independent steps (Section 3.2.3).

3.2.1 Preselection step

Based on the input data structure and step description, we can derive a subset of relevant steps before we calculate the actual path. For example, if we can derive that a procedure does not request any data about a citizen’s pets, we can ignore all steps that handle pet data. This lowers the number of steps that are taken into account during path calculation and lowers computation time for the SWP.


This preselection step can also be used when calculating the subpaths. First, the steps are divided into journey, container, and component level steps, because when calculating a journey level subpath, you only need to take container level steps into account. Then, based on the derived input data structure, the divided steps can be further filtered to exclude steps that cannot handle the derived input data structure.

3.2.2 Each step only once

To adhere to the Once-Only Principle (OOP), we modified the SWP in such a way that workflows include each step once at most. This is very helpful for the FAST use cases, as FAST’s use cases typically handle life events: data is modified because the environment is modified (e.g., moving between cities). In these types of use cases, each step only needs to be completed once. By making sure each step is only taken into account once, we lower the number of relevant steps for each path calculation and lower computation time for the SWP.


This modification remains configurable, as there are other types of use cases where certain steps need to be completed multiple times.

3.2.3 No permutations

When many steps are independent of each other and we solely rely on ordered lists of steps, many alternative workflows can be created, e.g., “fill in first name then last name” and “fill in last name then first name” are two different workflows, whereas “fill in first name and last name” is only one workflow. The combinatorial explosion decreases performance of the SWP, e.g., five independent steps result in 120 alternative workflows.


We modified the original path algorithm to derive which steps are independent of each other (i.e., when their required input states do not overlap), and if so, return these steps in an unordered instead of ordered list. As such, five independent steps result in a single workflow (or subpath). As such, we provide meaningful options whilst guaranteeing performance.

4. API Enhancement: User Feedback

It is important for the user (in our use case, the citizen) to see which data is used, especially when specific steps submit data to third parties. To improve user experience, we included support in the SWP. For each step, the user can now ask which concrete data is taken into account that will also be the data which is submitted to third parties. Specific documentation on this feedback loop is available at the SWP documentation.

5. Conclusion

We discussed the application of a use case to the SWP. Specifically, Section 2 details some best practices on how to manually create well-structured OSLO-STEPS descriptions to be used by the SWP. Separate from the fact that the SWP successfully handled the given use cases, some general performance enhancements emerged.


First, the usage of step levels increases performance and user experiences, and allows for more advanced configuration of the UX. In this project, three levels of steps seemed sufficient and was implemented using a preprocessing step. However, when needed, more advanced nesting could be designed.


Second, it became clear that limiting the options greatly improves the performance of the SWP. We saw this when optimizing the performance: nesting the steps, filtering on only relevant steps, limiting each step to be executed only once, and disallowing permutations all greatly reduced the amount of (equivalent) workflow alternatives, and improved performance. As such, additional preprocessing steps to further filter the set of irrelevant options could be proven to be beneficial.


Moreover, these additional preprocessing steps are dependent on the OSLO-STEPS descriptions and thus need to be calculated each time the descriptions change, however, they are not dependent on the user information taken into account when calculating the (sub)paths. As such, these preprocessing steps need not influence the processing time when a user asks for a personalized workflow, as they can be implemented to only influence the update time when the OSLO-STEPS descriptions change.