{"id":"c8c49eef-7a1d-4f3d-803a-1b0747418839","slug":"analytical-figures-skill","kind":"skill","name":"analytical-figures-skill","summary":"Claude Code skill: publication-grade figures + gated analysis for analytical chemistry (FTIR/ATR, PXRD, calibration, chemometrics, CIF/crystal, cocrystal ID)","owner":"TheDop","repoUrl":"https://github.com/TheDop/analytical-figures-skill","license":"MIT","licenseTier":"permissive","trust":"community","stars":1,"match":null,"fewStars":true,"format":"skill","createdAt":"2026-09-11T11:49:42.000Z","addedAt":"2026-10-21T10:25:04.474Z","logoUrl":"https://avatars.githubusercontent.com/u/33497324?v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["developer-tools"],"tags":["analytical-chemistry","calibration","chemometrics","claude-code","cocrystal","crystallography","ftir","matplotlib"],"runtime":"binary","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-22T10:13:09.820Z","sample":false,"url":"https://conduits.dev/tools/analytical-figures-skill","language":"Python","transports":["stdio"],"install":{},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"# analytical-figures\n\nAsk [Claude Code](https://claude.com/claude-code) for a calibration curve from your `.spc` files\nand get a journal-ready figure with LOD/LOQ, confidence and prediction bands and a residual panel,\nplus one self-contained Python script that reproduces it. Ask for a calculated PXRD pattern from\na CIF, a cocrystal-vs-physical-mixture call or PLS diagnostics with leakage-safe cross-validation\nand get the same: a figure that passed its checks, and the code that made it.\n\n`analytical-figures` is a Claude Code skill for analytical chemistry and spectroscopy: FTIR/ATR\nand PXRD spectra, calibration with figures of merit (LOD/LOQ, recovery, ICH Q2), multivariate\ncalibration (PLS/PCR/PCA), crystal structures from CIFs (validation table, calculated PXRD,\ndeterministic 3D views) and cocrystal identification.\n\nIt is not a plotting tutorial. It does two things a plotting library does not:\n\n- **It gates the numbers before they reach a figure.** Ingest checks, identical processing across\n  a batch, named error statistics (SD vs SEM vs CI, with n), leakage-safe cross-validation,\n  mandatory residual panels, no extrapolation past the calibrated range, and a static critic\n  that flags a figure-of-merit hand-typed onto a figure instead of interpolated from the code.\n- **It hands over a script, not a PNG.** The deliverable is one self-contained Python file\n  (`bundle.py` amalgamates the modules an analysis uses) with a provenance header, so every\n  figure is reproducible, editable and attachable to a report.\n\n<!-- gallery:start -->\n<table>\n<tr><td align=\"center\"><a href=\"docs/gallery/full/composite_method.png\"><img src=\"docs/gallery/tiles/composite_method.png\" width=\"140\" alt=\"Method-comparison page figure: four calibrations, each over its own residual strip, and the figures of merit with the direction of good in every title\" title=\"Method-comparison page figure: four calibrations, each over its own residual strip, and the figures of merit with the direction of good in every title — data: Bansal, Singh &amp; Kaur 2021, BMC Chemistry 15:27, 10.1186/s13065-021-00752-3 (published peak-area tables)\"></a></td><td align=\"center\"><a href=\"docs/gallery/full/composite_specificity.png\"><img src=\"docs/gallery/tiles/composite_specificity.png\" width=\"140\" alt=\"Specificity composite: the full spectra, the carbonyl window with both integration windows on one shared baseline, and the band-area ratio it yields against DM\" title=\"Specificity composite: the full spectra, the carbonyl window with both integration windows on one shared baseline, and the band-area ratio it yields against DM — data: Wang 2023 pectin ATR-FTIR spectra, Mendeley Data 10.17632/gkwbp3wc49.1 (CC BY 4.0)\"></a></td><td align=\"center\"><a href=\"docs/gallery/full/composite_pxrd.png\"><img src=\"docs/gallery/tiles/composite_pxrd.png\" width=\"140\" alt=\"Phase-ID composite: stacked calculated patterns over the full range and the low-angle window, one right-margin key serving both panels\" title=\"Phase-ID composite: stacked calculated patterns over the full range and the low-angle window, one right-margin key serving both panels — data: Aspirin COD 7247819, urea COD 1008785, lactose COD 2206486 (Crystallography Open Database)\"></a></td><td align=\"center\"><a href=\"docs/gallery/full/pls_diag.png\"><img src=\"docs/gallery/tiles/pls_diag.png\" width=\"140\" alt=\"Leakage-safe PLS diagnostics on six real standards: RMSECV per preprocessing with the parsimony pick ringed, coefficients, scores, loadings\" title=\"Leakage-safe PLS diagnostics on six real standards: RMSECV per preprocessing with the parsimony pick ringed, coefficients, scores, loadings — data: Wang 2023 pectin ATR-FTIR spectra, Mendeley Data 10.17632/gkwbp3wc49.1 (CC BY 4.0) — the six calibration standards, leave-one-out\"></a></td><td align=\"center\"><a href=\"docs/gallery/full/calib_pectin.png\"><img src=\"docs/gallery/tiles/calib_pectin.png\" width=\"140\" alt=\"Calibration with confidence and prediction bands and the mandatory residual panel\" title=\"Calibration with confidence and prediction bands and the mandatory residual panel — data: Wang 2023 pectin ATR-FTIR spectra, Mendeley Data 10.17632/gkwbp3wc49.1 (CC BY 4.0) — the authors&#x27; published band areas\"></a></td><td align=\"center\"><a href=\"docs/gallery/full/waterfall.png\"><img src=\"docs/gallery/tiles/waterfall.png\" width=\"140\" alt=\"Waterfall of the six calibration standards, right-margin keys instead of a legend\" title=\"Waterfall of the six calibration standards, right-margin keys instead of a legend — data: Wang 2023 pectin ATR-FTIR spectra, Mendeley Data 10.17632/gkwbp3wc49.1 (CC BY 4.0)\"></a></td></tr>\n<tr><td align=\"center\"><a href=\"docs/gallery/full/ddsimca.png\"><img src=\"docs/gallery/tiles/ddsimca.png\" width=\"140\" alt=\"DD-SIMCA acceptance plot of eighteen replicate sample spectra: all inside the acceptance boundary, the extreme and outlier limits drawn\" title=\"DD-SIMCA acceptance plot of eighteen replicate sample spectra: all inside the acceptance boundary, the extreme and outlier limits drawn — data: Wang 2023 pectin ATR-FTIR spectra, Mendeley Data 10.17632/gkwbp3wc49.1 (CC BY 4.0) — the eighteen sample spectra (6 preparations × 3 replicates), SNV, 2 PCs\"></a></td><td align=\"center\"><a href=\"docs/gallery/full/integration.png\"><img src=\"docs/gallery/tiles/integration.png\" width=\"140\" alt=\"Two overlapping bands on one shared baseline with the vertical drop at the window boundary — the drop-perpendicular method the validation showed is required\" title=\"Two overlapping bands on one shared baseline with the vertical drop at the window boundary — the drop-perpendicular method the validation showed is required — data: Wang 2023 pectin ATR-FTIR spectra, Mendeley Data 10.17632/gkwbp3wc49.1 (CC BY 4.0) — the DM 70.5 % standard\"></a></td><td align=\"center\"><a href=\"docs/gallery/full/ortep.png\"><img src=\"docs/gallery/tiles/ortep.png\" width=\"140\" alt=\"ORTEP displacement ellipsoids at 50 % probability, deterministic PCA camera\" title=\"ORTEP displacement ellipsoids at 50 % probability, deterministic PCA camera — data: Aspirin, COD 7247819 (296 K)\"></a></td><td align=\"center\"><a href=\"docs/gallery/full/calib_bmc.png\"><img src=\"docs/gallery/tiles/calib_bmc.png\" width=\"140\" alt=\"Doxorubicin carbonyl-area calibration; LOD/LOQ from the residual SD, and the sigma is named\" title=\"Doxorubicin carbonyl-area calibration; LOD/LOQ from the residual SD, and the sigma is named — data: Bansal, Singh &amp; Kaur 2021, BMC Chemistry 15:27, 10.1186/s13065-021-00752-3 (published peak-area tables)\"></a></td><td align=\"center\"><a href=\"docs/gallery/full/groundtruth.png\"><img src=\"docs/gallery/tiles/groundtruth.png\" width=\"140\" alt=\"Shared vs per-window baseline against a closed-form answer: the per-window pedestal bias, measured\" title=\"Shared vs per-window baseline against a closed-form answer: the per-window pedestal bias, measured — data: Analytic Gaussian bands with known areas, checked against scipy.integrate.quad\"></a></td><td align=\"center\"><a href=\"docs/gallery/full/pxrd_calc.png\"><img src=\"docs/gallery/tiles/pxrd_calc.png\" width=\"140\" alt=\"Calculated PXRD from a CIF at Cu Kα, top peak cross-checked against pymatgen\" title=\"Calculated PXRD from a CIF at Cu Kα, top peak cross-checked against pymatgen — data: Aspirin, COD 7247819\"></a></td></tr>\n<tr><td align=\"center\"><a href=\"docs/gallery/full/pxrd_realism.png\"><img src=\"docs/gallery/tiles/pxrd_realism.png\" width=\"140\" alt=\"Lab-realistic pattern: Cu Kα₁/Kα₂ doublet and Caglioti broadening on the Dans reflection list\" title=\"Lab-realistic pattern: Cu Kα₁/Kα₂ doublet and Caglioti broadening on the Dans reflection list — data: Aspirin, COD 7247819\"></a></td><td align=\"center\"><a href=\"docs/gallery/full/packing.png\"><img src=\"docs/gallery/tiles/packing.png\" width=\"140\" alt=\"Packing diagram down a with hydrogen bonds and the cell box\" title=\"Packing diagram down a with hydrogen bonds and the cell box — data: Aspirin, COD 7247819\"></a></td><td align=\"center\"><a href=\"docs/gallery/full/hbond_env.png\"><img src=\"docs/gallery/tiles/hbond_env.png\" width=\"140\" alt=\"Hydrogen-bond environment of the asymmetric unit, symmetry mates superscripted\" title=\"Hydrogen-bond environment of the asymmetric unit, symmetry mates superscripted — data: Aspirin, COD 7247819\"></a></td><td align=\"center\"><a href=\"docs/gallery/full/unit_cell.png\"><img src=\"docs/gallery/tiles/unit_cell.png\" width=\"140\" alt=\"Unit-cell contents with the cell box and a/b/c\" title=\"Unit-cell contents with the cell box and a/b/c — data: Aspirin, COD 7247819\"></a></td><td align=\"center\"><a href=\"docs/gallery/full/flufenamic.png\"><img src=\"docs/gallery/tiles/flufenamic.png\" width=\"140\" alt=\"Z′ = 3 with CF₃ disorder groups honoured: no bonds between mutually exclusive sites\" title=\"Z′ = 3 with CF₃ disorder groups honoured: no bonds between mutually exclusive sites — data: Flufenamic acid, COD 4118081\"></a></td><td align=\"center\"><a href=\"docs/gallery/full/pyvista.png\"><img src=\"docs/gallery/tiles/pyvista.png\" width=\"140\" alt=\"PyVista/VTK still: real depth buffer, ambient occlusion, orthographic camera from the same orientation engine\" title=\"PyVista/VTK still: real depth buffer, ambient occlusion, orthographic camera from the same orientation engine — data: Aspirin, COD 7247819\"></a></td></tr>\n</table>\n\n<sub>Hover a tile for what it is and where the data came from; click for full size. Every tile is produced by <code>docs/build_gallery.py</code> from public data and public CIFs — nothing is drawn by hand and nothing is synthetic. Interactive version with placards: <a href=\"https://thedop.github.io/analytical-figures-skill/\">https://thedop.github.io/analytical-figures-skill/</a></sub>\n<!-- gallery:end -->\n\n<!-- examples:start -->\n## From a prompt to a figure\n\nThree prompts, run in this repository with the skill loaded. Left: the prompt and the gates that fired on the way. Right: what came back. Every run hands over one self-contained script that regenerates its figure, and the caption is interpolated from the computed values, never typed.\n\n<table>\n<tr>\n<td width=\"46%\" valign=\"top\">\n<p><b>“Doxorubicin peak areas from Bansal 2021, transmittance mode, 0.6–1.4 % w/w. Fit the calibration, give me LOD and LOQ, and read off the concentration for peak areas of 3.5 and 6.0 mm².”</b></p>\n<p><sub>WHAT THE SKILL DID</sub><br>\n· fit, 95 % bands and the mandatory residual panel from the published table<br>\n· <code>LOD 0.109 % w/w, LOQ 0.330 % w/w  (residual_sd (3.3σ/m, 10σ/m))</code><br>\n· read-back of 6.0 mm²: <code>REFUSED: predicted x=1.865 is outside the calibrated range [0.6, 1.4] - extrapolation refused (extend the calibration instead)</code></p>\n<p><sub>WHAT YOU GET</sub><br>\n<a href=\"docs/examples/bmc_lod/bmc_lod_standalone.py\">one self-contained script</a> · <a href=\"docs/examples/bmc_lod/figure.png\">the figure</a> · <a href=\"docs/examples/bmc_lod/caption.txt\">its caption</a></p>\n<details><summary>full log</summary>\n<pre>$ python analysis.py\n  [INFO] calibration: 5 pts, monotonic, finite - OK\n  y = 3.002 x +0.402   R2 0.9919   n=5\n  LOD 0.109 % w/w, LOQ 0.330 % w/w  (residual_sd (3.3σ/m, 10σ/m))\n  intercept 0.4017 [-0.1161, 0.9195] (95% CI), t=2.47, p=0.090 -&gt; no significant constant bias (CI includes 0)\n  peak area 3.50 mm2 -&gt; 1.032 % w/w\n  peak area 6.00 mm2 -&gt; REFUSED: predicted x=1.865 is outside the calibrated range [0.6, 1.4] - extrapolation refused (extend the calibration instead)\n  [INFO] layout: layout audit clean (data present, no tofu, ticks clear, panels labelled)\n  wrote C:\\Users\\uriba\\projects\\IR_CoCr_Project\\.claude\\skills\\analytical-figures\\docs\\examples\\bmc_lod\\out\\bmc_dox_calibration.pdf + .png, caption.txt\n[exit 0]\n$ python bundle.py analysis.py -o bmc_lod_standalone.py\ncritic pass (number provenance):\n  [INFO] critic: no hard-typed figures-of-merit - on-figure numbers trace to the code\nwrote bmc_lod_standalone.py\n[exit 0]\n$ python bmc_lod_standalone.py      # the handed-over script, re-run on its own\n 0.109 % w/w, LOQ 0.330 % w/w  (residual_sd (3.3σ/m, 10σ/m))\n  intercept 0.4017 [-0.1161, 0.9195] (95% CI), t=2.47, p=0.090 -&gt; no significant constant bias (CI includes 0)\n  peak area 3.50 mm2 -&gt; 1.032 % w/w\n  peak area 6.00 mm2 -&gt; REFUSED: predicted x=1.865 is outside the calibrated range [0.6, 1.4] - extrapolation refused (extend the calibration instead)\n  [INFO] layout: layout audit clean (data present, no tofu, ticks clear, panels labelled)\n  wrote C:\\Users\\uriba\\projects\\IR_CoCr_Project\\.claude\\skills\\analytical-figures\\docs\\examples\\bmc_lod\\out\\bmc_dox_calibration.pdf + .png, caption.txt\n[exit 0]</pre></details>\n</td>\n<td width=\"54%\" valign=\"top\"><a href=\"docs/examples/bmc_lod/figure.png\"><img src=\"docs/examples/bmc_lod/figure.png\" width=\"100%\" alt=\"bmc_lod\"></a></td>\n</tr>\n<tr>\n<td width=\"46%\" valign=\"top\">\n<p><b>“Here is aspirin&#x27;s CIF (COD 7247819). Validate it, calculate its Cu Kα powder pattern, and give me an ORTEP view with the hydrogen bonds.”</b></p>\n<p><sub>WHAT THE SKILL DID</sub><br>\n· validated before drawing anything: <code>density (i)=1.3999 (ii)=1.3997 (iii)=1.4 -&gt; PASS (all consistent)</code><br>\n· Cu Kα pattern from a Mo-refined CIF, independently cross-checked: <code>cross-check [pymatgen]: top peak 15.607 vs 15.619, delta 0.012 deg (gate &lt;0.05)</code><br>\n· ORTEP at 50 % probability, C–H hidden, heteroatoms labelled, the hydrogen bond drawn</p>\n<p><sub>WHAT YOU GET</sub><br>\n<a href=\"docs/examples/cif_report/cif_report_standalone.py\">one self-contained script</a> · <a href=\"docs/examples/cif_report/figure_1.png\">the figure</a> · <a href=\"docs/examples/cif_report/caption.txt\">its caption</a></p>\n<details><summary>full log</summary>\n<pre>$ python analysis.py\n  [INFO] density: density (i)=1.3999 (ii)=1.3997 (iii)=1.4 -&gt; PASS (all consistent)\n  [INFO] geometry: Block B: 21 unique bonds, 0 outside the covalent envelope (±0.25 A; coarse bound, not Mogul)\n  [INFO] hbonds: 1 H-bonds + 0 geometric contacts (donors=[&#x27;N&#x27;, &#x27;O&#x27;], acceptors=[&#x27;Cl&#x27;, &#x27;F&#x27;, &#x27;N&#x27;, &#x27;O&#x27;, &#x27;S&#x27;], floor=120 deg)\n  [INFO] wavelength: wavelength 0.71073 A matches Mo\n  [INFO] spacegroup: space group P 1 21/c 1: 4 symops, consistent\nVALIDATION TABLE — 7247819  (P 1 21/c 1, 4 ops)\n----------------------------------------------------------------\nBlock A — crystal data\n  cell volume   calc 854.83   decl 854.8\n  density (i)   1.3999  (expanded-cell, assumption-free, CODATA)\n  density (ii)  1.3997  (checkCIF formula, 1.66042)\n  density (iii) 1.4  (declared)\n  RD            1.0002  (ok)\n     -&gt; PASS (all consistent)\n  F(000)        calc 376   decl 376.0\n  formula wt    from formula_sum 180.15742   decl 180.15\n  elements      [&#x27;C&#x27;, &#x27;H&#x27;, &#x27;O&#x27;]\n  wavelength    0.71073  (MoK\\a)  [wavelength 0.71073 A matches Mo]\n  space group   space group P 1 21/c 1: 4 symops, consistent\n  special pos   none\n  echo (not recomputed)  T=296(2)  R(gt)=0.0524  GoF=1.037  reflns=1946.0  th_max=27.442  size=0.463x0.186x0.03\nBlock B — geometry (bond lengths vs covalent-radii sum +/-0.25 A; coarse bound, not Mogul)\n  0 outlier(s) of 21 unique bonds\nBlock C — H-bonds (D-H...A, X-H normalized; D...A class by Jeffrey; sym = operator on A)\n  D      H      A         D-H   H..A   D..A    ang  class     sym\n  O1     H1     O2      0.820  1.673  2.652  173.2  moderate  3_656\n  [INFO] pxrd: PXRD: lambda=1.54060 A (cfg); 31 peaks &gt;2%; strongest 2theta=15.607\n  [INFO] pxrd: cross-check [pymatgen]: top peak 15.607 vs 15.619, delta 0.012 deg (gate &lt;0.05)\n  [INFO] layout: layout audit clean (data present, no tofu, ticks clear, panels labelled)\n  [INFO] pxrd: PXRD: lambda=1.54060 A (cfg); 31 peaks &gt;2%; strongest 2theta=15.607\n  [INFO] pxrd: cross-check [pymatgen]: top peak 15.607 vs 15.619, delta 0.012 deg (gate &lt;0.05)\n  strongest reflection: {&#x27;two_theta&#x27;: 15.607, &#x27;d&#x27;: 5.6733, &#x27;hkl&#x27;: &#x27;0 0 -2&#x27;, &#x27;I&#x27;: 100.0}\n  [INFO] hbond_env: symmetry key for the caption: (i) -x+1, -y, -z+1\n  wrote pxrd.pdf/.png, structure_ellipsoid.png, hbonds.csv, peaks.csv, caption.txt\n[exit 0]\n$ python bundle.py analysis.py -o cif_report_standalone.py\ncritic pass (number provenance):\n  [INFO] critic: no hard-typed figures-of-merit - on-figure numbers trace to the code\nwrote cif_report_standalone.py\n[exit 0]\n$ python cif_report_standalone.py      # the handed-over script, re-run on its own\nanels labelled)\n  [INFO] pxrd: PXRD: lambda=1.54060 A (cfg); 31 peaks &gt;2%; strongest 2theta=15.607\n  [INFO] pxrd: cross-check [pymatgen]: top peak 15.607 vs 15.619, delta 0.012 deg (gate &lt;0.05)\n  strongest reflection: {&#x27;two_theta&#x27;: 15.607, &#x27;d&#x27;: 5.6733, &#x27;hkl&#x27;: &#x27;0 0 -2&#x27;, &#x27;I&#x27;: 100.0}\n  [INFO] hbond_env: symmetry key for the caption: (i) -x+1, -y, -z+1\n  wrote pxrd.pdf/.png, structure_ellipsoid.png, hbonds.csv, peaks.csv, caption.txt\n[exit 0]</pre></details>\n</td>\n<td width=\"54%\" valign=\"top\"><a href=\"docs/examples/cif_report/figure_1.png\"><img src=\"docs/examples/cif_report/figure_1.png\" width=\"50%\" alt=\"cif_report\"></a><a href=\"docs/examples/cif_report/figure_2.png\"><img src=\"docs/examples/cif_report/figure_2.png\" width=\"50%\" alt=\"cif_report\"></a></td>\n</tr>\n<tr>\n<td width=\"46%\" valign=\"top\">\n<p><b>“Six pectin calibration standards as CSV (DM 3–70 %). Give me a publication figure of the carbonyl region and the ester/(ester+carboxylate) band-area calibration, with the LOD.”</b></p>\n<p><sub>WHAT THE SKILL DID</sub><br>\n· every file gated on ingest: <code>DM3: 3736 pts, monotonic, finite - OK</code><br>\n· one shared baseline for the two overlapping bands, split at 1700 cm⁻¹<br>\n· and it says when a method is weak: <code>p=0.001 -&gt; constant bias: intercept significantly != 0 (CI excludes 0)</code></p>\n<p><sub>WHAT YOU GET</sub><br>\n<a href=\"docs/examples/pectin_calibration/pectin_calibration_standalone.py\">one self-contained script</a> · <a href=\"docs/examples/pectin_calibration/figure.png\">the figure</a> · <a href=\"docs/examples/pectin_calibration/caption.txt\">its caption</a> · <a href=\"docs/examples/pectin_calibration/methods.txt\">paste-ready methods text</a></p>\n<details><summary>full log</summary>\n<pre>$ python analysis.py\n  [INFO] ingest: DM3.csv: 108345 B\n  [INFO] DM3: 3736 pts, monotonic, finite - OK\n  [INFO] ingest: DM20.csv: 108344 B\n  [INFO] DM20: 3736 pts, monotonic, finite - OK\n  [INFO] ingest: DM37.csv: 108344 B\n  [INFO] DM37: 3736 pts, monotonic, finite - OK\n  [INFO] ingest: DM55.csv: 108344 B\n  [INFO] DM55: 3736 pts, monotonic, finite - OK\n  [INFO] ingest: DM62.8.csv: 108344 B\n  [INFO] DM62.8: 3736 pts, monotonic, finite - OK\n  [INFO] ingest: DM70.5.csv: 108344 B\n  [INFO] DM70.5: 3736 pts, monotonic, finite - OK\n  [INFO] calibration: 6 pts, monotonic, finite - OK\n  slope 0.00297 per % DM [0.00139, 0.00455]  R2 0.872  n=6\n  LOD 37.1 % DM, LOQ 112.4 % DM  (residual_sd (3.3σ/m, 10σ/m))\n  intercept 0.2539 [0.1783, 0.3294] (95% CI), t=9.32, p=0.001 -&gt; constant bias: intercept significantly != 0 (CI excludes 0)\n  [INFO] layout: layout audit clean (data present, no tofu, ticks clear, panels labelled)\n  wrote C:\\Users\\uriba\\projects\\IR_CoCr_Project\\.claude\\skills\\analytical-figures\\docs\\examples\\pectin_calibration\\out\\pectin_calibration.pdf + .png, caption.txt\n[exit 0]\n$ python bundle.py analysis.py -o pectin_calibration_standalone.py\ncritic pass (number provenance):\n  [INFO] critic: no hard-typed figures-of-merit - on-figure numbers trace to the code\nwrote pectin_calibration_standalone.py\n[exit 0]\n$ python pectin_calibration_standalone.py      # the handed-over script, re-run on its own\n  [INFO] DM70.5: 3736 pts, monotonic, finite - OK\n  [INFO] calibration: 6 pts, monotonic, finite - OK\n  slope 0.00297 per % DM [0.00139, 0.00455]  R2 0.872  n=6\n  LOD 37.1 % DM, LOQ 112.4 % DM  (residual_sd (3.3σ/m, 10σ/m))\n  intercept 0.2539 [0.1783, 0.3294] (95% CI), t=9.32, p=0.001 -&gt; constant bias: intercept significantly != 0 (CI excludes 0)\n  [INFO] layout: layout audit clean (data present, no tofu, ticks clear, panels labelled)\n  wrote C:\\Users\\uriba\\projects\\IR_CoCr_Project\\.claude\\skills\\analytical-figures\\docs\\examples\\pectin_calibration\\out\\pectin_calibration.pdf + .png, caption.txt\n[exit 0]</pre></details>\n</td>\n<td width=\"54%\" valign=\"top\"><a href=\"docs/examples/pectin_calibration/figure.png\"><img src=\"docs/examples/pectin_calibration/figure.png\" width=\"100%\" alt=\"pectin_calibration\"></a></td>\n</tr>\n</table>\n<!-- examples:end -->\n\n## Install\n\nAs a plugin, from inside Claude Code (two commands; the repo is its own marketplace):\n\n```\n/plugin marketplace add TheDop/analytical-figures-skill\n/plugin install analytical-figures@analytical-figures-marketplace\n```\n\nOr clone into a Claude Code skills directory, user-level or per project:\n\n```\ngit clone https://github.com/TheDop/analytical-figures-skill ~/.claude/skills/analytical-figures        # available in every project\ngit clone https://github.com/TheDop/analytical-figures-skill .claude/skills/analytical-figures           # this project only\n```\n\nEither way, install the Python side once:\n\n```\npython -m pip install -r requirements.txt        # from the cloned/installed skill directory\n```\n\n`numpy` and `matplotlib` are required; `scipy` is strongly recommended. Everything else is\nper-family and imported lazily, so an FTIR job never pulls it: `requirements.txt` adds\n`scikit-learn` (chemometrics) and `rdkit` (the cocrystal predictor); the crystal family needs\n`python -m pip install gemmi Dans-Diffraction` (plus `pymatgen` for the PXRD cross-check and\n`pyvista` for the VTK renders), which the skill asks for by name the first time a CIF job runs.\nClaude Code loads the skill at session start. Then ask for what you need: a replicate overlay\nwith %RSD, a calibration curve with LOD/LOQ, a calculated PXRD pattern from a CIF, a\ncocrystal-vs-mixture call.\n\n## What is inside\n\n| Path | Role |\n|---|---|\n| `SKILL.md` | The operating instructions Claude reads: workflow, hard principles, the single-CONFIG rule. |\n| `scripts/` | The maintained modules: `config`, `style`, `verify`, `spectra`, `calibration`, `chemometrics`, `charts`, `doe`, `report`, `crystal_engine`, `crystal_pxrd`, `crystal_view`, `pxrd_realism`, `cocrystal`, `sources`. |\n| `references/` | Decision docs: `figure_selection.md` (what to plot, and why), `cocrystal_id.md` (which evidence identifies a cocrystal), `databases.md` (where to source each external fact), plus per-family conventions. |\n| `templates/analysis_template.py` | Copy this to start an analysis; `bundle.py` turns it into a standalone deliverable. |\n| `cli.py` | QC shortcuts: `.spc` replicates to band area/%RSD, a conc/signal CSV to slope, R², LOD/LOQ. |\n| `assets/styles/` | Journal `.mplstyle` presets (Nature, ACS, IEEE, general). |\n| `tests/`, `skill_validation/` | The test gate and the numeric-parity suites, below. |\n| `ROADMAP.md`, `VISION.md` | Backlog and build log; longer-range ideas. |\n\nThe modules are plain Python, so the skill also works without Claude: run from the skill root\nand `from scripts import spectra, calibration` as the template does.\n\n## Validation\n\n`python -m pytest tests/` is one green gate for the whole skill (81 tests). It checks module\nimports, the `SKILL.md` structure, the `bundle.py` round-trip, p-value correction against\n`statsmodels`, the style presets and the CLI, and it bridges in three numeric-parity suites:\n\n| Suite | Checks | Validated against |\n|---|---|---|\n| `skill_validation/chemometrics/` | 100 | closed-form fixtures, `scikit-learn`, published R `mdatools` critical limits, the biospectools EMSC gold |\n| `skill_validation/cocrystal/` | 26 | closed-form Rwp, `scipy.optimize.nnls`, the Cruz-Cabeza ΔpKa zones |\n| `skill_validation/pxrd/` | 23 | March–Dollase, Cu Kα₁/Kα₂ doublet, Caglioti, pseudo-Voigt |\n\n| `skill_validation/ftir_integration/` | 14 | closed-form Gaussian areas and `scipy.integrate.quad` under sloping baselines, noise and band overlap; plus the integrator run against the published pectin-DM (Wang 2023) and BMC 2021 calibrations |\n| `skill_validation/crystal/` | 9 CIFs | a CIF edge-case set (disorder groups, special positions, Z′ = 9, chiral, multi-block, synthetic broken inputs): the engine's table, an independent gemmi-only second opinion, ADP U_eq, the Dans reflection list vs `pymatgen`. Needs `gemmi` + `Dans_Diffraction`; pytest skips it otherwise |\n\nThe 24 pectin spectra (CC BY 4.0) are vendored. Data that cannot be redistributed is fetched on\ndemand instead: the biospectools EMSC gold and the R `pls` gasoline set\n(`skill_validation/chemometrics/datasets/fetch.py`) and the CCDC CIFs. Every check that needs\none of them skips cleanly when it is absent.\n\n## Known limitations\n\n- The crystal family is written for small-molecule cells. Bond, geometry and H-bond searches use a\n  KD-tree on a cached supercell, but the first calculated pattern per structure still parses the CIF\n  and computes structure factors (seconds for a few hundred atoms); later calls reuse the cache.\n- The realistic PXRD profile evaluates each reflection within ±40 FWHM of its centre (`window_fwhm`);\n  the discarded Lorentzian tail is below counting quantisation on lab patterns, and `window_fwhm=None`\n  restores the exact full-grid sum.\n- `audit_layout` is a set of heuristics (tick overlap, clipping, letters, unit cues). It catches the\n  common faults; reading the saved PNG is still part of the workflow, not a formality.\n\n## Provenance and licence\n\nBuilt during an MSc analytical-chemistry project: quantifying an API in a binary excipient\nmixture by ATR-FTIR, then identifying cocrystals by FTIR and PXRD. The worked examples in the\ndocs (aspirin in lactose, theophylline cocrystals) come from that work.\n\nMIT licence (see `LICENSE`). The figure-selection scaffolding and the panel-label alignment\ntrick are adapted from [scipilot-figure-skill](https://github.com/Haojae/scipilot-figure-skill)\n(MIT, Haojae); details in `THIRD_PARTY_NOTICES.md`.\n"}