draftomen 0.3.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.
Files changed (59) hide show
  1. draftomen/__init__.py +17 -0
  2. draftomen/assets/draftomen.icns +0 -0
  3. draftomen/assets/draftomen.ico +0 -0
  4. draftomen/assets/draftomen_logo.png +0 -0
  5. draftomen/audit.py +606 -0
  6. draftomen/backtest.py +449 -0
  7. draftomen/benchmark.py +834 -0
  8. draftomen/carddb.py +1869 -0
  9. draftomen/cardimages.py +313 -0
  10. draftomen/cli.py +891 -0
  11. draftomen/config.py +133 -0
  12. draftomen/deckbuilder.py +2723 -0
  13. draftomen/events.py +772 -0
  14. draftomen/logfollow.py +451 -0
  15. draftomen/mock_session.py +745 -0
  16. draftomen/paths.py +120 -0
  17. draftomen/pickengine.py +1117 -0
  18. draftomen/pool.py +1259 -0
  19. draftomen/preferences.py +289 -0
  20. draftomen/qml/AboutDialog.qml +156 -0
  21. draftomen/qml/AppBar.qml +120 -0
  22. draftomen/qml/BacktestView.qml +316 -0
  23. draftomen/qml/BuildView.qml +1151 -0
  24. draftomen/qml/CardPreview.qml +434 -0
  25. draftomen/qml/DimensionalButton.qml +42 -0
  26. draftomen/qml/DimensionalComboBox.qml +182 -0
  27. draftomen/qml/DimensionalSurface.qml +118 -0
  28. draftomen/qml/DimensionalTabButton.qml +41 -0
  29. draftomen/qml/LiveDraftView.qml +468 -0
  30. draftomen/qml/Main.qml +175 -0
  31. draftomen/qml/NavigationRail.qml +226 -0
  32. draftomen/qml/PoolSummaryPanel.qml +322 -0
  33. draftomen/qml/PrivacyDialog.qml +96 -0
  34. draftomen/qml/RecentPickThumbnail.qml +83 -0
  35. draftomen/qml/RecentPicksGallery.qml +333 -0
  36. draftomen/qml/RecommendationRow.qml +404 -0
  37. draftomen/qml/SettingsSwitch.qml +141 -0
  38. draftomen/qml/SettingsView.qml +531 -0
  39. draftomen/qml/StateBanner.qml +189 -0
  40. draftomen/qml/StatusStrip.qml +84 -0
  41. draftomen/qml/Theme.qml +71 -0
  42. draftomen/qml/qmldir +23 -0
  43. draftomen/qt_adapter.py +884 -0
  44. draftomen/qt_gui.py +338 -0
  45. draftomen/qt_mock.py +63 -0
  46. draftomen/ranking.py +126 -0
  47. draftomen/replay.py +480 -0
  48. draftomen/session.py +3668 -0
  49. draftomen/setinfo.py +26 -0
  50. draftomen/seventeen.py +2766 -0
  51. draftomen/splash.py +617 -0
  52. draftomen/tui.py +4007 -0
  53. draftomen/watch.py +336 -0
  54. draftomen-0.3.0.dist-info/METADATA +156 -0
  55. draftomen-0.3.0.dist-info/RECORD +59 -0
  56. draftomen-0.3.0.dist-info/WHEEL +5 -0
  57. draftomen-0.3.0.dist-info/entry_points.txt +6 -0
  58. draftomen-0.3.0.dist-info/licenses/LICENSE +21 -0
  59. draftomen-0.3.0.dist-info/top_level.txt +1 -0
draftomen/watch.py ADDED
@@ -0,0 +1,336 @@
1
+ """Adapt shared live-session events to plain-text watch output.
2
+ The adapter preserves the established command and output contracts.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import sys
8
+ import time
9
+ from collections.abc import Callable, Iterable
10
+ from dataclasses import replace
11
+ from os import PathLike
12
+ from typing import TextIO, TypeAlias
13
+
14
+ from draftomen.carddb import CardDatabase
15
+ from draftomen.config import POLL_INTERVAL_SECONDS
16
+ from draftomen.deckbuilder import DeckBuilderError, format_build_result
17
+ from draftomen.events import (
18
+ AccountEvent,
19
+ DraftCompletedEvent,
20
+ DraftStartedEvent,
21
+ PackOfferedEvent,
22
+ PickMadeEvent,
23
+ QuickDraftDetectedEvent,
24
+ )
25
+ from draftomen.replay import (
26
+ format_draft_completed_event,
27
+ format_pack_offered_event,
28
+ format_pick_made_event,
29
+ )
30
+ from draftomen.session import (
31
+ LiveSession,
32
+ LiveSessionEvent,
33
+ OperationKind,
34
+ RequestBuild,
35
+ )
36
+ from draftomen.seventeen import SeventeenLandsData
37
+
38
+ PathInput: TypeAlias = str | PathLike[str]
39
+ RatingsLoader: TypeAlias = Callable[[str], SeventeenLandsData]
40
+
41
+
42
+ class PlainLogWatcher:
43
+ """Render one shared live session as incremental plain text.
44
+ Tests can call poll_once to simulate one live polling cycle exactly.
45
+ """
46
+
47
+ def __init__(
48
+ self,
49
+ *,
50
+ log_path: PathInput,
51
+ card_database: CardDatabase,
52
+ app_dir: PathInput | None = None,
53
+ poll_interval: float = POLL_INTERVAL_SECONDS,
54
+ previous_log_path: PathInput | None = None,
55
+ ratings_loader: RatingsLoader | None = None,
56
+ splash_enabled: bool = True,
57
+ ) -> None:
58
+ self.card_database = card_database
59
+ self.splash_enabled = splash_enabled
60
+ self._events: list[LiveSessionEvent] = []
61
+ self.session = LiveSession(
62
+ log_path=log_path,
63
+ card_database=card_database,
64
+ app_dir=app_dir,
65
+ poll_interval=poll_interval,
66
+ previous_log_path=previous_log_path,
67
+ event_publisher=self._capture_event,
68
+ ratings_loader=ratings_loader,
69
+ lazy_pair_card_ratings=True,
70
+ splash_enabled=splash_enabled,
71
+ )
72
+ self.log_path = self.session.log_path
73
+
74
+ def _capture_event(self, published: LiveSessionEvent) -> None:
75
+ if (
76
+ isinstance(published.event, DraftCompletedEvent)
77
+ and published.snapshot.draft is not None
78
+ ):
79
+ snapshot = self.session.dispatch(
80
+ command=RequestBuild(allow_splash=self.splash_enabled),
81
+ )
82
+ published = replace(published, snapshot=snapshot)
83
+
84
+ self._events.append(published)
85
+
86
+ def poll_once(self) -> str:
87
+ """Process one follower poll cycle and return rendered output.
88
+ Empty strings mean no complete draft events were available this cycle.
89
+ """
90
+
91
+ self._events.clear()
92
+ self.session.poll_once()
93
+ return self._render_published_events()
94
+
95
+ def scan_startup_files(self, *, include_previous: bool = True) -> str:
96
+ """Process Player-prev.log and Player.log from the beginning.
97
+ This is opt-in recovery for a watch session started mid-draft.
98
+ """
99
+
100
+ self._events.clear()
101
+ self.session.scan_startup_files(include_previous=include_previous)
102
+ return self._render_published_events()
103
+
104
+ def process_lines(self, *, lines: Iterable[str]) -> str:
105
+ """Pass complete log lines through the shared live session.
106
+ Formatting consumes only published session events and commands.
107
+ """
108
+
109
+ self._events.clear()
110
+ self.session.process_lines(lines=lines)
111
+ return self._render_published_events()
112
+
113
+ def _render_published_events(self) -> str:
114
+ output_lines: list[str] = []
115
+ for published in self._events:
116
+ output_lines.extend(self._format_event(published=published))
117
+
118
+ self._events.clear()
119
+ return _join_output_lines(lines=output_lines)
120
+
121
+ def _format_event(self, *, published: LiveSessionEvent) -> list[str]:
122
+ event = published.event
123
+ if isinstance(event, AccountEvent):
124
+ label = _account_label(published=published, account_id=event.client_id)
125
+ if event.previous_client_id is None:
126
+ headline = f"Active account: {label}"
127
+ else:
128
+ previous_label = _account_label(
129
+ published=published,
130
+ account_id=event.previous_client_id,
131
+ )
132
+ headline = f"Account switched: {previous_label} -> {label}"
133
+
134
+ return [headline, f"Status: active account {label}", ""]
135
+
136
+ if isinstance(event, QuickDraftDetectedEvent):
137
+ return []
138
+
139
+ if isinstance(event, DraftStartedEvent):
140
+ account_label = _account_label(
141
+ published=published,
142
+ account_id=event.account_id,
143
+ )
144
+ return [
145
+ "Draft started: "
146
+ f"{event.event_name} (set {event.set_code}, draft {event.course_id})",
147
+ f"Status: active account {account_label}, draft {event.course_id}",
148
+ "",
149
+ ]
150
+
151
+ if isinstance(event, PackOfferedEvent):
152
+ if published.scored_pack is None:
153
+ raise RuntimeError("Shared live session did not score the offered pack.")
154
+
155
+ pack_lines = format_pack_offered_event(
156
+ event=event,
157
+ card_database=self.card_database,
158
+ scored_pack=published.scored_pack,
159
+ )
160
+ account_label = _account_label(
161
+ published=published,
162
+ account_id=event.account_id,
163
+ )
164
+ lines = [
165
+ "Status: "
166
+ f"active account {account_label}, "
167
+ f"pick P{event.pack_number + 1}P{event.pick_number + 1}, "
168
+ f"{_color_status_from_pack_lines(lines=pack_lines)}, "
169
+ f"data {_data_source_from_pack_lines(lines=pack_lines)}"
170
+ ]
171
+ lines.extend(_pack_lines_without_color_status(lines=pack_lines))
172
+ return lines
173
+
174
+ if isinstance(event, PickMadeEvent):
175
+ lines = format_pick_made_event(
176
+ event=event,
177
+ card_database=self.card_database,
178
+ )
179
+ lines.append("")
180
+ return lines
181
+
182
+ if isinstance(event, DraftCompletedEvent):
183
+ lines = format_draft_completed_event(event=event)
184
+ lines.append("")
185
+ if published.snapshot.draft is not None:
186
+ lines.extend(self._format_build_sheet(published=published))
187
+ return lines
188
+
189
+ return []
190
+
191
+ def _format_build_sheet(self, *, published: LiveSessionEvent) -> list[str]:
192
+ result = published.snapshot.build
193
+ if result is None:
194
+ error = next(
195
+ (
196
+ error
197
+ for error in published.snapshot.errors
198
+ if error.operation == OperationKind.BUILD
199
+ ),
200
+ None,
201
+ )
202
+ if error is None:
203
+ raise DeckBuilderError("Deck build did not return a result.")
204
+
205
+ message = error.message.removeprefix("Deck build failed: ")
206
+ raise DeckBuilderError(message)
207
+
208
+ if (
209
+ result.domain_pool is None
210
+ or result.domain_selection is None
211
+ or result.domain_spell_selection is None
212
+ or result.domain_mana_base is None
213
+ ):
214
+ raise DeckBuilderError("Deck build did not include domain details.")
215
+
216
+ pool = replace(
217
+ result.domain_pool,
218
+ source_label=(
219
+ f"watch {result.domain_pool.account_id}/"
220
+ f"{result.domain_pool.draft_id}"
221
+ ),
222
+ )
223
+ return format_build_result(
224
+ pool=pool,
225
+ selection=result.domain_selection,
226
+ spell_selection=result.domain_spell_selection,
227
+ mana_base=result.domain_mana_base,
228
+ ).rstrip("\n").splitlines()
229
+
230
+
231
+ def run_plain_watch(
232
+ *,
233
+ log_path: PathInput,
234
+ card_database: CardDatabase,
235
+ app_dir: PathInput | None = None,
236
+ output: TextIO | None = None,
237
+ poll_interval: float = POLL_INTERVAL_SECONDS,
238
+ once: bool = False,
239
+ startup_scan: bool = False,
240
+ stop_after_empty_polls: int | None = None,
241
+ ratings_loader: RatingsLoader | None = None,
242
+ splash_enabled: bool = True,
243
+ ) -> int:
244
+ """Run watch --plain until interrupted or a test stop condition fires.
245
+ The process returns zero unless a caller catches and maps an exception.
246
+ """
247
+
248
+ if output is None:
249
+ output = sys.stdout
250
+
251
+ watcher = PlainLogWatcher(
252
+ log_path=log_path,
253
+ card_database=card_database,
254
+ app_dir=app_dir,
255
+ poll_interval=poll_interval,
256
+ ratings_loader=ratings_loader,
257
+ splash_enabled=splash_enabled,
258
+ )
259
+ output.write("Draft Omen watch\n")
260
+ output.write(f"Watching: {watcher.log_path}\n")
261
+ output.write("Mode: plain-text\n\n")
262
+ output.flush()
263
+
264
+ if startup_scan:
265
+ _write_if_present(output=output, text=watcher.scan_startup_files())
266
+
267
+ if once:
268
+ _write_if_present(output=output, text=watcher.poll_once())
269
+ return 0
270
+
271
+ empty_polls = 0
272
+ while True:
273
+ text = watcher.poll_once()
274
+ if text:
275
+ _write_if_present(output=output, text=text)
276
+ empty_polls = 0
277
+ else:
278
+ empty_polls += 1
279
+
280
+ if stop_after_empty_polls is not None and empty_polls >= stop_after_empty_polls:
281
+ return 0
282
+
283
+ time.sleep(poll_interval)
284
+
285
+
286
+ def _write_if_present(*, output: TextIO, text: str) -> None:
287
+ if not text:
288
+ return
289
+
290
+ output.write(text)
291
+ output.flush()
292
+
293
+
294
+ def _account_label(*, published: LiveSessionEvent, account_id: str | None) -> str:
295
+ if account_id is None:
296
+ return "unknown"
297
+
298
+ identity = next(
299
+ (
300
+ account
301
+ for account in published.snapshot.accounts
302
+ if account.account_id == account_id
303
+ ),
304
+ None,
305
+ )
306
+ if identity is None or identity.screen_name is None:
307
+ return account_id
308
+
309
+ return f"{identity.screen_name} ({account_id})"
310
+
311
+
312
+ def _data_source_from_pack_lines(*, lines: list[str]) -> str:
313
+ for line in lines:
314
+ if line.startswith("Data source: "):
315
+ return line.removeprefix("Data source: ")
316
+
317
+ return "unknown"
318
+
319
+
320
+ def _color_status_from_pack_lines(*, lines: list[str]) -> str:
321
+ for line in lines:
322
+ if line.startswith("Status: inferred pair "):
323
+ return line.removeprefix("Status: ")
324
+
325
+ return "inferred pair open, commitment 0% (open), pool unknown"
326
+
327
+
328
+ def _pack_lines_without_color_status(*, lines: list[str]) -> list[str]:
329
+ return [line for line in lines if not line.startswith("Status: inferred pair ")]
330
+
331
+
332
+ def _join_output_lines(*, lines: list[str]) -> str:
333
+ if not lines:
334
+ return ""
335
+
336
+ return "\n".join(lines) + "\n"
@@ -0,0 +1,156 @@
1
+ Metadata-Version: 2.4
2
+ Name: draftomen
3
+ Version: 0.3.0
4
+ Summary: An unofficial Quick Draft assistant for MTG Arena
5
+ Author: Andrea Grandi
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/andreagrandi/draftomen
8
+ Project-URL: Repository, https://github.com/andreagrandi/draftomen
9
+ Project-URL: Issues, https://github.com/andreagrandi/draftomen/issues
10
+ Keywords: draft,magic-the-gathering,mtg-arena,terminal,tui
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: End Users/Desktop
14
+ Classifier: Operating System :: MacOS
15
+ Classifier: Operating System :: Microsoft :: Windows
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Games/Entertainment
20
+ Requires-Python: >=3.12
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: pyside6<7,>=6.9
24
+ Requires-Dist: textual>=8.2.8
25
+ Requires-Dist: textual-image>=0.13.2
26
+ Dynamic: license-file
27
+
28
+ <p align="center">
29
+ <img src="https://raw.githubusercontent.com/andreagrandi/draftomen/master/docs/assets/draftomen_banner.png" alt="Draft Omen banner" width="100%">
30
+ </p>
31
+
32
+ # Draft Omen
33
+
34
+ Draft Omen is an unofficial desktop draft assistant for MTG Arena Quick Drafts. Its PySide6/QML application reads Arena's local `Player.log`, recognizes each pack and pick, and ranks the available cards while you draft. When the draft is complete, it also suggests a 40-card deck from your pool. A terminal interface with the same draft behavior is available as `draftomen-tui`.
35
+
36
+ Draft Omen is read-only: it does not write to, inject into, or automate MTG Arena. Card details come from [Scryfall](https://scryfall.com/) and draft statistics come from [17Lands](https://www.17lands.com/).
37
+
38
+ ## Screenshots
39
+
40
+ ### Live Draft GUI — pick recommendations
41
+
42
+ The Live Draft view shows Pack 1, Pick 3 with 12 cards available and Glóin the Mighty selected. Its DO Score, 17Lands win rate, grade, and color fit appear alongside focused-card details and a pool summary.
43
+
44
+ ![Draft Omen Live Draft GUI showing Pack 1, Pick 3 with 12 cards available, Glóin the Mighty selected, ranked card rows, DO Score, 17Lands win rate, grade, color fit, focused-card details, and pool summary](https://raw.githubusercontent.com/andreagrandi/draftomen/master/docs/assets/draft-pick-recommendations.png)
45
+
46
+ ### Suggested deck GUI
47
+
48
+ After the draft, the Suggested deck view shows an automatic UR pair and a 40-card build with 23 spells, 17 lands, a mana base and curve, grouped main-deck spells, and selected-card details.
49
+
50
+ ![Draft Omen Suggested deck GUI with an automatic UR pair, 40-card build with 23 spells and 17 lands, mana base, mana curve, grouped main-deck spells, and Tidings of War selected](https://raw.githubusercontent.com/andreagrandi/draftomen/master/docs/assets/suggested-deck-build.png)
51
+
52
+ ## How recommendations work
53
+
54
+ Draft Omen keeps the data behind every recommendation visible instead of presenting a black-box pick order:
55
+
56
+ - **17Lands ratings:** each card shows its Games-in-Hand win rate (`17L WR`) and a 17Lands-style grade. Quick Draft data is preferred; Premier Draft data is used as a fallback when Quick Draft samples are missing or too small.
57
+ - **DO Score:** the default 0-100 ranking normalizes 17Lands win rates across the set. A card without a reliable sample starts from a neutral score of 50, adjusted by its Average Last Seen At (`ALSA`) when available. The five freely available basic lands instead score 0 and rank after draftable cards.
58
+ - **Color fit:** early picks stay open. From pick 6 onward, scores gradually favor the colors supported by the drafted pool; from pick 16, on-color cards receive the full bonus and off-color cards the full penalty. Strong picks influence the inferred color pair more than filler.
59
+ - **Close early picks:** when two cards have similar scores, set- and format-specific 17Lands color-pair win rates can break the tie without forcing an early commitment.
60
+ - **Conservative splashing:** an `A`-range, single-pip card can be marked as a visible third-color splash when it is materially better than the on-color choices and the pool can support its mana. Draft Omen limits this to one extra color and two cards.
61
+ - **Transparent alternatives:** press `s` to compare rankings by DO Score, raw 17Lands win rate, ALSA, or mana value. The interface also identifies neutral-prior and Premier-fallback rows.
62
+ - **Deck suggestion:** the builder evaluates two-color pairs using card quality and 17Lands pair performance, then chooses spells and lands while considering creatures, curve, colored mana requirements, and cached 17Lands deck-structure targets when available.
63
+
64
+ DO Score is the default because it performed better than raw 17Lands win rate in Draft Omen's offline pick benchmarks. It is still guidance rather than a perfect pick order: public statistics reflect the decks, players, and contexts in which cards were played.
65
+
66
+ For the complete methodology, see [pick scoring](docs/pick-scoring.md), [benchmarking](docs/benchmarking.md), and the [deck builder](docs/deck-builder.md).
67
+
68
+ ## How to use it
69
+
70
+ ### 1. Install
71
+
72
+ On macOS, install Draft Omen with Homebrew:
73
+
74
+ ```bash
75
+ brew install andreagrandi/tap/draftomen
76
+ ```
77
+
78
+ On other platforms, [install uv](https://docs.astral.sh/uv/getting-started/installation/) and run:
79
+
80
+ ```bash
81
+ uv tool install draftomen
82
+ ```
83
+
84
+ ### 2. Enable Arena logs
85
+
86
+ In MTG Arena, open **Settings → Account**, enable **Detailed Logs (Plugin Support)**, and restart Arena.
87
+
88
+ ### 3. Start Draft Omen
89
+
90
+ Start Draft Omen before entering a Quick Draft:
91
+
92
+ ```bash
93
+ draftomen
94
+ ```
95
+
96
+ The PySide6/QML desktop application loads card metadata when needed, watches
97
+ Arena's standard log location, detects the set, and follows the draft
98
+ automatically. If 17Lands ratings are not cached for that set, it offers to
99
+ download them.
100
+
101
+ ### Terminal interface
102
+
103
+ For a terminal workflow, use the stable `draftomen-tui` command. It preserves
104
+ the watch, replay, build, backtest, benchmark, and data-refresh subcommands,
105
+ including plain-text mode:
106
+
107
+ ```bash
108
+ draftomen-tui
109
+ draftomen-tui watch --plain
110
+ ```
111
+
112
+ Use the arrow keys or `j`/`k` to browse cards, `s` to change the ranking,
113
+ `b` to open the current build, `c` to configure the view and optional splash
114
+ recommendations, and `q` to quit.
115
+
116
+ ### Visual development
117
+
118
+ For deterministic visual development, launch the explicit forced-mock entry
119
+ point:
120
+
121
+ ```bash
122
+ draftomen-gui-mockup
123
+ ```
124
+
125
+ For an automated, non-interactive GUI smoke check, use the default command
126
+ with its mock provider and bounded smoke flag:
127
+
128
+ ```bash
129
+ QT_QPA_PLATFORM=offscreen draftomen --provider mock --smoke-test
130
+ ```
131
+
132
+ See the [desktop GUI guide](docs/gui-mockup.md) for live and mock launch
133
+ options, selectable states, and responsive review targets. For reproducible
134
+ unsigned development bundles, see the [desktop bundle guide](docs/desktop-bundles.md).
135
+
136
+ Live recommendations currently support Quick Draft. Windows support is best-effort.
137
+
138
+ ## Local draft audit data
139
+
140
+ Live sessions started with `draftomen-tui`, including `watch --plain`, keep an
141
+ independent, append-only JSONL audit log for every draft under
142
+ `~/.draftomen/audit/drafts/<account-id>/<draft-id>.jsonl`. Each record includes
143
+ the offered pack, pool snapshot, ratings source, scoring configuration, complete
144
+ candidate calculations, splash eligibility and mana-source reasoning, all
145
+ supported rankings, the recommendation visible when the pick was made, and the
146
+ card actually chosen. Stable record IDs prevent
147
+ startup scans and log rotation from rewriting prior evidence.
148
+
149
+ Offline `replay` and backtest commands do not write audit records. For the schema
150
+ and retention details, see [draft audit logging](docs/draft-audit-log.md).
151
+
152
+ ## Branding and compliance
153
+
154
+ Draft Omen is not affiliated with, sponsored by, approved by, or endorsed by Wizards of the Coast, Scryfall, or 17Lands.
155
+
156
+ Draft Omen is unofficial Fan Content permitted under the Fan Content Policy. Not approved/endorsed by Wizards. Portions of the materials used are property of Wizards of the Coast. ©Wizards of the Coast LLC. Card data from Scryfall and 17Lands; neither service endorses this tool.
@@ -0,0 +1,59 @@
1
+ draftomen/__init__.py,sha256=USYipFyFaVtpkSZILaGYIqL88qSKyZ0PJxzj44YZpdg,608
2
+ draftomen/audit.py,sha256=IXqU_OoxKbENVG0UvwP2iM24QrJjcNb6tNZpDqaq4Dg,19803
3
+ draftomen/backtest.py,sha256=1A8jm4nnFOGEX0lPdFtphzXLNNGoTmgBKYpeeU-WNo8,13177
4
+ draftomen/benchmark.py,sha256=j45GaKwrwA4wAINGPTTw6I1UWsSFcBJUvrwTLJSa37Q,24449
5
+ draftomen/carddb.py,sha256=ZMswc9fYAWT5lzpIHeDwUNPCUjcm4K_whHWJBV-YqdM,57641
6
+ draftomen/cardimages.py,sha256=CF0OjYiMmtp_vFFxlNP05gckS99wXKswJMYR1Il-XXE,11101
7
+ draftomen/cli.py,sha256=jJW0rCM4E2ubuU9_ztvdX9YuTJJ6cknPRvLWD693Lcw,28136
8
+ draftomen/config.py,sha256=1Y9eNkUVFPryORdpwV88zD62sEBrpMAC6Oxy2V7Ud4g,4321
9
+ draftomen/deckbuilder.py,sha256=4BKKpdY0j3hV2z5FRlKXtcNo70v_8RJ3vCfF_LD-QcE,81979
10
+ draftomen/events.py,sha256=-KJRAUGnzUDaEsIlTKmJTUJq2WzRZuw8cN2Egbjkz8I,22223
11
+ draftomen/logfollow.py,sha256=uNIojOTWVAYzRY_NSG3CxhiVU7wQFjb7mCreoJUR8Rk,14631
12
+ draftomen/mock_session.py,sha256=6vMiYSbDQQOwkCGkgjxtKQku4T_j0kbWtrtq6lX2lts,22737
13
+ draftomen/paths.py,sha256=O0OlBU3FLKortvtpzK0T-TZ9FdMMQrLpmfqk5U1lKdQ,3322
14
+ draftomen/pickengine.py,sha256=7B_RUX4rwS5KayI6NkWYpP782nNtsW8_qHYBSG981SU,32891
15
+ draftomen/pool.py,sha256=oypWhQvWz3OigtPE1j3WoCnxodoU5JDvXzzp7r1Na_A,41046
16
+ draftomen/preferences.py,sha256=AvyGrrVweTgrilNnEwQt8Dq9Dj7VWrJJ7ZE8z8yzF_s,9427
17
+ draftomen/qt_adapter.py,sha256=W4quF7-qw4QLWlrlqGou3CWgB4YjG5gBWWjsWqyO7Dg,29754
18
+ draftomen/qt_gui.py,sha256=gu6ViXFumsxJrLdfJ23ktwC9EVihoADq8TA5ek_GrWg,10936
19
+ draftomen/qt_mock.py,sha256=3OkUfSbQYu6cuBweWvh5b2JZvLTNuSoGoaKAVBs0zws,1825
20
+ draftomen/ranking.py,sha256=AD4egfWNMXVFK4-LVZxMHoYZtQyflD24KPuswRupqdI,3371
21
+ draftomen/replay.py,sha256=dun5F_1Em58uZ_MArkJcwd1PHIxH7ZRbiP3HhffDn_4,14324
22
+ draftomen/session.py,sha256=lfAtpi1yPU3_1W9Wl6_lhboJ-OOltgNizimAO4lFo_I,130801
23
+ draftomen/setinfo.py,sha256=7xoOfYpBPK-fo0BV0-IkvCVx4mMsXALwFAJY-Qd8020,654
24
+ draftomen/seventeen.py,sha256=xXIkYWCujD9QXIw8r5cdFHqtP2U3WzbmgkH9b3cTQso,87441
25
+ draftomen/splash.py,sha256=Z0Ft8HmPC_xXtGs9yc-tCC6WrcwBn0i8QxZXMmfd9xw,18736
26
+ draftomen/tui.py,sha256=RlkZOrT1qCVuc1V7GJXi_oJhyVJG519SnKR11wvcImE,134053
27
+ draftomen/watch.py,sha256=EVFpEK2dpy7EPjaAO65BfTpIqOeehwRrk5Ght8OQxUQ,10942
28
+ draftomen/assets/draftomen.icns,sha256=EmMNL-ZKAV5f43F_ABtcrrAw21Y4wd7j0FsXRgS5qVY,994218
29
+ draftomen/assets/draftomen.ico,sha256=8f-sLKQFP0AufFAv_Cchft_kzbg8w-CTqy6D-CY_RZ8,130414
30
+ draftomen/assets/draftomen_logo.png,sha256=FMv_XxOg1Jst0htu1yy3j_AnJgNFsCEgOgE74_P81Io,1613066
31
+ draftomen/qml/AboutDialog.qml,sha256=swZBXKYnUPlwHxfWMOLpP-_WBipTatVT54RRLIiNltc,4692
32
+ draftomen/qml/AppBar.qml,sha256=XgjiXtMi2c4JyIjGL5Ar9S6bYq7RXNpJS1Y525WnWzQ,3940
33
+ draftomen/qml/BacktestView.qml,sha256=42VaEtgUgPCGgOyzrFhrr_DRKsmv9zVAJVjovqVrRRs,17209
34
+ draftomen/qml/BuildView.qml,sha256=i4JS6Su1qJ9xCdwkEEZ4bdHk8YFIXEZfCXKxrE0O2zs,56161
35
+ draftomen/qml/CardPreview.qml,sha256=Krz68J1Cyj8vop-Edh_x6aivajpz_91FQNkP2Mf9q7M,16830
36
+ draftomen/qml/DimensionalButton.qml,sha256=b4mcHZA--2Y2-4tISZ05XqAeKWnRSYvZdCY90paAgCk,1258
37
+ draftomen/qml/DimensionalComboBox.qml,sha256=QYsvCnfVv-QSSvLu27vxrJb2xGvlNDmiTdMw5cR-GFY,6022
38
+ draftomen/qml/DimensionalSurface.qml,sha256=RZMy3J-5-9SPymjETK1i13iP8O1F6Z6gzcu9LLJTEto,3989
39
+ draftomen/qml/DimensionalTabButton.qml,sha256=I7Fk8bYrZhXpOEBe8OlOGZ3mS2ryYUWu243vRkGPnCA,1205
40
+ draftomen/qml/LiveDraftView.qml,sha256=83BP3JRk4SG7XFs16LOuZe-IkGTqbXYVAUN5Xegs3jk,18908
41
+ draftomen/qml/Main.qml,sha256=c2dTfNoneRPSwbZNfuWRlLRBJDKvvse0CxwFgX9E3Rw,5894
42
+ draftomen/qml/NavigationRail.qml,sha256=hNXPsxj7VdG1zY8YiuCCejoFdVR_2zN1Q6zaqViD-dA,8749
43
+ draftomen/qml/PoolSummaryPanel.qml,sha256=dbCWwxD-xVAne9zbahb7p2x2m6BLDK7OGopSeanqvi0,11571
44
+ draftomen/qml/PrivacyDialog.qml,sha256=pDIO5HsFF7B6uHi8Z6n-PzpDs59p92PEvuwuT0smmZA,2610
45
+ draftomen/qml/RecentPickThumbnail.qml,sha256=zdJTKAkNcWdU-SNyGkd1WS9EMYh5kdhzbHS3IaefOGI,2719
46
+ draftomen/qml/RecentPicksGallery.qml,sha256=zlPfxBWsB1tnTM0GS1q_C53oiydMmDnJYy4hPSMeM_A,10705
47
+ draftomen/qml/RecommendationRow.qml,sha256=3HsvplhIqxhsc-hbSsq7Ur6K67C-hHPxYzMAtoiuJlo,15093
48
+ draftomen/qml/SettingsSwitch.qml,sha256=y6caW0kq_xxtKC5IsQH67aoAMwG_uR5NWOlHvKHF_4o,5320
49
+ draftomen/qml/SettingsView.qml,sha256=OD_XYAsfTANNJmyJmjmIMhsnIYo4tV-1KfoAwsOVa4M,25987
50
+ draftomen/qml/StateBanner.qml,sha256=GHJjMXpxVu6L5SRWFB5a_-ndIHZbWL2GYmKq6fnZjjA,6443
51
+ draftomen/qml/StatusStrip.qml,sha256=AsqyWcCTuEs3sZlbJvdw5raSsYdP46yw0Wr-B4jGVM8,2446
52
+ draftomen/qml/Theme.qml,sha256=uq8ZOO_dg6ycPYSySk0Thp8a5YJVoSoJwZ0GYqGXoTg,2783
53
+ draftomen/qml/qmldir,sha256=xcXbi_ZXRxDGstJ8tAyeRvEAArkXoODXch-MrBZWVxI,810
54
+ draftomen-0.3.0.dist-info/licenses/LICENSE,sha256=CAbXVt-kglXwH27kdm-0fJMnAjrvdPBxFjC_ZC-1TBY,1070
55
+ draftomen-0.3.0.dist-info/METADATA,sha256=WTNdkSAwhNYcqnFRbbIFX0ivbl35R6BJenZFjiSAsh8,8182
56
+ draftomen-0.3.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
57
+ draftomen-0.3.0.dist-info/entry_points.txt,sha256=FqGt9cbwb7-C2LC0D8-TA41NJrWnlNSGEOYk02KBOmc,148
58
+ draftomen-0.3.0.dist-info/top_level.txt,sha256=2itiKkfjeHhMwCsocHw6TEuFXfICgYIseDZs7pQK7kw,10
59
+ draftomen-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,6 @@
1
+ [console_scripts]
2
+ draftomen-gui-mockup = draftomen.qt_mock:main
3
+ draftomen-tui = draftomen.cli:main
4
+
5
+ [gui_scripts]
6
+ draftomen = draftomen.qt_gui:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Andrea Grandi
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 @@
1
+ draftomen