@prefix bpa: <https://w3id.org/fm/bpa#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix xml: <http://www.w3.org/XML/1998/namespace> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix vann: <http://purl.org/vocab/vann/> .
@prefix dct: <http://purl.org/dc/terms/> .
@prefix dce: <http://purl.org/dc/elements/1.1/> .
@prefix widoco: <https://w3id.org/widoco/vocab#> .
@base <https://w3id.org/fm/bpa#> .

<https://w3id.org/fm/bpa>
    rdf:type owl:Ontology ;
    vann:preferredNamespaceUri    "https://w3id.org/fm/bpa#" ;
    vann:preferredNamespacePrefix "bpa" ;
    owl:versionIRI <https://w3id.org/fm/bpa/1.0.1> ;
    owl:versionInfo "1.0.1" ;
    rdfs:label "The Basic Process and Asset Ontology"@en ;
    rdfs:comment "An ontology defining basic process and asset concepts for manufacturing, including process steps, assets, and their relationships"@en ;
    dct:created  "2026-01-30"^^xsd:date ;
    dct:modified "2026-01-30"^^xsd:date ;
    dct:issued   "2026-01-30"^^xsd:date ;
    dce:title "The Basic Process and Asset Ontology"@en ;
    dct:license <https://opensource.org/license/mit> ;

    dce:abstract        '''The Basic Process and Asset (BPA) Ontology provides a foundational description of process structure and the involvement of assets in the process. It is designed for manufacturing environments where processes are composed of ordered steps that consume, produce, transform, or involve various assets. The core concepts include Process, ProcessStep, Asset, and ControlFlow. The ontology is intentionally kept simple and extensible, serving as a base for more specialised domain ontologies.'''@en ;
    widoco:introduction '''The BPA Ontology supports the following aspects:

- **Process structure**: Defines process steps, their sequencing via control flow, and the concept of a process head as entry point.
- **Asset involvement**: Models the relationship between process steps and the assets they consume, produce, transform, or otherwise involve.
- **Type vs. instance separation**: Distinguishes between process step type definitions (the plan) and process step execution instances (what actually happened).

The ontology is intentionally kept simple:

- It only reflects the (to be) executed steps and their potential sequences and their actual sequence during execution. Processes can fork (e.g. controlFlow from one node to multiple nodes), but the ontology does not describe the decision logic for that. Such decision logic can be applied independently (e.g. variability ontology).
- It does not reflect process planning, scheduling, etc.
- It does not yet support hierarchical processes (nesting). If required, a ProcessStep could point to a subProcess to allow for this.

The relationship between ProcessNodes and ProcessSteps is modelled through the `processStepType` property, while the `executedProcessNode` property links actual execution instances back to the planned node in the process definition.

## Relationship to SOSA

BPA is designed to complement the W3C **SOSA/SSN 2023** standard (https://www.w3.org/TR/vocab-ssn-2023/). While BPA captures process structure and asset involvement, SOSA provides the formal model for observations — measured values captured by sensors or monitoring systems during process execution. Together they enable:

- Linking observations to the process steps during which they were made.
- Explicit sensor and measurement provenance.
- Temporal alignment between process execution and measurement data.

Domain ontologies that extend BPA are encouraged to use SOSA 2023 concepts for measured data and to avoid deprecated features from earlier SOSA versions.'''@en ;
    dce:description     '''## Concepts

![BPA Ontology Diagram](./resources/diagram-class-bpa.png)

The BPA ontology provides a foundational model for describing manufacturing processes and the assets involved in them. It focuses on process structure and asset involvement while intentionally remaining simple and extensible.

**This is a base ontology** - you are expected to create domain-specific extensions by:
- Defining custom asset types as subclasses of `bpa:Asset` (e.g., `Gripper`, `Cobot`, `PCB`)
- Defining custom process step types as subclasses of `bpa:ProcessStep` (e.g., `Pick`, `Place`, `Push`)
- Optionally defining custom process-asset relationships as subproperties of the 4 base PSAssetRelationTypes such as `bpa:consumedAsset`.

See the [examples](#examples) section for a concrete demonstration of domain-specific extension for a flashlight assembly process.

### Manufacturing Elements

**`bpa:ManufacturingElement`** - The base class for all manufacturing elements, including both assets and process steps. All manufacturing elements must have a unique identifier (`bpa:id`). The scope of their uniqueness is to be defined by the domain. Assets of different types could have the same `bpa:id` if the types are clearly distinguishable within that domain. For example, a robot could have the same serial number (assigned as `bpa:id`) as a flashlight housing. Note that the IRIs of these individuals *do* determine their global uniqueness.

### Assets

**`bpa:Asset`** - Represents any resource involved in a manufacturing process. Assets are disjoint from ProcessSteps and can be classified into the following subtypes:

- **`bpa:Human`** - Human operators or workers
- **`bpa:Machine`** - Manufacturing equipment, robots, or machinery
- **`bpa:Material`** - Raw materials or consumables
- **`bpa:Product`** - Finished or intermediate products

**`bpa:AssetType`** - A metaclass containing all subclasses of `bpa:Asset` as its instances. This enables referencing asset types (classes) as individuals in RDF statements.

### Process Structure

**`bpa:ProcessStep`** - A step in a manufacturing process, representing either a type-level definition or an execution instance. Process steps have temporal properties (`bpa:startTime`, `bpa:endTime`) and can be linked to the process node they execute (`bpa:executedProcessNode`).

**`bpa:Process`** - A complete process describing the control flow between its nodes. A `bpa:Process` is a `bpa:ProcessStep` itself, allowing for future hierarchical nesting. A process contains:
- **`bpa:processNode`** - The constituent nodes of the process
- **`bpa:processHead`** - The starting node of the process
- **`bpa:controlFlow`** - Definitions of transitions between nodes

**`bpa:ProcessNode`** - A node in a process definition representing a step in the process workflow. Multiple process nodes in the same process can be of the same type. One could have multiple WeldingProcessSteps in some process. The process node is used to represent all steps and how the process moves from one to the next. A node links to a process step type via `bpa:processStepType`, indicating what kind of process step it represents.

**`bpa:ControlFlow`** - Defines the control flow between ProcessNodes, specifying:
- **`bpa:fromProcessNode`** - Source node
- **`bpa:toProcessNode`** - Target node

By specifying multiple control flows from a same source node, a fork in the process can be represented. Similarly, a join is represented by multiple control flows with the same target.

**`bpa:ProcessStepType`** - A metaclass containing all subclasses of `bpa:ProcessStep` as its instances. This enables referencing process step types (classes) as individuals in RDF statements, for example via the `bpa:processStepType` property.

### Asset-Process Relationships

The ontology defines several relationships between ProcessSteps and Assets through descendants of the base property **`bpa:ps_asset_relation`**:

- **`bpa:consumedAsset`** - Asset that is consumed during a process step
- **`bpa:producedAsset`** - Asset that is produced during a process step
- **`bpa:transformedAsset`** - Asset that is transformed during a process step
- **`bpa:involvedAsset`** - Asset that participates in a process step without being consumed, produced, or transformed (e.g., tools, equipment)

**`bpa:PSAssetRelationType`** - A metaclass containing all subproperties of `bpa:ps_asset_relation` as its instances. This enables referencing asset-process relationship types (properties) as individuals in RDF statements.

## Rationale and Choices

### Simplicity and Extensibility

The ontology is intentionally kept simple to serve as a foundation that **must be extended** for specific domains. The base classes (`bpa:Asset`, `bpa:ProcessStep`) are too generic for direct use and should be specialized with domain-specific subclasses:

- **No decision logic**: The ontology describes process steps and their potential sequences but does not model the decision logic for process forking or branching. This allows external systems (e.g., variability ontology) to handle such logic independently.

- **No planning/scheduling**: Planning and scheduling concerns are deliberately excluded to keep the ontology focused on process structure and execution.

- **No hierarchical nesting (yet)**: The current version doesn't support hierarchical processes, though this could be added if needed by allowing ProcessSteps to point to sub-processes. Currently, the only hint at hierarchical nesting is the fact that processes are process steps themselves, so could occur as a child process somewhere in another process.

### Type vs. Instance Separation

The ontology distinguishes between:

- **Process definitions**: `Process` instances model the definition of a process. They reference process step types via `ProcessNode` instances.
- **Process execution**: Individual `ProcessStep` instances represent actual executions, linked to their definitions via `bpa:executedProcessNode`. If a process step is executed that was not foreseen in the original process definition, the `executedProcessNode` property will be absent. This may occur for ad-hoc steps, such as transportation steps or unexpected operator interventions.

This separation allows the same process definition to be reused across multiple execution instances.

### Asset Relationship Flexibility

By defining subproperties of the four base `bpa:ps_asset_relation` properties (`consumedAsset`, `producedAsset`, `transformedAsset`, `involvedAsset`), specific domains can refine their semantics while maintaining a consistent pattern.

### SHACL Constraints

The ontology includes SHACL shapes to enforce data quality, such as:
- Manufacturing elements must have an ID
- Process steps must have a start time
- Control flows define single source and target nodes

## Examples

The ontology includes two comprehensive examples demonstrating **domain-specific extension and usage**:

### Process Definition Example

[process_definition_example.ttl](example/process_definition_example.ttl) - Shows how to extend the BPA ontology for a flashlight assembly domain by defining custom types (e.g., `iex:Gripper`, `iex:PCB_Pick`). The example includes:
- A complete process workflow with 17 process nodes
- Sequential control flow for PCB, cover, housing, and lens assembly
- Process step types for each manufacturing operation (pick, place, push, etc.)

### Process Instance Example

[process_instance_example.ttl](example/process_instance_example.ttl) - Demonstrates instantiation using the domain-specific extensions from the process definition. The example shows an execution instance of the flashlight assembly with:
- Specific asset instances (grippers, cobots, compensation units)
- Material instances (PCB, housing, cover, lens)
- Timestamped process step executions
- Asset consumption, production, and involvement relationships
- Sub-assemblies as work-in-progress products

These examples illustrate how the same process definition can be instantiated with concrete assets and timing information for actual production runs.

## Adding Properties to Assets and Process Steps

The BPA ontology provides the structural foundation, but real manufacturing data requires capturing additional properties on your domain-specific assets and process steps. There are two approaches depending on the nature of the data:

1. **Direct properties** (datatype or object properties) — for configured settings, static attributes, or parameters
2. **SOSA observations** — for measured values from sensors or monitoring systems

### Approach 1: Direct Properties

Use this approach for **parameters** — values that are configured or known before/during the process step, such as settings, specifications, or static asset attributes.

#### Adding properties to asset types

Define datatype properties on your custom asset subclasses. Use `schema:domainIncludes` (not `rdfs:domain`) to avoid unwanted OWL inference side-effects:

```turtle
@prefix schema: <https://schema.org/> .

# ── Property definitions ──────────────────────────────────
iex:maxGripperForce a owl:DatatypeProperty ;
    schema:domainIncludes iex:Gripper ;
    rdfs:range xsd:decimal ;
    rdfs:label "maximum gripper force"@en ;
    rdfs:comment "Maximum force the gripper can apply (in Newtons)."@en .

iex:serialNumber a owl:DatatypeProperty ;
    schema:domainIncludes iex:Cobot ;
    rdfs:range xsd:string ;
    rdfs:label "serial number"@en .

iex:numberOfCavities a owl:DatatypeProperty ;
    schema:domainIncludes iex:Housing ;
    rdfs:range xsd:integer ;
    rdfs:label "number of cavities"@en .

# ── Use on instances ──────────────────────────────────────
iexd:Gripper_1 a iex:Gripper ;
    bpa:id "GRP001" ;
    iex:maxGripperForce "150.0"^^xsd:decimal ;
    rdfs:label "Gripper 1" .

iexd:Cobot_1 a iex:Cobot ;
    bpa:id "COB001" ;
    iex:serialNumber "COB-2024-0042" ;
    rdfs:label "Cobot 1" .
```

#### Adding properties to process step types

Similarly, define properties for your process step types to capture operational parameters:

```turtle
# ── Property definitions ──────────────────────────────────
iex:targetSpeed a owl:DatatypeProperty ;
    schema:domainIncludes iex:PCB_Pick ;
    rdfs:range xsd:decimal ;
    rdfs:label "target speed"@en ;
    rdfs:comment "Target movement speed for the pick operation (mm/s)."@en .

iex:approachHeight a owl:DatatypeProperty ;
    schema:domainIncludes iex:PCB_ApproachPick ;
    rdfs:range xsd:decimal ;
    rdfs:label "approach height"@en ;
    rdfs:comment "Height above part when approaching (in mm)."@en .

iex:cycleCount a owl:DatatypeProperty ;
    schema:domainIncludes iex:PrepareKitting ;
    rdfs:range xsd:integer ;
    rdfs:label "cycle count"@en .

# ── Use on step instances ─────────────────────────────────
iexd:FL001_Step_PCB_Pick a iex:PCB_Pick ;
    bpa:id "FL001_S01_2" ;
    bpa:executedProcessNode iex:Node_PCB_Pick ;
    bpa:startTime "2025-04-01T09:06:00"^^xsd:dateTime ;
    bpa:endTime "2025-04-01T09:07:00"^^xsd:dateTime ;
    iex:targetSpeed "25.0"^^xsd:decimal ;
    bpa:involvedAsset iexd:Gripper_1, iexd:Cobot_1 .

iexd:FL001_Step_PCB_ApproachPick a iex:PCB_ApproachPick ;
    bpa:id "FL001_S01_1" ;
    bpa:executedProcessNode iex:Node_PCB_ApproachPick ;
    bpa:startTime "2025-04-01T09:05:00"^^xsd:dateTime ;
    bpa:endTime "2025-04-01T09:06:00"^^xsd:dateTime ;
    iex:approachHeight "50.0"^^xsd:decimal ;
    bpa:involvedAsset iexd:Gripper_1, iexd:Cobot_1 .
```

#### When to use direct properties

| Criterion | Use direct properties |
|-----------|----------------------|
| **Who sets it?** | A person or system *configures* it |
| **When is it known?** | Before or at the start of the step |
| **Does it change per measurement?** | No — fixed for this step/asset instance |
| **Examples** | Target speed, approach height, gripper force limit, serial number, cavity count |

### Approach 2: SOSA Observations

Use this approach for **measured data** — values captured by sensors or monitoring systems during or after process execution, such as actual forces, temperatures, images, or time-series curves.

> **Note on SOSA version**: This guide aligns with **SOSA/SSN 2023** (https://www.w3.org/TR/vocab-ssn-2023/), which is not yet a full W3C Recommendation but represents the current evolution of the standard. Use SOSA 2023 concepts and avoid deprecated features from earlier versions.

The W3C **SOSA/SSN** standard provides a formal model for observations that complements BPA's process structure. SOSA adds:
- Explicit sensor/measurement provenance
- Linkage between observations and the process steps during which they were made
- Support for complex result types (curves, images, statistical aggregates)
- Unit and quantity kind modeling via QUDT

#### Basic SOSA observation pattern

![SOSA use for BPA](./resources/BPAOntology-SOSA.png)

> Note: The bpa:Value and bpa:NumericValue in the diagram above are not implemented. SOSA does not fix the way results are to be modeled, nor does BPA. A separate ontology for values and value references may be used.

The fundamental unit in SOSA is an **Observation** — a single measurement of one property:

```
Observation (one per measured property)
├── sosa:hasFeatureOfInterest  →  the thing being measured (e.g., SubAssembly01)
├── sosa:madeBySensor          →  the sensor that made the observation
├── sosa:observedProperty      →  what was measured (e.g., RobotForce)
├── sosa:resultTime            →  when the measurement was taken
├── sosa:observationRelatedTo  →  the BPA process step during which it was observed
└── sosa:hasResult             →  the actual value (with unit)
```

**Optional grouping with ObservationCollection**: When you have multiple simultaneous observations from the same measurement event (e.g., force AND torque measured at the same time), you can group them:

```
ObservationCollection (optional, for grouping related observations)
├── sosa:hasFeatureOfInterest  →  shared: what was measured
├── sosa:madeBySensor          →  shared: sensor/system used
├── sosa:resultTime            →  shared: when measurements were taken
├── sosa:observationRelatedTo  →  shared: the BPA process step
└── sosa:hasMember             →  individual Observation instances
     └── Observation (one per measured property)
          ├── sosa:observedProperty  →  what this observation measured
          └── sosa:hasResult         →  the actual value
```

The collection pattern avoids repeating shared context (sensor, time, feature of interest, process step) across multiple observations.

#### Step 1: Mark your product as a Feature of Interest

The product (or whatever is being measured) must be both a BPA asset and a SOSA Feature of Interest:

```turtle
@prefix sosa: <http://www.w3.org/ns/sosa/> .

iex:SubAssembly01 rdfs:subClassOf bpa:Product, sosa:FeatureOfInterest ;
    rdfs:label "Sub-assembly 01" ;
    rdfs:comment "PCB + Housing assembly, subject to quality observations."@en .
```

#### Step 2: Define your sensor/monitoring system

Model sensors as dual BPA equipment and SOSA sensors:

```turtle
iex:RobotSensor a owl:Class ;
    rdfs:subClassOf bpa:Machine, sosa:Sensor ;
    rdfs:label "Robot Sensor"@en ;
    rdfs:comment "Force and torque sensor integrated in the robot."@en .

# Sensor instance
iexd:Cobot1_ForceSensor a iex:RobotSensor ;
    bpa:id "FSENS_COB001" ;
    rdfs:label "Cobot 1 Force Sensor" .
```

#### Step 3: Define SOSA observable properties

Each distinct measurable quantity gets its own `sosa:ObservableProperty` individual:

```turtle
@prefix qudt: <http://qudt.org/schema/qudt/> .
@prefix quantitykind: <http://qudt.org/vocab/quantitykind/> .

iex:RobotForce a sosa:ObservableProperty ;
    rdfs:label "robot force"@en ;
    rdfs:comment "Force measured by robot gripper during pick/place operations."@en ;
    qudt:hasQuantityKind quantitykind:Force .

iex:RobotTorque a sosa:ObservableProperty ;
    rdfs:label "robot torque"@en ;
    rdfs:comment "Torque measured at robot joints during operations."@en ;
    qudt:hasQuantityKind quantitykind:Torque .

iex:PickDuration a sosa:ObservableProperty ;
    rdfs:label "pick duration"@en ;
    rdfs:comment "Actual time taken for pick operation."@en ;
    qudt:hasQuantityKind quantitykind:Time .
```

#### Step 4: Define result types

Extend `sosa:Result` for your result structures. For scalar measurements with units:

```turtle
iex:ScalarResult a owl:Class ;
    rdfs:subClassOf sosa:Result ;
    rdfs:label "Scalar Result"@en ;
    rdfs:comment "A numeric measurement with unit."@en .

# For statistical aggregates (e.g., from multiple readings):
iex:StatisticalResult a owl:Class ;
    rdfs:subClassOf sosa:Result ;
    rdfs:label "Statistical Result"@en .

iex:mean a owl:DatatypeProperty ;
    schema:domainIncludes iex:StatisticalResult ;
    rdfs:range xsd:decimal ;
    rdfs:label "mean"@en .

iex:stdDev a owl:DatatypeProperty ;
    schema:domainIncludes iex:StatisticalResult ;
    rdfs:range xsd:decimal ;
    rdfs:label "standard deviation"@en .

iex:maximum a owl:DatatypeProperty ;
    schema:domainIncludes iex:StatisticalResult ;
    rdfs:range xsd:decimal ;
    rdfs:label "maximum"@en .

iex:minimum a owl:DatatypeProperty ;
    schema:domainIncludes iex:StatisticalResult ;
    rdfs:range xsd:decimal ;
    rdfs:label "minimum"@en .
```

#### Step 5: Create observation instances

> **Note on the use of blank nodes**: For the sake of readability, the examples below use blank node notation. For production use, this is not ideal. Use UUID-based nodes for that purpose, e.g. \<urn:uuid:1b9caff3-6457-481c-aec9-880dc266f6aa\>.

**Simple case: Single observation**

When a sensor measures one property, create a single observation:

```turtle
@prefix unit: <http://qudt.org/vocab/unit/> .

# A single force observation during the pick operation
_:force_obs a sosa:Observation ;
    sosa:hasFeatureOfInterest iexd:FL001_pcb ;         # what was measured
    sosa:madeBySensor iexd:Cobot1_ForceSensor ;        # sensor used
    sosa:observedProperty iex:RobotForce ;             # property measured
    sosa:resultTime "2025-04-01T09:06:45"^^xsd:dateTime ;
    sosa:observationRelatedTo iexd:FL001_Step_PCB_Pick ;  # BPA process step link
    sosa:hasResult [
        a iex:StatisticalResult ;
        iex:mean "45.3"^^xsd:decimal ;
        iex:stdDev "2.1"^^xsd:decimal ;
        iex:maximum "48.7"^^xsd:decimal ;
        iex:minimum "42.1"^^xsd:decimal ;
        qudt:hasUnit unit:N  # Newtons
    ] .
```

**Grouped case: Multiple simultaneous observations**

When multiple properties are measured simultaneously during the same event (e.g., force AND torque from the same sensor at the same time), use an `ObservationCollection` to avoid repeating shared context:

```turtle
# Collection groups related observations sharing context
_:pcb_pick_obs_collection a sosa:ObservationCollection ;
    sosa:hasFeatureOfInterest iexd:FL001_pcb ;      # shared: what was measured
    sosa:madeBySensor iexd:Cobot1_ForceSensor ;     # shared: sensor used
    sosa:resultTime "2025-04-01T09:06:45"^^xsd:dateTime ;  # shared: when
    sosa:observationRelatedTo iexd:FL001_Step_PCB_Pick ;   # shared: which step
    sosa:hasMember _:force_obs, _:torque_obs .

# Individual observations reference only their specific property and result
_:force_obs a sosa:Observation ;
    sosa:observedProperty iex:RobotForce ;
    sosa:hasResult [
        a iex:StatisticalResult ;
        iex:mean "45.3"^^xsd:decimal ;
        iex:stdDev "2.1"^^xsd:decimal ;
        iex:maximum "48.7"^^xsd:decimal ;
        iex:minimum "42.1"^^xsd:decimal ;
        qudt:hasUnit unit:N
    ] .

_:torque_obs a sosa:Observation ;
    sosa:observedProperty iex:RobotTorque ;
    sosa:hasResult [
        a iex:StatisticalResult ;
        iex:mean "12.8"^^xsd:decimal ;
        iex:stdDev "0.9"^^xsd:decimal ;
        iex:maximum "14.2"^^xsd:decimal ;
        iex:minimum "11.5"^^xsd:decimal ;
        qudt:hasUnit unit:N-M
    ] .
```

**When to use ObservationCollection**: Use it when you have multiple observations that:
- Were made by the same sensor/system
- Happened at the same time
- Measured the same feature of interest
- Relate to the same process step

This avoids repeating the same `hasFeatureOfInterest`, `madeBySensor`, `resultTime`, and `observationRelatedTo` on every observation.

#### When to use SOSA observations

| Criterion | Use SOSA observations |
|-----------|----------------------|
| **Who sets it?** | A sensor or system *measures* it |
| **When is it known?** | During or after the step |
| **Does it change per measurement?** | Yes — each measurement has its own value |
| **Need provenance?** | Yes — sensor identity, timestamp, and method matter |
| **Examples** | Actual measured force/torque, temperatures, cycle times, pressure curves, quality check images |

### Summary: Direct Properties vs. SOSA

| Aspect | Direct Properties | SOSA Observations |
|--------|------------------|-------------------|
| **What it captures** | Configured settings, specifications | Measured values from sensors |
| **Modeling approach** | Datatype/object properties on asset or step | `sosa:Observation` with `sosa:observedProperty` |
| **When defined** | Before or at process start | During or after process execution |
| **Provenance** | Not usually tracked | Sensor, timestamp, method explicitly modeled |
| **Unit handling** | Optional, often implicit | Explicit via QUDT (`qudt:hasUnit`) |
| **Complexity** | Simple property-value pairs | Richer structure supporting metadata |
| **Example (Gripper)** | `iex:maxGripperForce "150.0"` | Observation of actual grip force: 45.3 N ± 2.1 N |
| **Example (Process)** | `iex:targetSpeed "25.0"` | Observation of actual duration: 1.24 seconds |

### Complete Example: Flashlight Assembly with Properties and Observations

```turtle
# ── Asset with direct property ────────────────────────────
iexd:Gripper_1 a iex:Gripper ;
    bpa:id "GRP001" ;
    iex:maxGripperForce "150.0"^^xsd:decimal ;  # configured limit
    rdfs:label "Gripper 1" .

# ── Process step with parameter ───────────────────────────
iexd:FL001_Step_PCB_Pick a iex:PCB_Pick ;
    bpa:id "FL001_S01_2" ;
    bpa:executedProcessNode iex:Node_PCB_Pick ;
    bpa:startTime "2025-04-01T09:06:00"^^xsd:dateTime ;
    bpa:endTime "2025-04-01T09:07:00"^^xsd:dateTime ;
    iex:targetSpeed "25.0"^^xsd:decimal ;  # target parameter
    bpa:involvedAsset iexd:Gripper_1, iexd:Cobot_1 .

# ── Simple SOSA observation: single measurement ───────────
_:actual_force a sosa:Observation ;
    sosa:hasFeatureOfInterest iexd:FL001_pcb ;
    sosa:madeBySensor iexd:Cobot1_ForceSensor ;
    sosa:observedProperty iex:RobotForce ;
    sosa:resultTime "2025-04-01T09:06:30"^^xsd:dateTime ;
    sosa:observationRelatedTo iexd:FL001_Step_PCB_Pick ;  # linked to BPA step
    sosa:hasResult [
        a iex:ScalarResult ;
        qudt:numericValue "47.5"^^xsd:decimal ;
        qudt:hasUnit unit:N
    ] .

# ── Alternative: Multiple observations from same event ────
# If you have force AND duration from the same sensor at the same time,
# you can group them in a collection to avoid repeating shared context:

_:pcb_pick_obs_collection a sosa:ObservationCollection ;
    sosa:hasFeatureOfInterest iexd:FL001_pcb ;
    sosa:madeBySensor iexd:Cobot1_ForceSensor ;
    sosa:resultTime "2025-04-01T09:06:30"^^xsd:dateTime ;
    sosa:observationRelatedTo iexd:FL001_Step_PCB_Pick ;
    sosa:hasMember _:force_obs, _:duration_obs .

_:force_obs a sosa:Observation ;
    sosa:observedProperty iex:RobotForce ;
    sosa:hasResult [
        a iex:ScalarResult ;
        qudt:numericValue "47.5"^^xsd:decimal ;
        qudt:hasUnit unit:N
    ] .

_:duration_obs a sosa:Observation ;
    sosa:observedProperty iex:PickDuration ;
    sosa:hasResult [
        a iex:ScalarResult ;
        qudt:numericValue "1.24"^^xsd:decimal ;
        qudt:hasUnit unit:SEC
    ] .
```

This example shows:
- **Direct property**: `iex:maxGripperForce` on the gripper (configured specification)
- **Direct property**: `iex:targetSpeed` on the process step (operational parameter)
- **SOSA observation**: Single standalone observation of actual force during the pick operation
- **Optional grouping**: Alternative approach using `ObservationCollection` when multiple properties are measured simultaneously

All SOSA observations are linked back to the BPA process step via `sosa:observationRelatedTo`, enabling queries like *"Show all force measurements during PCB pick operations"* while maintaining the separation between configured parameters and measured values.

## Issues or Questions

### Ontology

- **Hierarchical Processes**: Should the ontology support nested sub-processes? This could be implemented by allowing a ProcessStep to reference a sub-Process, but the requirement is not yet confirmed.'''@en .  

#################################################################
#    Object Properties
#################################################################

bpa:PSAssetRelationType a owl:Class;
    owl:equivalentClass
    [ a owl:Restriction;
      owl:onProperty rdfs:subPropertyOf;
      owl:hasValue bpa:ps_asset_relation ];
    rdfs:comment """Class that contains all ps_asset_relation descendants as its instances."""@en .

bpa:ps_asset_relation rdf:type owl:ObjectProperty ;
                      rdfs:range bpa:Asset ;
                      rdfs:subPropertyOf owl:topObjectProperty ;
                      rdfs:comment """A grouping of all kinds of relationships between a ProcessStep and Asset.
These relationships need to be on type level, so they will be specialised for each subject/object type.
There will be individuals for these properties, representing the actual flow or involvement in the process."""@en .

bpa:consumedAsset rdf:type owl:ObjectProperty ;
                  rdfs:range bpa:Asset ;
                  rdfs:subPropertyOf bpa:ps_asset_relation ;
                  rdfs:label "consumed asset"@en ;
                  rdfs:comment "Asset that is consumed during a process step"@en .

bpa:involvedAsset rdf:type owl:ObjectProperty ;
                  rdfs:range bpa:Asset ;
                  rdfs:subPropertyOf bpa:ps_asset_relation ;
                  rdfs:label "involved asset"@en ;
                  rdfs:comment "Asset that is involved in a process step without being consumed, produced, or transformed"@en .

bpa:producedAsset rdf:type owl:ObjectProperty ;
                  rdfs:range bpa:Asset ;
                  rdfs:subPropertyOf bpa:ps_asset_relation ;
                  rdfs:label "produced asset"@en ;
                  rdfs:comment "Asset that is produced during a process step"@en .

bpa:transformedAsset rdf:type owl:ObjectProperty ;
                     rdfs:range bpa:Asset ;
                     rdfs:subPropertyOf bpa:ps_asset_relation ;
                     rdfs:label "transformed asset"@en ;
                     rdfs:comment "Asset that is transformed during a process step"@en .

bpa:processNode rdf:type owl:ObjectProperty ;
                rdfs:range bpa:ProcessNode ;
                rdfs:label "process node"@en ;
                rdfs:comment "Links a Process to its constituent ProcessNodes"@en .

bpa:processHead rdf:type owl:ObjectProperty ;
                rdfs:range bpa:ProcessNode ;
                rdfs:label "process head"@en ;
                rdfs:comment "Points to the starting ProcessNode of a Process"@en .

bpa:controlFlow rdf:type owl:ObjectProperty ;
                rdfs:range bpa:ControlFlow ;
                rdfs:label "control flow"@en ;
                rdfs:comment "Links a Process to its ControlFlow definitions"@en .

bpa:fromProcessNode rdf:type owl:ObjectProperty ;
                    rdfs:range bpa:ProcessNode ;
                    rdfs:label "from process node"@en ;
                    rdfs:comment "Source ProcessNode in a ControlFlow"@en .

bpa:toProcessNode rdf:type owl:ObjectProperty ;
                  rdfs:range bpa:ProcessNode ;
                  rdfs:label "to process node"@en ;
                  rdfs:comment "Target ProcessNode in a ControlFlow"@en .

# Reasoned alternative to from/toProcessNode: 
# - controlFlowFrom is the inverses of toProcessNode,
#   to enhance the reliability of the 'next' inference below.
bpa:controlFlowFrom rdf:type owl:ObjectProperty ;
                    owl:inverseOf bpa:fromProcessNode ;
                    rdfs:label "control flow from"@en ;
                    rdfs:comment "Inverse of fromProcessNode: links a ProcessNode to the ControlFlows departing from it."@en .

# The 'next' property allows to directly infer the successor ProcessNode in the process flow,
# without having to go through the ControlFlow intermediary.
# It uses the controlFlowFrom as a more reliable basis for inference.
# It is not intended to explicitly add next relationships,
# but to infer them based on the control flow definitions to ease querying of the process flow.
# Beware: for non-linear processes (with forks and joins), there can be multiple 'next' nodes for a given node.
bpa:next rdf:type owl:ObjectProperty ;
         rdfs:label "next"@en ;
         rdfs:comment "Inferred successor ProcessNode, derived via property chain from ControlFlow."@en ;
         owl:propertyChainAxiom ( bpa:controlFlowFrom bpa:toProcessNode ) .

bpa:processStepType rdf:type owl:ObjectProperty ;
                    rdfs:range bpa:ProcessStepType ;
                    rdfs:label "process step type"@en ;
                    rdfs:comment "Links a ProcessNode to its ProcessStep type definition"@en .

bpa:executedProcessNode rdf:type owl:ObjectProperty ;
                        rdfs:range bpa:ProcessNode ;
                        rdfs:label "executed process node"@en ;
                        rdfs:comment "Links a ProcessStep instance to the ProcessNode it executes"@en .

#################################################################
#    Data properties
#################################################################

bpa:endTime rdf:type owl:DatatypeProperty ;
            rdfs:range xsd:dateTime ;
            rdfs:label "end time"@en ;
            rdfs:comment "End time of a process step"@en .

bpa:id rdf:type owl:DatatypeProperty ;
       rdfs:range xsd:string ;
       rdfs:label "identifier"@en ;
       rdfs:comment "Unique identifier for a manufacturing element"@en .

bpa:startTime rdf:type owl:DatatypeProperty ;
              rdfs:range xsd:dateTime ;
              rdfs:label "start time"@en ;
              rdfs:comment "Start time of a process step"@en .

bpa:timestamp rdf:type owl:DatatypeProperty ;
              rdfs:range xsd:dateTime ;
              rdfs:label "timestamp"@en ;
              rdfs:comment "Timestamp for a time point"@en .

bpa:value rdf:type owl:DatatypeProperty ;
          rdfs:label "value"@en ;
          rdfs:comment "Generic value property"@en .

#################################################################
#    Classes and Shapes - Core
#################################################################

bpa:ManufacturingElement rdf:type owl:Class ;
                         rdfs:comment "Base class for all manufacturing elements including assets and process steps"@en .

bpa:ManufacturingElementShape
    a sh:NodeShape ;
    sh:targetClass bpa:ManufacturingElement ;
    sh:property [
        sh:path bpa:id ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] .

#################################################################
#    Classes and Shapes - Assets
#################################################################

bpa:Asset rdf:type owl:Class ;
          rdfs:subClassOf bpa:ManufacturingElement ;
          owl:disjointWith bpa:ProcessStep .

bpa:AssetType a owl:Class;
    owl:equivalentClass
    [ a owl:Restriction;
      owl:onProperty rdfs:subClassOf;
      owl:hasValue bpa:Asset ];
    rdfs:comment """Class that contains all Asset descendants as its instances."""@en .

bpa:Human rdf:type owl:Class ;
          rdfs:comment "A human involved in the manufacturing process, either as an operator, supervisor, or other role"@en ;
          rdfs:subClassOf bpa:Asset .

bpa:Equipment rdf:type owl:Class ;
              rdfs:comment "Equipment used in the manufacturing process, such as machines, tools, or fixtures"@en ;
              rdfs:subClassOf bpa:Asset .

bpa:Material rdf:type owl:Class ;
             rdfs:comment "Materials used in the manufacturing process, such as raw materials or components"@en ;
             rdfs:subClassOf bpa:Asset .

bpa:Product rdf:type owl:Class ;
            rdfs:comment "A product being manufactured, which can be a raw material, intermediate product, or final product"@en ;
            rdfs:subClassOf bpa:Asset .

#################################################################
#    Classes and Shapes - Processes
#################################################################

bpa:ProcessStep rdf:type owl:Class ;
                rdfs:subClassOf bpa:ManufacturingElement ;
                rdfs:label "process step"@en ;
                rdfs:comment "A step in a manufacturing process, either a type-level definition or an execution instance"@en .

bpa:ProcessStepShape
    a sh:NodeShape ;
    sh:targetClass bpa:ProcessStep ;
    sh:property [
        sh:path bpa:startTime ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path bpa:endTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path bpa:executedProcessNode ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:description "The absence of this property indicates that the step was not foreseen originally, but has been executed anyway. This can happen e.g. for additional transportation steps."@en ;
    ] .

bpa:Process rdf:type owl:Class ;
            rdfs:subClassOf bpa:ProcessStep ;
            rdfs:comment """A Process, describing the control flow between its nodes."""@en .

bpa:ProcessShape
    a sh:NodeShape ;
    sh:targetClass bpa:Process ;
    sh:property [
        sh:path bpa:processNode ;
        sh:minCount 0 ;
    ] ;
    sh:property [
        sh:path bpa:processHead ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path bpa:controlFlow ;
        sh:minCount 0 ;
    ] .

bpa:ProcessStepType a owl:Class;
    owl:equivalentClass
    [ a owl:Restriction;
      owl:onProperty rdfs:subClassOf;
      owl:hasValue bpa:ProcessStep ];
    rdfs:comment """Class that contains all ProcessStep descendants as its instances."""@en .

bpa:ProcessNode rdf:type owl:Class ;
                rdfs:comment """A node in a process definition representing a step in the process workflow. 
ProcessNodes define the structure of a Process and link to ProcessStep types."""@en .

bpa:ProcessNodeShape
    a sh:NodeShape ;
    sh:targetClass bpa:ProcessNode ;
    sh:property [
        sh:path bpa:processStepType ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
    ] .

bpa:ControlFlow rdf:type owl:Class ;
                rdfs:comment """Defines the control flow between ProcessNodes in a Process, 
specifying the sequence and transitions between process steps."""@en .

bpa:ControlFlowShape
    a sh:NodeShape ;
    sh:targetClass bpa:ControlFlow ;
    sh:property [
        sh:path bpa:fromProcessNode ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path bpa:toProcessNode ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
    ] .

# Serialization annotations added by generate_docs.py
<https://w3id.org/fm/bpa>
    <https://w3id.org/widoco/vocab#turtleSerialization> "bpa.ttl" ;
    <https://w3id.org/widoco/vocab#rdfxmlSerialization> "bpa.rdf" ;
    <https://w3id.org/widoco/vocab#ntSerialization> "bpa.nt" ;
    <https://w3id.org/widoco/vocab#jsonldSerialization> "bpa.jsonld" .
