blindgrid 0.2.0__py3-none-any.whl

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.
blindgrid/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ """blindgrid — lottery grids from cryptographic randomness, inside a hard budget.
2
+
3
+ This package does not predict anything, and no part of it should ever try to.
4
+ Lottery draws are independent events: no combination is more likely than any
5
+ other, and no amount of code changes that. What the tool does is remove human
6
+ bias from number selection and make the monthly gambling budget explicit and
7
+ capped.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ __version__ = "0.2.0"
13
+ __all__ = ["__version__"]
blindgrid/__main__.py ADDED
@@ -0,0 +1,13 @@
1
+ """Entry point for ``python -m blindgrid``.
2
+
3
+ The installed ``blindgrid`` command is the normal way in. This exists for
4
+ environments where a pip-installed script does not land on the PATH — the iOS
5
+ shells are the usual case — so the tool is still reachable there.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from blindgrid.cli import app
11
+
12
+ if __name__ == "__main__":
13
+ app()
@@ -0,0 +1,81 @@
1
+ """Splitting a monthly budget across lotteries by weight.
2
+
3
+ Weights are relative shares of one envelope, not draw counts. A lottery with
4
+ ``weight = 2.0`` receives twice the money of one at ``1.0``, whatever their
5
+ respective grid prices happen to be.
6
+
7
+ Two rules make the result honest rather than merely tidy:
8
+
9
+ * A share too small to buy a single grid skips that lottery for the month and
10
+ says so. It never borrows from another lottery's share to round itself up.
11
+ * Leftover money inside a share is not redistributed and not spent. Total
12
+ spend simply lands below budget. A tool that tried to consume the envelope
13
+ exactly would be a tool that encourages spending.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from collections.abc import Sequence
19
+ from decimal import ROUND_DOWN, Decimal
20
+
21
+ from blindgrid.models import CENTS, Allocation, Lottery
22
+
23
+
24
+ def _share_of(budget: Decimal, weight: float, total_weight: float) -> Decimal:
25
+ """Return one lottery's slice of ``budget``, rounded down to the cent.
26
+
27
+ Rounding down matters: with several lotteries, rounding to nearest could
28
+ push the sum of the shares one cent above the budget, and the whole point
29
+ of the ceiling is that it never moves.
30
+ """
31
+ ratio = Decimal(str(weight)) / Decimal(str(total_weight))
32
+ return (budget * ratio).quantize(CENTS, rounding=ROUND_DOWN)
33
+
34
+
35
+ def allocate(budget: Decimal, lotteries: Sequence[Lottery]) -> tuple[Allocation, ...]:
36
+ """Split ``budget`` across ``lotteries`` proportionally to their weights.
37
+
38
+ Every lottery passed in comes back with an allocation, including the ones
39
+ that end up with nothing, so the caller can report why.
40
+ """
41
+ total_weight = sum(lottery.weight for lottery in lotteries if lottery.is_enabled)
42
+
43
+ if total_weight <= 0:
44
+ return tuple(
45
+ Allocation(
46
+ lottery=lottery,
47
+ share=Decimal("0.00"),
48
+ grid_count=0,
49
+ note="disabled (weight is 0)",
50
+ )
51
+ for lottery in lotteries
52
+ )
53
+
54
+ allocations = []
55
+ for lottery in lotteries:
56
+ if not lottery.is_enabled:
57
+ allocations.append(
58
+ Allocation(
59
+ lottery=lottery,
60
+ share=Decimal("0.00"),
61
+ grid_count=0,
62
+ note="disabled (weight is 0)",
63
+ )
64
+ )
65
+ continue
66
+
67
+ share = _share_of(budget, lottery.weight, total_weight)
68
+ grid_count = int(share // lottery.price_per_grid)
69
+ note = (
70
+ None
71
+ if grid_count
72
+ else (
73
+ f"share of {share} is below the {lottery.price_per_grid} grid price, "
74
+ f"skipped this month"
75
+ )
76
+ )
77
+ allocations.append(
78
+ Allocation(lottery=lottery, share=share, grid_count=grid_count, note=note)
79
+ )
80
+
81
+ return tuple(allocations)