weakness-report 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 (33) hide show
  1. weakness_report-0.1.0/LICENSE +21 -0
  2. weakness_report-0.1.0/PKG-INFO +524 -0
  3. weakness_report-0.1.0/README.md +483 -0
  4. weakness_report-0.1.0/pyproject.toml +64 -0
  5. weakness_report-0.1.0/setup.cfg +4 -0
  6. weakness_report-0.1.0/tests/test_weakness.py +586 -0
  7. weakness_report-0.1.0/weakness_report/__init__.py +3 -0
  8. weakness_report-0.1.0/weakness_report/aggregate.py +256 -0
  9. weakness_report-0.1.0/weakness_report/batch.py +303 -0
  10. weakness_report-0.1.0/weakness_report/board.py +78 -0
  11. weakness_report-0.1.0/weakness_report/bridge.py +203 -0
  12. weakness_report-0.1.0/weakness_report/buckets.py +288 -0
  13. weakness_report-0.1.0/weakness_report/cli.py +473 -0
  14. weakness_report-0.1.0/weakness_report/exportcsv.py +62 -0
  15. weakness_report-0.1.0/weakness_report/features.py +218 -0
  16. weakness_report-0.1.0/weakness_report/findings.py +218 -0
  17. weakness_report-0.1.0/weakness_report/jobs.py +153 -0
  18. weakness_report-0.1.0/weakness_report/paths.py +82 -0
  19. weakness_report-0.1.0/weakness_report/pdf.py +580 -0
  20. weakness_report-0.1.0/weakness_report/pipeline.py +160 -0
  21. weakness_report-0.1.0/weakness_report/report.py +142 -0
  22. weakness_report-0.1.0/weakness_report/server.py +380 -0
  23. weakness_report-0.1.0/weakness_report/sources.py +501 -0
  24. weakness_report-0.1.0/weakness_report/store.py +203 -0
  25. weakness_report-0.1.0/weakness_report/web/app.js +700 -0
  26. weakness_report-0.1.0/weakness_report/web/index.html +238 -0
  27. weakness_report-0.1.0/weakness_report/web/style.css +428 -0
  28. weakness_report-0.1.0/weakness_report.egg-info/PKG-INFO +524 -0
  29. weakness_report-0.1.0/weakness_report.egg-info/SOURCES.txt +31 -0
  30. weakness_report-0.1.0/weakness_report.egg-info/dependency_links.txt +1 -0
  31. weakness_report-0.1.0/weakness_report.egg-info/entry_points.txt +2 -0
  32. weakness_report-0.1.0/weakness_report.egg-info/requires.txt +16 -0
  33. weakness_report-0.1.0/weakness_report.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shubhro Dev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,524 @@
1
+ Metadata-Version: 2.4
2
+ Name: weakness-report
3
+ Version: 0.1.0
4
+ Summary: Review your whole game history and find out what you are actually bad at.
5
+ Author-email: Shubhro Dev <shubhro2004@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/spearb0lt/Lichess-Essentials
8
+ Project-URL: Repository, https://github.com/spearb0lt/Lichess-Essentials
9
+ Project-URL: Issues, https://github.com/spearb0lt/Lichess-Essentials/issues
10
+ Project-URL: Documentation, https://github.com/spearb0lt/Lichess-Essentials/blob/main/Weakness-Report/README.md
11
+ Keywords: chess,lichess,statistics,weaknesses,report,game-review
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Environment :: Web Environment
15
+ Classifier: Framework :: FastAPI
16
+ Classifier: Intended Audience :: End Users/Desktop
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Games/Entertainment :: Board Games
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: chess>=1.11
28
+ Requires-Dist: requests>=2.31
29
+ Requires-Dist: reportlab>=4.0
30
+ Requires-Dist: fastapi>=0.110
31
+ Requires-Dist: uvicorn[standard]>=0.27
32
+ Requires-Dist: pydantic>=2.0
33
+ Requires-Dist: chess-game-analyzer>=0.1.0
34
+ Provides-Extra: review
35
+ Requires-Dist: chess-game-analyzer>=0.1.0; extra == "review"
36
+ Provides-Extra: diagrams
37
+ Requires-Dist: lichess-study-to-pdf>=0.1.0; extra == "diagrams"
38
+ Provides-Extra: dev
39
+ Requires-Dist: pytest>=8.0; extra == "dev"
40
+ Dynamic: license-file
41
+
42
+ # Weakness Report
43
+
44
+ [![PyPI](https://img.shields.io/pypi/v/weakness-report?logo=pypi&logoColor=white)](https://pypi.org/project/weakness-report/)
45
+ [![Python](https://img.shields.io/pypi/pyversions/weakness-report)](https://pypi.org/project/weakness-report/)
46
+ [![Downloads](https://static.pepy.tech/badge/weakness-report)](https://pepy.tech/project/weakness-report)
47
+ [![Downloads](https://static.pepy.tech/badge/weakness-report/month)](https://pepy.tech/project/weakness-report)
48
+ [![License](https://img.shields.io/pypi/l/weakness-report)](LICENSE)
49
+
50
+
51
+ Review your whole game history and find out what you are actually bad at.
52
+
53
+ A single game review tells you what went wrong in that game. Four hundred of
54
+ them, sliced by the kind of position you were in, tell you what keeps going
55
+ wrong — which is the thing you can do something about:
56
+
57
+ > **When you are ahead** — 1.46 pawns a game worse than your average, over 664
58
+ > moves in 45 games (76 centipawns a move against your 63).
59
+
60
+ ```powershell
61
+ cd Weakness-Report
62
+ & "..\.lichess\Scripts\python.exe" -m weakness_report.cli serve # port 8781
63
+ ```
64
+
65
+ Then open <http://127.0.0.1:8781>.
66
+
67
+ ![The findings tab: overall figures across the top, then the kinds of position costing you most, each with the sample it rests on](https://raw.githubusercontent.com/spearb0lt/Lichess-Essentials/main/Weakness-Report/docs/report.png)
68
+
69
+ ---
70
+
71
+ ## Install
72
+
73
+ ```bash
74
+ pip install weakness-report
75
+ weakness serve
76
+ ```
77
+
78
+ `chess-game-analyzer` is a real dependency and pip installs it with this one:
79
+ the review has to agree with that app. Board diagrams in the PDF are
80
+ optional: `pip install "weakness-report[diagrams]"`.
81
+
82
+ Or take all five at once with `pip install lichess-essentials`. Installed this
83
+ way your files live in the usual per-user folder for your platform, and the
84
+ app prints the path in its startup banner. To run it from a checkout instead,
85
+ see [the repository README](https://github.com/spearb0lt/Lichess-Essentials/blob/main/README.md#setup-from-a-checkout).
86
+
87
+ ## The number the whole thing rests on
88
+
89
+ Three ways of ranking a slice, and only one of them answers "what should I
90
+ work on".
91
+
92
+ | Rank by | What you get |
93
+ |---|---|
94
+ | **Rate** (bucket ACPL) | Whichever bucket is smallest. Eleven moves in opposite-side castling at 300 centipawns beats everything, for ever. |
95
+ | **Total loss** (centipawns given away) | The middlegame, every time, because that is where most of your moves are. It is true and it is useless. |
96
+ | **Excess loss** | `moves × (bucket ACPL − your overall ACPL)`. How much a kind of position costs you **beyond what you cost yourself anyway**. |
97
+
98
+ Excess is the one this report ranks by, because it multiplies *how bad you are
99
+ at something* by *how often it happens to you*. Divided by the number of
100
+ games, it becomes the sentence you actually want: **0.4 pawns a game more than
101
+ your average, in queenless middlegames**.
102
+
103
+ Two guards sit in front of it:
104
+
105
+ - **A sample floor.** Nothing is claimed below 40 counted moves across 5
106
+ games. Buckets under the floor stay in the tables, with their counts, so you
107
+ can see what was set aside. Both numbers are adjustable in the browser, and
108
+ changing them costs no engine time at all.
109
+ - **Deduplication by moves, not by names.** "Middlegame", "queens off" and
110
+ "queenless middlegames" can be the same two hundred moves; ranked naively
111
+ your top three findings are one finding three times. Findings are chosen
112
+ greedily and anything that shares most of its *actual move set* with
113
+ something already said is dropped. The comparison is Jaccard similarity, not
114
+ containment — containment would throw away every specific finding in favour
115
+ of the vague one that happens to contain it.
116
+
117
+ ---
118
+
119
+ ## Where the review comes from
120
+
121
+ **There is one implementation of "was that a blunder", and it is not in this
122
+ app.** Accuracy, the move labels, where the opening stops and the middlegame
123
+ starts, the engine pool — all of that is ChessAnalyzer's, tuned and written
124
+ down in that app's README. This app aggregates it.
125
+
126
+ That is a deliberate choice, and it is the reason a figure here means exactly
127
+ what the same figure means over there. A second implementation would drift,
128
+ and a report whose numbers disagree with the app you check them in is worse
129
+ than no report.
130
+
131
+ It is found automatically, in this order:
132
+
133
+ 1. `chess_analyzer` already installed (`pip install -e ChessAnalyzer`)
134
+ 2. `$CHESS_ANALYZER_DIR`, for a checkout somewhere unusual
135
+ 3. **the sibling folder in this repository**, added to `sys.path` — the same
136
+ trick ChessAnalyzer's own test suite uses on itself
137
+
138
+ Because of (3) a normal checkout needs no install at all. The startup banner
139
+ says which route it took. If it cannot be found, the app says so plainly and
140
+ refuses to invent a second opinion.
141
+
142
+ There is a test — `test_our_accuracy_matches_the_analyzers_exactly` — that
143
+ fails the moment the two could disagree.
144
+
145
+ ### What this app does change
146
+
147
+ Two settings, both about making a report you can **run again in three months
148
+ and believe the difference**:
149
+
150
+ - **Fixed depth, not movetime.** ChessAnalyzer's presets are movetime budgets,
151
+ which is right for one game you are reading move by move — that app says so
152
+ itself about its own presets. It is wrong for a number averaged over four
153
+ hundred games, because every figure would then depend on how busy the
154
+ machine was. Batches here search to a fixed depth.
155
+ - **One engine thread by default.** Stockfish splits its search across threads
156
+ and they do not always finish in the same order, so multi-threaded search is
157
+ non-deterministic *even at a fixed depth*. One thread is slower and gives the
158
+ same answer twice.
159
+
160
+ Both are settings; trade either away for speed if you would rather.
161
+
162
+ | Preset | Depth | Roughly |
163
+ |---|---|---|
164
+ | Sweep | 10 | ~1 second per 10 moves. Finds blunders and big patterns. |
165
+ | Standard | 14 | The sensible default for a few hundred games. |
166
+ | Deep | 18 | Several times slower. Worth it for a history you intend to keep. |
167
+
168
+ Sixty games at Sweep took 65 seconds on the machine this was written on.
169
+ The estimate in the app is deliberately crude and labelled as such: engine
170
+ speed varies by an order of magnitude across machines.
171
+
172
+ ---
173
+
174
+ ## Setting up
175
+
176
+ Everything shares the one virtualenv at the repository root:
177
+
178
+ ```powershell
179
+ # Windows PowerShell
180
+ python -m venv .lichess
181
+ .\.lichess\Scripts\python.exe -m pip install -r Weakness-Report\requirements.txt
182
+ .\.lichess\Scripts\python.exe -m pip install -e Lichess-Study-to-PDF
183
+ ```
184
+
185
+ ```bash
186
+ # Git Bash on Windows
187
+ python -m venv .lichess
188
+ ./.lichess/Scripts/python.exe -m pip install -r Weakness-Report/requirements.txt
189
+ ./.lichess/Scripts/python.exe -m pip install -e Lichess-Study-to-PDF
190
+
191
+ # macOS / Linux
192
+ python -m venv .lichess
193
+ ./.lichess/bin/python -m pip install -r Weakness-Report/requirements.txt
194
+ ./.lichess/bin/python -m pip install -e Lichess-Study-to-PDF
195
+ ```
196
+
197
+ The study exporter is optional and buys the board diagrams in the PDF, plus
198
+ the font handling that lets it draw them. ChessAnalyzer needs no install as
199
+ long as its folder is beside this one.
200
+
201
+ A [Stockfish](https://stockfishchess.org/download/) binary in
202
+ `Lichess-Study-to-PDF/engine/` is what does the actual reviewing; ChessAnalyzer
203
+ can also download one from its own engine picker. Without an engine there is
204
+ nothing to review.
205
+
206
+ **No API token is needed.** Every endpoint is public. A Lichess token raises
207
+ the rate limit while pulling a few hundred games; paste one into the Token
208
+ dialog and it is kept in memory for that session only, never written to disk.
209
+
210
+ ---
211
+
212
+ ## Running it
213
+
214
+ ```powershell
215
+ # Windows PowerShell
216
+ cd Weakness-Report
217
+ & "..\.lichess\Scripts\python.exe" -m weakness_report.cli serve
218
+ ```
219
+
220
+ ```bash
221
+ # Git Bash on Windows
222
+ cd Weakness-Report
223
+ ../.lichess/Scripts/python.exe -m weakness_report.cli serve
224
+
225
+ # macOS / Linux
226
+ cd Weakness-Report
227
+ ../.lichess/bin/python -m weakness_report.cli serve
228
+ ```
229
+
230
+ Open <http://127.0.0.1:8781>. `Ctrl+C` stops it. `--port 8782` if 8781 is
231
+ taken; `--host 0.0.0.0` exposes it to your network, which has no
232
+ authentication unless you set the password gate below — and this app serves
233
+ your own games and your own numbers, so only do that on a network you trust.
234
+
235
+ A review of a few hundred games takes minutes, not seconds. It runs in the
236
+ background, reports progress, survives you switching tabs, and can be
237
+ cancelled; anything already reviewed is kept, so a cancelled run is not a
238
+ wasted one.
239
+
240
+ ---
241
+
242
+ ## Where the games come from
243
+
244
+ | Source | |
245
+ |---|---|
246
+ | **Lichess** | One streaming request, PGN and clocks included. |
247
+ | **Chess.com** | Monthly archives, newest first, until there are enough. Bounded at 36 months. |
248
+ | **A PGN file or folder** | Anything you exported from anywhere, including games played over the board. |
249
+ | **ChessAnalyzer's library** | Games you have already reviewed there. Read only — this app never writes into another app's folder. |
250
+
251
+ Reviews are filed **by game id, not by dataset**, so the same game reviewed
252
+ once counts for every report that includes it, whichever door it came through.
253
+ Adding fifty games to a history of four hundred costs fifty reviews.
254
+
255
+ A saved review is only reused when it was searched **the same way**. Changing
256
+ the preset re-reviews rather than quietly mixing depth-10 and depth-18 numbers
257
+ into one average — a report that did that would look completely normal and be
258
+ entirely wrong. *Existing reviews* in the settings relaxes this if you would
259
+ rather have the speed, and the report then says its settings were mixed.
260
+
261
+ ---
262
+
263
+ ## How it slices your history
264
+
265
+ Fourteen dimensions, and a move can be in several buckets of the same one.
266
+
267
+ | | |
268
+ |---|---|
269
+ | **Kinds of position** | Queenless middlegames, middlegames with queens on, opposite-side castling, your king still in the centre, open and closed positions, rook / pawn / minor-piece / opposite-bishop endings, when you are ahead, when you are behind. A curated list rather than a full cross product: phase × queens × centre × material is 108 buckets, nearly all too small to say anything about. |
270
+ | **Game phase** | Opening, middlegame, endgame — boundaries found from the position, not a fixed move number. |
271
+ | **Queens, pawn structure, material, your king, kind of ending** | The raw position features, each computable from a FEN alone and each written out in the glossary. |
272
+ | **Time pressure** | Seconds left on your clock when you moved, from the PGN's own `[%clk]` comments. Games with no clock are left out of this slice entirely rather than lumped together. |
273
+ | **Opening** | Grouped by family, so a variation does not become its own bucket of one game. |
274
+ | **Colour, time control, opponent strength** | As White or Black, bullet through classical, and against players rated 100 points either side of you. |
275
+ | **Move label, move number** | How the move was labelled, and ten-move bands, which sometimes shows a slump the phase split hides. |
276
+
277
+ ![Every slice as a table, each bucket against your own average, with the bar showing which side of it you are on](https://raw.githubusercontent.com/spearb0lt/Lichess-Essentials/main/Weakness-Report/docs/slices.png)
278
+
279
+ Every table shows moves, games, ACPL, accuracy, blunders and the gap to your
280
+ own average, so you can disagree with the ranking and read the numbers
281
+ yourself.
282
+
283
+ ### What the features mean
284
+
285
+ All of them are computed from the position alone, and all are deliberately
286
+ simple, because a feature nobody can check is a feature nobody should trust:
287
+
288
+ - **queens off / one queen / queens on** — who still has a queen
289
+ - **open / semi-open / closed centre** — pawn pairs standing head to head:
290
+ none, one or two, three or more
291
+ - **material ahead / level / behind** — a pawn or more either way, **from your
292
+ side**, not White's
293
+ - **your king** — which wing it is standing on, counted only once the opening
294
+ is over, because before that the bucket is really "have you castled yet"
295
+ - **rook / pawn / minor piece / opposite bishops / queen ending** — by which
296
+ pieces are left, and only once the position is actually an ending
297
+
298
+ ### An honest limit
299
+
300
+ Slices overlap and none of them is controlled for the others. A bucket that
301
+ happens to hold most of your middlegame will inherit how you play middlegames.
302
+ Near-duplicates are dropped, but a partial overlap survives — so read two
303
+ findings that cover the same moves as one observation, not two. The report
304
+ says this on its method page too.
305
+
306
+ ---
307
+
308
+ ## The worst moves
309
+
310
+ The single moves that cost most across the whole history, at most two from any
311
+ one game so that one collapse does not fill the page. Forced moves are
312
+ excluded: there was nothing else to play.
313
+
314
+ ![The worst moves as a grid of diagrams, with the selected one shown large beside its opening, situation and the move the engine wanted](https://raw.githubusercontent.com/spearb0lt/Lichess-Essentials/main/Weakness-Report/docs/moments.png)
315
+
316
+ ---
317
+
318
+ ## Exporting
319
+
320
+ **PDF** is a document rather than a dump: what was measured and how, the
321
+ findings in order, what you do well, every slice as a table with a bar against
322
+ your average, the worst moves as diagrams, and the method. This is the new
323
+ layout — the study exporter lays out *lines of chess*, and a weakness report is
324
+ a different shape — but it borrows that app's board renderer for the diagrams
325
+ and its font handling, both optional.
326
+
327
+ <img src="docs/pdf-cover.png" width="420" alt="The first page of the exported PDF: the figures it was built from, then the ranked findings each with a bar and its sample, then what you do well"> <img src="docs/pdf-moments.png" width="420" alt="A PDF page of the worst moves as board diagrams, each with the move played, what it cost, the engine's move and the kind of position">
328
+
329
+ **CSV** gives every bucket of every slice as one file, with the dimension as a
330
+ column, for anyone who would rather sort by their own column.
331
+
332
+ ---
333
+
334
+ ## On a phone
335
+
336
+ One column below 860px, with the report first, then the position you tapped,
337
+ then the history list and settings. Settings collapse into a fold, the tab bar
338
+ scrolls, and the board is never allowed to fill a tablet.
339
+
340
+ <img src="docs/phone.png" width="300" alt="Weakness Report on a phone: a two-row header, the summary tiles, and the findings list">
341
+
342
+ ---
343
+
344
+ ## What is kept on disk
345
+
346
+ ```
347
+ history/ (WEAKNESS_DIR, or ./history)
348
+ settings.json
349
+ games/lichess-you.json the games of one dataset, PGN included
350
+ reviews/lichess-Wi8IPxc3.json one review per game -- the expensive part
351
+ reports/lichess-you.json the finished aggregation
352
+ data/positions.json the engine's position cache
353
+ ```
354
+
355
+ Reviews are the only thing here that is expensive, and forgetting a report
356
+ keeps them. The position cache is this app's own rather than ChessAnalyzer's:
357
+ sharing that file would be a write into another app's folder, which the apps in
358
+ this repository do not do to each other.
359
+
360
+ `history/` is gitignored — it holds your games and a page of numbers about
361
+ your play.
362
+
363
+ ---
364
+
365
+ ## Command line
366
+
367
+ Reviewing four hundred games is the sort of thing you start before going to
368
+ bed, so everything the browser does is also a command, and the long one prints
369
+ progress rather than sitting silent for an hour.
370
+
371
+ ```bash
372
+ weakness run <username> --source lichess --limit 300 --preset standard
373
+ weakness run you --source chesscom --speed blitz --days 180
374
+ weakness run me --source pgn --path ~/games/otb.pgn
375
+ weakness run me --source analyzer # reuse ChessAnalyzer's reviews
376
+ weakness estimate <username> --preset deep # how long would that take?
377
+ weakness games <username> --limit 500 # fetch only, review nothing
378
+ weakness show <key> --slice situation --slice clock
379
+ weakness reslice <key> --min-moves 80 --min-games 10
380
+ weakness list
381
+ weakness forget <key> # the report; reviews are kept
382
+ weakness pdf <key> --out report.pdf
383
+ weakness csv <key> --out slices.csv
384
+ weakness serve [--host H] [--port P]
385
+ ```
386
+
387
+ Run them through the venv the same way as `serve`, e.g.
388
+ `../.lichess/Scripts/python.exe -m weakness_report.cli list`.
389
+
390
+ ---
391
+
392
+ ## How the pieces fit
393
+
394
+ | Module | |
395
+ |---|---|
396
+ | `bridge.py` | finding ChessAnalyzer and the study exporter, and degrading honestly without them |
397
+ | `sources.py` | your games in, from a site, a PGN, or ChessAnalyzer's library |
398
+ | `store.py` | the history folder; reviews filed per game so nothing is done twice |
399
+ | `batch.py` | the review driver: fixed depth, resumable, cancellable, settings-checked |
400
+ | `features.py` | what kind of position this is, from a FEN alone |
401
+ | `buckets.py` | every way the report cuts your history, in one place |
402
+ | `aggregate.py` | ACPL, accuracy and excess loss — ChessAnalyzer's definitions, unchanged |
403
+ | `findings.py` | buckets into ranked claims, deduplicated by the moves they cover |
404
+ | `report.py` | the whole document as one object |
405
+ | `pipeline.py` | fetch, review, aggregate, save — shared by the CLI and the server |
406
+ | `pdf.py` | the new layout |
407
+ | `exportcsv.py` | every slice as one spreadsheet |
408
+ | `board.py` | board diagrams for the browser |
409
+ | `jobs.py` | background work with progress and cancellation |
410
+ | `server.py` / `web/` | the HTTP layer and the browser interface |
411
+ | `cli.py` | the same operations as commands, for the overnight run |
412
+
413
+ `aggregate.py` and `findings.py` are pure: no network, no engine, no disk.
414
+ That is what makes the evidence floor adjustable in the browser — re-slicing
415
+ four hundred games is milliseconds.
416
+
417
+ ## Tests
418
+
419
+ ```bash
420
+ ../.lichess/Scripts/python.exe -m pytest tests -q # 42 tests
421
+ ```
422
+
423
+ No network, no engine, no Stockfish. Four of them exist because of a specific
424
+ quiet failure this app is built to avoid:
425
+
426
+ - **`test_a_review_at_other_settings_is_not_reused`** — a depth-10 review
427
+ inside a depth-18 batch produces a normal-looking report with every figure
428
+ wrong.
429
+ - **`test_a_specific_bucket_survives_being_inside_a_vague_one`** — the overlap
430
+ check was written with containment first, which silently threw away every
431
+ specific finding in favour of the vague bucket holding it. A test caught it;
432
+ the fix was Jaccard similarity.
433
+ - **`test_acpl_excludes_book_moves`** — ChessAnalyzer excludes them, and a
434
+ report that did not would flatter anyone with preparation.
435
+ - **`test_no_rule_stretches_a_checkbox_across_its_container`** — the one
436
+ stylesheet assertion, and it is here because `.dialog input` and
437
+ `.check input` have identical specificity, so whichever was written last
438
+ won. The later one set `width: 100%`, which turned each checkbox in the
439
+ export dialog into a full-width slab that shouldered its own label out
440
+ through the right-hand edge. Nothing in Python could see it and the
441
+ stylesheet reads correctly. The rule now says `:not([type="checkbox"])`
442
+ outright, and the test asks the general question — does any selector setting
443
+ `width: 100%` still reach a bare `input` — so it keeps holding for rules
444
+ nobody has written yet.
445
+
446
+ Two more were written wrong before the code was: a test assumed a rare
447
+ disaster must outrank a common leak, and the arithmetic says it depends on the
448
+ product. That is the intended behaviour, and it is now an assertion rather
449
+ than an assumption.
450
+
451
+ ---
452
+
453
+ ## Hosting it for free
454
+
455
+ Same profile as the other four apps — FastAPI/Uvicorn needing a real
456
+ container, not a serverless host. This one needs **two** siblings, so the
457
+ Docker build context has to be the **repository root**.
458
+
459
+ [`Dockerfile`](https://github.com/spearb0lt/Lichess-Essentials/blob/main/Weakness-Report/Dockerfile) lives here but must be built with the repo root as
460
+ its context. Docker keeps "where is the Dockerfile" and "what can `COPY` see"
461
+ separate, which is what Render's **Root Directory** and **Dockerfile Path**
462
+ fields are for. It installs Stockfish via `apt-get` and installs ChessAnalyzer
463
+ as a package, so there is still exactly one implementation of the review in the
464
+ image.
465
+
466
+ **Reviewing on a free container is a bad idea** and worth saying plainly: a
467
+ shared vCPU searching to depth 14 is slow, and `history/` is wiped by every
468
+ redeploy, so the reviews — the expensive part — do not survive. Host it to
469
+ *read* reports if you like; review locally.
470
+
471
+ ### Render
472
+
473
+ 1. Push this repo to GitHub.
474
+ 2. **New Web Service** → connect the repo → **Root Directory**: leave blank →
475
+ **Dockerfile Path**: `Weakness-Report/Dockerfile` → **Free** instance.
476
+ 3. Optional secrets: `WEAKNESS_AUTH_USER` / `WEAKNESS_AUTH_PASS`.
477
+ 4. Deploy.
478
+
479
+ ### Hugging Face Spaces
480
+
481
+ 1. **New Space** → **SDK: Docker** → **CPU basic (free)**.
482
+ 2. Clone the Space's repo and copy in `Lichess-Study-to-PDF/`,
483
+ `ChessAnalyzer/` and `Weakness-Report/`, then copy
484
+ `Weakness-Report/Dockerfile` to the Space repo's own root.
485
+ 3. Add to the top of the Space's `README.md`:
486
+ ```yaml
487
+ ---
488
+ title: Weakness Report
489
+ sdk: docker
490
+ app_port: 7860
491
+ ---
492
+ ```
493
+ 4. **Settings → Repository secrets**: the same variables as Render.
494
+
495
+ ### Locking it behind a password
496
+
497
+ [`server.py`](https://github.com/spearb0lt/Lichess-Essentials/blob/main/Weakness-Report/weakness_report/server.py) has an HTTP Basic Auth gate that only
498
+ activates when both `WEAKNESS_AUTH_USER` and `WEAKNESS_AUTH_PASS` are set, so
499
+ local use is never asked for credentials. Do **not** set `LICHESS_TOKEN` on a
500
+ public deployment: it would be shared by every visitor.
501
+
502
+ ---
503
+
504
+ ## If something goes wrong
505
+
506
+ | Symptom | Cause and fix |
507
+ |---|---|
508
+ | `No module named weakness_report` | You are in the wrong directory. `cd` into `Weakness-Report` first. |
509
+ | "ChessAnalyzer could not be found" | Keep its folder beside this one, set `CHESS_ANALYZER_DIR`, or `pip install -e ChessAnalyzer` from the repo root. |
510
+ | "No Stockfish found" | Put a binary in `Lichess-Study-to-PDF/engine/`, or download one from ChessAnalyzer's engine picker. |
511
+ | Nothing clears the evidence floor | Too few games. Review more, or lower *least moves* / *least games* — that re-slices instantly, no engine. |
512
+ | The report says settings were mixed | Some reviews came from another preset. Set *Existing reviews* to *ignore* and run again for a clean set. |
513
+ | Reviewing seems stuck | It is not: a few hundred games at depth 14 is genuinely tens of minutes. The progress bar counts games. Stop it and every finished review is kept. |
514
+ | A second run takes as long as the first | You changed the preset, so nothing could be reused. That is deliberate — mixing depths would silently corrupt every average. |
515
+ | The PDF has no diagrams | The study exporter is not installed. `pip install -e Lichess-Study-to-PDF`. |
516
+ | Lichess says 429 | You pulled a lot of games. Wait a minute; a token raises the limit. |
517
+
518
+ ## Licence
519
+
520
+ MIT — see [LICENSE](https://github.com/spearb0lt/Lichess-Essentials/blob/main/LICENSE). Chess piece artwork in the diagrams comes from
521
+ python-chess (Colin M.L. Burnett's Cburnett set, CC BY-SA 3.0); opening names
522
+ come from [lichess-org/chess-openings](https://github.com/lichess-org/chess-openings)
523
+ (CC0). The accuracy formula and move-label rules are ChessAnalyzer's, which
524
+ takes the published Lichess thresholds for the three Lichess judgments.