Step 7: Requirements Traceability¶
In this step, you'll learn how to define engineering requirements and link them to verifications. This creates a complete traceability chain from requirements to verification results.
Why Requirements Matter¶
So far, we've created verifications that check if values meet certain conditions. But in engineering projects, we need to:
- Document requirements - What constraints must the design satisfy?
- Track coverage - Which requirements have verifications?
- Show traceability - Which verifications prove which requirements?
- Report status - Are all requirements satisfied?
veriq's requirement system addresses all these needs.
Defining Requirements¶
Requirements are created using scope.requirement():
import veriq as vq
project = vq.Project("MySatellite")
power = vq.Scope("Power")
project.add_scope(power)
# Define a requirement
power.requirement(
"REQ-PWR-001",
"Battery capacity must be at least 100 Wh.",
)
Each requirement has:
- ID - A unique identifier (e.g., "REQ-PWR-001")
- Description - Human-readable explanation
Linking Verifications to Requirements¶
The key to traceability is linking verifications to requirements using verified_by with vq.Ref:
@power.verification()
def verify_battery_capacity(
capacity: Annotated[float, vq.Ref("$.battery_capacity")],
) -> bool:
return capacity >= 100.0
# Link the verification to the requirement using vq.Ref
power.requirement(
"REQ-PWR-001",
"Battery capacity must be at least 100 Wh.",
verified_by=[vq.Ref("?verify_battery_capacity")],
)
The ? prefix indicates a verification reference (similar to how $ references models and @ references calculations).
Now when you run veriq trace, you can see that REQ-PWR-001 is verified by verify_battery_capacity.
Hierarchical Requirements¶
Real projects have hierarchical requirements. Use the context manager pattern to create parent-child relationships:
system = vq.Scope("System")
project.add_scope(system)
# Parent requirement with children
with system.requirement("REQ-SYS-001", "System shall meet all subsystem requirements."):
# Child requirements (automatically nested under REQ-SYS-001)
power.requirement(
"REQ-PWR-001",
"Battery capacity must be at least 100 Wh.",
verified_by=[vq.Ref("?verify_battery_capacity", scope="Power")],
)
power.requirement(
"REQ-PWR-002",
"Power margin must be positive in all modes.",
verified_by=[vq.Ref("?verify_power_margin", scope="Power")],
)
The parent requirement's status is derived from its children:
- SATISFIED - All children pass (parent has no direct verifications)
- FAILED - Any child fails
Cross-Scope Requirements¶
Requirements can span multiple scopes. Use fetch_requirement() to add children to an existing requirement:
# Define parent in System scope
with system.requirement("REQ-SYS-002", "Thermal constraints."):
pass # Children added later
# Add child from Thermal scope
with system.fetch_requirement("REQ-SYS-002"):
thermal.requirement(
"REQ-TH-001",
"Solar panel temperature must be within limits.",
verified_by=[vq.Ref("?verify_temperature", scope="Thermal")],
)
You can also reference verifications from other scopes directly:
# Reference a verification from another scope
system.requirement(
"REQ-SYS-003",
"Cross-scope verification example.",
verified_by=[vq.Ref("?verify_power", scope="Power")],
)
Complete Example¶
Here's a complete example with hierarchical requirements:
import veriq as vq
from pydantic import BaseModel
from typing import Annotated
from enum import StrEnum
project = vq.Project("MySatellite")
system = vq.Scope("System")
power = vq.Scope("Power")
thermal = vq.Scope("Thermal")
project.add_scope(system)
project.add_scope(power)
project.add_scope(thermal)
class Mode(StrEnum):
NOMINAL = "nominal"
SAFE = "safe"
# --- System Scope (container for system-level requirements) ---
# Note: Every scope needs a root model, even if empty
@system.root_model()
class SystemModel(BaseModel):
pass
# --- Power Scope ---
@power.root_model()
class PowerModel(BaseModel):
battery_capacity: float
power_generation: vq.Table[Mode, float]
power_consumption: vq.Table[Mode, float]
@power.verification()
def verify_battery(
capacity: Annotated[float, vq.Ref("$.battery_capacity")],
) -> bool:
"""Battery capacity must be at least 100 Wh."""
return capacity >= 100.0
@power.verification()
def verify_power_margin(
generation: Annotated[vq.Table[Mode, float], vq.Ref("$.power_generation")],
consumption: Annotated[vq.Table[Mode, float], vq.Ref("$.power_consumption")],
) -> vq.Table[Mode, bool]:
"""Power generation must exceed consumption in all modes."""
return vq.Table({mode: generation[mode] > consumption[mode] for mode in Mode})
# --- Thermal Scope ---
@thermal.root_model()
class ThermalModel(BaseModel):
max_temperature: float
calculated_temperature: float
@thermal.verification()
def verify_temperature(
temp: Annotated[float, vq.Ref("$.calculated_temperature")],
max_temp: Annotated[float, vq.Ref("$.max_temperature")],
) -> bool:
"""Temperature must be within limits."""
return temp <= max_temp
# --- Requirements Definition ---
# System-level requirement with children
with system.requirement("REQ-SYS-001", "System shall meet all subsystem requirements."):
# Power subsystem requirements
with system.requirement("REQ-SYS-002", "Power subsystem requirements."):
power.requirement(
"REQ-PWR-001",
"Battery capacity must be sufficient.",
verified_by=[vq.Ref("?verify_battery")],
)
power.requirement(
"REQ-PWR-002",
"Power margin must be positive in all modes.",
verified_by=[vq.Ref("?verify_power_margin")],
)
# Thermal subsystem requirements
with system.requirement("REQ-SYS-003", "Thermal subsystem requirements."):
thermal.requirement(
"REQ-TH-001",
"Temperature must be within limits.",
verified_by=[vq.Ref("?verify_temperature")],
)
# Future requirement (not yet verified)
system.requirement("REQ-SYS-004", "Future requirement (placeholder).")
Input File¶
[System.model]
[Power.model]
battery_capacity = 150.0
[Power.model.power_generation]
nominal = 100.0
safe = 50.0
[Power.model.power_consumption]
nominal = 80.0
safe = 30.0
[Thermal.model]
max_temperature = 85.0
calculated_temperature = 75.0
Running Traceability Analysis¶
Use veriq trace to see the requirement tree with verification status:
Example output:
Requirement Description Status Verifications
REQ-SYS-001 System shall meet all subsystem ? NOT_VERIFIED -
requirements.
├── REQ-SYS-002 Power subsystem requirements. ○ SATISFIED -
│ ├── REQ-PWR-001 Battery capacity must be sufficient. ✓ VERIFIED ✓ Power::?verify_battery
│ └── REQ-PWR-002 Power margin must be positive in all ✓ VERIFIED ✓ Power::?verify_power_margin[nominal]
│ modes. ✓ Power::?verify_power_margin[safe]
├── REQ-SYS-003 Thermal subsystem requirements. ○ SATISFIED -
│ └── REQ-TH-001 Temperature must be within limits. ✓ VERIFIED ✓ Thermal::?verify_temperature
└── REQ-SYS-004 Future requirement (placeholder). ? NOT_VERIFIED -
╭───────────────────────────────────────────────────── Summary ──────────────────────────────────────────────────────╮
│ Total requirements: 7 │
│ ✓ Verified: 3 │
│ ○ Satisfied: 2 │
│ ✗ Failed: 0 │
│ ? Not verified: 2 │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
✓ All requirements satisfied
Requirement Statuses¶
| Status | Symbol | Meaning |
|---|---|---|
| VERIFIED | ✓ | Has direct verifications, all passed |
| SATISFIED | ○ | No direct verifications, but all children pass |
| FAILED | ✗ | Some verification or child failed |
| NOT_VERIFIED | ? | Leaf requirement with no verifications (coverage gap) |
Expected Failures (xfail)¶
Sometimes you need to track requirements that are known to fail temporarily. Use xfail=True:
power.requirement(
"REQ-PWR-003",
"Advanced power management (not yet implemented).",
verified_by=[vq.Ref("?verify_advanced_power")],
xfail=True, # Expected to fail
)
Expected failures are marked differently in the trace output and don't cause veriq trace to exit with an error code.
Requirement Dependencies¶
Use vq.depends() to declare that one requirement depends on another:
req_power = power.requirement("REQ-PWR-001", "Power requirement.")
with power.requirement("REQ-PWR-002", "Dependent requirement."):
vq.depends(req_power) # REQ-PWR-002 depends on REQ-PWR-001
If REQ-PWR-001 fails, REQ-PWR-002 will also be marked as failed.
What You Learned¶
- Requirements - Define engineering requirements with IDs and descriptions
verified_by- Link verifications to requirements usingvq.Ref("?verification_name")- Cross-scope references - Use
vq.Ref("?name", scope="ScopeName")to reference verifications in other scopes - Hierarchical requirements - Use context managers for parent-child relationships
fetch_requirement()- Add children to requirements across scopesveriq trace- View requirement tree with verification status- Requirement statuses - VERIFIED, SATISFIED, FAILED, NOT_VERIFIED
xfail- Mark requirements as expected failuresvq.depends()- Declare dependencies between requirements
Summary¶
The requirement system in veriq provides:
- Documentation - Requirements are defined in code alongside verifications
- Traceability - Clear links from requirements to verifications
- Coverage analysis - Identify requirements without verifications (NOT_VERIFIED)
- Status propagation - Parent requirements inherit status from children
- Reporting -
veriq traceshows the complete picture
This completes the tutorial! You now know how to use veriq to:
- Define projects with scopes and models
- Create calculations with automatic dependency resolution
- Add verifications to check requirements
- Use tables for multi-dimensional data
- Reference external files with checksum tracking
- Define requirements with full traceability