blindgrid 0.2.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 (51) hide show
  1. blindgrid-0.2.0/.gitignore +26 -0
  2. blindgrid-0.2.0/CHANGELOG.md +109 -0
  3. blindgrid-0.2.0/CONTRIBUTING.md +126 -0
  4. blindgrid-0.2.0/LICENSE +21 -0
  5. blindgrid-0.2.0/PKG-INFO +488 -0
  6. blindgrid-0.2.0/README.md +437 -0
  7. blindgrid-0.2.0/config.example.toml +112 -0
  8. blindgrid-0.2.0/docs/installation.md +405 -0
  9. blindgrid-0.2.0/docs/logo.svg +45 -0
  10. blindgrid-0.2.0/install.ps1 +338 -0
  11. blindgrid-0.2.0/install.py +258 -0
  12. blindgrid-0.2.0/install.sh +323 -0
  13. blindgrid-0.2.0/pyproject.toml +87 -0
  14. blindgrid-0.2.0/src/blindgrid/__init__.py +13 -0
  15. blindgrid-0.2.0/src/blindgrid/__main__.py +13 -0
  16. blindgrid-0.2.0/src/blindgrid/allocation.py +81 -0
  17. blindgrid-0.2.0/src/blindgrid/cli.py +769 -0
  18. blindgrid-0.2.0/src/blindgrid/config.py +337 -0
  19. blindgrid-0.2.0/src/blindgrid/errors.py +27 -0
  20. blindgrid-0.2.0/src/blindgrid/export.py +119 -0
  21. blindgrid-0.2.0/src/blindgrid/filters.py +157 -0
  22. blindgrid-0.2.0/src/blindgrid/generator.py +65 -0
  23. blindgrid-0.2.0/src/blindgrid/household.py +72 -0
  24. blindgrid-0.2.0/src/blindgrid/i18n/__init__.py +118 -0
  25. blindgrid-0.2.0/src/blindgrid/i18n/de.py +192 -0
  26. blindgrid-0.2.0/src/blindgrid/i18n/en.py +188 -0
  27. blindgrid-0.2.0/src/blindgrid/i18n/es.py +188 -0
  28. blindgrid-0.2.0/src/blindgrid/i18n/fr.py +190 -0
  29. blindgrid-0.2.0/src/blindgrid/models.py +263 -0
  30. blindgrid-0.2.0/src/blindgrid/paths.py +49 -0
  31. blindgrid-0.2.0/src/blindgrid/plan.py +202 -0
  32. blindgrid-0.2.0/src/blindgrid/planner.py +52 -0
  33. blindgrid-0.2.0/src/blindgrid/render.py +401 -0
  34. blindgrid-0.2.0/src/blindgrid/store.py +224 -0
  35. blindgrid-0.2.0/tests/conftest.py +78 -0
  36. blindgrid-0.2.0/tests/test_allocation.py +88 -0
  37. blindgrid-0.2.0/tests/test_cli.py +423 -0
  38. blindgrid-0.2.0/tests/test_config.py +277 -0
  39. blindgrid-0.2.0/tests/test_export.py +60 -0
  40. blindgrid-0.2.0/tests/test_filters.py +87 -0
  41. blindgrid-0.2.0/tests/test_generator.py +84 -0
  42. blindgrid-0.2.0/tests/test_household.py +102 -0
  43. blindgrid-0.2.0/tests/test_i18n.py +202 -0
  44. blindgrid-0.2.0/tests/test_packaging.py +68 -0
  45. blindgrid-0.2.0/tests/test_paths.py +93 -0
  46. blindgrid-0.2.0/tests/test_plan.py +109 -0
  47. blindgrid-0.2.0/tests/test_plan_household.py +166 -0
  48. blindgrid-0.2.0/tests/test_planner.py +100 -0
  49. blindgrid-0.2.0/tests/test_randomness.py +45 -0
  50. blindgrid-0.2.0/tests/test_render.py +229 -0
  51. blindgrid-0.2.0/tests/test_store.py +156 -0
@@ -0,0 +1,26 @@
1
+ # Local configuration and generated output
2
+ config.toml
3
+ plan.md
4
+
5
+ # Python
6
+ __pycache__/
7
+ *.py[cod]
8
+ *.egg-info/
9
+ build/
10
+ dist/
11
+ .venv/
12
+ venv/
13
+
14
+ # Tooling
15
+ .pytest_cache/
16
+ .ruff_cache/
17
+ .coverage
18
+ htmlcov/
19
+
20
+ # OS
21
+ .DS_Store
22
+
23
+ # Not committed: this is an application installed from source, not a
24
+ # reproducible deployment. Pinning here would freeze contributors to one
25
+ # resolution of the dependency tree.
26
+ uv.lock
@@ -0,0 +1,109 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
5
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.2.0] - 2026-07-27
10
+
11
+ ### Added
12
+
13
+ - **Households.** Declare people with `blindgrid player add`, or `[[player]]`
14
+ blocks in the config, and `generate` plans for everyone in one pass. Each
15
+ person keeps their own ceiling and their own lotteries, with their own
16
+ weights; nobody's budget affects anyone else's share, and there is
17
+ deliberately no household cap.
18
+ - Draws are spread across dates so the same lottery is not played twice on the
19
+ same day while free dates remain. This does **not** improve anyone's odds —
20
+ two grids are two independent chances either way — and the documentation
21
+ says so plainly. When dates run short they are shared rather than grids
22
+ dropped, and the output reports it.
23
+ - `player add`, `player list` and `player remove`.
24
+ - **Windows support.** `install.ps1` mirrors the Unix installer, and
25
+ configuration and state follow `%APPDATA%` / `%LOCALAPPDATA%` on Windows
26
+ while keeping XDG elsewhere. CI now runs the test suite on Linux, macOS and
27
+ Windows, and exercises both installers end to end on each.
28
+ - Past draws are struck through when a plan is shown again, so what is left to
29
+ play stands out.
30
+ - **Translations.** The interface is available in English, French, Spanish and
31
+ German, chosen with `--lang`, `BLINDGRID_LANG`, the config file or your
32
+ system locale. Tests enforce that every catalogue matches the English one
33
+ key for key and placeholder for placeholder. Terms follow each country's own
34
+ vocabulary: a played grid is an *apuesta* in Spanish, a *Tipp* in German.
35
+ - **Published on PyPI**, so installing is `pip install blindgrid` rather than a
36
+ ninety-character URL. Releases run from a tag and refuse to publish unless
37
+ the tag matches the declared version, the changelog carries an entry, and the
38
+ built wheel installs and runs.
39
+ - A month is now drawn once. `generate` saves the plan to the state directory
40
+ and shows it again on later runs, so it can be found while the grids are
41
+ being filled in. `--force` draws a new one and states what it replaces.
42
+ - `config show` reports where the current plan lives and when it was drawn.
43
+ - **Runs on an iPad.** `install.py` installs anywhere Python exists, which is
44
+ what iOS needs: a-Shell has neither bash nor git, so it pulls a source
45
+ archive rather than a git URL. `python -m blindgrid` works where a
46
+ pip-installed command does not reach the PATH.
47
+ - The plan lays itself out for narrow terminals. Below the width the table
48
+ needs, each draw becomes two lines with its numbers unwrapped on their own —
49
+ at 60 columns the table used to stack them one digit per line and cut the
50
+ lottery name to `Eur…`. `--compact` and `--table` force either layout.
51
+
52
+ ### Changed
53
+
54
+ - Number columns line up across the whole plan. Lotteries of different shapes
55
+ used to leave the separator and pool names drifting from row to row, which
56
+ is exactly the wrong thing when numbers are being copied onto a slip.
57
+ Weights share one precision so their decimal points align.
58
+ - Amounts accept the way people type them: `30 €`, `30€`, `30 EUR`, commas and
59
+ non-breaking spaces. Nonsense is still refused.
60
+ - `--lang` is accepted before the command as well as after it.
61
+ - The stored plan is one file, replaced when the month turns, and a
62
+ self-contained snapshot: editing the configuration afterwards does not alter
63
+ a plan already drawn. This narrows the original "no persistence" rule to
64
+ what it was protecting against — an accumulated history of past grids, which
65
+ is still out of scope — while removing the ability to reroll a month until
66
+ the numbers look right.
67
+
68
+ ### Fixed
69
+
70
+ - Asked to prompt with no terminal attached — a pipe, a cron job, one of the
71
+ iOS shells — the tool now says which option to use instead, rather than
72
+ raising a bare `OSError` from inside prompt_toolkit.
73
+ - The help screen no longer mixed languages: Typer takes a command's docstring
74
+ as its help text unless told otherwise, which left the top-level commands in
75
+ English while the sub-apps were translated.
76
+ - The message shown when no configuration exists yet — the first thing a new
77
+ user sees — is translated.
78
+ - A language given before the command is no longer overwritten when the
79
+ configuration is read.
80
+
81
+ ## [0.1.0] - 2026-07-25
82
+
83
+ First public release.
84
+
85
+ ### Added
86
+
87
+ - `generate` — interactive monthly planning: prompts for a budget and a set of
88
+ lotteries, allocates the money by weight, picks draw dates at random and
89
+ produces one grid per selected draw.
90
+ - Weighted budget allocation with a hard ceiling that has no override. Shares
91
+ round down, leftovers stay unspent, and a share too small for one grid skips
92
+ that lottery with an explicit note rather than borrowing from another.
93
+ - Draw planner enumerating real calendar dates, capped by the draws actually
94
+ remaining in the period.
95
+ - Grid generation from `secrets.SystemRandom` with rejection sampling against
96
+ five anti-pattern filters, each expressed relative to pool size and each
97
+ switching itself off on pools too small to satisfy it.
98
+ - `rich` table output and a Markdown export, overwritten on every run.
99
+ - `config init` / `config show` / `config edit` and `lottery add` /
100
+ `lottery list`.
101
+ - TOML configuration describing lotteries generically: any game in any country
102
+ is a matter of price, draw days and pools of numbers.
103
+ - Test suite covering allocation, planning, filters, generation, config and
104
+ the CLI, including a contract test asserting that `random` is never imported
105
+ by the package.
106
+
107
+ [Unreleased]: https://github.com/adrnbttr/blindgrid/compare/v0.2.0...HEAD
108
+ [0.2.0]: https://github.com/adrnbttr/blindgrid/compare/v0.1.0...v0.2.0
109
+ [0.1.0]: https://github.com/adrnbttr/blindgrid/releases/tag/v0.1.0
@@ -0,0 +1,126 @@
1
+ # Contributing
2
+
3
+ Thanks for taking a look. This is a small project with a narrow purpose, so
4
+ the most useful thing to read first is the section below on what will not be
5
+ merged — it is not a long list, but it is a firm one.
6
+
7
+ ## Out of scope, permanently
8
+
9
+ The README explains the reasoning; this is the short version.
10
+
11
+ - **Anything predictive.** Frequency analysis, hot/cold numbers, historical
12
+ draw data, "due" numbers, statistical models of past results. Lottery draws
13
+ are independent events. A feature that implies otherwise is a bug in the
14
+ product, not a missing capability.
15
+ - **Seeding or reproducible output.** No `--seed`, no deterministic mode, no
16
+ fixed generator in the package. A test enforces this.
17
+ - **A history of past months.** The current plan is kept in one file so it can
18
+ be found again, and replaced when the month turns. Accumulating months is a
19
+ different thing: it makes past grids comparable to results, which is where
20
+ pattern-hunting starts.
21
+ - **Consuming the budget exactly.** Leftover money stays unspent.
22
+ - **Hardcoding a country or a game.** Lotteries live in configuration. If
23
+ something cannot be expressed as a price, a set of draw days and pools of
24
+ numbers, open an issue and let's discuss the model rather than special-case
25
+ the code.
26
+ - **Claiming that spreading a household's draws improves anyone's odds.** It
27
+ does not. Two grids are two independent chances whether they sit on one draw
28
+ or two. Spreading buys exposure to more distinct jackpots and avoids
29
+ near-duplicate tickets in one house; the wording in the code and docs says
30
+ exactly that, and should stay that way.
31
+
32
+ Everything else is fair game: better rendering, clearer errors, more
33
+ platforms, faster tests, documentation.
34
+
35
+ ## Setup
36
+
37
+ ```bash
38
+ git clone https://github.com/adrnbttr/blindgrid.git
39
+ cd blindgrid
40
+ uv venv
41
+ uv pip install -e ".[dev]"
42
+ ```
43
+
44
+ ## Before opening a pull request
45
+
46
+ ```bash
47
+ ruff check .
48
+ ruff format .
49
+ pytest
50
+ shellcheck install.sh # only if you touched the Unix installer
51
+ ```
52
+
53
+ CI runs the same commands on Linux (3.11, 3.12, 3.13), macOS and Windows, and
54
+ a pull request needs them green. It also installs the project with
55
+ `install.sh` and `install.ps1` on clean runners and uninstalls them again, so
56
+ a change to either is exercised for real rather than merely linted.
57
+
58
+ If you touched `install.ps1` and have no Windows machine, push and let CI run
59
+ PSScriptAnalyzer and the install-uninstall round trip — that is how it is
60
+ maintained here. The same goes for `install.py`, which exists for iOS: no CI
61
+ runner is an iPad, so it is checked on a bare interpreter, at 55 columns, and
62
+ with no terminal attached.
63
+
64
+ ### Platform-specific code
65
+
66
+ There is exactly one place that branches on the operating system:
67
+ `src/blindgrid/paths.py`, which decides where configuration and state live.
68
+ Everything else is platform-neutral, and should stay that way. `tests/
69
+ test_paths.py` fakes each platform, so both layouts are checked wherever the
70
+ suite runs.
71
+
72
+ ## Conventions
73
+
74
+ - **Commits** follow [Conventional Commits](https://www.conventionalcommits.org):
75
+ `feat:`, `fix:`, `docs:`, `test:`, `refactor:`, `chore:`, `ci:`.
76
+ - **Line length** is 100 characters, enforced by ruff.
77
+ - **Type annotations** are required on new functions; `ANN` rules are on.
78
+ - **Money** is `Decimal`, never `float`. Budget arithmetic in binary floating
79
+ point drifts, and the whole point of the ceiling is that it does not move.
80
+ - **Randomness** comes from the `RandomSource` protocol, and production code
81
+ passes `secrets.SystemRandom`. Do not import `random` inside the package.
82
+
83
+ ## Adding an anti-pattern filter
84
+
85
+ Think twice. Every active rule shrinks the sample space, and a heavily
86
+ filtered draw is a predictable draw. If a rule is still worth it:
87
+
88
+ 1. Add the predicate functions and a `Rule` entry in `src/blindgrid/filters.py`.
89
+ 2. Give it an `applies_to` that switches it off for pools too small to satisfy
90
+ it, expressed relative to `count` and `maximum` — never against a specific
91
+ lottery's numbers.
92
+ 3. Add it to the `[filters]` block in `config.example.toml`.
93
+ 4. Cover both the rejection and the degradation in `tests/test_filters.py`.
94
+
95
+ ## Adding a language
96
+
97
+ One file in `src/blindgrid/i18n/`, copied from `en.py` and translated, plus one
98
+ line in that package's `__init__.py` and one entry in `LANGUAGE_NAMES`.
99
+
100
+ Tests then check that your catalogue carries exactly the English keys, with
101
+ exactly the same `{placeholders}`, and that nothing longer than three words was
102
+ left in English. A half-finished translation fails the build rather than
103
+ leaking English into someone's session.
104
+
105
+ Translate the interface only. Lottery labels, pool names and player names come
106
+ from configuration, and the weekday keys in the config file (`monday`, …) are
107
+ a file format rather than text for reading — both stay as they are.
108
+
109
+ ## Regenerating the README image
110
+
111
+ `docs/demo.gif` is recorded from a real run, not drawn by hand. It needs
112
+ [vhs](https://github.com/charmbracelet/vhs):
113
+
114
+ ```bash
115
+ brew install vhs # or see the vhs README for other platforms
116
+ vhs docs/demo.tape # from the repository root
117
+ ```
118
+
119
+ The tape points the tool at a throwaway config in a temporary directory, so
120
+ recording never touches your own `config.toml`. Regenerate after any change to
121
+ `render.py`, and expect different numbers every time — the demo draws from the
122
+ real CSPRNG like everything else.
123
+
124
+ Keep it in English and keep it short. Switching languages mid-recording spends
125
+ five seconds saying what one line of the README says better, and every second
126
+ is weight on a page people load before deciding whether to read it.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Adrien Bouttier
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.