yomitoki 0.1.1

Fast, explainable, route-free molecular synthesizability diagnostics.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
# yomitoki

[![CI](https://github.com/kent-tokyo/yomitoki/actions/workflows/ci.yml/badge.svg)](https://github.com/kent-tokyo/yomitoki/actions/workflows/ci.yml)
[![crates.io](https://img.shields.io/crates/v/yomitoki.svg)](https://crates.io/crates/yomitoki)
[![docs.rs](https://docs.rs/yomitoki/badge.svg)](https://docs.rs/yomitoki)
[![License](https://img.shields.io/crates/l/yomitoki.svg)](#license)

**English** | [日本語]README_ja.md | [中文]README_zh.md

Fast, explainable, route-free molecular synthesizability diagnostics.

yomitoki is a fast, explainable, route-free molecular
synthesizability diagnostics library built on [chematic](https://github.com/kent-tokyo/chematic).

Instead of returning only a synthetic accessibility score,
yomitoki reads molecular structure and explains why a molecule
appears easy or difficult to synthesize. It identifies the
structural evidence behind the assessment and reports how
confident that judgment is.

The name comes from the Japanese word *yomitoki*(読み解き),
meaning to carefully examine something and uncover its meaning —
that is the library's whole job: not to change the molecule, but
to read it and explain what it finds.

> yomitoki does not merely estimate synthesizability; it exposes
> the evidence and reasoning behind the estimate.

> **Status: `0.1.1` — correctness/dependency/infrastructure patch on
> `0.1.0`, the first non-alpha release.**
> All six planned components are implemented, including
> `fragment_precedent` (renamed from `fragment_rarity` in round 18, since
> it argues difficulty both up *and* down, not just up) — opt-in: no
> fragment-frequency corpus ships with yomitoki itself (AGENTS.md §5.4
> forbids embedding one directly as a huge binary), so it stays inactive
> unless you build one (`tools/build-fragment-corpus/`) and configure it
> (`AnalysisConfig.fragment_model`). **`fragment_precedent` is an
> explanatory reference-corpus signal, not a direct synthetic-difficulty
> term** — round 20 found the corpus-relative signal too corpus-sensitive
> to trust as a scoring input (two honestly-labeled synthesis-focused
> corpora disagreed with each other on its direction 34.6% of the time
> over 500 probe molecules; plain pyridine swung between
> `LikelyAccessible` and `HighlyChallenging` purely from which corpus was
> configured), so round 21 removed it from `overall.difficulty` entirely
> (option C). **If you configure a corpus:** `fragment_precedent` is still
> computed and still reported (`SynthesizabilityReport.fragment_precedent`)
> as explanatory evidence, but it can no longer change
> `overall.difficulty`, `dominant_penalties`, or `dominant_supports`> configuring a corpus (or switching which one) never changes the score,
> only the evidence available alongside it. See Limitations for the full
> before/after data and `rules.rs`'s "Fragment precedent" section for the
> complete round 16–21 history. See [`CHANGELOG.md`]CHANGELOG.md for the
> full version history.
> See [`docs/architecture.md`]docs/architecture.md for the current scope
> and what's still missing.

## Where it sits

```text
chematic    Molecular representation and cheminformatics
    |
yomitoki    Read and explain molecular synthesizability
    |
renkin      Plan retrosynthetic routes
```

[chematic](https://github.com/kent-tokyo/chematic) · [renkin](https://github.com/kent-tokyo/renkin)

yomitoki never runs route search — that boundary is permanent, not a v0.1
scoping choice. See "What it does not do" below.

## What it does

* Parses a molecule (via `chematic`) and returns a structured
  `SynthesizabilityReport`, not a single number.
* Breaks the assessment down into independent components (ring topology,
  size/topology, stereochemical burden, functional-group liabilities, input
  quality/applicability, and fragment rarity — the last is opt-in, see
  "Limitations" below).
* Separates **score** (synthesizability/difficulty), **confidence** (how
  reliable the judgment is), and **applicability** (whether the molecule is
  even in the model's domain) into distinct fields — a hard-to-make molecule
  is not automatically a low-confidence one.
* Emits machine-readable finding codes with structured evidence, not just
  prose.
* Never runs retrosynthesis search. yomitoki evaluates a molecule on its own;
  it does not plan a route to make it.
* Ships a `yomitoki` CLI for single-molecule and batch (`.sdf`/SMILES-file)
  analysis — see Command-line interface below.
* `analyze_batch(&[Molecule], &AnalysisConfig) -> Vec<Result<...>>` gives
  library callers the same input-order-preserving batch entry point, without
  going through the CLI or a file format.

## What it does not do

* Retrosynthesis planning, reaction template application, precursor
  generation, or route ranking — that's [RENKIN]https://github.com/kent-tokyo/renkin's job.
* Molecule parsing, ring perception, aromaticity, or stereochemistry
  assignment — that's [chematic]https://github.com/kent-tokyo/chematic's job; yomitoki only consumes it.
* Toxicity, SDS/hazard classification, yield prediction, or cost prediction.
* Full periodic-table or organometallic/polymer coverage in v0.1.

## Quick start

```rust
use yomitoki::{analyze_smiles, AnalysisConfig};

let config = AnalysisConfig::default();
let report = analyze_smiles("C1CC2CCC1C2", &config)?; // norbornane

println!("{:?}", report.overall.verdict);
println!("difficulty = {}", report.overall.difficulty.value());
println!("confidence = {}", report.overall.confidence.value());

for finding in &report.findings {
    println!("{:?}: {}", finding.code, finding.explanation);
}
```

Run the full example:

```bash
cargo run --example basic
```

## Command-line interface

```bash
yomitoki analyze "C1CC2CCC1C2" --format json
yomitoki analyze --input molecules.sdf --format jsonl --output reports.jsonl
```

* `yomitoki analyze "<SMILES>" [--format human|json|jsonl] [--fragment-corpus <dir>]`
  — analyze one molecule from an argument.
* `yomitoki analyze --input <file> [--format human|json|jsonl] [--output <file>] [--fragment-corpus <dir>]`
  — batch mode. `<file>` may be a `.sdf` file or a SMILES-per-line file
  (optionally with a whitespace-separated name column, the standard `.smi`
  convention).
* Batch mode preserves input order and never stops on one record's failure —
  a failed record becomes an error entry (JSON `"error"` field, or an
  `ERROR:` block in human format), not a skipped one. The process exits
  non-zero only after every record has been attempted, if any failed.
* `jsonl` output uses the same `{"input", "report"|"error"}` wrapper shape
  in both single-molecule and batch mode, so a downstream line-by-line
  parser sees one schema regardless of which invocation form produced it.
* Exit codes: `0` success, `1` a molecule failed to parse/analyze (single
  mode) or at least one batch record failed, `2` a usage error (bad
  arguments).
* `--fragment-corpus <dir>` loads a `tools/build-fragment-corpus` output
  directory and enables `fragment_precedent` for the run (loaded once,
  before any molecule is analyzed). Without it, reports have
  `fragment_precedent: null`, same as before this flag existed — no corpus
  ships with yomitoki itself, see Limitations below.

## Report shape

Actual output of `cargo run --example basic`, current as of this component
set. Norbornane (`C1CC2CCC1C2`) exercises ring topology, and its bridged
ring produces a simplification suggestion:

```text
Verdict: ModeratelyAccessible
Synthesizability: 0.66
Confidence: 1.00
Dominant penalties:
1. Bridged ring system spanning 7 atoms — bridgehead connectivity typically increases synthetic difficulty.
Simplification suggestions (heuristic, not a guarantee):
1. ReplaceBridgedRingWithMonocyclicAnalog: Bridgehead connectivity in this ring system is a direct driver of the ring_topology contribution to difficulty. A monocyclic (or less-fused) analog, if the target application allows one, would remove this specific burden — this is a structural heuristic, not a guarantee the replacement is chemically equivalent or that synthesis actually becomes easier.
```

A stereocenter-dense fragment (`CC(O)C(N)C(C)C(O)C(N)C`) exercises
`stereochemical_burden` and `functional_group_liability` (difficulty) plus
applicability's independent confidence penalty for unspecified
stereochemistry — note that difficulty and confidence move separately, not
together:

```text
Verdict: ModeratelyAccessible
Synthesizability: 0.69
Confidence: 0.85
Dominant penalties:
1. 5 tetrahedral stereocenter(s) (specified or unspecified) requiring synthetic control.
2. Stereocenter density 0.42 is above the 0.25 threshold — stereocenters are concentrated in a compact region, leaving little room for staged, orthogonal control.
3. Reactive/unstable functional group detected: primary amine (Brenk et al. 2008 structural alert).
Simplification suggestions (heuristic, not a guarantee):
1. ReduceStereocenterDensity: Stereocenters are concentrated in a compact region, leaving little room for staged, orthogonal stereocontrol. Reducing the number of stereocenters, or spreading them further apart in the structure, would lower this contribution to difficulty — this is a structural heuristic, not a guarantee.
```

An epoxide (`C1CO1`) exercises `functional_group_liability` on its own —
it wraps chematic's Brenk et al. (2008) structural-alert set directly:

```text
Verdict: LikelyAccessible
Synthesizability: 0.87
Confidence: 1.00
Dominant penalties:
1. Reactive/unstable functional group detected: epoxide (Brenk et al. 2008 structural alert).
```

A 9-membered ring (`C1CCCCCCCC1`) exercises the macrocycle branch of
`ring_topology`, and its own simplification suggestion:

```text
Verdict: LikelyAccessible
Synthesizability: 0.75
Confidence: 1.00
Dominant penalties:
1. Macrocyclic ring of 9 atoms (at or above the 9-atom macrocycle threshold).
Simplification suggestions (heuristic, not a guarantee):
1. SimplifyMacrocyclicClosure: Macrocyclic ring closure is a direct driver of the ring_topology contribution to difficulty (large-ring closures often need high-dilution or specialized macrocyclization methods). A smaller ring or acyclic analog, if chemically acceptable, would remove this burden — this is a structural heuristic, not a guarantee.
```

Pentaerythritol tetraacetate (`CC(=O)OCC(COC(C)=O)(COC(C)=O)COC(C)=O`) has 4
distinct, non-adjacent ester environments and exercises
`functional_group_liability`'s "dense functionalization" signal
(`chematic::chem::identify_functional_groups`, Ertl 2017 clustering) on top
of its Brenk alerts:

```text
Verdict: LikelyAccessible
Synthesizability: 0.81
Confidence: 1.00
Dominant penalties:
1. 4 distinct functional-group environments (Ertl 2017 clustering), above the 3 threshold — multiple independent reactive/functional regions can compete for reagent selectivity and complicate protecting-group strategy.
2. Reactive/unstable functional group detected: ketone alpha (Brenk et al. 2008 structural alert).
3. Reactive/unstable functional group detected: acetal ketal (Brenk et al. 2008 structural alert).
```

Alaninate (`C[C@@H](N)C(=O)[O-]`, deprotonated alanine) has a specified
stereocenter *and* a negatively charged atom. Through chematic 0.12,
the negative charge triggered a real upstream overflow bug
([chematic#267](https://github.com/kent-tokyo/chematic/issues/267)) —
yomitoki worked around it by skipping stereo analysis entirely for any
negatively charged atom, at the cost of a real confidence penalty and a
silently-zeroed `stereochemical_burden` for every such molecule.
**chematic 0.13.0 fixed the bug directly** (verified: alaninate now
returns the identical result its neutral-acid form would); the
workaround was removed accordingly, and this molecule now gets full,
correct stereo analysis like any other:

```text
Verdict: LikelyAccessible
Synthesizability: 0.86
Confidence: 1.00
Dominant penalties:
1. 1 tetrahedral stereocenter(s) (specified or unspecified) requiring synthetic control.
2. Reactive/unstable functional group detected: primary amine (Brenk et al. 2008 structural alert).
3. Reactive/unstable functional group detected: acetal ketal (Brenk et al. 2008 structural alert).
```

Configuring a fragment corpus (see below) only changes the `fragment_precedent`
evidence field's contents — since round 21 (option C), `overall.difficulty`/
`overall.synthesizability` are computed from `ring_topology`/`size_topology`/
`stereochemical_burden`/`functional_group_liability` alone and are
identical with or without a corpus configured.

Every report also carries a `Provenance` block (schema version, yomitoki
version, chematic version, ruleset version, fragment-corpus model version,
config hash) so results are comparable across versions — see
`docs/architecture.md`.

## Component status (v0.1)

| Component | Status |
|---|---|
| `input_quality` / applicability | implemented |
| `ring_topology` | implemented |
| `size_topology` | implemented |
| `stereochemical_burden` | implemented (tetrahedral centers only — see Limitations) |
| `functional_group_liability` | implemented (reactive/unstable groups + dense functionalization — see Limitations) |

`fragment_precedent` is implemented and opt-in (`None` unless
`AnalysisConfig.fragment_model` has a corpus configured), but is **not** a
`ComponentScores` field — since round 21 it doesn't contribute to
`overall.difficulty`, so it lives in its own top-level
`SynthesizabilityReport.fragment_precedent: Option<FragmentPrecedentEvidence>`
field: a corpus-relative percentile signal reported as explanatory
evidence only, see Limitations.

Components stay `None` in `ComponentScores`/`fragment_precedent` when
genuinely not evaluated, never as fabricated zero scores.

`suggestions: Vec<SimplificationSuggestion>` is populated for 3 of its 6
possible codes (`ReplaceBridgedRingWithMonocyclicAnalog`,
`SimplifyMacrocyclicClosure`, `ReduceStereocenterDensity`) — see
Limitations. `IncreaseFragmentPrecedent` is retired (unreachable since
round 21 — kept in the enum for schema stability, never emitted, since
"this would lower this contribution to difficulty" is no longer a true
statement). Every suggestion is diagnostic-only, heuristic, and never
claims certainty (`expected_effect` is always `MayReduceDifficulty`, never
`LikelyReducesDifficulty`).

## How this differs from existing tools

* **SAscore** returns fragment-frequency and complexity penalties as a
  single number. yomitoki returns component-wise diagnostics, confidence,
  applicability, evidence, and simplification suggestions.
* **SYBA** is an easy/hard classifier. yomitoki is a diagnostic and
  explanation tool.
* **SCScore** is a learned synthetic-complexity score. yomitoki decomposes
  transparent, chemically-named factors instead.
* **RAscore** approximates retrosynthesis success. yomitoki is route-free and
  explains the structural reasons behind its assessment.
* **BR-SAScore** retrains SAscore's fragment table on USPTO
  reaction/eMolecules building-block data and reports a single
  reaction/building-block-informed score. yomitoki returns five
  independent structural components plus applicability, confidence, and
  machine-readable findings/simplification-suggestion codes — a
  structured diagnostic report, not a single number, and requires no
  reaction corpus to produce `overall.difficulty` at all (corpus choice
  is contractually guaranteed not to change it — see
  [External benchmark]#external-benchmark-v010 below for how the two
  actually compare on accuracy).
* **AiZynthFinder, ASKCOS, RENKIN** are route planners. yomitoki never
  generates a route.

## Comparison with SAscore

A minimum in-process comparison against `chematic::chem::sa_score` (Ertl &
Schuffenhauer 2009) — the completion criterion in AGENTS.md §27. Not a
calibration or accuracy claim: the two scores aren't fit against each other
and measure different things (SAscore: fragment frequency + complexity
penalty; yomitoki: structural burden, decomposed by component). Scales run
in opposite directions and aren't rescaled onto a shared axis here — SAscore
is `1` (easy) to `10` (hard); yomitoki's `difficulty` is `0.0` to `1.0`.

Real output of `cargo run --example sa_score_comparison`:

```text
molecule                                sa_score      yomitoki_diff  verdict
ethanol                                     3.45               0.01  LikelyAccessible
benzene                                     2.40               0.10  LikelyAccessible
norbornane (bridged)                        8.20               0.34  ModeratelyAccessible
stereocenter-dense fragment                 8.32               0.31  ModeratelyAccessible
epoxide                                     6.23               0.13  LikelyAccessible
aspirin                                     4.67               0.27  ModeratelyAccessible
paracetamol                                 4.56               0.24  LikelyAccessible
caffeine (fused heterocycle)                4.94               0.29  ModeratelyAccessible
acyl halide                                 6.62               0.09  LikelyAccessible
cyclopropane (strained)                     3.94               0.13  LikelyAccessible
nitrile                                     4.97               0.04  LikelyAccessible
alanine (specified stereocenter)            3.55               0.14  LikelyAccessible
bridged ring + several stereocenters       10.00               0.72  Challenging
spiro ring system                           5.52               0.20  LikelyAccessible
```

The interesting rows are where the two diverge, not where they agree — a
divergence isn't automatically a yomitoki bug. Acyl halide is the sharpest
case: SAscore rates it `6.62` (fragment-uncommon), yomitoki rates it `0.09`
(`LikelyAccessible`) — a small, cheap, extremely common acylating reagent
with essentially no structural burden by yomitoki's own model. Aspirin
(`4.67` vs. `0.27`) is the same shape as the Brenk-validity gap already
described in Limitations. Where they broadly agree (caffeine, spiro,
bridged-plus-stereo), that's not evidence either one is "correct" — neither
has been validated against real synthesis outcomes yet.

`yomitoki_diff` for "stereocenter-dense fragment" and "bridged ring +
several stereocenters" moved (`0.27→0.31`, `0.69→0.72`) with the
chematic 0.13.0 upgrade — a separate stereocenter-*counting* bug fix
from the negatively-charged-atom fix below (an implicit-hydrogen rank
-0 sentinel could collide with a real atom's normalized rank 0,
silently undercounting stereocenters at certain positions). Both
numbers are now higher, correctly, not lower — the old counts were an
undercount, not an overcount.

## External benchmark (v0.1.0)

yomitoki v0.1.0's frozen default was measured against BR-SAScore's own
TS1/TS2/TS3 test sets, alongside SAscore and BR-SAScore itself, on the
exact same molecules. Full methodology, per-molecule results, and honest
limitations: **[docs/benchmark.md](docs/benchmark.md)**.

Stated plainly, not rounded up: yomitoki is competitive with BR-SAScore
on TS1 (ROC-AUC 0.952 vs. 0.983; balanced accuracy and MCC at yomitoki's
own threshold actually exceed SAscore's), has **no discriminative power
on TS2** (ROC-AUC 0.476 — chance level, diagnosed as a genuine structural
finding: TS2's easy/hard classes are ring/size/stereo/functional-group
homogeneous under yomitoki's model), and is weaker than both competitors
on TS3 (0.673 vs. 0.839 / 0.905). The confidence-based selective-prediction
evaluation this benchmark set out to validate as a differentiator **did
not confirm that story** — on TS1, higher-confidence predictions were
measurably *less* accurate than lower-confidence ones, traced to
`overall.confidence` acting as a proxy for dataset provenance (stereo-tag
completeness) rather than for prediction correctness. All of this is
reported in `docs/benchmark.md` because it's true, not because it's
flattering — the same document states what would need to change before
these numbers improve, and yomitoki's per-round development process
treats these results as confirmatory, not as something to retune against.

**TS2's chance-level result was checked against a second, unrelated
dataset — and it reproduced.** [`benchmarks/synthesizability/DEVELOPMENT_SET.md`](benchmarks/synthesizability/DEVELOPMENT_SET.md)
validates the frozen baseline against MPScore, a published
expert-chemist-labeled dataset (three chemists' independent
easy/difficult ratings, methodologically unrelated to TS1/2/3's
retrosynthesis-planner-derived labels, ~0.03% molecule overlap):
ROC-AUC 0.513 on the full set, still inside the 95% CI for chance. Two
independent ground-truth sources — one algorithmic, one human — now
agree that yomitoki's four structural components miss a large share of
what makes a molecule hard to synthesize in practice (72.6% false
negatives on that set). That document also runs a controlled,
unlabeled ablation panel isolating *which* structural axes the
components respond to (and where they saturate or respond backwards),
and records four evidence-based design-change candidates without
implementing any of them yet — development-only, kept deliberately
separate from the TS1/2/3 confirmatory numbers above.

## Limitations

* All six planned components are implemented (see table above), but
  `fragment_precedent` **never** contributes to `overall.difficulty`/
  `overall.synthesizability` — since round 21 (option C), configuring a
  corpus (`AnalysisConfig.fragment_model`; no corpus ships with yomitoki
  itself, AGENTS.md §5.4) changes what `fragment_precedent` reports, not
  what `overall.difficulty` measures. `overall.difficulty` always reflects
  exactly `ring_topology`/`size_topology`/`stereochemical_burden`/
  `functional_group_liability`, corpus configured or not.
* `size_topology`'s rotatable-bond term over-penalizes simple, commercially
  available long unbranched chains (many rotatable bonds, essentially no
  synthetic difficulty) — e.g. dodecane's `overall.difficulty` is `0.068`,
  aspirin's (tripping several Brenk alerts in `functional_group_liability`)
  is `0.273`, `ModeratelyAccessible`, despite being one of the most
  trivially synthesizable molecules there is. Through round 20,
  `fragment_precedent` corrected this once a corpus was configured
  (dodecane `→ 0.000`, aspirin `→ 0.095`); **round 21 removed that
  correction** (see the next point for why), so this over-penalization is
  now an acknowledged, currently-unaddressed limitation regardless of
  corpus configuration. `fragment_precedent` still reports (as explanatory
  evidence, not a score adjustment) that such fragments are strongly
  precedented, so a report reader can see the mismatch even though the
  score no longer reflects it.
* **`fragment_precedent` is an explanatory reference-corpus signal, not a
  direct synthetic-difficulty term** — this is the public contract, not
  just a caveat. It used to feed `overall.difficulty` (rounds 17–20); round
  20's cross-corpus validation found that unsafe: configured against
  ChEMBL vs. two different real, honestly-labeled synthesis-focused
  corpora (Open Reaction Database, SynRXN), several structurally
  -legitimate molecules (caffeine, norbornane, spiro/stereocenter-dense
  systems) scored *harder* under one corpus and *not* under another, with
  no way to predict which in advance — worst case, plain pyridine (no
  plausible synthetic-difficulty story at all) swung between
  `LikelyAccessible` and `HighlyChallenging` purely from which corpus was
  configured, driven entirely by this component's own uncapped penalty
  term. **Round 21 (option C) removed `fragment_precedent` from
  `overall.difficulty` entirely** — the signal is still computed exactly
  the same way and still fully reported
  (`SynthesizabilityReport.fragment_precedent`), but it can no longer
  change a score, a verdict, `dominant_penalties`, or `dominant_supports`.
  Verified end-to-end: `overall.difficulty` is now bit-for-bit identical
  regardless of which corpus (ChEMBL/ORD/SynRXN/none) is configured, for
  every molecule tested. See `rules.rs`'s "Fragment precedent" section for
  the full round 16–21 history and reasoning.
* `stereochemical_burden` only covers tetrahedral stereocenter count and
  density. Investigated and still not implemented, each for a different
  reason (see `docs/architecture.md` for the full evidence):
  * E/Z double-bond stereo — chematic *can* assign E/Z directly from SMILES
    `/`/`\` bond markers (no 2D coordinates needed, correcting an earlier,
    inaccurate note here), but only for bonds the input SMILES already
    marked. There's no detector for a stereogenic-but-*unspecified* double
    bond the way tetrahedral centers have one, so a specified-only count
    would measure how carefully the SMILES was written, not how many E/Z
    centers exist — the same class of problem atropisomerism was rejected
    for below, arriving a different way.
  * Atropisomerism — chematic's `detect_atropisomers` was tested directly
    and disqualified: it rates unsubstituted biphenyl as an atropisomer
    when written `c1ccccc1-c2ccccc2` but not when written
    `c1ccccc1c2ccccc2` (same molecule), and rates *para*-substituted
    biphenyl identically to genuinely hindered *ortho*-substituted biphenyl.
    Wrapping it would violate yomitoki's own atom-order/representation
    invariance guarantee.
  * Contiguous stereocenter runs, quaternary-carbon adjacency — both need
    an atom-level list of stereocenter candidates (specified *and*
    unspecified), which chematic doesn't expose (only aggregate counts).
    Building that inside yomitoki would make this crate the owner of
    stereocenter perception instead of a consumer of a validated chematic
    primitive — a line every implemented component has stayed on the other
    side of so far.
  * Meso compound detection — needs graph automorphism / topological
    symmetry classes; chematic has this internally
    (`chematic-smiles::canonical_automorphism`) but doesn't expose it.
* `functional_group_liability` covers reactive/unstable functional groups
  (chematic's Brenk et al. 2008 structural-alert set directly) and dense
  functionalization (distinct functional-group cluster count, via
  chematic's Ertl 2017 `identify_functional_groups`). Mutually incompatible
  functional-group combinations and protecting-group pressure are not
  implemented — unlike the two liabilities above, neither has a citable,
  validated primitive to build on (chematic exposes none), and hand-curating
  either would be exactly the "chemically weak rules, over-generalized"
  AGENTS.md warns against. Chemoselectivity burden, polyfunctional symmetry
  breaking, and difficult oxidation-state combinations are also not
  implemented — chematic exposes no oxidation-state API to build the last
  one on. Brenk's set was validated as a med-chem screening-library
  desirability filter, not a synthetic-difficulty signal, so several of its
  alerts fire on common, cheaply-precedented groups — aspirin, for example,
  trips four Brenk alerts and lands at `ModeratelyAccessible` despite being
  trivially synthesizable, the same score regardless of corpus
  configuration since round 21. This is a known gap with the same shape as
  the rotatable-bond one above — through round 20, `fragment_precedent`
  corrected it once a corpus was configured (aspirin `0.273 → 0.095`);
  round 21 removed that correction (see the fragment_precedent contract
  point above) rather than continue tuning a signal round 20 found too
  corpus-sensitive to trust for scoring. Dense functionalization has
  its own known gap: it counts
  topologically *disconnected* functional-group clusters, so a single
  densely interconnected polyfunctional system (e.g. glucose's ring of
  hydroxyls, or a fused β-lactam) collapses to one cluster — identical in
  count to a molecule with a single, ordinary functional group.
* No fragment corpus ships with yomitoki (AGENTS.md §5.4), so out of the
  box, novel/rare substructures are not detected — `fragment_precedent` stays
  `None` until you build one (`tools/build-fragment-corpus`) and configure
  it. No decision has been made yet about shipping one by default (the
  `yomitoki-core`/`yomitoki-models`/`yomitoki-data` split §5.4 sketches, or
  a feature-flagged external file).
* Simplification suggestions cover 3 of `SuggestionCode`'s 6 variants
  (bridged ring, macrocycle, stereocenter density) — the other 3 are
  unreachable, each for a different reason: `IncreaseFragmentPrecedent`
  was retired in round 21 (it can no longer be truthfully described as
  reducing difficulty); quaternary-carbon adjacency isn't computed
  anywhere; and `brenk_matches_detailed` unions atoms per pattern rather
  than per occurrence (so "remove one of several similar reactive groups"
  can't identify which occurrence to point at). Every suggestion's
  confidence is a flat, named constant (0.5), not per-suggestion-code,
  since none are calibrated against real synthesis outcomes.
* `ApplicabilityReport.domain_distance` is always `None` until a calibration
  corpus exists (Phase 2+).
* Coverage is limited to a curated organic-element subset — no attempt at
  full periodic-table or organometallic support.
* Scores and thresholds are rule-based, not fit to any labeled dataset.
  An external benchmark now exists (see
  [External benchmark]#external-benchmark-v010 above) with mixed
  results — competitive with BR-SAScore on TS1, chance-level on TS2,
  weaker than both competitors on TS3 — and no weight/threshold has been
  changed in response to it (see that section's test-set-integrity rule).

## Reproducibility

Given the same input, `AnalysisConfig`, and yomitoki/chematic/ruleset
versions, `analyze`/`analyze_smiles` always return the same report — no
randomness is used in the core evaluation path.

## License

Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or
[MIT license](LICENSE-MIT) at your option.

## Citation

No paper or citable release exists yet.

## Roadmap

Remaining planned work: a shipped fragment corpus (`fragment_precedent`
itself is implemented but opt-in — see Limitations), *calibration* against
SAscore/RAscore/route-search outcomes (a minimum *comparison* against
SAscore, not calibration, now exists — see above), and eventually Python
bindings.