STD: Managing Quantities Rule (RQUANTASQUAL)
Version: v1.0.0 Status: Draft SRS Source:
docusaurus/docs/srs/rules/rule-managing-quantities.mdRule Name: RQUANTASQUAL Domain: RULES-QUANTITIES
Overview
This document specifies tests for the Managing Quantities rule using decision tables and test vectors. The rule substitutes configurable placeholders ({LOQ}, {QUANT}) in LIMS export message templates with actual quantity values and limit-of-quantification thresholds.
Rule Characteristics:
- Pure business logic (no UI)
- String substitution with placeholder parsing
- Lab-specific rounding rules applied before substitution
- Graceful degradation for missing/invalid data
Test Method: TM-API (per Test Plan Section 3.3 - Rules use automated API tests)
Verification Approach: Rule verification is performed using data-driven test vectors. Each row in a decision table represents a complete verification scenario with defined inputs and expected outputs. This format enables exhaustive condition coverage while remaining concise and auditable.
Coverage Summary
| REQ ID | Title | Conditions | Test Vectors | Coverage | Gaps |
|---|---|---|---|---|---|
| REQ-RULES-QUANTITIES-001 | Quantity Placeholders in LIMS Export | 16 | 24 | 100% | None |
Totals: 1 REQ, 16 Conditions, 24 Test Vectors, 100% Coverage
REQ-RULES-QUANTITIES-001: Quantity Placeholders in LIMS Export
Input Variables
| Variable | Type | Valid Values | Description |
|---|---|---|---|
template | string | Any string | Message template (may contain placeholders) |
well.quantity | float/null | null, numeric >= 0 | Calculated quantity from standard curve |
group.loq | float/null | null, numeric > 0 | Limit of quantification for assay group |
lab.rounding_rules | config/null | null, configured | Lab-specific rounding configuration |
lab.rounding_mode | string | nearest_10, nearest_100, none | Rounding behavior when configured |
Output Variables
| Variable | Type | Description |
|---|---|---|
output | string | Processed message with placeholders resolved |
warning_logged | bool | Whether a warning was logged for unknown placeholder |
Decision Table: Placeholder Detection
| TV | template | has_placeholder | action | Covers |
|---|---|---|---|---|
| TV-MNGQTY-001-001 | "Detected" | false | pass_through | AC: No placeholders pass through unchanged |
| TV-MNGQTY-001-002 | "Detected: {QUANT}" | true | substitute | AC: QUANT placeholder detected |
| TV-MNGQTY-001-003 | "Detected:<{LOQ}" | true | substitute | AC: LOQ placeholder detected |
| TV-MNGQTY-001-004 | "Result: {QUANT} (LOQ: {LOQ})" | true | substitute_all | AC: Multiple placeholders detected |
Decision Table: LOQ Placeholder Substitution
| TV | template | group.loq | expected_output | Covers |
|---|---|---|---|---|
| TV-MNGQTY-001-005 | "Detected:<{LOQ}" | 100 | "Detected:<100" | AC: LOQ substituted with group value |
| TV-MNGQTY-001-006 | "LOQ is {LOQ} copies/mL" | 500 | "LOQ is 500 copies/mL" | AC: LOQ in context |
| TV-MNGQTY-001-007 | "Detected:<{LOQ}" | null | "Detected:<{LOQ}" | AC: LOQ not configured, skip substitution |
| TV-MNGQTY-VARIANT-LOQ | "Below LOQ ({LOQ})" | 211 | "Below LOQ (211)" | Variant: Alt LOQ template (BT-9582) |
Decision Table: QUANT Placeholder Substitution
| TV | template | well.quantity | lab.rounding_rules | expected_output | Covers |
|---|---|---|---|---|---|
| TV-MNGQTY-001-008 | "Detected: {QUANT}" | 5000 | null | "Detected: 5000" | AC: QUANT substituted, no rounding |
| TV-MNGQTY-001-009 | "Report {QUANT}" | 1040 | nearest_100 | "Report 1000" | AC: Rounding applied (1040 -> 1000) |
| TV-MNGQTY-001-010 | "Report {QUANT}" | 1050 | nearest_100 | "Report 1100" | AC: Rounding applied (1050 -> 1100) |
| TV-MNGQTY-001-011 | "Report {QUANT}" | 1049 | nearest_100 | "Report 1000" | AC: Rounding boundary (1049 -> 1000) |
| TV-MNGQTY-001-012 | "Detected: {QUANT}" | null | null | "Detected: N/A" | AC: Null quantity -> "N/A" |
| TV-MNGQTY-001-013 | "Detected: {QUANT}" | -100 | null | "Detected: N/A" | AC: Invalid (negative) -> "N/A" |
Decision Table: Multiple Placeholders
| TV | template | well.quantity | group.loq | expected_output | Covers |
|---|---|---|---|---|---|
| TV-MNGQTY-001-014 | "Result: {QUANT} (LOQ: {LOQ})" | 500 | 100 | "Result: 500 (LOQ: 100)" | AC: Both placeholders substituted |
| TV-MNGQTY-001-015 | "{QUANT} > {LOQ}" | 1000 | 500 | "1000 > 500" | AC: Multiple in expression |
| TV-MNGQTY-001-016 | "{QUANT} {QUANT}" | 200 | null | "200 200" | AC: Same placeholder repeated |
| TV-MNGQTY-VARIANT-015-G5 | "Result: {QUANT} (LOQ: {LOQ})" | 700 | 211 | "Result: 700 (LOQ: )" | Variant: Multi-placeholder with Group 5 rounding (BT-9582) |
The PHP implementation uses preg_replace_array(), which performs sequential (left-to-right) placeholder replacement rather than simultaneous substitution. Each {xxx} token is matched by a single regex, and replacements are consumed from an array one at a time in match order. This has two practical consequences:
- Array exhaustion: If a template contains more
{xxx}tokens than the replacement array has entries, excess tokens receive an empty string. For example,"{QUANT} {QUANT}"with a single-entry array replaces the first{QUANT}but empties the second. - Unknown tokens consumed: All
{xxx}patterns are matched by the regex regardless of whether they are recognized placeholder names ({QUANT},{LOQ}). Unknown tokens like{FOO}are consumed by the sequential replacement rather than being preserved.
In practice, the application constructs replacement arrays that match the expected placeholder count, so standard templates produce correct output. Edge cases (TV-MNGQTY-001-015, TV-MNGQTY-001-016, TV-MNGQTY-001-022) where the STD assumes simultaneous substitution may differ from actual behavior. Confirmed by BT-9582 passing tests.
Decision Table: Placeholder Syntax Validation
| TV | template | valid_syntax | action | Covers |
|---|---|---|---|---|
| TV-MNGQTY-001-017 | "{QUANT}" | true | substitute | AC: Valid uppercase token |
| TV-MNGQTY-001-018 | "{LOQ}" | true | substitute | AC: Valid uppercase token |
| TV-MNGQTY-001-019 | "{quant}" | false | pass_through | AC: Lowercase not matched |
| TV-MNGQTY-001-020 | "{UNKNOWN}" | true | pass_through_with_warning | AC: Valid syntax but unknown token |
Decision Table: Unknown Placeholder Handling
| TV | template | placeholder | expected_output | warning_logged | Covers |
|---|---|---|---|---|---|
| TV-MNGQTY-001-021 | "Value: {UNKNOWN}" | {UNKNOWN} | "Value: {UNKNOWN}" | true | AC: Unknown passed through, warning logged |
| TV-MNGQTY-001-022 | "{FOO} and {BAR}" | {FOO}, {BAR} | "{FOO} and {BAR}" | true | AC: Multiple unknowns passed through |
Boundary and Edge Cases
Decision Table: Quantity Boundaries
| TV | well.quantity | lab.rounding_rules | expected_output | Covers |
|---|---|---|---|---|
| TV-MNGQTY-001-023 | 0 | null | "0" | Boundary: Zero quantity valid |
| TV-MNGQTY-001-024 | 0.001 | null | "0.001" | Boundary: Very small positive |
| TV-MNGQTY-001-025 | 999999999 | null | "999999999" | Boundary: Large quantity |
| TV-MNGQTY-001-026 | 50 | nearest_100 | "100" | Boundary: Rounding up from below |
| TV-MNGQTY-001-027 | 49 | nearest_100 | "0" | Boundary: Rounding down to zero |
Test Implementation Structure
Parameterized Test Cases
Test vectors are implemented as parameterized data providers. Each decision table row maps to a single test case execution.
Test Data Provider Structure:
| Provider Name | Vectors | Focus |
|---|---|---|
loqSubstitutionProvider | TV-MNGQTY-001-005 to TV-MNGQTY-001-007, TV-MNGQTY-VARIANT-LOQ | LOQ placeholder handling |
quantSubstitutionProvider | TV-MNGQTY-001-008 to TV-MNGQTY-001-013 | QUANT placeholder with rounding |
multiplePlaceholderProvider | TV-MNGQTY-001-014 to TV-MNGQTY-001-016, TV-MNGQTY-VARIANT-015-G5 | Combined placeholder scenarios |
unknownPlaceholderProvider | TV-MNGQTY-001-021 to TV-MNGQTY-001-022 | Unknown token handling |
boundaryValueProvider | TV-MNGQTY-001-023 to TV-MNGQTY-001-027 | Edge cases and boundaries |
Test File Locations
| Requirement | Test File | Automation Status |
|---|---|---|
| REQ-RULES-QUANTITIES-001 | tests/Unit/Rules/QuantityWeightRuleTest.php | Automated |
Traceability to Existing Tests
| Requirement | SRS Test IDs | Jira References | Config Version | Status |
|---|---|---|---|---|
| REQ-RULES-QUANTITIES-001 | TC-RQUANTASQUAL-001 to 005 | BT-5204 | Viracor v31 pp based.xlsx | Existing |
| REQ-RULES-QUANTITIES-001 | TC-RQUANT-001 to 004 | BT-5203 | Viracor 2.25.0.xlsx (v2) | Existing |
| REQ-RULES-QUANTITIES-001 | TV-MNGQTY-001-001 to TV-MNGQTY-001-024 | BT-9522, BT-9531, BT-9581, BT-9582, BT-9609 | Viracor v31 pp based.xlsx + variants | Automated |
| REQ-RULES-QUANTITIES-001 | TV-MNGQTY-VARIANT-LOQ, TV-MNGQTY-VARIANT-015-G5 | BT-9582 | Viracor v31 MNGQTY variants | Automated |
| REQ-RULES-QUANTITIES-001 | TV-MNGQTY-001-012 | BT-5243 | Viracor 2.25.0.xlsx (v2) | Legacy |
SRS Test Mapping
| SRS Test ID | Maps to TV | Description |
|---|---|---|
| TC-RQUANTASQUAL-001 | TV-MNGQTY-001-005 | LOQ placeholder substitution |
| TC-RQUANTASQUAL-002 | TV-MNGQTY-001-008 | QUANT placeholder without rounding |
| TC-RQUANTASQUAL-003 | TV-MNGQTY-001-009 | QUANT with Viracor rounding |
| TC-RQUANTASQUAL-004 | TV-MNGQTY-001-014 | Multiple placeholders |
| TC-RQUANTASQUAL-005 | TV-MNGQTY-001-001 | No placeholders pass-through |
Gap Analysis
Identified Gaps
| Gap | Condition | Description | Priority | Remediation |
|---|---|---|---|---|
| None | - | All acceptance criteria covered by test vectors | - | - |
Coverage Verification
| AC Category | AC Count | TV Count | Status |
|---|---|---|---|
| Placeholder Substitution | 5 | 10 | Covered |
| Template Pass-through | 1 | 2 | Covered |
| Rounding | 2 | 5 | Covered |
| Data Validation | 2 | 3 | Covered |
| Error Handling | 4 | 4 | Covered |
| Variant Configs | - | 2 | Covered |
Total: 14 ACs, 24 TVs (22 core + 2 variant), 100% Coverage