Skill Tracer
Skills › Scientific Agent Skills › analytical-method-validation › _catalog.py

_catalog.py

499 lines · skills/analytical-method-validation/scripts/_catalog.py at 5f2bc03

1#!/usr/bin/env python3
2"""Framework catalogue for analytical method validation.
3
4Content sourced 2026-09-30 from the freely published ICH guidelines, which ICH
5licenses for reuse with acknowledgement. Compendial (USP) and CLSI documents are
6copyrighted and paywalled: they are referenced here by designation, title, and
7scope only. No proprietary text is reproduced.
8
9See ../references/source-ledger.md for the provenance of every entry.
10"""
12from __future__ import annotations
14from typing import Any
16RESEARCH_DATE = "2026-09-30"
18# --------------------------------------------------------------------------
19# Frameworks
20# --------------------------------------------------------------------------
22FRAMEWORKS: dict[str, dict[str, Any]] = {
23 "ich-q2r2": {
24 "title": "ICH Q2(R2) Validation of Analytical Procedures",
25 "adopted": "2023-11-01",
26 "effective_note": (
27 "Adopted by the ICH Assembly 1 Nov 2023; an error correction to Table 5 and "
28 "Tables 6-11 is dated 30 Nov 2023. Confirm the adoption/implementation date for "
29 "your region with the regional regulator."
30 ),
31 "supersedes": "ICH Q2(R1) (2005)",
32 "url": "https://database.ich.org/sites/default/files/ICH_Q2%28R2%29_Guideline_2023_1130.pdf",
33 "reproducible": True,
34 "scope": (
35 "Analytical procedures for release and stability testing of commercial drug "
36 "substances and products; applicable to other control-strategy procedures on a "
37 "risk basis, and phase-appropriately during clinical development."
38 ),
39 "governs": ["assay", "potency", "purity", "impurity-quantitative",
40 "impurity-limit", "identity", "dissolution", "content-uniformity"],
41 "companion": "ICH Q14 (analytical procedure development, robustness, lifecycle)",
42 },
43 "ich-m10": {
44 "title": "ICH M10 Bioanalytical Method Validation and Study Sample Analysis",
45 "adopted": "2022-05-24",
46 "effective_note": (
47 "Step 4 adopted 24 May 2022. Regional implementation dates differ; confirm with "
48 "the regional regulator."
49 ),
50 "supersedes": (
51 "Harmonises region-specific bioanalytical guidance (e.g., FDA 2018 BMV, "
52 "EMA 2011); what it replaces depends on the region's implementation"
53 ),
54 "url": "https://database.ich.org/sites/default/files/M10_Guideline_Step4_2022_0524.pdf",
55 "reproducible": True,
56 "scope": (
57 "Bioanalytical methods quantifying drug/metabolite concentrations in biological "
58 "matrices supporting nonclinical and clinical studies, plus study sample analysis."
59 ),
60 "governs": ["pk-concentration", "toxicokinetics", "bioequivalence"],
61 "companion": "Distinct criteria for chromatographic methods vs ligand binding assays",
62 },
63 "usp-1220": {
64 "title": "USP General Chapter <1220> Analytical Procedure Life Cycle",
65 "adopted": "official 2022-05-01",
66 "effective_note": (
67 "Official 1 May 2022 (USP Council of Experts report). "
68 "Confirm the current official text and any revision in the USP-NF."
69 ),
70 "supersedes": "integrates the concepts of <1224>, <1225>, and <1226> into a lifecycle",
71 "url": "https://doi.usp.org/USPNF/USPNF_M10975_02_01.html",
72 "reproducible": False,
73 "scope": (
74 "Three-stage lifecycle: procedure design (Stage 1), performance qualification "
75 "(Stage 2), ongoing performance verification (Stage 3), organised around an "
76 "analytical target profile."
77 ),
78 "governs": ["compendial-lifecycle"],
79 "companion": "<1225> validation, <1226> verification, <1224> transfer, <1010> data treatment",
80 },
81 "usp-1225": {
82 "title": "USP General Chapter <1225> Validation of Compendial Procedures",
83 "adopted": "see current USP-NF",
84 "effective_note": "The linked 2025 page is a revision proposal, not proof of official status; confirm the effective controlled text in USP-NF.",
85 "supersedes": "",
86 "url": "https://doi.usp.org/USPNF/USPNF_M99945_40101_01.html",
87 "reproducible": False,
88 "scope": (
89 "Validation of non-compendial procedures and of compendial procedures used "
90 "outside their stated scope; Stage 2 activities under <1220>."
91 ),
92 "governs": ["assay", "impurity-quantitative", "impurity-limit", "identity"],
93 "companion": "<1226> when verifying a compendial procedure as written",
94 },
95 "usp-1226": {
96 "title": "USP General Chapter <1226> Verification of Compendial Procedures",
97 "adopted": "see current USP-NF",
98 "effective_note": "Confirm the current official text and revision in the USP-NF.",
99 "supersedes": "",
100 "url": "https://doi.usp.org/USPNF/USPNF_M870_03_01.html",
101 "reproducible": False,
102 "scope": (
103 "Assessment of selected performance characteristics to show a compendial "
104 "procedure works under actual conditions of use. Verification is not "
105 "revalidation and does not repeat the full validation."
106 ),
107 "governs": ["compendial-verification"],
108 "companion": "<1225> when the procedure is used outside its compendial scope",
109 },
110 "clsi": {
111 "title": "CLSI EP series (clinical laboratory measurement procedures)",
112 "adopted": "per document",
113 "effective_note": (
114 "Publisher listings checked 30 Sep 2026; EP05 is fourth edition, other editions "
115 "are recorded in references/compendial-and-clsi.md. "
116 "Verify the current edition before designing a study."
117 ),
118 "supersedes": "",
119 "url": "https://clsi.org/shop/standards/",
120 "reproducible": False,
121 "scope": (
122 "Establishment and user verification of performance for clinical laboratory "
123 "measurement procedures, under CLIA/CAP and ISO 15189 quality systems."
124 ),
125 "governs": ["clinical-verification", "clinical-establishment"],
126 "companion": "ISO 15189 for the surrounding medical laboratory quality system",
127 },
128 "iso-17025": {
129 "title": "ISO/IEC 17025:2017 (testing and calibration laboratory competence)",
130 "adopted": "2017",
131 "effective_note": "Copyrighted. Obtain an authorised copy from ISO or a national member.",
132 "supersedes": "ISO/IEC 17025:2005",
133 "url": "https://www.iso.org/standard/66912.html",
134 "reproducible": False,
135 "scope": (
136 "Clause 7.2 covers selection, verification and validation of methods; clause 7.6 "
137 "covers measurement uncertainty. Method validation is required to the extent "
138 "necessary for the intended use."
139 ),
140 "governs": ["nonstandard-method", "lab-developed-method", "modified-standard-method"],
141 "companion": (
142 "The repo's iso-standards-readiness skill covers the surrounding quality system; "
143 "this skill covers the individual procedure."
144 ),
145 },
148# --------------------------------------------------------------------------
149# ICH Q2(R2) Table 1 -- which validation tests for which measured attribute
150# Source: ICH Q2(R2), Table 1. "+" normally conducted, "-" not normally conducted.
151# --------------------------------------------------------------------------
153Q2R2_TESTS_BY_ATTRIBUTE: dict[str, dict[str, str]] = {
154 "identity": {
155 "specificity": "required",
156 "response": "not-normally",
157 "lower-range-limit": "not-normally",
158 "accuracy": "not-normally",
159 "repeatability": "not-normally",
160 "intermediate-precision": "not-normally",
161 },
162 "impurity-quantitative": {
163 "specificity": "required",
164 "response": "required",
165 "lower-range-limit": "required-QL",
166 "accuracy": "required",
167 "repeatability": "required",
168 "intermediate-precision": "required-unless-reproducibility",
169 },
170 "impurity-limit": {
171 "specificity": "required",
172 "response": "not-normally",
173 "lower-range-limit": "required-DL",
174 "accuracy": "not-normally",
175 "repeatability": "not-normally",
176 "intermediate-precision": "not-normally",
177 },
178 "assay": {
179 "specificity": "required",
180 "response": "required",
181 "lower-range-limit": "not-normally",
182 "accuracy": "required",
183 "repeatability": "required",
184 "intermediate-precision": "required-unless-reproducibility",
185 },
188ATTRIBUTE_ALIASES = {
189 "content": "assay",
190 "potency": "assay",
191 "assay": "assay",
192 "identification": "identity",
193 "identity": "identity",
194 "id": "identity",
195 "impurity": "impurity-quantitative",
196 "impurities": "impurity-quantitative",
197 "related-substances": "impurity-quantitative",
198 "purity": "impurity-quantitative",
199 "impurity-quantitative": "impurity-quantitative",
200 "impurity-limit": "impurity-limit",
201 "limit-test": "impurity-limit",
204# ICH Q2(R2) Table 2 -- examples of reportable ranges.
205Q2R2_REPORTABLE_RANGE: dict[str, dict[str, str]] = {
206 "assay": {
207 "low": "80% of declared content, or 80% of the lower specification acceptance criterion",
208 "high": "120% of declared content, or 120% of the upper specification acceptance criterion",
209 },
210 "potency": {
211 "low": "lowest specification acceptance criterion -20%",
212 "high": "highest specification acceptance criterion +20%",
213 },
214 "content-uniformity": {
215 "low": "70% of declared content",
216 "high": "130% of declared content",
217 },
218 "dissolution-ir-one-point": {
219 "low": "Q - 45% of the lowest strength specification",
220 "high": "130% of declared content of the highest strength",
221 },
222 "dissolution-ir-multi-point": {
223 "low": "lower limit of reportable range as justified by the specification, or QL",
224 "high": "130% of declared content of the highest strength",
225 },
226 "dissolution-modified-release": {
227 "low": "lower limit of reportable range as justified by the specification, or QL",
228 "high": "130% of declared content of the highest strength",
229 },
230 "impurity-quantitative": {
231 "low": "reporting threshold",
232 "high": "120% of the specification acceptance criterion",
233 },
234 "purity-area-percent": {
235 "low": "80% of the lower specification acceptance criterion",
236 "high": "upper specification acceptance criterion, or 100%",
237 },
240# ICH Q2(R2) recommended data, section 3.
241Q2R2_STUDY_DESIGN: dict[str, dict[str, str]] = {
242 "response": {
243 "requirement": "minimum of 5 concentrations appropriately distributed across the range",
244 "reference": "Q2(R2) 3.2.2.1",
245 "report": (
246 "plot of the data, correlation coefficient or coefficient of determination, "
247 "y-intercept, slope, and an analysis of deviation of points from the line "
248 "(residual pattern)"
249 ),
250 },
251 "accuracy": {
252 "requirement": (
253 "appropriate number of determinations and levels across the reportable range "
254 "(e.g., 3 concentrations / 3 replicates each of the full procedure)"
255 ),
256 "reference": "Q2(R2) 3.3.1.4",
257 "report": (
258 "mean percent recovery of a known added amount, or difference between mean and "
259 "accepted true value, with a 100(1-alpha)% confidence interval"
260 ),
261 },
262 "repeatability": {
263 "requirement": (
264 "minimum 9 determinations covering the reportable range (e.g., 3 concentrations "
265 "/ 3 replicates), or minimum 6 determinations at 100% of the test concentration"
266 ),
267 "reference": "Q2(R2) 3.3.2.1",
268 "report": "standard deviation, relative standard deviation, and a 100(1-alpha)% CI",
269 },
270 "intermediate-precision": {
271 "requirement": (
272 "effects of random events -- typically different days, environmental conditions, "
273 "analysts, equipment. Studying effects individually is not necessary; DoE is "
274 "encouraged. Extent justified by development understanding and risk (ICH Q14)"
275 ),
276 "reference": "Q2(R2) 3.3.2.2",
277 "report": "standard deviation, relative standard deviation, and a 100(1-alpha)% CI",
278 },
279 "reproducibility": {
280 "requirement": (
281 "inter-laboratory trial; usually NOT required for a regulatory submission, but "
282 "consider for pharmacopoeial standardisation or multi-site procedures"
283 ),
284 "reference": "Q2(R2) 3.3.2.3",
285 "report": "standard deviation, relative standard deviation, and a 100(1-alpha)% CI",
286 },
287 "specificity": {
288 "requirement": (
289 "absence of interference, orthogonal procedure comparison, or technology-inherent "
290 "justification. For a stability-indicating claim, include samples containing "
291 "relevant degradation products (spiked, stressed, or aged)"
292 ),
293 "reference": "Q2(R2) 3.1, 2.4",
294 "report": "interference data, resolution/peak purity, or orthogonal comparison",
295 },
296 "lower-range-limit": {
297 "requirement": (
298 "DL/QL by visual evaluation, signal-to-noise, standard deviation of the response "
299 "and slope, or direct accuracy and precision at the lower limit. An estimated "
300 "limit should then be confirmed with samples at or near that limit"
301 ),
302 "reference": "Q2(R2) 3.2.3",
303 "report": "the limit and the approach used to determine it",
304 },
305 "robustness": {
306 "requirement": (
307 "deliberate variation of procedure parameters plus solution stability. Normally "
308 "performed during development under ICH Q14; submitted case-by-case or available "
309 "on request"
310 ),
311 "reference": "Q2(R2) 3.4, ICH Q14 section 5",
312 "report": "parameters varied, ranges, and the effect on the reportable result",
313 },
316# ICH Q2(R2) 3.2.3.2/3.2.3.3 -- the estimation approaches and their constants.
317DL_QL_APPROACHES = {
318 "visual": {
319 "dl": "lowest level reliably detected by analysis of known concentrations",
320 "ql": "lowest level reliably quantitated by analysis of known concentrations",
321 "note": "acceptable for both non-instrumental and instrumental procedures",
322 },
323 "signal-to-noise": {
324 "dl": "S/N of 3:1 generally acceptable",
325 "ql": "S/N of at least 10:1 acceptable",
326 "note": "only for procedures exhibiting baseline noise; define the noise region",
327 },
328 "sd-and-slope": {
329 "dl": "DL = 3.3 * sigma / S",
330 "ql": "QL = 10 * sigma / S",
331 "note": (
332 "sigma from the SD of blank responses, the residual SD of the regression line, "
333 "or the SD of y-intercepts of regression lines; S is the calibration slope"
334 ),
335 },
336 "accuracy-precision": {
337 "dl": "not applicable",
338 "ql": "QL validated directly by accuracy and precision at the lower range limit",
339 "note": "avoids relying on an estimate; Q2(R2) 3.2.3.4",
340 },
343# --------------------------------------------------------------------------
344# ICH M10 acceptance criteria. Verified against the Step 4 guideline text.
345# Chromatographic (CC) and ligand binding assay (LBA) criteria differ and are
346# the single most commonly conflated pair in bioanalysis.
347# --------------------------------------------------------------------------
349M10_CRITERIA: dict[str, dict[str, Any]] = {
350 "chromatographic": {
351 "label": "Chromatographic assays (ICH M10 section 3)",
352 "calibration_min_levels": 6,
353 "calibration_tolerance_pct": 15.0,
354 "calibration_tolerance_lloq_pct": 20.0,
355 "calibration_tolerance_uloq_pct": 15.0,
356 "calibration_min_pass_fraction": 0.75,
357 "accuracy_tolerance_pct": 15.0,
358 "accuracy_tolerance_lloq_pct": 20.0,
359 "precision_cv_pct": 15.0,
360 "precision_cv_lloq_pct": 20.0,
361 "limit_levels": "LLOQ",
362 "qc_levels_accuracy_precision": 4,
363 "qc_levels_routine_run": 3,
364 "ap_replicates_per_run": 5,
365 "ap_min_runs": 3,
366 "ap_min_days": 2,
367 "qc_run_pass_fraction": 2.0 / 3.0,
368 "qc_run_pass_fraction_per_level": 0.50,
369 "qc_run_tolerance_pct": 15.0,
370 "total_error_pct": None,
371 "total_error_pct_at_limits": None,
372 "isr_tolerance_pct": 20.0,
373 "isr_pass_fraction": 2.0 / 3.0,
374 "carryover_blank_pct_of_lloq": 20.0,
375 "carryover_blank_pct_of_is": 5.0,
376 "selectivity_min_sources": 6,
377 "dilution_tolerance_pct": 15.0,
378 "stability_tolerance_pct": 15.0,
379 "notes": (
380 "Accuracy/precision validation QCs at a minimum of 4 levels: LLOQ, low QC within "
381 "3x the LLOQ, medium QC around 30-50% of the calibration range, and high QC at "
382 "least 75% of the ULOQ. Within-run uses at least 5 replicates per level per run; "
383 "between-run uses each level in at least 3 runs over at least 2 days. Routine "
384 "(non-accuracy-and-precision) runs may use low, medium and high QCs in duplicate. "
385 "M10 states no explicit total-error criterion for chromatographic assays."
386 ),
387 },
388 "lba": {
389 "label": "Ligand binding assays (ICH M10 section 4)",
390 "calibration_min_levels": 6,
391 "calibration_tolerance_pct": 20.0,
392 "calibration_tolerance_lloq_pct": 25.0,
393 "calibration_tolerance_uloq_pct": 25.0,
394 "calibration_min_pass_fraction": 0.75,
395 "accuracy_tolerance_pct": 20.0,
396 "accuracy_tolerance_lloq_pct": 25.0,
397 "precision_cv_pct": 20.0,
398 "precision_cv_lloq_pct": 25.0,
399 "limit_levels": "LLOQ and ULOQ",
400 "qc_levels_accuracy_precision": 5,
401 "qc_levels_routine_run": 3,
402 "ap_replicates_per_run": 3,
403 "ap_min_runs": 6,
404 "ap_min_days": 2,
405 "qc_run_pass_fraction": 2.0 / 3.0,
406 "qc_run_pass_fraction_per_level": 0.50,
407 "qc_run_tolerance_pct": 20.0,
408 "total_error_pct": 30.0,
409 "total_error_pct_at_limits": 40.0,
410 "isr_tolerance_pct": 30.0,
411 "isr_pass_fraction": 2.0 / 3.0,
412 "carryover_blank_pct_of_lloq": None,
413 "carryover_blank_pct_of_is": None,
414 "selectivity_min_sources": 10,
415 "dilution_tolerance_pct": 20.0,
416 "stability_tolerance_pct": 20.0,
417 "notes": (
418 "Anchor points outside the quantitation range are excluded from the calibration "
419 "pass count. Accuracy and precision are evaluated at 5 QC levels (LLOQ, low, "
420 "medium (geometric mean of the range), high, ULOQ) with at least 3 replicates per level per run in at least 6 "
421 "runs over 2 or more days. LBAs carry an additional total-error criterion: the "
422 "sum of absolute bias (%) and precision CV (%) must not exceed 30%, or 40% at "
423 "the LLOQ and ULOQ. Chromatographic assays have no such criterion."
424 ),
425 },
428# --------------------------------------------------------------------------
429# Technique notes distilled from ICH Q2(R2) Annex 2 (illustrative, not mandatory)
430# --------------------------------------------------------------------------
432TECHNIQUE_NOTES: dict[str, dict[str, str]] = {
433 "hplc": {
434 "annex_table": "Table 3 (quantitative separation techniques)",
435 "robustness": (
436 "extraction volume/time/temperature, dilution, column or capillary lot, mobile "
437 "phase and buffer composition and pH, column temperature, flow rate, detection "
438 "wavelength; plus stability of sample and reference preparations"
439 ),
440 "special": (
441 "Relative response factors: if the RRF falls outside 0.8-1.2, apply a correction "
442 "factor. If an impurity is overestimated it may be acceptable to omit the "
443 "correction. Determine RRF under final procedure conditions and document it."
444 ),
445 },
446 "gc": {"annex_table": "Table 3 (quantitative separation techniques)",
447 "robustness": "as for HPLC, plus inlet temperature, split ratio, carrier flow, oven ramp",
448 "special": "same relative response factor 0.8-1.2 consideration as HPLC"},
449 "ce": {"annex_table": "Table 3 (quantitative separation techniques)",
450 "robustness": "capillary lot, buffer composition and pH, capillary temperature, voltage",
451 "special": "same relative response factor consideration as HPLC"},
452 "icp": {"annex_table": "Table 4 (elemental impurities by ICP-OES or ICP-MS)",
453 "robustness": "plasma conditions, sample introduction, internal standard, matrix matching",
454 "special": "spectral and non-spectral interference; ICH Q3D drives which elements matter"},
455 "dissolution": {"annex_table": "Table 5 (dissolution with HPLC as product performance test)",
456 "robustness": "medium composition and volume, deaeration, agitation, sinker, filter",
457 "special": (
458 "Table 5 was corrected on 30 Nov 2023 (reportable range linearity "
459 "formulae). Use the corrected text."
460 )},
461 "qnmr": {"annex_table": "Table 6 (quantitative 1H-NMR for assay of a drug substance)",
462 "robustness": "pulse angle, relaxation delay, number of scans, temperature, shimming",
463 "special": "internal standard purity and signal selection dominate accuracy"},
464 "bioassay": {"annex_table": "Table 7 (biological assays)",
465 "robustness": "cell passage, incubation time and temperature, reagent lot, plate layout",
466 "special": (
467 "Non-linear (4- or 5-parameter logistic) response is expected. Linearity "
468 "of the concentration-response relationship is NOT required; evaluate "
469 "proportionality of results to expected values instead."
470 )},
471 "qpcr": {"annex_table": "Table 8 (quantitative PCR)",
472 "robustness": "primer/probe lot, master mix, cycling parameters, template input",
473 "special": "amplification efficiency and specificity of amplicon detection"},
474 "particle-size": {"annex_table": "Table 9 (particle size measurement)",
475 "robustness": "dispersion medium, sonication, pump speed, obscuration",
476 "special": "technology-inherent justification may substitute for some characteristics"},
477 "nir": {"annex_table": "Table 10 (NIR analytical procedure)",
478 "robustness": "instrument, probe, sample presentation, temperature, humidity",
479 "special": (
480 "Multivariate: validate in two phases (calibration plus internal testing, "
481 "then an independent validation set). Report RMSEP against RMSEC. Reference "
482 "procedure performance must equal or exceed the multivariate procedure's."
483 )},
484 "lcms": {"annex_table": "Table 11 (quantitative LC/MS)",
485 "robustness": "source conditions, mobile phase additives, column lot, matrix lots",
486 "special": (
487 "Matrix effects and ion suppression need explicit evaluation. For a "
488 "bioanalytical purpose, ICH M10 governs instead of Q2(R2)."
489 )},
493def resolve_attribute(name: str) -> str:
494 key = name.strip().lower().replace("_", "-")
495 if key in ATTRIBUTE_ALIASES:
496 return ATTRIBUTE_ALIASES[key]
497 raise KeyError(
498 f"unknown attribute {name!r}; choose from: {', '.join(sorted(set(ATTRIBUTE_ALIASES)))}"
499 )