This is a ready-to-use verification test protocol for one analytics tool. Approve it before execution, record actual results and evidence at the time of execution, and handle any failure as a documented deviation rather than a quiet correction. Replace every <<FILL: ...>> placeholder with your own specifics. A worked filled specimen with real arithmetic follows. This content is educational reference, not legal or regulatory advice; verify each cited regulation against the current source before you rely on it.
Approval page
Testing does not begin until this page is signed.
| Field | Entry |
|---|---|
| Protocol title | Verification Testing, <<FILL: tool name>> |
| Protocol number | <<FILL: VP-ID>> |
| Version | <<FILL>> |
| Tool ID | <<FILL>> |
| Risk class | <<FILL: A / B / C>> |
| Specification reference and version | <<FILL>> |
| Version under test | Tag <<FILL>> at commit <<FILL>> |
| Governing procedure | <<FILL: SOP-ID>> |
Pre-execution approvals
| Role | Name | Signature | Date |
|---|---|---|---|
| Author of protocol | <<FILL>> | ||
| Tool owner | <<FILL>> | ||
| Independent reviewer or SME | <<FILL>> | ||
| Validation or CSV lead | <<FILL>> | ||
| Quality Assurance | <<FILL>> |
Post-execution approvals
| Role | Name | Signature | Date |
|---|---|---|---|
| Executed by | <<FILL>> | ||
| Reviewed by (independent of the executor) | <<FILL>> | ||
| Validation or CSV lead | <<FILL>> | ||
| Quality Assurance | <<FILL>> |
1. Objective
To verify that <<FILL: tool name>>, at the version identified on the approval page, performs as stated in <<FILL: specification reference>> when executed in the controlled environment, and to record documented evidence sufficient to support release for its intended GxP use.
Specifically, this protocol demonstrates that:
- The calculation agrees with an independently derived reference on representative inputs.
- The tool behaves correctly at boundaries, including the rounding boundary.
- The tool fails visibly and produces no result on corrupt, missing, or invalid input.
- The tool is reproducible: the same input produces the same output, and the tagged version reproduces an archived result.
- Where the tool is a dashboard, each measure agrees with an independent calculation on the same source data, and filters, joins, and row-level security neither drop nor duplicate rows.
2. Scope
In scope: <<FILL: the specific tool, version, and environment>>
Out of scope, with the control that covers each:
| Out of scope | Covered by |
|---|---|
| Qualification of the compute platform, server, or analytics service | <<FILL: infrastructure qualification reference>> |
| Validation of the source system supplying the input data | <<FILL: source system validation reference>> |
| Correctness of the underlying scientific or statistical method as a method | <<FILL: method validation or SOP reference>>. This protocol verifies the implementation of that method, not the choice of it. |
| Code correctness by inspection | <<FILL: code review record reference>>. Code review and testing are complementary; neither replaces the other. |
3. Description of the tool and its environment
| Field | Entry |
|---|---|
| What the tool does | <<FILL: two sentences>> |
| GxP decision supported | <<FILL>> |
| Tool type | Script / Notebook / Dashboard / Query / Macro workbook / Pipeline job |
| Language, platform, or application and version | <<FILL>> |
| Repository and tag under test | <<FILL>> |
| Environment identifier | <<FILL: manifest file, package snapshot, or image digest>> |
| Execution host or workspace | <<FILL>> |
| Input source and extraction method | <<FILL>> |
| Output destination | <<FILL>> |
| Test environment relationship to production | <<FILL: identical image and pinned versions / stated differences and their justification>> |
Any difference between the test and production environments is stated here and assessed. Testing in an environment that differs from production in a way that could affect results invalidates the reproducibility conclusion.
4. Prerequisites
All of the following are confirmed before the first test case is executed. Record the evidence reference for each.
| # | Prerequisite | Confirmed | Evidence reference |
|---|---|---|---|
| P1 | The specification is approved and current, and this protocol traces to its requirement IDs | <<FILL>> | <<FILL>> |
| P2 | The version under test is tagged in version control and the tag points to the commit named on the approval page | <<FILL>> | <<FILL>> |
| P3 | The independent code review for this exact version is complete with no open Critical or Major findings | <<FILL>> | <<FILL>> |
| P4 | The environment is pinned, and the running environment matches the manifest, verified by listing installed versions | <<FILL>> | <<FILL>> |
| P5 | Test data is controlled, documented, and its origin is recorded: production extract, anonymised extract, or constructed dataset | <<FILL>> | <<FILL>> |
| P6 | Expected results were derived independently of the tool and were recorded before execution | <<FILL>> | <<FILL>> |
| P7 | Testers are trained on this protocol and on the governing procedure | <<FILL>> | <<FILL>> |
| P8 | The independent reference method or second tool used for known-answer verification is identified and is itself fit for that purpose | <<FILL>> | <<FILL>> |
| P9 | Access to the source system for test execution is read-only | <<FILL>> | <<FILL>> |
| P10 | The evidence capture method is defined: screenshots, output files, log extracts, or automated test run records | <<FILL>> | <<FILL>> |
Expected results must be written down before execution. An expected result derived from the tool’s own output is not a test; it is a description.
5. Roles
| Role | Responsibility during execution |
|---|---|
| Tester | Executes each step exactly as written, records the actual result verbatim, attaches evidence, signs and dates each case. Does not modify the protocol or the tool during execution. |
| Independent verifier of expected results | Derived and recorded the expected results before execution, by hand calculation, a validated system, or a second tool. |
| Reviewer | Reviews executed cases for completeness, evidence, and correct pass/fail determination. Independent of the tester. |
| Tool owner | Available during execution to answer questions about intended behaviour; does not determine pass or fail. |
| Validation or CSV lead | Adjudicates deviations, approves the summary and conclusion. |
| Quality Assurance | Approves the protocol before execution and the summary after, and approves the disposition of every deviation. |
6. Acceptance criteria
Testing is acceptable, and the tool may proceed to release, when all of the following are true.
| # | Criterion |
|---|---|
| AC1 | Every test case in section 8 has been executed against the version named on the approval page, with actual results and evidence recorded. |
| AC2 | Every requirement in the specification traces to at least one executed test case, and the traceability table in section 10 shows no untested requirement. |
| AC3 | Every known-answer test agrees with the independently derived expected value to the precision stated in the specification. |
| AC4 | Every boundary case, including the rounding boundary, returns the specified value. |
| AC5 | Every error case produces no usable output, a visible failure message, and the specified failure signal. No error case produces a plausible wrong result. |
| AC6 | Reproducibility is demonstrated: identical input produces identical output on repeat execution, and the tagged version reproduces the archived reference result. |
| AC7 | For dashboards, every specified measure agrees with an independent recomputation on the same frozen dataset, and the row-count reconciliation shows no dropped or duplicated rows. |
| AC8 | Every deviation is documented, assessed, and closed, and no open deviation affects a GxP-relevant result. |
| AC9 | Any test case that failed and was re-executed after a code change was re-executed against a new commit that has itself been code-reviewed. |
| AC10 | The executed protocol is reviewed and approved by a person independent of the tester. |
A test case marked pass with no recorded actual result and no evidence does not satisfy AC1.
7. Test data
| Dataset | Origin | Contents | Controlled location | Why it is fit for this test |
|---|---|---|---|---|
<<FILL: reference dataset>> | <<FILL>> | <<FILL>> | <<FILL>> | <<FILL>> |
<<FILL: boundary dataset>> | <<FILL>> | <<FILL>> | <<FILL>> | <<FILL>> |
<<FILL: corrupt dataset>> | Constructed | <<FILL: the specific corruption introduced>> | <<FILL>> | <<FILL>> |
<<FILL: archived dataset for reproducibility>> | <<FILL: prior production run>> | <<FILL>> | <<FILL>> | <<FILL>> |
Test data containing production GxP data is handled under <<FILL: SOP-ID for data handling>>. Constructed data is documented so that a reviewer can see what was constructed and why.
8. Test cases
Scale the case count to the risk class. Class A executes all applicable groups with pre-approved expected results. Class B executes groups 8.1, 8.2, 8.3, and 8.4 in reduced form. Class C may execute group 8.1 only, recorded as a verification note.
Record actual results verbatim. “As expected” is not an actual result.
8.1 Known-answer verification
| Test ID | Requirement traced | Step | Input | Expected result | Actual | Pass/Fail | Tester | Date |
|---|---|---|---|---|---|---|---|---|
<<FILL>> | <<FILL>> | Execute the tool against the reference dataset and record the primary result | <<FILL>> | <<FILL: value derived independently, stated to the specified precision>> | ||||
<<FILL>> | <<FILL>> | Compare the primary result to an independent recomputation in a second tool or by hand | <<FILL>> | <<FILL>> | ||||
<<FILL>> | <<FILL>> | Execute against a second, structurally different representative dataset | <<FILL>> | <<FILL>> | ||||
<<FILL>> | <<FILL>> | Confirm a controlled parameter is honoured rather than a constant, by executing with a different parameter value | <<FILL>> | <<FILL>> |
8.2 Boundary and edge cases
| Test ID | Requirement traced | Step | Input | Expected result | Actual | Pass/Fail | Tester | Date |
|---|---|---|---|---|---|---|---|---|
<<FILL>> | <<FILL>> | Execute at the rounding boundary, just above | <<FILL: value at .xx5>> | <<FILL>> | ||||
<<FILL>> | <<FILL>> | Execute at the rounding boundary, just below | <<FILL>> | <<FILL>> | ||||
<<FILL>> | <<FILL>> | Execute at the minimum permitted record count | <<FILL>> | <<FILL>> | ||||
<<FILL>> | <<FILL>> | Execute one record below the minimum permitted count | <<FILL>> | <<FILL: rejection>> | ||||
<<FILL>> | <<FILL>> | Execute with a value exactly at a specification limit | <<FILL>> | <<FILL>> | ||||
<<FILL>> | <<FILL>> | Execute with a value at each end of the allowed input range | <<FILL>> | <<FILL>> | ||||
<<FILL>> | <<FILL>> | Execute with empty input | Empty file with headers | <<FILL>> |
8.3 Error handling and negative tests
| Test ID | Requirement traced | Step | Input | Expected result | Actual | Pass/Fail | Tester | Date |
|---|---|---|---|---|---|---|---|---|
<<FILL>> | <<FILL>> | Execute with a required column absent | <<FILL>> | No output written, visible message naming the missing column, failure signal raised | ||||
<<FILL>> | <<FILL>> | Execute with non-numeric text in a numeric column | <<FILL>> | No output written, message naming the offending row, failure signal raised | ||||
<<FILL>> | <<FILL>> | Execute with a value outside the allowed range | <<FILL>> | <<FILL>> | ||||
<<FILL>> | <<FILL>> | Execute with a truncated or corrupt file | <<FILL>> | <<FILL>> | ||||
<<FILL>> | <<FILL>> | Execute with a mathematically undefined condition, for example zero variance or division by zero | <<FILL>> | <<FILL>> | ||||
<<FILL>> | <<FILL>> | Execute with the output destination not writable | <<FILL>> | <<FILL>> | ||||
<<FILL>> | <<FILL>> | Attempt a write to the source system using the tool’s credentials | <<FILL>> | Write refused; source unchanged |
8.4 Reproducibility
| Test ID | Requirement traced | Step | Input | Expected result | Actual | Pass/Fail | Tester | Date |
|---|---|---|---|---|---|---|---|---|
<<FILL>> | <<FILL>> | Execute twice on the same input in the same environment and compare outputs field by field | <<FILL>> | Identical in every field except the run timestamp | ||||
<<FILL>> | <<FILL>> | Check out the tagged version into a clean environment built from the pinned manifest and execute against the archived input | <<FILL>> | Result identical to the archived output to the stated precision | ||||
<<FILL>> | <<FILL>> | Confirm the provenance block on the output names the tag, commit, environment, timestamp, operator, and input identifier | <<FILL>> | All elements present and correct | ||||
<<FILL>> | <<FILL>> | List the installed runtime and library versions in the execution environment and compare to the pinned manifest | <<FILL>> | Exact match on every pinned entry |
8.5 Notebook-specific tests (execute where the tool is a notebook)
| Test ID | Requirement traced | Step | Input | Expected result | Actual | Pass/Fail | Tester | Date |
|---|---|---|---|---|---|---|---|---|
<<FILL>> | <<FILL>> | Restart the kernel and run all cells top to bottom; compare every displayed output to the saved output | <<FILL>> | Identical outputs; execution counts sequential from 1 | ||||
<<FILL>> | <<FILL>> | Execute the notebook non-interactively through the controlled runner and compare to the interactive run | <<FILL>> | Identical results | ||||
<<FILL>> | <<FILL>> | Execute cells deliberately out of order and confirm the tool detects or prevents reliance on stale state | <<FILL>> | <<FILL: expected safeguard behaviour>> |
8.6 Dashboard-specific tests (execute where the tool is a dashboard or visual data-preparation flow)
Every specified measure gets its own row. Do not sample measures.
| Test ID | Requirement traced | Step | Input | Expected result | Actual | Pass/Fail | Tester | Date |
|---|---|---|---|---|---|---|---|---|
<<FILL>> | MEA-<<FILL>> | Compare the measure value on the frozen dataset to an independent recomputation from source | <<FILL: frozen dataset and filter state>> | <<FILL: independently computed value>> | ||||
<<FILL>> | <<FILL>> | Reconcile the row count after every transformation step against the specified rows-in and rows-out | <<FILL>> | <<FILL>> | ||||
<<FILL>> | <<FILL>> | Reconcile the total record count in the model against the source system count for the same period | <<FILL>> | Counts equal; no dropped rows | ||||
<<FILL>> | <<FILL>> | Confirm a one-to-many relationship does not duplicate the base measure, using a record deliberately having multiple child rows | <<FILL>> | Base measure unchanged; no double counting | ||||
<<FILL>> | <<FILL>> | Apply each slicer and filter in turn and reconcile the filtered totals back to the unfiltered total | <<FILL>> | Filtered subsets sum to the unfiltered total | ||||
<<FILL>> | <<FILL>> | Sign in as each row-level-security role and record the visible record count | <<FILL>> | Per-role counts sum to the full count, with no record visible to no role and none counted twice in a total | ||||
<<FILL>> | <<FILL>> | Confirm the default filter state on page load matches the specification and is visible to the user | <<FILL>> | <<FILL>> | ||||
<<FILL>> | <<FILL>> | Trigger a refresh failure and observe what the user sees | <<FILL>> | Explicit stale-data indication; data not presented as current; notification raised | ||||
<<FILL>> | <<FILL>> | Confirm the last successful refresh timestamp is displayed and correct | <<FILL>> | <<FILL>> | ||||
<<FILL>> | <<FILL>> | Export or print a page and compare every displayed value to the on-screen view | <<FILL>> | Identical values; filter state stated on the export |
9. Deviation handling
- A test case whose actual result does not match the expected result is recorded as Fail. The tester does not adjust the input, the expected result, or the tool to obtain a pass.
- Every failure raises a test deviation under
<<FILL: SOP-ID for validation deviations>>, recording: test ID, expected, actual, evidence, and the immediate assessment of whether other executed cases are affected. - The validation lead classifies the deviation:
| Class | Definition | Disposition |
|---|---|---|
| Tool defect | The tool does not do what the specification requires | Correct by a new commit, re-review the change, re-execute the affected cases and any case the change could have affected |
| Specification defect | The tool is correct and the specification is wrong, ambiguous, or incomplete | Revise the specification through change control, re-approve, re-execute the affected cases |
| Protocol or expected-result defect | The test step or expected value was wrong | Correct the protocol through a documented amendment approved before re-execution, and record why the expected value was wrong |
| Environment or data defect | The failure came from the test environment or the test data, not the tool | Correct and re-execute; assess whether the environment difference affects the reproducibility conclusion |
- A deviation is closed only when the corrective action is complete, the affected cases have been re-executed and passed, and the closure is approved by the validation lead and Quality Assurance.
- Where a code change is made, note that AC9 requires the new commit to have been code-reviewed before re-execution.
| Deviation ID | Test ID | Description | Classification | Corrective action | Cases re-executed | Closed by | Date |
|---|---|---|---|---|---|---|---|
<<FILL>> | <<FILL>> | <<FILL>> | <<FILL>> | <<FILL>> | <<FILL>> | <<FILL>> | <<FILL>> |
10. Traceability
| Requirement ID | Test case ID(s) | Executed | Result |
|---|---|---|---|
<<FILL>> | <<FILL>> | <<FILL>> | <<FILL>> |
Untested requirements: <<FILL: none, or ID and justification>>
11. Summary and conclusion
| Field | Entry |
|---|---|
| Test cases planned | <<FILL>> |
| Test cases executed | <<FILL>> |
| Passed on first execution | <<FILL>> |
| Failed and re-executed | <<FILL>> |
| Deviations raised | <<FILL>> |
| Deviations open at conclusion | <<FILL: should be zero>> |
| Version at conclusion of testing | Tag <<FILL>> at commit <<FILL>> |
| Acceptance criteria met | AC1 to AC10: <<FILL: met / not met, with detail on any not met>> |
| Limitations observed during testing | <<FILL>> |
Conclusion statement:
<<FILL: state whether the tool, at the identified version and in the identified environment, is verified against its specification and suitable for release for its intended use, and name any limitation that must appear on the release statement>>
12. Attachments
| # | Attachment | Reference |
|---|---|---|
| A | Approved specification | <<FILL>> |
| B | Independent code review record | <<FILL>> |
| C | Independently derived expected results, dated before execution | <<FILL>> |
| D | Test datasets and their control records | <<FILL>> |
| E | Output files produced during execution | <<FILL>> |
| F | Screenshots and log extracts | <<FILL>> |
| G | Environment listing from the execution host | <<FILL>> |
| H | Automated test run records, where used as evidence | <<FILL>> |
| I | Test deviation records | <<FILL>> |
| J | Training records for testers | <<FILL>> |
Filled specimen
Executed test-case block for tool ANL-0007, the class A stability regression script, at tag v1.0-validated, commit a3f19c4, executed 05 May 2026 on analytics server ANSRV-01 in container image sha256:9c1d4b....
Reference dataset, lot <<FILL: lot ID>>, long-term condition, seven timepoints:
| timepoint_months | potency_pct |
|---|---|
| 0 | 100.2 |
| 3 | 99.4 |
| 6 | 98.7 |
| 9 | 97.9 |
| 12 | 97.2 |
| 18 | 95.8 |
| 24 | 94.3 |
Expected values were derived on 30 April 2026 by hand calculation by the process statistician and confirmed in a second statistical package, and were signed and dated before the tool was run. The hand calculation gave: sum of t = 72, t_bar = 10.285714 months, sum of y = 683.5, y_bar = 97.642857%, Sxx = 429.428571, Sxy = -104.785714, slope = -0.2440% per month, intercept = 100.1527%, residual standard deviation = 0.040432%, one-sided t at 95% with 5 degrees of freedom = 2.0150.
Known-answer verification
| Test ID | Req | Step | Input | Expected | Actual | Pass/Fail | Tester | Date |
|---|---|---|---|---|---|---|---|---|
| TC-05 | CALC-01 | Execute and record the fitted slope | Reference dataset, T = 24 | Slope -0.2440 % per month | -0.2440 | Pass | R. Duarte | 05 May 2026 |
| TC-05b | CALC-01 | Compare to independent recomputation in a second statistical package | Same | Agreement to 4 decimal places | Second package returned -0.2440120; script full-precision value -0.2440120 | Pass | R. Duarte | 05 May 2026 |
| TC-06 | CALC-02 | Record predicted potency at the proposed shelf life | Reference dataset, T = 24 | 94.3 % (full precision 94.2964) | 94.3 (94.29640719 in the full-precision log) | Pass | R. Duarte | 05 May 2026 |
| TC-06b | CALC-02 | Re-execute with the shelf-life parameter changed, to prove the parameter is honoured rather than a constant | Reference dataset, T = 18 | 95.8 % (full precision 95.7605) | 95.8 (95.76047904) | Pass | R. Duarte | 05 May 2026 |
| TC-07 | CALC-03 | Record the one-sided lower 95% confidence bound at the shelf life | Reference dataset, T = 24 | 94.2 % (full precision 94.2343) | 94.2 (94.23431512) | Pass | R. Duarte | 05 May 2026 |
| TC-07b | CALC-03 | Confirm the bound against independent recomputation | Same | Margin = 2.0150 x 0.040432 x sqrt(1/7 + 13.714286^2 / 429.428571) = 0.0621; 94.2964 minus 0.0621 = 94.2343 | 94.23431512 | Pass | M. Larsen | 05 May 2026 |
| TC-10 | OUT-02 | Confirm the controlled result field is present and named as specified | Reference dataset | Field lcb_shelf_life_pct present and identified as the controlled result | Present, correctly named | Pass | R. Duarte | 05 May 2026 |
Boundary and rounding-boundary cases
| Test ID | Req | Step | Input | Expected | Actual | Pass/Fail | Tester | Date |
|---|---|---|---|---|---|---|---|---|
| TC-09a | RND-02 | Rounding boundary, just at the half | Reported potency full-precision value 99.95 | 100.0 (half-up to one decimal place) | 100.0 | Pass | R. Duarte | 05 May 2026 |
| TC-09b | RND-02 | Rounding boundary, just below the half | Reported potency full-precision value 99.94 | 99.9 | 99.9 | Pass | R. Duarte | 05 May 2026 |
| TC-09c | RND-01 | Confirm rounding is applied only at reporting | Reference dataset | Full-precision slope in the log carries at least 7 decimal places; reported slope carries 4 | Log -0.2440120, report -0.2440 | Pass | R. Duarte | 05 May 2026 |
| TC-04a | IN-04 | Minimum permitted timepoint count | Reference dataset reduced to 6 timepoints (0, 3, 6, 9, 12, 18) | Result produced, no rejection | Result produced | Pass | R. Duarte | 05 May 2026 |
| TC-04b | IN-04 | One below the minimum | Same reduced to 5 timepoints | No output; message stating 5 timepoints found, 6 required; non-zero exit | Message “lot <<FILL>>: 5 timepoints found, minimum 6 required”; exit status 2 | Pass | R. Duarte | 05 May 2026 |
| TC-08 | CALC-05 | Extrapolation guard against the supplied parameter | Reference dataset, T = 36, last observed timepoint 24, permitted extrapolation window 6 months | No output; message stating the requested shelf life exceeds the permitted extrapolation window; non-zero exit | Message raised, exit status 2 | Pass | R. Duarte | 05 May 2026 |
Error handling
| Test ID | Req | Step | Input | Expected | Actual | Pass/Fail | Tester | Date |
|---|---|---|---|---|---|---|---|---|
| TC-02 | IN-02 | Required column absent | Reference dataset with potency_pct removed | No output; message naming the missing column; non-zero exit | Message “required column potency_pct not found”; exit status 2 | Pass | R. Duarte | 05 May 2026 |
| TC-03 | IN-03 | Non-numeric value in a numeric column | Reference dataset with “not tested” in the 12-month potency cell | No output; message naming the offending row; non-zero exit | Message named row 5; exit status 2 | Pass | R. Duarte | 05 May 2026 |
| TC-11 | ERR-06 | Mathematically undefined condition | Dataset with all seven timepoints set to 0 (zero variance in t) | No output; message stating the regression is undefined; non-zero exit | Message raised; exit status 2 | Pass | R. Duarte | 05 May 2026 |
| TC-11b | ERR-01, ERR-12 | Unreadable input file | Input file permissions removed | No output; message naming the file; non-zero exit | Message raised; exit status 2 | Pass | R. Duarte | 05 May 2026 |
| TC-14 | SEC-01 | Attempt a write to the source using the tool’s account | Insert statement issued against the stability table on the replica with the tool’s credentials | Refused; source unchanged | Permission denied; row count on the source unchanged before and after | Pass | R. Duarte | 05 May 2026 |
Reproducibility
| Test ID | Req | Step | Input | Expected | Actual | Pass/Fail | Tester | Date |
|---|---|---|---|---|---|---|---|---|
| TC-15a | Reproducibility | Execute twice on the same input in the same environment | Reference dataset | Outputs identical in every field except the run timestamp | Identical; only run_timestamp differed | Pass | R. Duarte | 05 May 2026 |
| TC-15b | Reproducibility | Build a clean environment from the pinned manifest, check out tag v1.0-validated, execute against the archived input | Archived dataset from the March 2026 dry run, input hash on file | Slope, prediction and bound identical to the archived output | Slope -0.2440, prediction 94.3, bound 94.2, all identical | Pass | M. Larsen | 06 May 2026 |
| TC-12 | IN-05, OUT-03 | Inspect the provenance block | Reference dataset | Tag, commit, image digest, timestamp, operator, input SHA-256 all present and correct | All present; input hash matched the value recorded in the test data control record | Pass | R. Duarte | 05 May 2026 |
| TC-13 | ENV-02 | Compare the running environment against the pinned manifest | Environment listing | Exact match on python 3.11.8, numpy 1.26.4, pandas 2.2.1, scipy 1.12.0, statsmodels 0.14.1 | Exact match on all five | Pass | R. Duarte | 05 May 2026 |
One deviation raised during this execution
| Deviation ID | Test ID | Description | Classification | Corrective action | Re-executed | Closed |
|---|---|---|---|---|---|---|
| TD-2026-0033 | TC-09a | First execution reported 99.9 for a full-precision value of 99.95, because the formatting call used the language’s default banker’s rounding rather than half-up as required by RND-02 | Tool defect | Explicit half-up rounding applied at the formatter, committed at a3f19c4 and code-reviewed as finding F-05 before re-execution | TC-09a, TC-09b, TC-09c, TC-06, TC-07 | Closed 06 May 2026, approved by QA |
That deviation is the reason the rounding boundary is tested at all. The default rounding behaviour of several common numerical libraries is round-half-to-even, which disagrees with the half-up convention many validated laboratory systems use, and the disagreement only ever appears at the boundary. Without TC-09a the script would have gone into production quietly reporting a different value from the LIMS on one input in twenty.
Dashboard specimen extract, for contrast
Executed for tool ANL-0021, the class B release results dashboard, on a frozen dataset covering <<FILL: period>>.
| Test ID | Req | Step | Input | Expected | Actual | Pass/Fail |
|---|---|---|---|---|---|---|
| TC-D03 | MEA-03 | Compare “lots released within target” to independent recomputation from the source extract | Frozen dataset: 52 lots disposed, of which 47 within 30 days | 47 / 52 = 90.4% | 90.4% | Pass |
| TC-D07 | MEA-07 | Compare “out-of-specification result rate” to an independent query against the source replica | Frozen dataset: 1,240 reportable results, 18 flagged out of specification | 18 / 1,240 = 1.45% | 1.45% | Pass |
| TC-D15 | MEA-<<FILL>> | Reconcile total lot count in the model to the source count for the period | Same | 52 lots | 52 | Pass |
| TC-D16 | MEA-<<FILL>> | Confirm the lot-to-result relationship does not duplicate the lot count. Three lots in the frozen set carry a repeat test record | Same | Lot count remains 52, not 55 | 52 | Pass |
| TC-D17 | MEA-<<FILL>> | Sum the site slicer subsets back to the unfiltered total | Same | Site A 31 plus site B 21 equals 52 | 31, 21, 52 | Pass |
| TC-D18 | MEA-<<FILL>> | Sign in as each row-level-security role and record visible lot counts | Same | Site A role sees 31, site B role sees 21, no lot visible to both, sum equals 52 | 31 and 21, no overlap, sum 52 | Pass |
| TC-D19 | MEA-<<FILL>> | Suspend the gateway and open the report | Refresh deliberately failed | Stale-data banner shown with the last successful refresh timestamp; values not presented as current; notification sent to the data owner | Banner shown; notification received at 09:14 | Pass |
| TC-D20 | MEA-<<FILL>> | Export the summary page and compare every value to the screen | Same | Identical values; applied filter state printed on the export | Identical; filter state printed | Pass |
TC-D16 is the case worth keeping in every dashboard protocol. Three repeat test records in a set of 52 lots is exactly the sort of small, ordinary data condition that turns a correct measure into an inflated one, and it is invisible unless somebody counts.
Common inspection findings this protocol prevents
- Testing was performed but the expected results were taken from the tool’s own output, so the test could not have failed.
- The protocol was executed against a version that cannot be identified, so the evidence does not apply to the version in production.
- Results were recorded as “OK” or “as expected” with no value and no evidence.
- A test failed, the code was quietly changed, and the case was re-executed with no deviation record and no re-review of the change.
- The rounding boundary was never tested, and the tool disagrees with the validated laboratory system on one value in twenty.
- The tool returns a plausible number on corrupt input because no negative testing was performed.
- Reproducibility was assumed rather than demonstrated, and the archived result cannot be regenerated.
- Only some of a dashboard’s measures were tested, and the untested ones are the ones that are wrong.
- A dashboard measure is correct on the full dataset but doubles when a filter is applied, because no row-count reconciliation was performed.
- Row-level security was configured but never tested, so one site sees another site’s data, or a set of records is visible to nobody and silently absent from every total.
- Testing was performed in an environment that differs from production, and the difference was never assessed.
How to adapt this protocol
- Scale by risk class before you scale by effort. Removing the negative tests to save time removes the tests most likely to find something, because a defect that produces a plausible wrong number is exactly what normal use will not reveal.
- Derive and sign the expected results before execution, on a separate dated page, and attach it as attachment C. This one practice is the difference between verification and demonstration.
- Where automated unit tests already encode the known-answer cases, reference the test run record as evidence for those cases rather than repeating them manually, provided the test code is under version control and was itself reviewed.
- Give every dashboard measure its own test row. Sampling measures is the most common shortcut and the one that most reliably leaves a wrong number in production.
- Add the row-count reconciliation at every transformation step for any tool that joins tables. It costs one row per step in the protocol and it catches the failure mode that produces confidently wrong charts.
- Keep group 8.4 even for class C tools if the output has any retention obligation. Reproducibility is the requirement that becomes impossible to retrofit once the environment has moved on.
- Set the extrapolation, minimum-count, and range limits in the specification first, then write the boundary tests against them. Boundary tests written without a specified boundary test nothing.
- Confirm the current status of every cited regulation and guidance in the governing SOP before issue.