chemstore 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. chemstore-0.1.0/PKG-INFO +434 -0
  2. chemstore-0.1.0/README.md +410 -0
  3. chemstore-0.1.0/pyproject.toml +53 -0
  4. chemstore-0.1.0/pyproject.toml.orig +54 -0
  5. chemstore-0.1.0/src/chemstore/__init__.py +24 -0
  6. chemstore-0.1.0/src/chemstore/api/__init__.py +0 -0
  7. chemstore-0.1.0/src/chemstore/api/dependencies.py +17 -0
  8. chemstore-0.1.0/src/chemstore/api/main.py +185 -0
  9. chemstore-0.1.0/src/chemstore/api/models.py +31 -0
  10. chemstore-0.1.0/src/chemstore/cli/main.py +138 -0
  11. chemstore-0.1.0/src/chemstore/data/reaction_templates.json +67 -0
  12. chemstore-0.1.0/src/chemstore/domain/Equation.py +8 -0
  13. chemstore-0.1.0/src/chemstore/domain/__init__.py +0 -0
  14. chemstore-0.1.0/src/chemstore/domain/compound.py +29 -0
  15. chemstore-0.1.0/src/chemstore/domain/equation.py +135 -0
  16. chemstore-0.1.0/src/chemstore/domain/reaction.py +85 -0
  17. chemstore-0.1.0/src/chemstore/domain/repository.py +34 -0
  18. chemstore-0.1.0/src/chemstore/infrastructure/repository_impl.py +45 -0
  19. chemstore-0.1.0/src/chemstore/infrastructure/smarts_loader.py +103 -0
  20. chemstore-0.1.0/src/chemstore/infrastructure/storage/__init__.py +0 -0
  21. chemstore-0.1.0/src/chemstore/infrastructure/storage/append_log.py +49 -0
  22. chemstore-0.1.0/src/chemstore/infrastructure/storage/engine.py +69 -0
  23. chemstore-0.1.0/src/chemstore/infrastructure/template_updater.py +345 -0
  24. chemstore-0.1.0/src/chemstore/usecases/__init__.py +0 -0
  25. chemstore-0.1.0/src/chemstore/usecases/balance_equation.py +17 -0
  26. chemstore-0.1.0/src/chemstore/usecases/calculate_mass.py +25 -0
  27. chemstore-0.1.0/src/chemstore/usecases/predict_reaction.py +49 -0
@@ -0,0 +1,434 @@
1
+ Metadata-Version: 2.4
2
+ Name: chemstore
3
+ Version: 0.1.0
4
+ Summary: High-performance cheminformatics calculation and reaction prediction engine with embedded Bitcask key-value storage
5
+ Keywords: chemistry,cheminformatics,rdkit,smiles,smarts,reaction-prediction,equation-balancer,bitcask
6
+ Author: mark
7
+ Author-email: mark <pythondev677@gmail.com>
8
+ License-Expression: MIT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Topic :: Scientific/Engineering :: Chemistry
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Requires-Dist: fastapi>=0.141.1
14
+ Requires-Dist: pydantic>=2.13.5
15
+ Requires-Dist: rdkit>=2026.3.6
16
+ Requires-Dist: requests>=2.34.2
17
+ Requires-Dist: typer[all]>=0.27.2
18
+ Requires-Dist: uvicorn>=0.53.0
19
+ Requires-Python: >=3.12
20
+ Project-URL: Homepage, https://github.com/munenevictor577-blip/chemstore
21
+ Project-URL: Repository, https://github.com/munenevictor577-blip/chemstore
22
+ Project-URL: Issues, https://github.com/munenevictor577-blip/chemstore/issues
23
+ Description-Content-Type: text/markdown
24
+
25
+ # ChemStore
26
+
27
+ ChemStore is a fast, domain-driven chemical informatics toolkit and command-line application equipped with an embedded log-structured (Bitcask-style) storage engine. It provides exact molar mass calculations, stoichiometric chemical equation balancing via linear algebra, and automated organic reaction product prediction.
28
+
29
+ ---
30
+
31
+ ## Features
32
+
33
+ - **Molar Mass & Formula Calculation**:
34
+ - Validates and parses SMILES strings into molecular structures using RDKit.
35
+ - Computes exact molecular weight and Hill-system molecular formulas.
36
+ - **Stoichiometric Chemical Equation Balancing**:
37
+ - Supports reaction equations with `=` or `>>` operators.
38
+ - Automatically parses and expands nested polyatomic ion brackets (e.g., `Mg(OH)2` $\rightarrow$ `MgOHOH`).
39
+ - Solves the null space using exact rational arithmetic (`fractions.Fraction`) via Gaussian elimination, eliminating floating-point rounding errors.
40
+ - Derives the lowest positive whole-integer stoichiometric coefficients using Least Common Multiple (LCM) scaling.
41
+ - **Intelligent Organic Reaction Product Prediction**:
42
+ - Curated library of standard organic reaction templates (Amide coupling, Fischer esterification, Suzuki coupling, Diels-Alder, Williamson-style ether synthesis, Imine formation, Saponification, and Alkene hydrogenation).
43
+ - **Zero-Configuration Automatic Reaction Detection**: Intelligently identifies compatible reactions from input reactant SMILES without requiring the user to specify the reaction type.
44
+ - **Order-Independent Matching**: Permutes reactants to match template reaction centers regardless of the order provided (e.g., alcohol + acid vs. acid + alcohol).
45
+ - Custom Reaction SMARTS support for advanced user-defined reactions.
46
+ - **Embedded Bitcask Key-Value Cache Engine**:
47
+ - High-performance append-only binary log storage format with low overhead.
48
+ - In-memory hash indexing mapping compound keys to disk byte offsets for $O(1)$ random lookups.
49
+ - Automatic index reconstruction on startup from binary headers.
50
+ - Online compaction to prune obsolete records and reclaim disk space.
51
+ - **Clean Architecture / Domain-Driven Design (DDD)**:
52
+ - Clean separation into `domain`, `infrastructure`, `usecases`, and `cli` layers for high maintainability, modularity, and testability.
53
+
54
+ ---
55
+
56
+ ## Architecture
57
+
58
+ The project adheres to Clean Architecture principles:
59
+
60
+ ```
61
+ src/chemstore/
62
+ ├── domain/ # Enterprise business rules & entities (pure domain)
63
+ │ ├── compound.py # Compound entity (SMILES parsing, molecular mass & formula)
64
+ │ ├── Equation.py # Equation model, matrix composition, & rational Gaussian null-space solver
65
+ │ ├── reaction.py # ReactionPredictor (isolated domain logic)
66
+ │ └── repository.py # Abstract ChemicalRepository interface
67
+ ├── data/
68
+ │ └── reaction_templates.json # Externalized JSON reaction SMARTS definitions
69
+ ├── api/ # FastAPI HTTP REST API service
70
+ │ ├── main.py # Endpoint declarations & error handling
71
+ │ ├── models.py # Pydantic request & response schemas
72
+ │ └── dependencies.py # Use case dependency injection
73
+ ├── infrastructure/ # External systems & persistence mechanisms
74
+ │ ├── smarts_loader.py # On-demand reaction SMARTS template loader
75
+ │ ├── storage/
76
+ │ │ ├── append_log.py # Low-level binary append-only disk logger
77
+ │ │ └── engine.py # ChemStoreEngine (Bitcask key-value store with compaction)
78
+ │ └── repository_impl.py # KVChemicalRepository implementation
79
+ ├── usecases/ # Application use cases orchestrating domain models & cache
80
+ │ ├── calculate_mass.py # CalculateMassUseCase (cache-aside mass lookup)
81
+ │ ├── balance_equation.py # BalanceEquationUseCase (cache-aside equation balancing)
82
+ │ └── predict_reaction.py # PredictReactionUseCase (auto-detection & cached product prediction)
83
+ └── cli/
84
+ └── main.py # CLI entry point (argparse)
85
+ ```
86
+
87
+ ### Storage Engine Architecture (Bitcask Log)
88
+
89
+ Every record is stored sequentially in an append-only binary file using an 8-byte little-endian header:
90
+
91
+ ```
92
+ +-------------------+---------------------+------------------+--------------------+
93
+ | Key Size (4 bytes)| Value Size (4 bytes)| Key UTF-8 Bytes | Value UTF-8 Bytes |
94
+ +-------------------+---------------------+------------------+--------------------+
95
+ ```
96
+
97
+ 1. **Write Path (`put`)**: Appends record to the end of the log file, returns the file offset, and updates the in-memory hash table: `index[key] = offset`.
98
+ 2. **Read Path (`get`)**: Performs an $O(1)$ in-memory index lookup to get the byte offset, jumps (`seek`) directly to that offset in the log, unpacks the header, and reads the value.
99
+ 3. **Recovery (`_build_index`)**: On startup, sequentially scans the log headers to reconstruct the in-memory hash index without loading payloads.
100
+ 4. **Compaction (`compact`)**: Rewrites only the latest values of active keys to a fresh file, discarding dead and overwritten records.
101
+
102
+ ---
103
+
104
+ ## Installation
105
+
106
+ ### Requirements
107
+ - Python `>= 3.12`
108
+ - `uv` (recommended) or `pip`
109
+
110
+ ### Setup with `uv`
111
+
112
+ ```bash
113
+ git clone https://github.com/your-org/chemstore.git
114
+ cd chemstore
115
+
116
+ # Install dependencies and package in editable mode
117
+ uv pip install -e .
118
+ ```
119
+
120
+ ### Setup with `venv` & `pip`
121
+
122
+ ```bash
123
+ python3 -m venv .venv
124
+ source .venv/bin/activate
125
+ pip install -e .
126
+ ```
127
+
128
+ ---
129
+
130
+ ## CLI Usage
131
+
132
+ The package installs the `chemstore` command-line utility.
133
+
134
+ ```
135
+ usage: chemstore [-h] {mass,balance,predict,admin} ...
136
+ ```
137
+
138
+ ### 1. Calculate Molar Mass (`mass`)
139
+
140
+ Calculates the molecular weight from a SMILES string:
141
+
142
+ ```bash
143
+ # Ethanol
144
+ chemstore mass "CCO"
145
+ # Output:
146
+ # 46.041864812
147
+
148
+ # Caffeine
149
+ chemstore mass "CN1C=NC2=C1C(=O)N(C(=O)N2C)C"
150
+ # Output:
151
+ # 194.08037556
152
+ ```
153
+
154
+ If cached, subsequent lookups retrieve the mass directly from the local Bitcask engine with near-zero latency.
155
+
156
+ ---
157
+
158
+ ### 2. Balance Chemical Equations (`balance`)
159
+
160
+ Balances equations containing `=` or `>>`, automatically handling polyatomic groups and complex stoichiometries:
161
+
162
+ ```bash
163
+ # Simple combustion
164
+ chemstore balance "H2 + O2 = H2O"
165
+ # Output:
166
+ # 2H2 + O2 = 2H2O
167
+
168
+ # Alkane combustion
169
+ chemstore balance "C2H6 + O2 = CO2 + H2O"
170
+ # Output:
171
+ # 2C2H6 + 7O2 = 4CO2 + 6H2O
172
+
173
+ # Yield operator (>>)
174
+ chemstore balance "C2H6 + O2 >> CO2 + H2O"
175
+ # Output:
176
+ # 2C2H6 + 7O2 >> 4CO2 + 6H2O
177
+
178
+ # Polyatomic brackets
179
+ chemstore balance "Mg(OH)2 + HCl = MgCl2 + H2O"
180
+ # Output:
181
+ # Mg(OH)2 + 2HCl = MgCl2 + 2H2O
182
+ ```
183
+
184
+ ---
185
+
186
+ ### 3. Predict Reaction Products (`predict`)
187
+
188
+ #### Automatic Reaction Detection (No Reaction Name Needed)
189
+ Simply provide the reactant SMILES. ChemStore automatically identifies the matching reaction template and evaluates the products:
190
+
191
+ ```bash
192
+ # Esterification (acetic acid + ethanol)
193
+ chemstore predict "CC(=O)O" "CCO"
194
+ # Output:
195
+ # Reactants: CC(=O)O + CCO
196
+ # Reaction: esterification (auto-detected)
197
+ # Products: CCOC(C)=O
198
+
199
+ # Order-independent (ethanol first, acetic acid second)
200
+ chemstore predict "CCO" "CC(=O)O"
201
+ # Output:
202
+ # Reactants: CCO + CC(=O)O
203
+ # Reaction: esterification (auto-detected)
204
+ # Products: CCOC(C)=O
205
+
206
+ # Diels-Alder (butadiene + ethylene)
207
+ chemstore predict "C=CC=C" "C=C"
208
+ # Output:
209
+ # Reactants: C=CC=C + C=C
210
+ # Reaction: diels_alder (auto-detected)
211
+ # Products: C1=CCCCC1
212
+
213
+ # Alkene hydrogenation
214
+ chemstore predict "CC=CC"
215
+ # Output:
216
+ # Reactants: CC=CC
217
+ # Reaction: alkene_hydrogenation (auto-detected)
218
+ # Products: CCCC
219
+ ```
220
+
221
+ #### List Available Reaction Templates (`--list` / `-l`)
222
+
223
+ ```bash
224
+ chemstore predict --list
225
+ ```
226
+ Output:
227
+ ```
228
+ Available reactions:
229
+ amide_coupling : Amide coupling: carboxylic acid + amine -> amide
230
+ esterification : Fischer esterification: carboxylic acid + alcohol -> ester
231
+ suzuki_coupling : Suzuki coupling: aryl halide + boronic acid -> biaryl
232
+ diels_alder : Diels-Alder: diene + dienophile -> cyclohexene
233
+ ether_synthesis : Ether synthesis: alkyl halide + alcohol -> ether
234
+ imine_formation : Imine formation: carbonyl (aldehyde/ketone) + primary amine -> imine
235
+ saponification : Saponification / ester hydrolysis: ester -> acid + alcohol
236
+ alkene_hydrogenation : Alkene hydrogenation: alkene -> alkane
237
+ ```
238
+
239
+ #### Explicit Reaction Selection (`--reaction` / `-r`)
240
+
241
+ ```bash
242
+ chemstore predict "CC(=O)O" "CCO" --reaction esterification
243
+ # Output:
244
+ # Reactants: CC(=O)O + CCO
245
+ # Reaction: esterification
246
+ # Products: CCOC(C)=O
247
+ ```
248
+
249
+ #### Custom SMARTS Rule (`--smarts` / `-s`)
250
+
251
+ You can provide custom RDKit reaction SMARTS to predict arbitrary transformations:
252
+
253
+ ```bash
254
+ chemstore predict "CC(=O)O" "CN" --smarts "[O:2]=[C:1][OH:3].[N:4]>>[O:2]=[C:1][N:4]"
255
+ ```
256
+
257
+ ---
258
+
259
+ ### 4. Database Operations (`admin`)
260
+
261
+ Reclaim disk space by running log compaction on the cache database:
262
+
263
+ ```bash
264
+ chemstore admin --compact
265
+ # Output:
266
+ # Connecting to the embedded database...
267
+ # Compaction complete.
268
+ ```
269
+
270
+ ---
271
+
272
+ ## Python API Usage
273
+
274
+ ChemStore can be integrated directly into Python scripts:
275
+
276
+ ### Molecular Mass & Formulas
277
+
278
+ ```python
279
+ from chemstore.domain.compound import Compound
280
+
281
+ compound = Compound("CN1C=NC2=C1C(=O)N(C(=O)N2C)C")
282
+ print("Formula:", compound.formula) # C8H10N4O2
283
+ print("Molecular Weight:", compound.molecular_weight) # 194.0803...
284
+ ```
285
+
286
+ ### Chemical Equation Balancing
287
+
288
+ ```python
289
+ from chemstore.domain.Equation import Equation
290
+
291
+ eq = Equation("C2H6 + O2 = CO2 + H2O")
292
+ print(eq.balanced) # 2C2H6 + 7O2 = 4CO2 + 6H2O
293
+ ```
294
+
295
+ ### Reaction Prediction
296
+
297
+ ```python
298
+ from chemstore.domain.reaction import ReactionPredictor
299
+
300
+ # 1. Automatic reaction detection
301
+ matches = ReactionPredictor.auto_predict(["CC(=O)O", "CCO"])
302
+ for name, products in matches:
303
+ print(f"Reaction: {name}, Products: {products}")
304
+ # Reaction: esterification, Products: ['CCOC(C)=O']
305
+
306
+ # 2. Named reaction by factory
307
+ predictor = ReactionPredictor.from_name("diels_alder")
308
+ products = predictor.run(["C=CC=C", "C=C"])
309
+ print("Products:", products) # ['C1=CCCCC1']
310
+
311
+ # 3. Custom SMARTS rule
312
+ custom_predictor = ReactionPredictor("[C:1](=[O:2])[OH].[N:3]>>[C:1](=[O:2])[N:3]")
313
+ products = custom_predictor.run(["CC(=O)O", "CN"])
314
+ print("Products:", products) # ['CNC(C)=O']
315
+ ```
316
+
317
+ ### Using Cached Use Cases
318
+
319
+ ```python
320
+ from chemstore.infrastructure.repository_impl import KVChemicalRepository
321
+ from chemstore.usecases.calculate_mass import CalculateMassUseCase
322
+ from chemstore.usecases.predict_reaction import PredictReactionUseCase
323
+
324
+ repo = KVChemicalRepository("chemistry_cache.db")
325
+
326
+ # Calculate mass with cache persistence
327
+ mass_calc = CalculateMassUseCase(repo)
328
+ mass = mass_calc.execute("CCO")
329
+
330
+ # Predict reaction with cache persistence (auto-detection)
331
+ rxn_calc = PredictReactionUseCase(repo)
332
+ products = rxn_calc.execute(None, ["CC(=O)O", "CCO"])
333
+ ```
334
+
335
+ ---
336
+
337
+ ## REST API Service (FastAPI)
338
+
339
+ ChemStore includes a FastAPI HTTP service exposing calculation and prediction endpoints. To support reverse-proxy routing (e.g. routing all `chemstore` traffic to `/chem`), endpoints are routed under `/chem/api/v1/...` while preserving `/api/v1/...` for direct calls and backward compatibility:
340
+
341
+ ```bash
342
+ # Start the API server
343
+ uvicorn chemstore.api.main:app --host 0.0.0.0 --port 8000 --reload
344
+ ```
345
+
346
+ ### Available Endpoints:
347
+ - `POST /chem/api/v1/mass` (or `/api/v1/mass`)
348
+ - `POST /chem/api/v1/balance` (or `/api/v1/balance`)
349
+ - `POST /chem/api/v1/predict` (or `/api/v1/predict`)
350
+
351
+ ### Reaction Prediction Endpoint (`POST /chem/api/v1/predict`)
352
+
353
+ Supports explicit reaction names, custom SMARTS strings, or zero-configuration auto-detection:
354
+
355
+ #### With Reaction Name:
356
+ ```bash
357
+ curl -X POST http://localhost:8000/chem/api/v1/predict \
358
+ -H "Content-Type: application/json" \
359
+ -d '{"reactants": ["CC(=O)O", "CCN"], "reaction_name": "amide_coupling"}'
360
+ ```
361
+ Response:
362
+ ```json
363
+ {
364
+ "reactants": ["CC(=O)O", "CCN"],
365
+ "reaction_name": "amide_coupling",
366
+ "reaction": "amide_coupling",
367
+ "products": ["CCNC(C)=O"]
368
+ }
369
+ ```
370
+
371
+ #### With Automatic Detection:
372
+ ```bash
373
+ curl -X POST http://localhost:8000/chem/api/v1/predict \
374
+ -H "Content-Type: application/json" \
375
+ -d '{"reactants": ["CC(=O)O", "CCO"]}'
376
+ ```
377
+ Response:
378
+ ```json
379
+ {
380
+ "reactants": ["CC(=O)O", "CCO"],
381
+ "reaction_name": "esterification",
382
+ "reaction": "esterification",
383
+ "products": ["CCOC(C)=O"]
384
+ }
385
+ ```
386
+
387
+ ---
388
+
389
+ ## Predefined Reaction Catalog
390
+
391
+ | Identifier | Name | Reaction Transformation | SMARTS Rule |
392
+ | :--- | :--- | :--- | :--- |
393
+ | `amide_coupling` | Amide Coupling | Carboxylic Acid + Amine $\rightarrow$ Amide | `[O:2]=[C:1][OH:3].[N:4]>>[O:2]=[C:1][N:4]` |
394
+ | `esterification` | Fischer Esterification | Carboxylic Acid + Alcohol $\rightarrow$ Ester | `[C:1](=[O:2])[OH].[O:3][C:4]>>[C:1](=[O:2])[O:3][C:4]` |
395
+ | `suzuki_coupling` | Suzuki Coupling | Aryl Halide + Boronic Acid $\rightarrow$ Biaryl | `[c:1][Br,I,Cl].[c:2]B(O)O>>[c:1][c:2]` |
396
+ | `diels_alder` | Diels-Alder Cycloaddition | Conjugated Diene + Dienophile $\rightarrow$ Cyclohexene | `[C:1]=[C:2][C:3]=[C:4].[C:5]=[C:6]>>[C:1]1[C:2]=[C:3][C:4][C:5][C:6]1` |
397
+ | `ether_synthesis` | Ether Synthesis | Alkyl Halide + Alcohol $\rightarrow$ Ether | `[C:1][Br,I,Cl].[O:2][C:3]>>[C:1][O:2][C:3]` |
398
+ | `imine_formation` | Imine Formation | Carbonyl (Aldehyde/Ketone) + Primary Amine $\rightarrow$ Imine | `[C;!$(C[O,N,S,Cl,Br,I,F]):1]=[O:2].[N;H2:3]>>[C:1]=[N:3]` |
399
+ | `saponification` | Saponification | Ester $\rightarrow$ Carboxylic Acid + Alcohol | `[C:1](=[O:2])[O:3][C:4]>>[C:1](=[O:2])[OH].[O:3][C:4]` |
400
+ | `alkene_hydrogenation` | Alkene Hydrogenation | Alkene $\rightarrow$ Alkane | `[C:1]=[C:2]>>[C:1][C:2]` |
401
+ | `alkane_chlorination` | Radical Chlorination | Alkane + $\text{Cl}_2$ $\rightarrow$ Alkyl Chloride | `[CX4;!H0:1]>>[C:1]Cl` |
402
+ | `alkane_bromination_tertiary` | Tertiary Radical Bromination | 3° Alkane + $\text{Br}_2$ $\rightarrow$ Tertiary Alkyl Bromide | `[CX4;H1:1]>>[C:1]Br` |
403
+ | `alkane_dehydrogenation` | Alkane Dehydrogenation | Alkane $\rightarrow$ Alkene + $\text{H}_2$ | `[CX4;!H0:1]-[CX4;!H0:2]>>[C:1]=[C:2]` |
404
+ | `alkane_nitration` | Gas-Phase Nitration | Alkane + $\text{HNO}_3$ $\rightarrow$ Nitroalkane + $\text{H}_2\text{O}$ | `[CX4;!H0:1]>>[C:1][N+](=[O])[O-]` |
405
+ | `alkane_sulfoxidation` | Alkane Sulfoxidation | Alkane + $\text{SO}_2$ + $\text{O}_2$ $\rightarrow$ Alkanesulfonic Acid | `[CX4;!H0:1]>>[C:1]S(=O)(=O)O` |
406
+
407
+ ---
408
+
409
+ ## Testing
410
+
411
+ ChemStore includes an extensive suite of automated tests covering domain logic, storage persistence, repository caching, and CLI workflows.
412
+
413
+ Run the test suite using `pytest`:
414
+
415
+ ```bash
416
+ # Run all tests
417
+ .venv/bin/pytest
418
+
419
+ # Run with verbose output
420
+ .venv/bin/pytest -v
421
+ ```
422
+
423
+ ### Test Coverage Highlights
424
+ - **Domain Tests** (`tests/domain/`): Valid/invalid SMILES parsing, polyatomic equation parsing, yield operator balancing, reaction templates, auto-prediction, order-independence.
425
+ - **Infrastructure Tests** (`tests/infrastructure/`): Low-level append log writes, byte offset seeks, index recovery on startup, compaction, tombstone eviction, and on-demand JSON SMARTS template loading.
426
+ - **API Tests** (`tests/test_api.py`): Mass calculation, chemical equation balancing, and reaction prediction with named reactions, custom SMARTS, auto-detection, and error responses.
427
+ - **Use Case Integration Tests** (`tests/test_repository.py`): Cache hits, cache writes, fallback lookups, and multi-round verification.
428
+ - **CLI Subprocess Tests** (`tests/test_cli.py`): Execution and error exits across `mass`, `balance`, `predict`, and `admin`.
429
+
430
+ ---
431
+
432
+ ## License
433
+
434
+ This project is licensed under the MIT License.