==============================================================================
A. The formulas as the PPL computes them, against statsmodels / SciPy
==============================================================================

A1. Clopper-Pearson vs proportion_confint(method="beta")
    level   intervals  worst |diff|   worst rel (low>0)  max passes  mean BINOMIAL calls/interval
    0.5          1945      1.89e-15           3.83e-15           5      15.7
    0.8          1945      3.33e-15           7.03e-15           5      17.6
    0.9          1945      3.66e-15           8.60e-15           6      18.4
    0.95         1945      3.22e-15           8.18e-15           6      18.3
    0.99         1945      3.66e-15           1.10e-14           7      19.6
    0.999        1945      5.94e-15           1.11e-13          13      22.5
  ok    Clopper-Pearson: 11670 intervals, worst |difference| from statsmodels 5.9e-15 (target 1E-8)
  ok    at most 13 passes (Newton steps or halvings) per end

A2. mid-P vs an independent brentq on the mid-P tails (levels 90 %, 95 %, 99 %; n = 1..60)
  ok    mid-P: worst |difference| 1.9e-15 over 5,490 intervals
  ok    mid-P always lies inside Clopper-Pearson (0 exceptions over 1,890 intervals at 95 %)

A3. Conditional MLE OR and exact CI vs scipy.stats.contingency.odds_ratio
    458 tables compared; 3 with an empty margin (undefined, CI (0, inf), as SciPy); 1 with N > 2000 (not computed, by design)
    worst relative difference from SciPy: 8.77e-12; max passes per root 5; max EXP terms per table 2126
  ok    conditional OR: worst relative difference from SciPy 8.8e-12
  ok    wherever the two differ by more than 1E-9, our answer solves its equation at least as well (0 differ; SciPy's brentq stops at an ABSOLUTE 1E-13 in psi)
  ok    at most 5 passes per root (bisection on ln psi with Newton steps)
    (12, 5, 8, 30)     MLE 8.558485 (2.087486-41.317307)  scipy 8.558485 (2.087486-41.317307)  212 EXP terms
    (7, 15, 58, 472)   MLE 3.783669 (1.251483-10.363494)  scipy 3.783669 (1.251483-10.363494)  253 EXP terms
    (700, 300, 280, 700) MLE 5.827442 (4.783758-7.113846)  scipy 5.827442 (4.783758-7.113846)  2126 EXP terms
  [A: 93 s]

==============================================================================
B. The PPL routines themselves, in the harness (ppl_run_v3d)
==============================================================================
  ok    es_CPInt (the PPL) vs statsmodels beta: 1569 intervals, worst |diff| 5.9e-15
  ok    es_CPInt mid-P (the PPL) vs brentq: worst |diff| 6.7e-16
    BINOMIAL_CDF + BINOMIAL calls per interval: mean 19.0, max 38  [12 s]
  ok    es_CondOR (the PPL) vs SciPy: 70 tables, worst relative difference 3.5e-08  [5 s]
  ok    es_CondOR states: 0 inside, 1 at the bottom, 2 at the top, 3 empty margin, 4 N > 2000
  ok    es_BinCheck passes with BINOMIAL_CDF(n, p, k)
  ok    es_BinCheck fails when BINOMIAL_CDF reads (n, k, p): exact CIs say "unavailable"
  ok    es_BinCheck / es_CPB / es_CPInt are line for line the same as CICatcher beta 2b's cq* copies (80 lines)

==============================================================================
C. Every CAS template, through desktop Giac (icas), against Python fractions
==============================================================================
    diff(ln(esq/(1-esq)),esq)                 -> 1/esq+1/(1-esq)
    simplify(that - 1/(esq*(1-esq)))          -> 0
  ok    the derivative Giac returns equals 1/(p(1-p)) (SymPy)
  ok    the check template returns "0" (the page then says "the CAS: the difference is 0")
    1/12+1/5+1/8+1/30                    -> 53/120                       fits
  ok    Help F fraction for (12, 5, 8, 30) equals Python Fraction 53/120
    1/40+1/60+1/20+1/80                  -> 5/48                         fits
  ok    Help F fraction for (40, 60, 20, 80) equals Python Fraction 5/48
    2/1+2/11+2/7+2/21                    -> 592/231                      fits
  ok    Help F fraction for (0, 5, 3, 10) equals Python Fraction 592/231
    2/11+2/1+2/7+2/21                    -> 592/231                      fits
  ok    Help F fraction for (5, 0, 3, 10) equals Python Fraction 592/231
    1/1+1/1+1/1+1/1                      -> 4                            fits
  ok    Help F fraction for (1, 1, 1, 1) equals Python Fraction 4
    1/700+1/300+1/280+1/700              -> 41/4200                      fits
  ok    Help F fraction for (700, 300, 280, 700) equals Python Fraction 41/4200
    1/1997+1/1993+1/1999+1/1987          -> 31712695576/15808695152273   fits
  ok    Help F fraction for (1997, 1993, 1999, 1987) equals Python Fraction 31712695576/15808695152273
    2/1+2/1+2/11+2/41                    -> 1908/451                     fits
  ok    Help F fraction for (0, 0, 5, 20) equals Python Fraction 1908/451
    1/999999+1/3+1/7+1/999998            -> 476191047617/999997000002    fits
  ok    Help F fraction for (999999, 3, 7, 999998) equals Python Fraction 476191047617/999997000002
    1-(1/40)^(1/n) and (1/40)^(1/n), n = 1..60, 100..2000: e.g. n = 1 -> 39/40, n = 2 -> 1-sqrt(1/40), n = 20 -> 1-(1/40)^(1/20)
  ok    every closed form evaluates to 1 - 0.025^(1/n) / 0.025^(1/n) (130 templates; longest answer 17 characters; page 6 shows up to 22)
  ok    1.7a: in the Prime's printing n = 2 reads 1-√(1/40) and √(1/40), no "sqrt" anywhere, none longer than desktop Giac's
    (why simplify() is NOT used: simplify(1-(1/40)^(1/3)) -> -(1/40)^(1/3)+1; simplify(1-(1/4)^(1/10)) -> 1-(1/4)^(1/10))

==============================================================================
D. End to end in the harness, cas = giac and cas = fail
==============================================================================

--- D1 module 1, 12 5 8 30, cas = giac ---
  ok    cohort tab, under the OR: '  cond. MLE OR = 8.56  (2.09-41.3): page 7' (orange)
  ok    cohort tab: the two v1.6 notes share one line
  ok    cohort tab: nine lines, the last at y 194 (footer at 224)
  ok    Diagnostic tab: 'Exact: sens (36.1-80.9%), spec (69.7-95.2%)' = statsmodels beta
  ok    Diagnostic tab keeps Wilson on the sensitivity line
  ok    page 7/7 is reached (Left from page 1)
  [page 7, 12 5 8 30]
    y  4 x  8 f3.0 #FFFFFF  EXACT ODDS RATIO
    y  6 x250 f2.0 #B4C8FF  7/7
    y 27 x  6 f1.0 #000000  Sample OR = ad/bc = 9.00, Woolf CI (2.45-33.11)
    y 39 x  6 f1.0 #FF4500  Conditional MLE OR = 8.56  (2.09-41.3)
    y 51 x  6 f1.0 #5A5A5A  (exact CI; R's fisher.test and SciPy report it)
    y 60 x225 f1.0 #FF0000  a = 12
    y152 x  4 f1.0 #5A5A5A  a =
    y152 x 35 f1.0 #5A5A5A  0
    y152 x111 f1.0 #5A5A5A  5
    y152 x186 f1.0 #5A5A5A  10
    y152 x262 f1.0 #5A5A5A  15
    y164 x  6 f1.0 #FF4500  orange = P(a) at the MLE: its mean is your a
    y176 x  6 f1.0 #5A5A5A  grey = P(a) if OR = 1 (page 4); its mean is 6.18
    y188 x  6 f1.0 #000000  Why two? This one fixes all four totals, as
    y200 x  6 f1.0 #000000  Fisher's test does: nearer 1; big N: the same.
    y224 x  8 f1.0 #1E90FF  ←/→ or tap left/right = page, Esc = menu
  ok    page 7: "Conditional MLE OR = 8.56  (2.09-41.3)"
  ok    page 7: the sample OR beside it
  ok    page 7: 10 orange bars (P(a) at the MLE)
  ok    page 7: the null mean (a+b)(a+c)/N = 6.18
  ok    EP_OR_CMLE = 8.558485, EP_OR_CMLE_CI = {2.087486, 41.317307}
  ok    module 1 needs no CAS at all: 0 calls

--- D1 module 1, 12 5 8 30, cas = fail ---
  ok    cohort tab, under the OR: '  cond. MLE OR = 8.56  (2.09-41.3): page 7' (orange)
  ok    cohort tab: the two v1.6 notes share one line
  ok    cohort tab: nine lines, the last at y 194 (footer at 224)
  ok    Diagnostic tab: 'Exact: sens (36.1-80.9%), spec (69.7-95.2%)' = statsmodels beta
  ok    Diagnostic tab keeps Wilson on the sensitivity line
  ok    page 7/7 is reached (Left from page 1)
  ok    page 7: "Conditional MLE OR = 8.56  (2.09-41.3)"
  ok    page 7: the sample OR beside it
  ok    page 7: 10 orange bars (P(a) at the MLE)
  ok    page 7: the null mean (a+b)(a+c)/N = 6.18
  ok    EP_OR_CMLE = 8.558485, EP_OR_CMLE_CI = {2.087486, 41.317307}
  ok    module 1 needs no CAS at all: 0 calls

--- D2 EpiStats2x2: every state of the conditional OR ---
  ok    EpiStats2x2(0, 5, 3, 10) -> page 7: 'MLE 0: your a is the smallest it can be'; 'Conditional MLE OR = 0  (0-6.61)'
  ok    EpiStats2x2(5, 0, 3, 10) -> page 7: 'MLE infinite: your a is the largest it can be'; 'Conditional MLE OR = infinite  (1.70-infinity)'
  ok    EpiStats2x2(0, 0, 5, 20) -> page 7: 'A row or column total is 0: with every table'; 'Conditional MLE OR = undefined (a total is 0)'
  ok    EpiStats2x2(700, 300, 600, 500) -> page 7: 'N > 2000: not computed here, the same cap'; 'Conditional MLE OR = not computed (N > 2000)'
  ok    EpiStats2x2(700, 300, 280, 700) -> page 7: 'orange = P(a) at the MLE: its mean is your a'; 'Conditional MLE OR = 5.83  (4.78-7.11)'

--- D3 module 2, 40 60 20 80: the risk bars ---
  ok    module 2 page 4 shown
  ok    legend names both whiskers
  ok    8 orange whisker lines (2 bars x 4)
  ok    EP_risk_exp_CP {0.3033, 0.5028}, EP_risk_unexp_CP {0.1267, 0.2918} = statsmodels beta

--- D4 module 3, 12 0 8 30 (spec 30/30, PPV 12/12): page 6, cas = giac ---
  ok    page 6 drawn 5 times (open, tap, Down, Up, tap): 5
  [page 6, row 2 picked (cas = giac)]
    y  4 x  8 f3.0 #FFFFFF  EXACT vs WILSON
    y  6 x250 f2.0 #B4C8FF  6/6
    y 28 x  4 f1.0 #000000  Sens 12/20
    y 40 x  4 f1.0 #5A5A5A  60.0%
    y 60 x  4 f1.0 #000000  Spec 30/30
    y 72 x  4 f1.0 #5A5A5A  100.0%
    y 92 x  4 f1.0 #000000  PPV 12/12
    y104 x  4 f1.0 #5A5A5A  100.0%
    y124 x  4 f1.0 #000000  NPV 30/38
    y136 x  4 f1.0 #5A5A5A  78.9%
    y156 x104 f1.0 #5A5A5A  0
    y156 x153 f1.0 #5A5A5A  25
    y156 x202 f1.0 #5A5A5A  50
    y156 x251 f1.0 #5A5A5A  75
    y156 x286 f1.0 #5A5A5A  100%
    y170 x  8 f1.0 #000000  Specificity d/(b+d) = 30/30 = 100.0%
    y182 x  8 f1.0 #000000  Wilson (88.6-100.0%)
    y182 x164 f1.0 #FF4500  exact (88.4-100.0%)
    y194 x  8 f1.0 #008C3C  mid-P (90.5-100.0%)
    y194 x164 f1.0 #FF4500  coverage >= 95 %
    y206 x  8 f1.0 #000000  exact lower = (1/40)^(1/30)
    y206 x244 f1.0 #1E90FF  exact (CAS)
    y224 x  8 f1.0 #1E90FF  tap a row = its numbers, ←/→ = page, Esc = menu
  ok    EP_sens_CP, EP_spec_CP, EP_ppv_CP, EP_npv_CP = statsmodels beta
  ok    panel for Specificity 30/30: Wilson (88.6-100.0%), exact (88.4-100.0%), mid-P (90.5-100.0%)
  ok    CAS line: "exact lower = (1/40)^(1/30)" with the badge "exact (CAS)"
  ok    the CAS is asked once per row with 0 or n, on the tap (1.7a: as string(...)): ['string((1/40)^(1/30))', 'string((1/40)^(1/12))']
  ok    1.7a: no CAS variable assigned, no CAS call of a variable
  ok    the panel follows the picked row (row 1 at open and after the last tap)
  ok    16 orange (exact) bar lines: 4 rows x 4
  ok    module 3 page 1 points to page 6 and reads 1/6

--- D4 module 3, 12 0 8 30 (spec 30/30, PPV 12/12): page 6, cas = fail ---
  ok    page 6 drawn 5 times (open, tap, Down, Up, tap): 5
  [page 6, row 2 picked (cas = fail)]
    y  4 x  8 f3.0 #FFFFFF  EXACT vs WILSON
    y  6 x250 f2.0 #B4C8FF  6/6
    y 28 x  4 f1.0 #000000  Sens 12/20
    y 40 x  4 f1.0 #5A5A5A  60.0%
    y 60 x  4 f1.0 #000000  Spec 30/30
    y 72 x  4 f1.0 #5A5A5A  100.0%
    y 92 x  4 f1.0 #000000  PPV 12/12
    y104 x  4 f1.0 #5A5A5A  100.0%
    y124 x  4 f1.0 #000000  NPV 30/38
    y136 x  4 f1.0 #5A5A5A  78.9%
    y156 x104 f1.0 #5A5A5A  0
    y156 x153 f1.0 #5A5A5A  25
    y156 x202 f1.0 #5A5A5A  50
    y156 x251 f1.0 #5A5A5A  75
    y156 x286 f1.0 #5A5A5A  100%
    y170 x  8 f1.0 #000000  Specificity d/(b+d) = 30/30 = 100.0%
    y182 x  8 f1.0 #000000  Wilson (88.6-100.0%)
    y182 x164 f1.0 #FF4500  exact (88.4-100.0%)
    y194 x  8 f1.0 #008C3C  mid-P (90.5-100.0%)
    y194 x164 f1.0 #FF4500  coverage >= 95 %
    y206 x  8 f1.0 #000000  exact lower = (α/2)^(1/n) = 0.8843
    y206 x244 f1.0 #1E90FF  decimal
    y224 x  8 f1.0 #1E90FF  tap a row = its numbers, ←/→ = page, Esc = menu
  ok    EP_sens_CP, EP_spec_CP, EP_ppv_CP, EP_npv_CP = statsmodels beta
  ok    panel for Specificity 30/30: Wilson (88.6-100.0%), exact (88.4-100.0%), mid-P (90.5-100.0%)
  ok    fallback: "exact lower = (alpha/2)^(1/n) = 0.8843" with the badge "decimal", no error
  ok    the panel follows the picked row (row 1 at open and after the last tap)
  ok    16 orange (exact) bar lines: 4 rows x 4
  ok    module 3 page 1 points to page 6 and reads 1/6

--- D5 Help E and F ---
  ok    Help F opened three times (3)
  [Help F, example table, cas = giac]
    y  4 x  8 f3.0 #FFFFFF  WHERE THE SE COMES FROM
    y 30 x 12 f1.0 #000000  Why is SE(ln OR) = sqrt(1/a+1/b+1/c+1/d)?
    y 43 x 12 f1.0 #000000  A row's log odds: g(p) = ln(p/(1-p)).
    y 56 x 12 f1.0 #C800C8  CAS: g'(p) = 1/p+1/(1-p)
    y 69 x 12 f1.0 #C800C8    = 1/(p(1-p))   (CAS: the difference is 0)
    y 82 x 12 f1.0 #003CC8  Delta method: Var g() is about g'(p)² Var()
    y 95 x 12 f1.0 #003CC8    = 1/(n p(1-p)) = 1/(np) + 1/(n(1-p))
    y108 x 12 f1.0 #003CC8    = 1/cases + 1/non-cases: 1/a + 1/b in row 1.
    y121 x 12 f1.0 #003CC8  The rows are independent: the four terms add.
    y134 x 12 f1.0 #000000  Example table: a = 12, b = 5, c = 8, d = 30
    y147 x 12 f1.0 #000000  Var = 1/12+1/5+1/8+1/30
    y160 x 12 f1.0 #008C3C    = 53/120
    y173 x 12 f1.0 #008C3C  SE = sqrt(53/120) = 0.6646
    y186 x 12 f1.0 #1E90FF  exact (CAS)
    y224 x  8 f1.0 #1E90FF  any key or tap = back
  ok    Help F: the CAS derivative "1/p+1/(1-p)" and its check
  ok    Help F, 12 5 8 30: "= 53/120", "SE = sqrt(53/120) = 0.6646", badge "exact (CAS)"
  ok    Help F, 0 5 3 10: 0.5 added, "= 592/231"
  ok    CAS calls: derivative, check, two fractions -- the second open asked nothing (['string(diff(ln(esq/(1-esq)),esq))', 'string(simplify(diff(ln(esq/(1-esq)),esq)-1/(esq*(1-esq))))', 'string(1/12+1/5+1/8+1/30)', 'string(2/1+2/11+2/7+2/21)'])
  ok    1.7a: every CAS text asks for string(...), every answer arrives quoted, no CAS variable assigned
  ok    Help E: 12 lines
  ok    the Help menu: list form, 16 items, E = "Exact CIs, Exact OR", F = "Where the SE Comes From"
  ok    Help F opened three times (3)
  [Help F, example table, cas = fail]
    y  4 x  8 f3.0 #FFFFFF  WHERE THE SE COMES FROM
    y 30 x 12 f1.0 #000000  Why is SE(ln OR) = sqrt(1/a+1/b+1/c+1/d)?
    y 43 x 12 f1.0 #000000  A row's log odds: g(p) = ln(p/(1-p)).
    y 56 x 12 f1.0 #C800C8  g'(p) = 1/p + 1/(1-p)  (textbook form)
    y 69 x 12 f1.0 #C800C8    = 1/(p(1-p))
    y 82 x 12 f1.0 #003CC8  Delta method: Var g() is about g'(p)² Var()
    y 95 x 12 f1.0 #003CC8    = 1/(n p(1-p)) = 1/(np) + 1/(n(1-p))
    y108 x 12 f1.0 #003CC8    = 1/cases + 1/non-cases: 1/a + 1/b in row 1.
    y121 x 12 f1.0 #003CC8  The rows are independent: the four terms add.
    y134 x 12 f1.0 #000000  Example table: a = 12, b = 5, c = 8, d = 30
    y147 x 12 f1.0 #000000  Var = 1/12+1/5+1/8+1/30
    y160 x 12 f1.0 #008C3C    = 0.441667
    y173 x 12 f1.0 #008C3C  SE = sqrt(0.441667) = 0.6646
    y186 x 12 f1.0 #1E90FF  decimal
    y224 x  8 f1.0 #1E90FF  any key or tap = back
  ok    fallback: the textbook form, "= 0.441667", the badge "decimal"
  ok    with the CAS failing it is asked 3 times in the run, not on every open (3)
  ok    Help E: 12 lines
  ok    the Help menu: list form, 16 items, E = "Exact CIs, Exact OR", F = "Where the SE Comes From"
  ok    nothing draws on G0 except BLIT_P(G0, ...)
  ok    1.7a: CAS( appears on one code line only, inside es_Cas, in the EXPR form (line [940])
  ok    es_Cas is called only from es_Help_SE and es_XCas (events): ['es_Help_SE', 'es_XCas']
  ok    layout on the new and changed pages (6.5/8/9.5 px a character): no problems

==============================================================================
E. The v1.6 verification suite (verify_EpiStats.py) against v1.7b, on harness v3d
==============================================================================
  --- layout audit over every page shown (262) ---
    0 warnings (estimates: 6.5/8/9.5 px per character for fonts 1/2/3)
  RESULT: ALL CHECKS PASSED
  ok    verify_EpiStats.py (v1.6: 120 formula checks, 83 harness checks, 28 scripted runs) passes on v1.7b, harness v3d  [31 s]
  V3D-SHIM: 27 harness runs, 0 scientific STRING results []
  ok    1.7b: the v1.6 suite's 27 harness runs on v1.7b print no scientific STRING (v1.7a: 7, in es_FmtCI, es_ForestDraw and es_RuleTxt)

==============================================================================
G. 1.7a: v1.7 in harness v3d, and v1.7a against v1.7 (kept from verify_EpiStats_v1.7a.py)
==============================================================================
  ok    1.7a: v1.7 in v3d shows its own CAS text on Help F as if it were the answer ("g'(p) = diff(ln(p/(1-p)),p)", "= 1/12+1/5+1/8+1/30") -- the emulator's finding

G. v1.7a: v1.7 in harness v3b, and v1.7a against v1.7
  ok    v1.7a: routines that differ from v1.7: ['EpiStats', 'es_Cas', 'es_Help_About'] (want EpiStats, es_Cas, es_Help_About)
  ok    v1.7a: outside those routines only comments differ
  ok    v1.7a: the #PYTHON blocks are unchanged
  routines that differ from v1.7: EpiStats, es_Cas, es_Help_About; everything else differs only in comments

==============================================================================
F. 1.7b: the round-2 string rules, v1.7b against v1.7a, both in harness v3d
==============================================================================

F1. es_Cas, called directly
    1/2+1/3 -> '5/6'; 1/0 -> '' (the CAS said "±∞"); 0/0 -> ''; a syntax error -> ''
  ok    1.7b: es_Cas (cas=giac): 5/6 with esCasOK 1; the infinity, undef and a syntax error give "" with esCasOK 0
  ok    1.7b: es_Cas with the CAS failing returns "" with esCasOK 0 and no error ('', None)
  ok    1.7b: v1.7a's es_Cas with the CAS failing stops: INSTRING: Invalid input (the first argument is an empty string -- emul

F2. every CAS page with cas = fail: v1.7b against v1.7a
    v1.7b module 3 page 6: exact lower = (α/2)^(1/n) = 0.8843
  ok    1.7b: module 3 page 6, Spec 30/30, CAS failing: "exact lower = (alpha/2)^(1/n) = 0.8843", "decimal", no error
    v1.7a module 3 page 6: PPLError: INSTRING: Invalid input (the first argument is an empty string -- emulator, 17 Oct 2026) [EpiStats > es_Module_SensSpec > es_ExactPage > es_XCas > es_Cas]
  ok    1.7b: v1.7a on the same run stops: PPLError: INSTRING: Invalid input (the first argument is an empty string -- emulator, 17 Oct 2026) [EpiStats >
    v1.7b Help F:   = 0.441667 | SE = sqrt(0.441667) = 0.6646 | decimal | any key or tap = back
  ok    1.7b: Help F, CAS failing: the textbook form, "= 0.441667", "SE = sqrt(0.441667) = 0.6646", "decimal", no error
    v1.7a Help F: PPLError: INSTRING: Invalid input (the first argument is an empty string -- emulator, 17 Oct 2026) [EpiStats > es_Module_Help > es_Help_SE > es_Cas]
  ok    1.7b: v1.7a on the same run stops: PPLError: INSTRING: Invalid input (the first argument is an empty string -- emulator, 17 Oct 2026) [EpiStats >

F3. tables that push a fixed-decimal display below 10^-d (cas = giac unless said)
    module 1, 1 2999 50 50 (pages 1-7, the three boxes, four tiles)
      v1.7a: ran; scientific in ['es_Arith', 'es_FmtCI', 'es_FmtPct', 'es_Mosaic'], e.g. ['2.46ᴇ-3', '3.2ᴇ-2', '3.333ᴇ-4', '3.334ᴇ-4']
      v1.7b: ran; scientific: 0
  ok    1.7b: module 1, 1 2999 50 50 (pages 1-7, the three boxes, four tiles) -- v1.7a prints scientific in es_Arith, es_FmtCI, es_FmtPct, es_Mosaic
  ok    1.7b: module 1, 1 2999 50 50 (pages 1-7, the three boxes, four tiles) -- v1.7b: no scientific STRING, and 'OR = ad/bc < 0.001   (0.00-0.00)'; 'OR = (a×d)/(b×c) = (1×50)/(2999×50) = 0.00'; '   = 0.00 to 0.00'; '   = 0.000 / 0.500 = 0.001'; 'row % = 0.0 %   (all rows: 1.6 %)'; 'Sample OR = ad/bc = 0.000333, Woolf CI (0.00-0.00)'
  ok    1.7b: module 1, 1 2999 50 50 (pages 1-7, the three boxes, four tiles) -- the same pages as v1.7a; every word that changed was a scientific number
    module 1, 500 500 502 501 (the paired reading)
      v1.7a: ran; scientific in ['es_Arith', 'es_FmtPct', 'es_ReadPage'], e.g. ['-1.0ᴇ-1', '-4.985ᴇ-4', '-5.0ᴇ-2', '-9.965ᴇ-4']
      v1.7b: ran; scientific: 0
  ok    1.7b: module 1, 500 500 502 501 (the paired reading) -- v1.7a prints scientific in es_Arith, es_FmtPct, es_ReadPage
  ok    1.7b: module 1, 500 500 502 501 (the paired reading) -- v1.7b: no scientific STRING, and 'McNemar χ² (cont. corr.) = 0.001, p = 0.9748'; '^Youden J = sens \\+ spec - 1 = -?0\\.000$'; '^Kappa = \\(po-pe\\)/\\(1-pe\\) = -?0\\.000 \\(Poor\\)$'; "Yule's Q = (ad-bc)/(ad+bc) = -0.001"
  ok    1.7b: module 1, 500 500 502 501 (the paired reading) -- the same pages as v1.7a; every word that changed was a scientific number
    module 1, 1 1 1 997 (page 7, the mosaic)
      v1.7a: ran; scientific in ['es_CORPage', 'es_Mosaic'], e.g. ['4.00ᴇ-3']
      v1.7b: ran; scientific: 0
  ok    1.7b: module 1, 1 1 1 997 (page 7, the mosaic) -- v1.7a prints scientific in es_CORPage, es_Mosaic
  ok    1.7b: module 1, 1 1 1 997 (page 7, the mosaic) -- v1.7b: no scientific STRING, and 'grey = P(a) if OR = 1 (page 4); its mean is 0.00'; 'a: Exposed+, Outcome+: O = 1, E = 0.00'
  ok    1.7b: module 1, 1 1 1 997 (page 7, the mosaic) -- the same pages as v1.7a; every word that changed was a scientific number
    module 1, 20000 20000 20000 20001 (a mosaic tile)
      v1.7a: ran; scientific in ['es_FmtPct', 'es_Sgn'], e.g. ['1.2ᴇ-3', '2.5ᴇ-3', '3.54ᴇ-3']
      v1.7b: ran; scientific: 0
  ok    1.7b: module 1, 20000 20000 20000 20001 (a mosaic tile) -- v1.7a prints scientific in es_FmtPct, es_Sgn
  ok    1.7b: module 1, 20000 20000 20000 20001 (a mosaic tile) -- v1.7b: no scientific STRING, and 'adj. residual = +0.00: about as expected'
  ok    1.7b: module 1, 20000 20000 20000 20001 (a mosaic tile) -- the same pages as v1.7a; every word that changed was a scientific number
    module 1, a count typed as 0.004
      v1.7a: ran; scientific in ['es_Grid'], e.g. ['4.00ᴇ-3']
      v1.7b: ran; scientific: 0
  ok    1.7b: module 1, a count typed as 0.004 -- v1.7a prints scientific in es_Grid
  ok    1.7b: module 1, a count typed as 0.004 -- v1.7b: no scientific STRING, and 'Counts are whole numbers: 0.00 was rounded to the nearest one.'
  ok    1.7b: module 1, a count typed as 0.004 -- the same pages as v1.7a; every word that changed was a scientific number
    module 2, 1 2999 1 999 (risk bars, NNT)
      v1.7a: ran; scientific in ['es_FmtPct', 'es_NNTIcons', 'es_RiskBars'], e.g. ['-6.7ᴇ-2', '3.3ᴇ-2', '5.0ᴇ-2', '6.667ᴇ-4']
      v1.7b: ran; scientific: 0
  ok    1.7b: module 2, 1 2999 1 999 (risk bars, NNT) -- v1.7a prints scientific in es_FmtPct, es_NNTIcons, es_RiskBars
  ok    1.7b: module 2, 1 2999 1 999 (risk bars, NNT) -- v1.7b: no scientific STRING, and 'NNT = 1/0.000667 = 1500.00, round up: 1500'; 'the outcome is rare here (Ru = 0.1 %).'
  ok    1.7b: module 2, 1 2999 1 999 (risk bars, NNT) -- the same pages as v1.7a; every word that changed was a scientific number
    module 2, 3 2997 1 2999 (OR vs RR, rare)
      v1.7a: ran; scientific in ['es_FmtCI', 'es_FmtPct', 'es_NNTIcons', 'es_RiskBars'], e.g. ['-6.4ᴇ-2', '3.3ᴇ-2', '6.667ᴇ-4', '6.7ᴇ-2']
      v1.7b: ran; scientific: 0
  ok    1.7b: module 2, 3 2997 1 2999 (OR vs RR, rare) -- v1.7a prints scientific in es_RiskBars
  ok    1.7b: module 2, 3 2997 1 2999 (OR vs RR, rare) -- v1.7b: no scientific STRING, and 'OR = 3.00 overstates RR = 3.00 by only 0 %:'; 'the outcome is rare here (Ru = 0.0 %).'
  ok    1.7b: module 2, 3 2997 1 2999 (OR vs RR, rare) -- the same pages as v1.7a; every word that changed was a scientific number
    module 2, 1 999 600 401 (OR, RR below 0.005)
      v1.7a: ran; scientific in ['es_FmtCI', 'es_RiskBars'], e.g. ['1.67ᴇ-3', '2.35ᴇ-4', '4.77ᴇ-3', '6.69ᴇ-4']
      v1.7b: ran; scientific: 0
  ok    1.7b: module 2, 1 999 600 401 (OR, RR below 0.005) -- v1.7a prints scientific in es_FmtCI, es_RiskBars
  ok    1.7b: module 2, 1 999 600 401 (OR, RR below 0.005) -- v1.7b: no scientific STRING, and 'OR = 0.00 exaggerates RR = 0.00 by 60 % here'; '95% CI: (0.00-0.00)'; '95% CI: (0.00-0.01)'
  ok    1.7b: module 2, 1 999 600 401 (OR, RR below 0.005) -- the same pages as v1.7a; every word that changed was a scientific number
    module 3, 1 2999 2 999 (prevalence page)
      v1.7a: ran; scientific in ['es_FmtCI', 'es_FmtPct', 'es_PctTxt', 'es_PrevDraw'], e.g. ['1.7ᴇ-3', '3.3ᴇ-2', '5.9ᴇ-3', '7.5ᴇ-2']
      v1.7b: ran; scientific: 0
  ok    1.7b: module 3, 1 2999 2 999 (prevalence page) -- v1.7a prints scientific in es_FmtCI, es_FmtPct, es_PctTxt, es_PrevDraw
  ok    1.7b: module 3, 1 2999 2 999 (prevalence page) -- v1.7b: no scientific STRING, and 'PPV = 0.0 %'; 'sample: 0.1 %'; 'p = 0.1 %'
  ok    1.7b: module 3, 1 2999 2 999 (prevalence page) -- the same pages as v1.7a; every word that changed was a scientific number
    module 3, 1 1 2999 2999 (the ROC square)
      v1.7a: ran; scientific in ['es_FmtCI', 'es_FmtPct', 'es_PrevDraw', 'es_ROCPage'], e.g. ['1.7ᴇ-3', '3.333ᴇ-4', '3.3ᴇ-2', '5.9ᴇ-3']
      v1.7b: ran; scientific: 0
  ok    1.7b: module 3, 1 1 2999 2999 (the ROC square) -- v1.7a prints scientific in es_FmtCI, es_PrevDraw, es_ROCPage
  ok    1.7b: module 3, 1 1 2999 2999 (the ROC square) -- v1.7b: no scientific STRING, and 'TPR = sens = 0.0 %'; 'FPR = 1-spec = 0.0 %'; ' = 0.000/0.000 = 1.00'; 'Youden J = sens + spec - 1 = 0.000:'
  ok    1.7b: module 3, 1 1 2999 2999 (the ROC square) -- the same pages as v1.7a; every word that changed was a scientific number
    module 3, 0 1 60000 1, CAS failing (page 6 decimal)
      v1.7a: PPLError: INSTRING: Invalid input (the first argument is an empty string -- emulator, 17 Oct 2026) [EpiStats > es_Module_SensSpec > es_ExactPage > es_XCas > es_Cas]; scientific in ['es_FmtCI', 'es_FmtPct', 'es_PctTxt'], e.g. ['1.7ᴇ-3', '2.33ᴇ-7', '2.98ᴇ-4', '2.9ᴇ-4']
      v1.7b: ran; scientific: 0
  ok    1.7b: module 3, 0 1 60000 1, CAS failing (page 6 decimal) -- v1.7a stops (INSTRING)
  ok    1.7b: module 3, 0 1 60000 1, CAS failing (page 6 decimal) -- v1.7b: no scientific STRING, and 'exact upper = 1 - (α/2)^(1/n) = 0.0001'
    module 5 OR, opposite strata (forest: Breslow-Day p)
      v1.7a: ran; scientific in ['es_FmtCI', 'es_ForestDraw'], e.g. ['3.86ᴇ-6', '4.67ᴇ-3']
      v1.7b: ran; scientific: 0
  ok    1.7b: module 5 OR, opposite strata (forest: Breslow-Day p) -- v1.7a prints scientific in es_FmtCI, es_ForestDraw
  ok    1.7b: module 5 OR, opposite strata (forest: Breslow-Day p) -- v1.7b: no scientific STRING, and "Breslow-Day p < 0.01: are the strata's ORs alike?"
  ok    1.7b: module 5 OR, opposite strata (forest: Breslow-Day p) -- the same pages as v1.7a; every word that changed was a scientific number
    module 5 OR, 1 1 1 1 and 500 500 500 500 (forest weights)
      v1.7a: ran; scientific in ['es_ForestDraw'], e.g. ['2ᴇ-1']
      v1.7b: ran; scientific: 0
  ok    1.7b: module 5 OR, 1 1 1 1 and 500 500 500 500 (forest weights) -- v1.7a prints scientific in es_ForestDraw
  ok    1.7b: module 5 OR, 1 1 1 1 and 500 500 500 500 (forest weights) -- v1.7b: no scientific STRING, and 'w = 0 %'; 'w = 100 %'
  ok    1.7b: module 5 OR, 1 1 1 1 and 500 500 500 500 (forest weights) -- the same pages as v1.7a; every word that changed was a scientific number
    module 5 OR, crude and M-H 0.02 % apart
      v1.7a: ran; scientific in ['es_RuleTxt'], e.g. ['2ᴇ-2']
      v1.7b: ran; scientific: 0
  ok    1.7b: module 5 OR, crude and M-H 0.02 % apart -- v1.7a prints scientific in es_RuleTxt
  ok    1.7b: module 5 OR, crude and M-H 0.02 % apart -- v1.7b: no scientific STRING, and 'Crude and M-H differ by 0 % (of the M-H).'
  ok    1.7b: module 5 OR, crude and M-H 0.02 % apart -- the same pages as v1.7a; every word that changed was a scientific number
    module 5 RR, M-H chi-square 0.0006
      v1.7a: ran; scientific in ['es_MHSummary', 'es_RuleTxt'], e.g. ['6.223ᴇ-4', '8ᴇ-5']
      v1.7b: ran; scientific: 0
  ok    1.7b: module 5 RR, M-H chi-square 0.0006 -- v1.7a prints scientific in es_MHSummary, es_RuleTxt
  ok    1.7b: module 5 RR, M-H chi-square 0.0006 -- v1.7b: no scientific STRING, and 'M-H χ² (1 df) = 0.001, p = 0.9801'
  ok    1.7b: module 5 RR, M-H chi-square 0.0006 -- the same pages as v1.7a; every word that changed was a scientific number
    Help F with EP_table = 1E9 each
      v1.7a: ran; scientific in ['es_Help_SE'], e.g. ['6.3246ᴇ-5']
      v1.7b: ran; scientific: 0
  ok    1.7b: Help F with EP_table = 1E9 each -- v1.7a prints scientific in es_Help_SE
  ok    1.7b: Help F with EP_table = 1E9 each -- v1.7b: no scientific STRING, and 'SE = sqrt(1/250000000) = 0.0001'
  ok    1.7b: Help F with EP_table = 1E9 each -- the same pages as v1.7a; every word that changed was a scientific number

F4. every STRING(x, 2, d) in v1.7b: rounded first, or unable to reach 0 < |x| < 10^-d
    154 calls: 86 rounded first (STRING(ROUND(x, d), 2, d)), 68 left as they were:
        1  Re as the "common" risk: shown only when Re >= 0.1, so 10.0 % or more
       52  a count, a total or a loop index: a whole number, so 0 or at least 1 (d = 0)
        3  an axis label: a multiple of a 1-2-5 step with d = -FLOOR(LOG(step)), so 0 or at least 10^-d
        2  es_Cnt: a whole count (d = 0), or a Haldane half or a person-time of 0.1 or more (d = 1; below 0.1 es_SigTxt, 1.7d E11)
        6  guarded by the helper itself (es_PTxt, es_FmtNum, es_SigTxt: values below 10^-d take another form)
        1  the Breslow-Day p in the "homogeneous" branch: 0.05 or more
        1  the NNT page's |RD|: this branch runs only when |RD| >= 0.001
        1  the NNT, 1/|RD| with |RD| <= 1: 1 or more
        1  the mosaic's "all" bar: drawn only when its tile is 12 px or more of 112, so 10 % or more
  ok    1.7b: every bare STRING(x, 2, d) has a reason it cannot print scientific (unlisted: none; listed but gone: none)
  ok    1.7b: v1.7a has 155 such calls; v1.7b 154 (the forest plot's Breslow-Day p now goes through es_PTxt), 86 of them rounded first

F5. INSTRING, LEFT, MID and RIGHT in v1.7b (code only)
      870  IF LEFT(eqS, 1) == "<" OR LEFT(eqS, 1) == ">" THEN RETURN eqL + " " + eqS; END;
      882  IF LEFT(lbS, 1) == "<" OR LEFT(lbS, 1) == ">" THEN RETURN lbL; END;
      893  IF LEFT(evS, 1) == "<" OR LEFT(evS, 1) == ">" THEN RETURN evS; END;
      947  IF LEFT(cR, 1) == CHAR(34) AND MID(cR, DIM(cR), 1) == CHAR(34) THEN
      948  cR := MID(cR, 2, DIM(cR) - 2);
      956  IF INSTRING(cR, "undef") > 0 OR INSTRING(cR, "rror") > 0 OR INSTRING(cR, CHAR(8734)) > 0 THEN cR := ""; END;
      973  IF MID(swS, swI, swL) == swA THEN
      976  swR := swR + MID(swS, swI, 1); swI := swI + 1;
  ok    1.7b: the one INSTRING line ([956]) runs only inside IF DIM(cR) > 0
  ok    1.7b: every LEFT counts 1 (7), every MID starts at 2, DIM(cR) >= 2 or swI >= 1 (4), no RIGHT

==============================================================================
H. 1.7c: no "=" before a relation; the chi-square p never 0 (v1.7c against v1.7b, harness v3d)
==============================================================================
    module 1, cohort 1 99999 50 50
      v1.7b only: ['OR = ad/bc = < 0.001   (0.00-0.00)', 'RR = Re/Ru = < 0.001   (0.00-0.00)']
      v1.7c only: ['OR = ad/bc < 0.001   (0.00-0.00)', 'RR = Re/Ru < 0.001   (0.00-0.00)']
  ok    1.7c: module 1, cohort 1 99999 50 50 -- v1.7c reads 'RR = Re/Ru < 0.001   (0.00-0.00)'; 'OR = ad/bc < 0.001   (0.00-0.00)' (v1.7b: 'RR = Re/Ru = < 0.001   (0.00-0.00)'; 'OR = ad/bc = < 0.001   (0.00-0.00)')
  ok    1.7c: module 1, cohort 1 99999 50 50 -- every other text is v1.7b's, or v1.7b's without "= <"
    module 1, 1 2000 2000 1 (page 7)
      v1.7b only: ['LR+ = sens/(1-spec) = < 0.01,  LR- = 2000.000', 'OR = ad/bc = < 0.001   (0.00-0.00)', 'RR = Re/Ru = < 0.001   (0.00-0.00)', 'Sample OR = ad/bc = < 0.000001, Woolf CI (0.00-0.00)']
      v1.7c only: ['LR+ = sens/(1-spec) < 0.01,  LR- = 2000.000', 'OR = ad/bc < 0.001   (0.00-0.00)', 'RR = Re/Ru < 0.001   (0.00-0.00)', 'Sample OR = ad/bc < 0.000001, Woolf CI (0.00-0.00)']
  ok    1.7c: module 1, 1 2000 2000 1 (page 7) -- v1.7c reads 'Sample OR = ad/bc < 0.000001, Woolf CI (0.00-0.00)'; 'LR+ = sens/(1-spec) < 0.01,  LR- = 2000.000' (v1.7b: 'Sample OR = ad/bc = < 0.000001, Woolf CI (0.00-0.00)'; 'LR+ = sens/(1-spec) = < 0.01,  LR- = 2000.000')
  ok    1.7c: module 1, 1 2000 2000 1 (page 7) -- every other text is v1.7b's, or v1.7b's without "= <"
    module 1, 9999 1 1 9999 (diagnostic tab)
      v1.7b only: ['LR+ = sens/(1-spec) = 9999,  LR- = < 0.001']
      v1.7c only: ['LR+ = sens/(1-spec) = 9999,  LR- < 0.001']
  ok    1.7c: module 1, 9999 1 1 9999 (diagnostic tab) -- v1.7c reads 'LR+ = sens/(1-spec) = 9999,  LR- < 0.001' (v1.7b: 'LR+ = sens/(1-spec) = 9999,  LR- = < 0.001')
  ok    1.7c: module 1, 9999 1 1 9999 (diagnostic tab) -- every other text is v1.7b's, or v1.7b's without "= <"
    module 1, 80 10 10 80 (chi-square 113)
      v1.7b only: ['0.00000 *']
      v1.7c only: ['< 0.00001 *']
  ok    1.7c: module 1, 80 10 10 80 (chi-square 113) -- v1.7c reads '< 0.00001 *' (v1.7b: '0.00000 *')
  ok    1.7c: module 1, 80 10 10 80 (chi-square 113) -- every other text is v1.7b's, or v1.7b's without "= <"
    module 2, 1 99999 50 50 (page 2)
      v1.7b only: ['OR = (a×d)/(b×c) =', 'RR = Re / Ru =']
      v1.7c only: ['OR = (a×d)/(b×c)', 'RR = Re / Ru']
  ok    1.7c: module 2, 1 99999 50 50 (page 2) -- v1.7c reads 'RR = Re / Ru'; 'OR = (a×d)/(b×c)' (v1.7b: 'RR = Re / Ru ='; 'OR = (a×d)/(b×c) =')
  ok    1.7c: module 2, 1 99999 50 50 (page 2) -- every other text is v1.7b's, or v1.7b's without "= <"
    module 2, 5002 4998 5000 5000 (ERR 0.0004)
      v1.7b only: ['ERR = RR - 1 =']
      v1.7c only: ['ERR = RR - 1']
  ok    1.7c: module 2, 5002 4998 5000 5000 (ERR 0.0004) -- v1.7c reads 'ERR = RR - 1' (v1.7b: 'ERR = RR - 1 =')
  ok    1.7c: module 2, 5002 4998 5000 5000 (ERR 0.0004) -- every other text is v1.7b's, or v1.7b's without "= <"
    module 3, 1 1000 999 10 (ROC, page 3)
      v1.7b only: [' = 0.001/0.990 = < 0.01', 'p=0.0000']
      v1.7c only: [' = 0.001/0.990 < 0.01', 'p<0.0001']
  ok    1.7c: module 3, 1 1000 999 10 (ROC, page 3) -- v1.7c reads ' = 0.001/0.990 < 0.01'; 'p<0.0001' (v1.7b: ' = 0.001/0.990 = < 0.01'; 'p=0.0000')
  ok    1.7c: module 3, 1 1000 999 10 (ROC, page 3) -- every other text is v1.7b's, or v1.7b's without "= <"
    module 3, 9999 1 1 9999 (ROC)
      v1.7b only: [' = 0.000/1.000 = < 0.001', 'p=0.0000']
      v1.7c only: [' = 0.000/1.000 < 0.001', 'p<0.0001']
  ok    1.7c: module 3, 9999 1 1 9999 (ROC) -- v1.7c reads ' = 0.000/1.000 < 0.001' (v1.7b: ' = 0.000/1.000 = < 0.001')
  ok    1.7c: module 3, 9999 1 1 9999 (ROC) -- every other text is v1.7b's, or v1.7b's without "= <"
    module 4, 1 50 200000 100
      v1.7b only: ['= < 0.01 per 1,000 person-time', 'IRR = IRe / IRu =']
      v1.7c only: ['< 0.01 per 1,000 person-time', 'IRR = IRe / IRu']
  ok    1.7c: module 4, 1 50 200000 100 -- v1.7c reads '< 0.01 per 1,000 person-time'; 'IRR = IRe / IRu' (v1.7b: '= < 0.01 per 1,000 person-time'; 'IRR = IRe / IRu =')
  ok    1.7c: module 4, 1 50 200000 100 -- every other text is v1.7b's, or v1.7b's without "= <"
    module 4, 10001 10000 1E9 1E9
      v1.7b only: ['= < 0.001 per 1,000 PT', 'ERR = IRR - 1 =', 'IRD = IRe - IRu =']
      v1.7c only: ['< 0.001 per 1,000 PT', 'ERR = IRR - 1', 'IRD = IRe - IRu']
  ok    1.7c: module 4, 10001 10000 1E9 1E9 -- v1.7c reads '< 0.001 per 1,000 PT'; 'ERR = IRR - 1'; 'IRD = IRe - IRu' (v1.7b: '= < 0.001 per 1,000 PT'; 'ERR = IRR - 1 ='; 'IRD = IRe - IRu =')
  ok    1.7c: module 4, 10001 10000 1E9 1E9 -- every other text is v1.7b's, or v1.7b's without "= <"
    module 4, 100 0 1 1 (NNT 0.01)
      v1.7b only: ['PT-NNH = 1/IRD =']
      v1.7c only: ['PT-NNH = 1/IRD']
  ok    1.7c: module 4, 100 0 1 1 (NNT 0.01) -- v1.7c reads 'PT-NNH = 1/IRD' (v1.7b: 'PT-NNH = 1/IRD =')
  ok    1.7c: module 4, 100 0 1 1 (NNT 0.01) -- every other text is v1.7b's, or v1.7b's without "= <"
    module 5 OR, two strata 1 9999 1000 1000
      v1.7b only: ['Crude OR = < 0.001  (0.00-0.00)', 'M-H OR = < 0.001  (0.00-0.00)', 'M-H OR = < 0.001  (0.00-0.00), both', 'OR₁ = < 0.001  (0.00-0.00)', 'OR₂ = < 0.001  (0.00-0.00)']
      v1.7c only: ['Crude OR < 0.001  (0.00-0.00)', 'M-H OR < 0.001  (0.00-0.00)', 'M-H OR < 0.001  (0.00-0.00), both', 'OR₁ < 0.001  (0.00-0.00)', 'OR₂ < 0.001  (0.00-0.00)']
  ok    1.7c: module 5 OR, two strata 1 9999 1000 1000 -- v1.7c reads 'OR₁ < 0.001  (0.00-0.00)'; 'Crude OR < 0.001  (0.00-0.00)'; 'M-H OR < 0.001  (0.00-0.00)'; 'M-H OR < 0.001  (0.00-0.00), both' (v1.7b: 'OR₁ = < 0.001  (0.00-0.00)'; 'Crude OR = < 0.001  (0.00-0.00)'; 'M-H OR = < 0.001  (0.00-0.00)'; 'M-H OR = < 0.001  (0.00-0.00), both')
  ok    1.7c: module 5 OR, two strata 1 9999 1000 1000 -- every other text is v1.7b's, or v1.7b's without "= <"
    module 6, two proportions, power 0.5001
      v1.7b only: ['z_α/2 = 1.960   z_β = < 0.001']
      v1.7c only: ['z_α/2 = 1.960   z_β < 0.001']
  ok    1.7c: module 6, two proportions, power 0.5001 -- v1.7c reads 'z_α/2 = 1.960   z_β < 0.001' (v1.7b: 'z_α/2 = 1.960   z_β = < 0.001')
  ok    1.7c: module 6, two proportions, power 0.5001 -- every other text is v1.7b's, or v1.7b's without "= <"
    module 6, two proportions, power 0.4999
      v1.7b only: ['z_α/2 = 1.960   z_β = > -0.001']
      v1.7c only: ['z_α/2 = 1.960   z_β > -0.001']
  ok    1.7c: module 6, two proportions, power 0.4999 -- v1.7c reads 'z_β > -0.001' (v1.7b: 'z_β = > -0.001')
  ok    1.7c: module 6, two proportions, power 0.4999 -- every other text is v1.7b's, or v1.7b's without "= <"
    module 6, two means, delta 0.0001
      v1.7b only: ["Effect size (Cohen's d) = < 0.001"]
      v1.7c only: ["Effect size (Cohen's d) < 0.001"]
  ok    1.7c: module 6, two means, delta 0.0001 -- v1.7c reads "Effect size (Cohen's d) < 0.001" (v1.7b: "Effect size (Cohen's d) = < 0.001")
  ok    1.7c: module 6, two means, delta 0.0001 -- every other text is v1.7b's, or v1.7b's without "= <"
    module 6, one proportion, alpha 0.99999
      v1.7b only: ['z_α/2 = < 0.0001']
      v1.7c only: ['z_α/2 < 0.0001']
  ok    1.7c: module 6, one proportion, alpha 0.99999 -- v1.7c reads 'z_α/2 < 0.0001' (v1.7b: 'z_α/2 = < 0.0001')
  ok    1.7c: module 6, one proportion, alpha 0.99999 -- every other text is v1.7b's, or v1.7b's without "= <"
    module 6, one mean, alpha 0.99999
      v1.7b only: ['z_α/2 = < 0.0001']
      v1.7c only: ['z_α/2 < 0.0001']
  ok    1.7c: module 6, one mean, alpha 0.99999 -- v1.7c reads 'z_α/2 < 0.0001' (v1.7b: 'z_α/2 = < 0.0001')
  ok    1.7c: module 6, one mean, alpha 0.99999 -- every other text is v1.7b's, or v1.7b's without "= <"

  H2. the chi-square p (1 df), as the lower normal tail
  ok    1.7c: 2 NORMALD_CDF(0, 1, -sqrt(x)) = chi2.sf(x, 1) to 1E-12 relative, x from 0 to 1000 (13 values)
    doubles:
      [30, 10, 10, 30]  chi-square 20.0, SciPy p 7.7e-06:  v1.7b ['< 0.00001 *', '0.00002 *'];  v1.7c ['< 0.00001 *', '0.00002 *']
      [40, 10, 10, 40]  chi-square 36.0, SciPy p 2e-09:  v1.7b ['< 0.00001 *', '< 0.00001 *'];  v1.7c ['< 0.00001 *', '< 0.00001 *']
      [60, 10, 10, 60]  chi-square 71.4, SciPy p 2.9e-17:  v1.7b ['0.00000 *', '< 0.00001 *'];  v1.7c ['< 0.00001 *', '< 0.00001 *']
      [80, 10, 10, 80]  chi-square 108.9, SciPy p 1.7e-25:  v1.7b ['0.00000 *', '0.00000 *'];  v1.7c ['< 0.00001 *', '< 0.00001 *']
  ok    1.7c (doubles): the 2 x 2 page's two p-values never read "0.00000" for a chi-square from 20 to 109 ("< 0.00001 *" from 36 up)
      v1.7b read "0.00000 *" in 2 of the 4
    the Prime's 12 digits:
      [30, 10, 10, 30]  chi-square 20.0, SciPy p 7.7e-06:  v1.7b ['< 0.00001 *', '0.00002 *'];  v1.7c ['< 0.00001 *', '0.00002 *']
      [40, 10, 10, 40]  chi-square 36.0, SciPy p 2e-09:  v1.7b ['< 0.00001 *', '< 0.00001 *'];  v1.7c ['< 0.00001 *', '< 0.00001 *']
      [60, 10, 10, 60]  chi-square 71.4, SciPy p 2.9e-17:  v1.7b ['0.00000 *', '0.00000 *'];  v1.7c ['< 0.00001 *', '< 0.00001 *']
      [80, 10, 10, 80]  chi-square 108.9, SciPy p 1.7e-25:  v1.7b ['0.00000 *', '0.00000 *'];  v1.7c ['< 0.00001 *', '< 0.00001 *']
  ok    1.7c (12 digits): the 2 x 2 page's two p-values never read "0.00000" for a chi-square from 20 to 109 ("< 0.00001 *" from 36 up)
      v1.7b read "0.00000 *" in 2 of the 4
  ok    1.7c (12 digits): v1.7b read "0.00000 *" (1 - CHISQUARE_CDF exactly 0) where a smaller chi-square read "< 0.00001 *" (2 of the 4 tables)
  ok    1.7c: five ordinary tables (chi-square below 50): module 1 is unchanged, every page

  H3. a sweep
    54 tables x 3 modules: 102 identical, 60 differ only as above, 0 otherwise
      "0.00000 *"  ->  "< 0.00001 *"
      "OR = (a×d)/(b×c) ="  ->  "OR = (a×d)/(b×c)"
      "OR = ad/bc = < 0.001   (0.00-0.00)"  ->  "OR = ad/bc < 0.001   (0.00-0.00)"
      "OR = ad/bc = < 0.001   (0.00-0.01)"  ->  "OR = ad/bc < 0.001   (0.00-0.01)"
      "RR = Re / Ru ="  ->  "RR = Re / Ru"
      "RR = Re/Ru = < 0.001   (0.00-0.00)"  ->  "RR = Re/Ru < 0.001   (0.00-0.00)"
      "p=0.0000"  ->  "p<0.0001"
      "p=< 0.0001"  ->  "p<0.0001"
      (8 distinct rewritten lines)
  ok    1.7c: 162 runs of modules 1, 2 and 3: v1.7c is v1.7b without "= <" (and the p-value and label forms above), nothing else

  H4. es_EqLb, es_EqV, es_EqTxt
  ok    1.7c: es_EqLb('IRR = IRe / IRu', '1.250') = "IRR = IRe / IRu ="
  ok    1.7c: es_EqLb('IRR = IRe / IRu', '< 0.001') = "IRR = IRe / IRu"
  ok    1.7c: es_EqLb('ERR = IRR - 1', '> -0.001') = "ERR = IRR - 1"
  ok    1.7c: es_EqLb('ERR = IRR - 1', '') = "ERR = IRR - 1 ="
  ok    1.7c: es_EqV('0.50',) = "= 0.50"
  ok    1.7c: es_EqV('< 0.01',) = "< 0.01"
  ok    1.7c: es_EqV('',) = "= "
  ok    1.7c: es_EqTxt('RR = Re/Ru', '< 0.001') = "RR = Re/Ru < 0.001"
  ok    1.7c: es_EqTxt('RR = Re/Ru', '3.000') = "RR = Re/Ru = 3.000"

  H5. the code
    code lines removed 33, added 47
  ok    1.7c: the three chi-square p lines (2603, 2618, 3996) are the only other computing change
  ok    1.7c: the added code is the two helpers, the 29 display sites and the p form (30), the 3 p lines and the 2 version strings, nothing else
  ok    1.7c: each changed display line, written back without the helpers, is v1.7b's line (28 lines)
  ok    1.7c: the new names es_EqLb, es_EqV, lbL, lbS, evS are not built-ins (ignoring case); the two helpers carry the es_ tag, are defined once, before their first use
  ok    1.7c: the version reads v1.7c in the banner, the menu title and About; a "What changed in v1.7c" paragraph; no code line says v1.7b
  ok    1.7c: no // comment inside an open bracket
  ok    1.7c: INSTRING as in v1.7b; every LOCAL line holds at most 6 names

  H6. left alone
  ok    1.7c: the conditional MLE OR (page 7, the reading) is at least 1.67e-06 at N <= 2000 (SciPy), above es_SigTxt's floor 1E-6, so its lines (2139, 2471) never start with "<"
    the risk NNT (1/RD, RD at most 1, so NNT >= 1): ['NNT (benefit) = 1/|RD| =', 'NNT: ICON ARRAY']
  ok    1.7c: the risk module's NNT is 1/|RD| >= 1, never floored (3187, 3195 left alone)

==============================================================================
G2. 1.7b: v1.7b against v1.7a
==============================================================================
  routines that differ from v1.7a (21): EpiStats, es_Arith, es_CORPage, es_Cas, es_ExactPage, es_FmtCI, es_FmtPct, es_ForestDraw, es_Grid, es_Help_About, es_Help_SE, es_MHSummary, es_Mosaic, es_NNTIcons, es_PctTxt, es_PrevDraw, es_ROCPage, es_ReadPage, es_RiskBars, es_RuleTxt, es_Sgn
  ok    1.7b: the routines that differ from v1.7a are exactly the 21 listed in CHANGES
  ok    1.7b: outside them only comments differ
  ok    1.7b: es_Cas keeps the IFERR line and the quote strip byte-identical
  ok    1.7b: es_Cas runs the unchanged undef/rror/infinity test only inside IF DIM(cR) > 0 (the brief's shape)
  ok    1.7b: the version reads v1.7b in the banner, the menu title and About; no code line says v1.7a

==============================================================================
I. 1.7d: E1, E2, E3, E4, E6, E7, E8, E9, E10, E11 (fleet review, 19 Oct 2026), v1.7d against v1.7c, harness v3e
==============================================================================

I1. E1: alpha and power outside (0, 1) in the four sample-size forms
    option 1 [0.5, 0.3, 0, 0.8]: v1.7c Unverified: NORMALD_ICDF(0, 1, 1): p = 1 outside (0,1) -- the device h; v1.7d refused ['α must be between 0 and 1 (0.05 is usual).']
  ok    1.7d (E1): option 1 [0.5, 0.3, 0, 0.8] -- v1.7d refuses it ("α must be ...") before any NORMALD_ICDF, no page; v1.7c reached NORMALD_ICDF with p outside (0, 1)
    option 1 [0.5, 0.3, 0.05, 1]: v1.7c Unverified: NORMALD_ICDF(0, 1, 1): p = 1 outside (0,1) -- the device h; v1.7d refused ['Power must be between 0 and 1 (0.80 is usual).']
  ok    1.7d (E1): option 1 [0.5, 0.3, 0.05, 1] -- v1.7d refuses it ("Power must be ...") before any NORMALD_ICDF, no page; v1.7c reached NORMALD_ICDF with p outside (0, 1)
    option 1 [0.5, 0.3, 0.05, 0]: v1.7c Unverified: NORMALD_ICDF(0, 1, 0): p = 0 outside (0,1) -- the device h; v1.7d refused ['Power must be between 0 and 1 (0.80 is usual).']
  ok    1.7d (E1): option 1 [0.5, 0.3, 0.05, 0] -- v1.7d refuses it ("Power must be ...") before any NORMALD_ICDF, no page; v1.7c reached NORMALD_ICDF with p outside (0, 1)
    option 1 [0.5, 0.3, 2, 0.8]: v1.7c Unverified: NORMALD_ICDF(0, 1, 0): p = 0 outside (0,1) -- the device h; v1.7d refused ['α must be between 0 and 1 (0.05 is usual).']
  ok    1.7d (E1): option 1 [0.5, 0.3, 2, 0.8] -- v1.7d refuses it ("α must be ...") before any NORMALD_ICDF, no page; v1.7c reached NORMALD_ICDF with p outside (0, 1)
    option 1 [0.5, 0.3, -0.05, 0.8]: v1.7c Unverified: NORMALD_ICDF(0, 1, 1.025): p = 1.025 outside (0,1) -- the ; v1.7d refused ['α must be between 0 and 1 (0.05 is usual).']
  ok    1.7d (E1): option 1 [0.5, 0.3, -0.05, 0.8] -- v1.7d refuses it ("α must be ...") before any NORMALD_ICDF, no page; v1.7c reached NORMALD_ICDF with p outside (0, 1)
    option 1 [0.5, 0.3, 0.05, 1.5]: v1.7c Unverified: NORMALD_ICDF(0, 1, 1.5): p = 1.5 outside (0,1) -- the devi; v1.7d refused ['Power must be between 0 and 1 (0.80 is usual).']
  ok    1.7d (E1): option 1 [0.5, 0.3, 0.05, 1.5] -- v1.7d refuses it ("Power must be ...") before any NORMALD_ICDF, no page; v1.7c reached NORMALD_ICDF with p outside (0, 1)
    option 2 [1, 2, 0, 0.8]: v1.7c Unverified: NORMALD_ICDF(0, 1, 1): p = 1 outside (0,1) -- the device h; v1.7d refused ['α must be between 0 and 1 (0.05 is usual).']
  ok    1.7d (E1): option 2 [1, 2, 0, 0.8] -- v1.7d refuses it ("α must be ...") before any NORMALD_ICDF, no page; v1.7c reached NORMALD_ICDF with p outside (0, 1)
    option 2 [1, 2, 0.05, 1]: v1.7c Unverified: NORMALD_ICDF(0, 1, 1): p = 1 outside (0,1) -- the device h; v1.7d refused ['Power must be between 0 and 1 (0.80 is usual).']
  ok    1.7d (E1): option 2 [1, 2, 0.05, 1] -- v1.7d refuses it ("Power must be ...") before any NORMALD_ICDF, no page; v1.7c reached NORMALD_ICDF with p outside (0, 1)
    option 3 [0.5, 0.05, 0]: v1.7c Unverified: NORMALD_ICDF(0, 1, 1): p = 1 outside (0,1) -- the device h; v1.7d refused ['α must be between 0 and 1 (0.05 is usual).']
  ok    1.7d (E1): option 3 [0.5, 0.05, 0] -- v1.7d refuses it ("α must be ...") before any NORMALD_ICDF, no page; v1.7c reached NORMALD_ICDF with p outside (0, 1)
    option 3 [0.5, 0.05, 2]: v1.7c Unverified: NORMALD_ICDF(0, 1, 0): p = 0 outside (0,1) -- the device h; v1.7d refused ['α must be between 0 and 1 (0.05 is usual).']
  ok    1.7d (E1): option 3 [0.5, 0.05, 2] -- v1.7d refuses it ("α must be ...") before any NORMALD_ICDF, no page; v1.7c reached NORMALD_ICDF with p outside (0, 1)
    option 4 [10, 2, 0]: v1.7c Unverified: NORMALD_ICDF(0, 1, 1): p = 1 outside (0,1) -- the device h; v1.7d refused ['α must be between 0 and 1 (0.05 is usual).']
  ok    1.7d (E1): option 4 [10, 2, 0] -- v1.7d refuses it ("α must be ...") before any NORMALD_ICDF, no page; v1.7c reached NORMALD_ICDF with p outside (0, 1)
    option 4 [10, 2, 2.5]: v1.7c Unverified: NORMALD_ICDF(0, 1, -0.25): p = -0.25 outside (0,1) -- the ; v1.7d refused ['α must be between 0 and 1 (0.05 is usual).']
  ok    1.7d (E1): option 4 [10, 2, 2.5] -- v1.7d refuses it ("α must be ...") before any NORMALD_ICDF, no page; v1.7c reached NORMALD_ICDF with p outside (0, 1)
    option 2 [1, 2, 1, 0.8]: v1.7c HarnessStall: es_WaitAny is waiting (polled by GETKEY) and the event q; v1.7d refused ['α must be between 0 and 1 (0.05 is usual).']
  ok    1.7d (E1): option 2 [1, 2, 1, 0.8] -- v1.7d refuses it ("α must be ...") before any NORMALD_ICDF, no page; v1.7c drew a page from z_alpha/2 = 0
    option 2 [1, 2, 0.05, -0.2]: v1.7c Unverified: NORMALD_ICDF(0, 1, -0.2): p = -0.2 outside (0,1) -- the de; v1.7d refused ['Power must be between 0 and 1 (0.80 is usual).']
  ok    1.7d (E1): option 2 [1, 2, 0.05, -0.2] -- v1.7d refuses it ("Power must be ...") before any NORMALD_ICDF, no page; v1.7c reached NORMALD_ICDF with p outside (0, 1)
  ok    1.7d (E1): six forms with alpha and power inside (0, 1), the extremes 0.999999 and 0.000001 among them, give the same pages as v1.7c, with no message

I2. E2: an empty row in Risk Analysis
    [0, 0, 5, 5]: v1.7c page 1 ['0.0%', '50.0%', '50.0%']; v1.7d ['A risk needs people in both rows: one row of the table is empty.']
  ok    1.7d (E2): [0, 0, 5, 5] -- v1.7d refuses the table with the message and sets no EP_* result; v1.7c printed a risk of 0.0% for the empty row
    [5, 5, 0, 0]: v1.7c page 1 ['50.0%', '0.0%', '50.0%']; v1.7d ['A risk needs people in both rows: one row of the table is empty.']
  ok    1.7d (E2): [5, 5, 0, 0] -- v1.7d refuses the table with the message and sets no EP_* result; v1.7c printed a risk of 0.0% for the empty row
    [0, 0, 0, 7]: v1.7c page 1 ['0.0%', '0.0%', '0.0%']; v1.7d ['A risk needs people in both rows: one row of the table is empty.']
  ok    1.7d (E2): [0, 0, 0, 7] -- v1.7d refuses the table with the message and sets no EP_* result; v1.7c printed a risk of 0.0% for the empty row
    [3, 0, 0, 0]: v1.7c page 1 ['100.0%', '0.0%', '100.0%']; v1.7d ['A risk needs people in both rows: one row of the table is empty.']
  ok    1.7d (E2): [3, 0, 0, 0] -- v1.7d refuses the table with the message and sets no EP_* result; v1.7c printed a risk of 0.0% for the empty row
    [0, 0, 1, 1999]: v1.7c page 1 ['0.0%', '0.1%', '0.1%']; v1.7d ['A risk needs people in both rows: one row of the table is empty.']
  ok    1.7d (E2): [0, 0, 1, 1999] -- v1.7d refuses the table with the message and sets no EP_* result; v1.7c printed a risk of 0.0% for the empty row
  ok    1.7d (E2): six tables with people in both rows (zero cells among them) -- all five pages as v1.7c 

I3. E3: an empty column in Sensitivity & Specificity
    [0, 5, 0, 30]: v1.7c sens '0.0%' spec '85.7%' LR+ '0.00' LR- '1.167' FNR '100.0%' FPR '14.3%' Youden '-0.143'
                   v1.7d sens 'undef (no Disease+)' spec '85.7%' LR+ 'undef (no Disease+)' LR- 'undef (no Disease+)' FNR 'undef' FPR '14.3%' Youden 'undef'
  ok    1.7d (E3): [0, 5, 0, 30] -- sensitivity reads 'undef (no Disease+)', both LRs 'undef (no Disease+)', FNR "undef", Youden's J "undef"; EP_LRpos, EP_LRneg, EP_youden = 999 (v1.7c: 0.0%)
  ok    1.7d (E3): [0, 5, 0, 30] -- pages 3, 4, 5 and 6 are v1.7c's
    [12, 0, 8, 0]: v1.7c sens '60.0%' spec '0.0%' LR+ '0.60' LR- 'undef' FNR '40.0%' FPR '100.0%' Youden '-0.400'
                   v1.7d sens '60.0%' spec 'undef (no Disease-)' LR+ 'undef (no Disease-)' LR- 'undef (no Disease-)' FNR '40.0%' FPR 'undef' Youden 'undef'
  ok    1.7d (E3): [12, 0, 8, 0] -- specificity reads 'undef (no Disease-)', both LRs 'undef (no Disease-)', FPR "undef", Youden's J "undef"; EP_LRpos, EP_LRneg, EP_youden = 999 (v1.7c: 0.0%)
  ok    1.7d (E3): [12, 0, 8, 0] -- pages 3, 4, 5 and 6 are v1.7c's
    [1, 0, 0, 0]: v1.7c sens '100.0%' spec '0.0%' LR+ '1.00' LR- 'undef' FNR '0.0%' FPR '100.0%' Youden '0.000'
                  v1.7d sens '100.0%' spec 'undef (no Disease-)' LR+ 'undef (no Disease-)' LR- 'undef (no Disease-)' FNR '0.0%' FPR 'undef' Youden 'undef'
  ok    1.7d (E3): [1, 0, 0, 0] -- specificity reads 'undef (no Disease-)', both LRs 'undef (no Disease-)', FPR "undef", Youden's J "undef"; EP_LRpos, EP_LRneg, EP_youden = 999 (v1.7c: 0.0%)
  ok    1.7d (E3): [1, 0, 0, 0] -- pages 3, 4, 5 and 6 are v1.7c's
    [0, 0, 0, 1]: v1.7c sens '0.0%' spec '100.0%' LR+ 'undef' LR- '1.000' FNR '100.0%' FPR '0.0%' Youden '0.000'
                  v1.7d sens 'undef (no Disease+)' spec '100.0%' LR+ 'undef (no Disease+)' LR- 'undef (no Disease+)' FNR 'undef' FPR '0.0%' Youden 'undef'
  ok    1.7d (E3): [0, 0, 0, 1] -- sensitivity reads 'undef (no Disease+)', both LRs 'undef (no Disease+)', FNR "undef", Youden's J "undef"; EP_LRpos, EP_LRneg, EP_youden = 999 (v1.7c: 0.0%)
  ok    1.7d (E3): [0, 0, 0, 1] -- pages 3, 4, 5 and 6 are v1.7c's
    [0, 1, 0, 0]: v1.7c sens '0.0%' spec '0.0%' LR+ '0.00' LR- 'undef' FNR '100.0%' FPR '100.0%' Youden '-1.000'
                  v1.7d sens 'undef (no Disease+)' spec '0.0%' LR+ 'undef (no Disease+)' LR- 'undef (no Disease+)' FNR 'undef' FPR '100.0%' Youden 'undef'
  ok    1.7d (E3): [0, 1, 0, 0] -- sensitivity reads 'undef (no Disease+)', both LRs 'undef (no Disease+)', FNR "undef", Youden's J "undef"; EP_LRpos, EP_LRneg, EP_youden = 999 (v1.7c: 0.0%)
  ok    1.7d (E3): [0, 1, 0, 0] -- pages 3, 4, 5 and 6 are v1.7c's
    [0, 0, 1, 0]: v1.7c sens '0.0%' spec '0.0%' LR+ '0.00' LR- 'undef' FNR '100.0%' FPR '100.0%' Youden '-1.000'
                  v1.7d sens '0.0%' spec 'undef (no Disease-)' LR+ 'undef (no Disease-)' LR- 'undef (no Disease-)' FNR '100.0%' FPR 'undef' Youden 'undef'
  ok    1.7d (E3): [0, 0, 1, 0] -- specificity reads 'undef (no Disease-)', both LRs 'undef (no Disease-)', FPR "undef", Youden's J "undef"; EP_LRpos, EP_LRneg, EP_youden = 999 (v1.7c: 0.0%)
  ok    1.7d (E3): [0, 0, 1, 0] -- pages 3, 4, 5 and 6 are v1.7c's
  ok    1.7d (E3): seven tables with both columns filled -- all six pages and EP_youden as v1.7c 

I4. E4: the 2x2 page's RD with an empty row
    [0, 0, 0, 5]: v1.7c ['RD = Re - Ru = 0.0%   (-100.0-100.0%)'] | page 5 ['0.0%', '(tap)', '95% CI: (-100.0-100.0%)']; v1.7d ['RD = Re - Ru = undef   '] | page 5 ['undef', '(tap)', '95% CI: ']
  ok    1.7d (E4): [0, 0, 0, 5] -- the cohort tab reads "RD = Re - Ru = undef", page 5 "undef" with no CI; EP_RD = 999, EP_RD_CI = {0, 0} (v1.7c: "0.0%   (-100.0-100.0%)")
  ok    1.7d (E4): [0, 0, 0, 5] -- every other text on the pages is v1.7c's []
    [5, 0, 0, 0]: v1.7c ['RD = Re - Ru = 0.0%   (-100.0-100.0%)'] | page 5 ['0.0%', '(tap)', '95% CI: (-100.0-100.0%)']; v1.7d ['RD = Re - Ru = undef   '] | page 5 ['undef', '(tap)', '95% CI: ']
  ok    1.7d (E4): [5, 0, 0, 0] -- the cohort tab reads "RD = Re - Ru = undef", page 5 "undef" with no CI; EP_RD = 999, EP_RD_CI = {0, 0} (v1.7c: "0.0%   (-100.0-100.0%)")
  ok    1.7d (E4): [5, 0, 0, 0] -- every other text on the pages is v1.7c's []
    [0, 5, 0, 0]: v1.7c ['RD = Re - Ru = 0.0%   (-100.0-100.0%)'] | page 5 ['0.0%', '(tap)', '95% CI: (-100.0-100.0%)']; v1.7d ['RD = Re - Ru = undef   '] | page 5 ['undef', '(tap)', '95% CI: ']
  ok    1.7d (E4): [0, 5, 0, 0] -- the cohort tab reads "RD = Re - Ru = undef", page 5 "undef" with no CI; EP_RD = 999, EP_RD_CI = {0, 0} (v1.7c: "0.0%   (-100.0-100.0%)")
  ok    1.7d (E4): [0, 5, 0, 0] -- every other text on the pages is v1.7c's []
    [0, 0, 5, 0]: v1.7c ['RD = Re - Ru = 0.0%   (-100.0-100.0%)'] | page 5 ['0.0%', '(tap)', '95% CI: (-100.0-100.0%)']; v1.7d ['RD = Re - Ru = undef   '] | page 5 ['undef', '(tap)', '95% CI: ']
  ok    1.7d (E4): [0, 0, 5, 0] -- the cohort tab reads "RD = Re - Ru = undef", page 5 "undef" with no CI; EP_RD = 999, EP_RD_CI = {0, 0} (v1.7c: "0.0%   (-100.0-100.0%)")
  ok    1.7d (E4): [0, 0, 5, 0] -- every other text on the pages is v1.7c's []
    [1, 1999, 0, 0]: v1.7c ['RD = Re - Ru = 0.0%   (-100.0-100.0%)'] | page 5 ['0.0%', '(tap)', '95% CI: (-100.0-100.0%)']; v1.7d ['RD = Re - Ru = undef   '] | page 5 ['undef', '(tap)', '95% CI: ']
  ok    1.7d (E4): [1, 1999, 0, 0] -- the cohort tab reads "RD = Re - Ru = undef", page 5 "undef" with no CI; EP_RD = 999, EP_RD_CI = {0, 0} (v1.7c: "0.0%   (-100.0-100.0%)")
  ok    1.7d (E4): [1, 1999, 0, 0] -- every other text on the pages is v1.7c's []
    [0, 0, 1, 1999]: v1.7c ['RD = Re - Ru = 0.0%   (-100.0-100.0%)'] | page 5 ['0.0%', '(tap)', '95% CI: (-100.0-100.0%)']; v1.7d ['RD = Re - Ru = undef   '] | page 5 ['undef', '(tap)', '95% CI: ']
  ok    1.7d (E4): [0, 0, 1, 1999] -- the cohort tab reads "RD = Re - Ru = undef", page 5 "undef" with no CI; EP_RD = 999, EP_RD_CI = {0, 0} (v1.7c: "0.0%   (-100.0-100.0%)")
  ok    1.7d (E4): [0, 0, 1, 1999] -- every other text on the pages is v1.7c's []
  ok    1.7d (E4): module 1 on 12 5 8 30, every page, as v1.7c

I5. E8: McNemar, M-H chi-square, Breslow-Day p as lower tails
    McNemar b    5 c   8: chi-square    0.308  v1.7c p 0.5791     v1.7d p 0.5791       chi2.sf 0.5791
    McNemar b   10 c   2: chi-square    4.083  v1.7c p 0.04331    v1.7d p 0.0433081    chi2.sf 0.0433081
    McNemar b    1 c   0: chi-square    0.000  v1.7c p 1          v1.7d p 1            chi2.sf 1
    McNemar b    3 c   3: chi-square    0.000  v1.7c p 1          v1.7d p 1            chi2.sf 1
    McNemar b   40 c   0: chi-square   38.025  v1.7c p 6.984e-10  v1.7d p 6.98439e-10  chi2.sf 6.98439e-10
    McNemar b  100 c   3: chi-square   89.476  v1.7c p 0          v1.7d p 3.10424e-21  chi2.sf 3.10424e-21
    McNemar b  200 c   0: chi-square  198.005  v1.7c p 0          v1.7d p 5.6911e-45   chi2.sf 5.6911e-45
    McNemar b 1000 c  10: chi-square  968.437  v1.7c p 0          v1.7d p 1.30351e-212 chi2.sf 1.30351e-212
  ok    1.7d (E8): es_McNemar's p = chi2.sf(x, 1) to 1.6e-13 relative at every b, c; b = 200, c = 0 (chi-square 198) gives 5.69e-45, where v1.7c gave exactly 0
    strata [(12, 4, 30, 50), (25, 10, 20, 60)]: M-H chi-square 29.002 p 7.23e-08 (v1.7c 7.23e-08); BD 0.278 p 0.5977 (v1.7c 0.5977)
    strata [(60, 10, 10, 60), (60, 10, 10, 60)]: M-H chi-square 141.837 p 1.056e-32 (v1.7c 0); BD 0.000 p 1 (v1.7c 1)
/usr/local/lib/python3.13/dist-packages/statsmodels/stats/contingency_tables.py:1259: RuntimeWarning: invalid value encountered in divide
  e11 = (-b + dr) / (2 * a)
    strata [(60, 10, 10, 60), (10, 60, 60, 10)]: M-H chi-square 0.000 p 1 (v1.7c 1); BD 142.857 p 6.316e-33 (v1.7c 0)
    strata [(10, 2, 2, 10), (2, 10, 10, 2)]: M-H chi-square 0.000 p 1 (v1.7c 1); BD 21.333 p 3.86e-06 (v1.7c 3.86e-06)
    strata [(5, 5, 5, 5), (5, 5, 6, 5)]: M-H chi-square 0.021 p 0.8845 (v1.7c 0.8845); BD 0.021 p 0.8842 (v1.7c 0.8842)
  ok    1.7d (E8): the M-H p (plain and corrected) and the Breslow-Day p = chi2.sf(x, 1) (= statsmodels test_equal_odds) to 2.1e-14; 3 of them were exactly 0 in v1.7c (M-H p 142, M-H p (corr.) 139, Breslow-Day p 143)
  ok    1.7d (E8): module 1, 10 200 0 10 -- EP_MCN_P = 5.691e-45 = chi2.sf (v1.7c: 0)
    module 5 OR, 60 10 10 60 / 10 60 60 10: EP_BD_P v1.7d 6.316e-33 (v1.7c 0.0); EP_MH_P 1 (v1.7c 1.0)
    module 5 OR, 60 10 10 60 twice: EP_MH_P v1.7d 1.056e-32 (v1.7c 0.0)
  ok    1.7d (E8): module 5 end to end -- EP_BD_P and EP_MH_P keep their digits where v1.7c's were 0; the pages are v1.7c's ("p < 0.0001" either way)
    the Prime's 12 digits (CDFs rounded): b 60 c 0: v1.7c 0, v1.7d 2.6e-14, b 70 c 1: v1.7c 0, v1.7d 7.02e-16, b 100 c 3: v1.7c 0, v1.7d 3.1e-21
  ok    1.7d (E8): with the CDFs rounded to 12 digits, McNemar chi-squares of 58, 65 and 89 give p = 0 in v1.7c and a positive p in v1.7d

I6, I7. E6 and E9: Incidence Rates page 3, fractional cases
    [1, 1, 1000000, 1000000]: v1.7c ['1000000 time units', '1000000 time units']; v1.7d ['─── Mean Waiting Times (time units) ───', 'Waiting time (exposed):', '1000000']; past x 320 (7 px a character): v1.7c 3, v1.7d 0
  ok    1.7d (E6): module 4 [1, 1, 1000000, 1000000] -- page 3 names the unit once, in the heading, and nothing runs past x 320 (v1.7c: "... time units" from x 200, 3 texts past the edge)
  ok    1.7d (E6): the three waiting times are v1.7c's numbers: ['1000000', '1000000', '1000000']
    [1, 3, 10000000.0, 2000000.0]: v1.7c ['10000000 time units', '666666.666667 time units']; v1.7d ['─── Mean Waiting Times (time units) ───', 'Waiting time (exposed):', '10000000']; past x 320 (7 px a character): v1.7c 3, v1.7d 0
  ok    1.7d (E6): module 4 [1, 3, 10000000.0, 2000000.0] -- page 3 names the unit once, in the heading, and nothing runs past x 320 (v1.7c: "... time units" from x 200, 3 texts past the edge)
  ok    1.7d (E6): the three waiting times are v1.7c's numbers: ['10000000', '666666.666667', '3000000']
    2.5 cases: v1.7c '2.5 cases / 1200 person-time'; v1.7d '3 cases / 1200 person-time', message [['Cases are whole numbers: the values were rounded.']]
  ok    1.7d (E9): module 4 with 2.5 cases -- rounded to 3 with a message (v1.7c: "2.5 cases" as typed)
  ok    1.7d (E9): whole cases with a fractional person-time (15, 8, 1200.5, 1500) -- no message; every text is v1.7c's apart from page 3's unit (E6) 

I8. E11: es_Cnt
    v1.7d [(0.05, '0.0500'), (0.0123, '0.0123'), (1e-07, '< 0.000001'), (12.5, '12.5'), (3.0, '3'), (0.5, '0.5'), (1234.5, '1234.5'), (0.0, '0'), (2000.0, '2000')]; v1.7c [(0.05, '5.0ᴇ-2'), (0.0123, '1.2ᴇ-2')]
  ok    1.7d (E11): es_Cnt -- 0.05 -> "0.0500", 0.0123 -> "0.0123" (v1.7c: the scientific form), a Haldane half and every whole count as before
  ok    1.7d (E11): the stratified rates grid with person-times 0.05 and 0.08 -- v1.7d prints "0.0500", "0.0800" (v1.7c printed ['5.0ᴇ-2', '8.0ᴇ-2'])

I9, I10. E10, the routine diff and the code
  ok    1.7d (E10): v1.7d in the banner, the comment banner (v1.7c's line 304 said v1.7b), the menu title and About; a "What changed in v1.7d" paragraph; no code line names an older version
  ok    1.7d: Roger's DISCLAIMER line (19 Oct 2026) sits beside the distribution terms in the header (a comment)
  ok    1.7d (E10, review item 22): part B compared the shared Clopper-Pearson routines with CICatcher beta 2b in out/ (the v1.7c script named the superseded beta 2a and stopped there)
    124 routines; changed (18): EpiStats, es_Cnt, es_Help_About, es_Help_Variables, es_IR_Page3, es_MHCalc, es_McNemar, es_Module_IncidenceRate, es_Module_RiskAnalysis, es_Module_SensSpec, es_Module_Table2x2, es_NNTIcons, es_Risk_Page3, es_SS_OneMean, es_SS_OneProportion, es_SS_Page1, es_SS_TwoMeans, es_SS_TwoProportions
  ok    1.7d: the routines that differ from v1.7c are exactly the 18 the fixes name (EpiStats and es_Help_About for the version only); the other 106 are byte-identical, none added or removed
  ok    1.7d: EpiStats and es_Help_About differ from v1.7c only in the version string
  ok    1.7d: outside those routines only comments differ (the header; the EXPORT list and file-level LOCALs unchanged)
  ok    1.7d (E8): no 1 - CHISQUARE_CDF left in the code; seven chi-square p lines are lower normal tails (v1.7c's three, McNemar, the two M-H and Breslow-Day)
  ok    1.7d: the new names ['sOK1', 'sOK2', 's1U', 's1S'] are not built-ins (ignoring case)
  ok    1.7d: no // comment inside an open bracket 
  ok    1.7d: INSTRING as in v1.7c; every LOCAL line holds at most 6 names (largest 6)
  ok    1.7d (E4): Help > Variables names the 999 sentinel on its formerly blank line, within the width

I11. E7: Risk Analysis with |RD| below 0.000001 (an NNT above 1,000,000)
    [1, 999999, 1, 1000000] RD 9.99999e-13: v1.7c page 5 ['NNT = 1/< 0.000001 = 1.00ᴇ12, round up: 1ᴇ12'], EP_NNT 1e+12, 100 cells, scientific ['1.00ᴇ12', '1ᴇ12']; v1.7d page 3 ['> 1,000,000', 'RD near 0 (|RD| < 0.000001): no meaningful NNT;'], page 5 ['NNT > 1,000,000 (RD near 0: no meaningful NNT).'], EP_NNT 999.0
  ok    1.7d (E7): [1, 999999, 1, 1000000] (RD 9.99999e-13) -- page 3 "NNT (harm) = 1/RD   > 1,000,000" and "no meaningful NNT", page 5 says the same and draws no cells, EP_NNT = 999 (EP_RD keeps 9.99999e-13), no scientific form, within the width; v1.7c drew 100 cells for an NNT of 1e+12, in scientific form ['1.00ᴇ12', '1ᴇ12']
    [1, 1000000, 1, 999999] RD -9.99999e-13: v1.7c page 5 ['NNT = 1/< 0.000001 = 1.00ᴇ12, round up: 1ᴇ12'], EP_NNT 1e+12, 100 cells, scientific ['1.00ᴇ12', '1ᴇ12']; v1.7d page 3 ['> 1,000,000', 'RD near 0 (|RD| < 0.000001): no meaningful NNT;'], page 5 ['NNT > 1,000,000 (RD near 0: no meaningful NNT).'], EP_NNT 999.0
  ok    1.7d (E7): [1, 1000000, 1, 999999] (RD -9.99999e-13) -- page 3 "NNT (benefit) = 1/|RD|   > 1,000,000" and "no meaningful NNT", page 5 says the same and draws no cells, EP_NNT = 999 (EP_RD keeps -9.99999e-13), no scientific form, within the width; v1.7c drew 100 cells for an NNT of 1e+12, in scientific form ['1.00ᴇ12', '1ᴇ12']
    [1, 99999, 1, 100000] RD 9.9999e-11: v1.7c page 5 ['NNT = 1/< 0.000001 = 10000100000.0, round up: 10000100000'], EP_NNT 1.00001e+10, 100 cells, scientific []; v1.7d page 3 ['> 1,000,000', 'RD near 0 (|RD| < 0.000001): no meaningful NNT;'], page 5 ['NNT > 1,000,000 (RD near 0: no meaningful NNT).'], EP_NNT 999.0
  ok    1.7d (E7): [1, 99999, 1, 100000] (RD 9.9999e-11) -- page 3 "NNT (harm) = 1/RD   > 1,000,000" and "no meaningful NNT", page 5 says the same and draws no cells, EP_NNT = 999 (EP_RD keeps 9.9999e-11), no scientific form, within the width; v1.7c drew 100 cells for an NNT of 1e+10
    [1, 200000, 1, 250000] RD 9.99991e-07: v1.7c page 5 ['NNT = 1/< 0.000001 = 1000009.00, round up: 1000010'], EP_NNT 1.00001e+06, 100 cells, scientific []; v1.7d page 3 ['> 1,000,000', 'RD near 0 (|RD| < 0.000001): no meaningful NNT;'], page 5 ['NNT > 1,000,000 (RD near 0: no meaningful NNT).'], EP_NNT 999.0
  ok    1.7d (E7): [1, 200000, 1, 250000] (RD 9.99991e-07) -- page 3 "NNT (harm) = 1/RD   > 1,000,000" and "no meaningful NNT", page 5 says the same and draws no cells, EP_NNT = 999 (EP_RD keeps 9.99991e-07), no scientific form, within the width; v1.7c drew 100 cells for an NNT of 1e+06
  ok    1.7d (E7): [1, 199999, 1, 250000], |RD| = 1.000016E-6 just above the line -- all five pages and EP_NNT (999984.00) as v1.7c
  ok    1.7d (E7): undoing the three E7 edits gives back the routines of v1.7d as it stood before E7 (sha256 9f552f68186a...), whose es_Risk_Page3 and es_NNTIcons are v1.7c's; E7 changed exactly es_Module_RiskAnalysis, es_Risk_Page3, es_NNTIcons
    the review's E7 check on v1.7c: FAIL  B3 (1, 999999, 1, 1000000): no scientific E, no "= <" on 11 pages ["sci: 'NNT = 1/< 0.000001 = 1.00ᴇ12, round up: 1ᴇ12'", "sci: 'Expose 1ᴇ12 (blue): a
    the review's E7 check on v1.7d before E7: FAIL  B3 (1, 999999, 1, 1000000): no scientific E, no "= <" on 11 pages ["sci: 'NNT = 1/< 0.000001 = 1.00ᴇ12, round up: 1ᴇ12'", "sci: 'Expose 1ᴇ12 (blue): a
    the review's E7 check on v1.7d: passes
  ok    1.7d (E7): the review's E7 check ("B3 (1, 999999, 1, 1000000): no scientific E, no "= <" ... pages") fails on v1.7c and on v1.7d before E7, and passes on v1.7d

==============================================================================
REVIEW. The review's check_EpiStats.py (511 checks), on v1.7d and on v1.7c, harness v3e
==============================================================================
  Expectations are moved where a fix changes what is right: D8's version string; B3's empty-row tables are now refused (E2); B5's 2.5 cases are now rounded (E9); C5's twelve forms are now refused (E1: the check asserted the missing guard); B3's EP_NNT and icon array for 1 999999 1 1000000 (|RD| about 1E-12) are now the 999 sentinel and no cells (E7). With them the script makes 508 checks: a refused empty-row table in B3 has one check where it had three, and B5 adds the message.
/usr/local/lib/python3.13/dist-packages/statsmodels/stats/contingency_tables.py:1263: RuntimeWarning: divide by zero encountered in divide
  1 / e11
/usr/local/lib/python3.13/dist-packages/statsmodels/stats/contingency_tables.py:1265: RuntimeWarning: divide by zero encountered in divide
  + 1 / (self._apb - e11)
/usr/local/lib/python3.13/dist-packages/statsmodels/stats/contingency_tables.py:1263: RuntimeWarning: invalid value encountered in add
  1 / e11
/usr/local/lib/python3.13/dist-packages/statsmodels/stats/contingency_tables.py:1123: RuntimeWarning: divide by zero encountered in scalar divide
  odds_ratio = np.sum(self._ad / self._n) / np.sum(self._bc / self._n)
/usr/local/lib/python3.13/dist-packages/statsmodels/stats/contingency_tables.py:1123: RuntimeWarning: invalid value encountered in scalar divide
  odds_ratio = np.sum(self._ad / self._n) / np.sum(self._bc / self._n)
/usr/local/lib/python3.13/dist-packages/statsmodels/stats/contingency_tables.py:1142: RuntimeWarning: invalid value encountered in scalar divide
  rr = np.sum(acd / self._n) / np.sum(cab / self._n)
/usr/local/lib/python3.13/dist-packages/statsmodels/stats/contingency_tables.py:1142: RuntimeWarning: divide by zero encountered in scalar divide
  rr = np.sum(acd / self._n) / np.sum(cab / self._n)
  v1.7d: 508 checks, 0 failed
  v1.7c: 508 checks, 36 failed
      FAIL  B2 (0, 0, 0, 5): a row is empty, so the RD line should say undefined, not 'RD = Re - Ru = 0.0%   (-100.0-100.0%)'
      FAIL  B2 (5, 0, 0, 0): a row is empty, so the RD line should say undefined, not 'RD = Re - Ru = 0.0%   (-100.0-100.0%)'
      FAIL  B2 (0, 5, 0, 0): a row is empty, so the RD line should say undefined, not 'RD = Re - Ru = 0.0%   (-100.0-100.0%)'
      FAIL  B2 (0, 0, 5, 0): a row is empty, so the RD line should say undefined, not 'RD = Re - Ru = 0.0%   (-100.0-100.0%)'
      FAIL  B2 (1, 1999, 0, 0): a row is empty, so the RD line should say undefined, not 'RD = Re - Ru = 0.0%   (-100.0-100.0%)'
      FAIL  B2 (0, 0, 1, 1999): a row is empty, so the RD line should say undefined, not 'RD = Re - Ru = 0.0%   (-100.0-100.0%)'
      FAIL  B3 (0, 0, 5, 5): an empty row is 0/0, so the module refuses the table with a message (E2): no risk, RD or NNT
      FAIL  B3 (5, 5, 0, 0): an empty row is 0/0, so the module refuses the table with a message (E2): no risk, RD or NNT
      FAIL  B3 (1, 999999, 1, 1000000): no scientific E, no "= <" on 11 pages ["sci: 'NNT = 1/< 0.000001 = 1.00ᴇ12, round up: 1ᴇ12'", "sci: 'Expose 1ᴇ12 (blue): about 1 ext
      FAIL  B3 (1, 999999, 1, 1000000) EP_NNT = 999 (undefined): |RD| < 0.000001, no meaningful NNT (E7)
      FAIL  B3 (1, 999999, 1, 1000000) icon array: none; "NNT > 1,000,000 (RD near 0: no meaningful NNT)." (E7)
      FAIL  B4 (0, 5, 0, 30): sensitivity is undefined (a + c = 0), page 1 should not print 0.0%
      FAIL  B4 (12, 0, 8, 0): specificity is undefined (b + d = 0), page 1 should not print 0.0%
      FAIL  B4 (1, 0, 0, 0): specificity is undefined (b + d = 0), page 1 should not print 0.0%
      FAIL  B4 (0, 0, 0, 1): sensitivity is undefined (a + c = 0), page 1 should not print 0.0%
      FAIL  B4 (0, 1, 0, 0): sensitivity is undefined (a + c = 0), page 1 should not print 0.0%
      FAIL  B4 (0, 0, 1, 0): specificity is undefined (b + d = 0), page 1 should not print 0.0%
      FAIL  B5 [2.5, 8, 1200, 1500] fractional cases: a message (E9)
      FAIL  B5 [2.5, 8, 1200, 1500] EP_IR_exp, EP_IR_unexp, EP_IRD
      FAIL  B5 [2.5, 8, 1200, 1500] EP_IRR = 0.4688
      FAIL  B width audit over every page shown: 2 texts past x 320 or in the soft-key band ["'1000000.001 time units' f2.0 x200: 354 px", "'1000000.001 time units' f2.0 x2
      FAIL  C5 option 1 [0.5, 0.3, 0, 0.8] refused with a message before any NORMALD_ICDF (E1)
      FAIL  C5 option 1 [0.5, 0.3, 0.05, 1] refused with a message before any NORMALD_ICDF (E1)
      FAIL  C5 option 1 [0.5, 0.3, 0.05, 0] refused with a message before any NORMALD_ICDF (E1)
      FAIL  C5 option 1 [0.5, 0.3, 2, 0.8] refused with a message before any NORMALD_ICDF (E1)
      FAIL  C5 option 1 [0.5, 0.3, -0.05, 0.8] refused with a message before any NORMALD_ICDF (E1)
      FAIL  C5 option 1 [0.5, 0.3, 0.05, 1.5] refused with a message before any NORMALD_ICDF (E1)
      FAIL  C5 option 2 [1, 2, 0, 0.8] refused with a message before any NORMALD_ICDF (E1)
      FAIL  C5 option 2 [1, 2, 0.05, 1] refused with a message before any NORMALD_ICDF (E1)
      FAIL  C5 option 3 [0.5, 0.05, 0] refused with a message before any NORMALD_ICDF (E1)
      FAIL  C5 option 3 [0.5, 0.05, 2] refused with a message before any NORMALD_ICDF (E1)
      FAIL  C5 option 4 [10, 2, 0] refused with a message before any NORMALD_ICDF (E1)
      FAIL  C5 option 4 [10, 2, 2.5] refused with a message before any NORMALD_ICDF (E1)
      FAIL  D1 no "1 - CHISQUARE_CDF" left (the 2x2 page was changed in 1.7c; these were not: M-H chi-square, Breslow-Day, McNemar)
      FAIL  D8 the only version string shown on screen is v1.7d
      FAIL  D8 the comment banner at line 304 says v1.7c (it says v1.7b: cosmetic)
  [192 s]
  scientific STRING results in the review's 109 runs of v1.7d: []
  ok    1.7d (E7): no scientific STRING result in any of the review's 109 runs of v1.7d (before E7 the NNT page of 1 999999 1 1000000 printed "1.00E12" and "1E12")
  ok    1.7d: the review's check passes on v1.7d, all 508 checks (with the moved expectations), E7's among them
  v1.7c's failures by finding: E4 6, E2 2, E3 6, E6 1, E8 1, E9 3, E1 12, D8 2, E7 3
  ok    regression: on v1.7c the same script fails every check these findings name, and nothing else -- E4 x 6, E2 x 2, E3 x 6, E6, E8, E9, E1 x 12, the two version checks (D8) and E7 x 3 (the scientific form, EP_NNT, the icon array); on v1.7d none is left

==============================================================================
SCI. Every scientific STRING result (h.sci_strings) of every harness run
==============================================================================
  v1.7d: 105 harness runs in this script (B, D, F, I), 0 scientific results: none
         plus the v1.6 suite (E): 27 runs, 0 scientific results
  v1.7a (F, for comparison): 19 runs, 66 distinct scientific results, in es_Arith, es_CORPage, es_FmtCI, es_FmtPct, es_ForestDraw, es_Grid, es_Help_SE, es_MHSummary, es_Mosaic, es_NNTIcons, es_PctTxt, es_PrevDraw, es_ROCPage, es_ReadPage, es_RiskBars, es_RuleTxt, es_Sgn
      es_Arith         -4.985ᴇ-4
      es_Arith         -5.0ᴇ-2
      es_Arith         -9.965ᴇ-4
      es_Arith         2.46ᴇ-3
      es_Arith         3.333ᴇ-4
      es_Arith         3.334ᴇ-4
      es_Arith         3.33ᴇ-4
      es_Arith         4.52ᴇ-5
      es_Arith         4.78ᴇ-3
      es_Arith         6.667ᴇ-4
      es_Arith         9.30ᴇ-5
      es_CORPage       4.00ᴇ-3
  ok    1.7b, 1.7d: no scientific STRING result in any of the 105 + 27 harness runs of v1.7d (the review's own runs: part REVIEW)

==============================================================================
ALL CHECKS PASSED: 302 checks, 0 failures  [770 s]
