korb 0.2.2__tar.gz → 0.2.4__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. {korb-0.2.2 → korb-0.2.4}/.coverage +0 -0
  2. {korb-0.2.2 → korb-0.2.4}/CHANGELOG.md +16 -0
  3. {korb-0.2.2 → korb-0.2.4}/PKG-INFO +18 -3
  4. {korb-0.2.2 → korb-0.2.4}/README.md +17 -2
  5. {korb-0.2.2 → korb-0.2.4}/korb/__init__.py +1 -1
  6. {korb-0.2.2 → korb-0.2.4}/korb/__main__.py +36 -0
  7. {korb-0.2.2 → korb-0.2.4}/korb/core.py +0 -4
  8. {korb-0.2.2 → korb-0.2.4}/korb/schedule.py +3 -3
  9. {korb-0.2.2 → korb-0.2.4/korb}/skills/SKILL_LEAGUE_TOP_N_ANALYSIS.md +1 -2
  10. {korb-0.2.2 → korb-0.2.4/korb}/skills/SKILL_TEAM_ANALYSIS.md +4 -5
  11. korb-0.2.4/korb/skills/__init__.py +28 -0
  12. {korb-0.2.2 → korb-0.2.4}/tests/test_cli.py +38 -1
  13. {korb-0.2.2 → korb-0.2.4}/tests/test_download.py +2 -9
  14. {korb-0.2.2 → korb-0.2.4}/tests/test_schedule.py +1 -1
  15. {korb-0.2.2 → korb-0.2.4}/.github/workflows/release.yml +0 -0
  16. {korb-0.2.2 → korb-0.2.4}/.github/workflows/test.yml +0 -0
  17. {korb-0.2.2 → korb-0.2.4}/.gitignore +0 -0
  18. {korb-0.2.2 → korb-0.2.4}/LICENSE +0 -0
  19. {korb-0.2.2 → korb-0.2.4}/files/.gitkeep +0 -0
  20. {korb-0.2.2 → korb-0.2.4}/korb/predict.py +0 -0
  21. {korb-0.2.2 → korb-0.2.4}/korb/py.typed +0 -0
  22. {korb-0.2.2 → korb-0.2.4}/korb/standings.py +0 -0
  23. {korb-0.2.2 → korb-0.2.4}/korb/team.py +0 -0
  24. {korb-0.2.2 → korb-0.2.4}/pyproject.toml +0 -0
  25. {korb-0.2.2 → korb-0.2.4}/tests/__init__.py +0 -0
  26. {korb-0.2.2 → korb-0.2.4}/tests/conftest.py +0 -0
  27. {korb-0.2.2 → korb-0.2.4}/tests/fixtures/ergebnisse_minimal.html +0 -0
  28. {korb-0.2.2 → korb-0.2.4}/tests/fixtures/spielplan_finalized.html +0 -0
  29. {korb-0.2.2 → korb-0.2.4}/tests/fixtures/spielplan_minimal.html +0 -0
  30. {korb-0.2.2 → korb-0.2.4}/tests/test_core.py +0 -0
  31. {korb-0.2.2 → korb-0.2.4}/tests/test_predict.py +0 -0
  32. {korb-0.2.2 → korb-0.2.4}/tests/test_standings.py +0 -0
  33. {korb-0.2.2 → korb-0.2.4}/tests/test_team.py +0 -0
Binary file
@@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/).
6
6
 
7
+ ## [0.2.3] — 2026-04-10
8
+
9
+ ### Added
10
+
11
+ - `skill` subcommand — prints built-in AI skill prompts to stdout.
12
+ - `skill --list` / `-l` flag — lists available skill names and filenames.
13
+ - `korb/skills/` package with `SKILL_MAP` and `get_skill_text()` using `importlib.resources`.
14
+ - Two bundled skills: `analysis` (team deep-dive) and `prediction` (league top-N forecast).
15
+ - Skill markdown files now shipped inside the `korb` package (included in pip distributions).
16
+
17
+ ### Changed
18
+
19
+ - Migrated `skills/` from project root into `korb/skills/` for proper wheel inclusion.
20
+
21
+ ---
22
+
7
23
  ## [0.2.2] — 2026-04-09
8
24
 
9
25
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: korb
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: A CLI toolkit for DBB basketball league analysis
5
5
  Project-URL: Homepage, https://github.com/malvavisc0/korb
6
6
  Project-URL: Repository, https://github.com/malvavisc0/korb
@@ -47,6 +47,7 @@ A zero-dependency Python CLI that parses HTML from the **DBB** (Deutscher Basket
47
47
  - 🔮 **Predictions** — efficiency-model-based final standings forecast
48
48
  - 🥇 **Top N** — quick leaderboard with ASCII bar chart
49
49
  - 📥 **Download** — fetch fresh HTML data directly from basketball-bund.net
50
+ - 🤖 **Skills** — built-in AI skill prompts for team analysis & league prediction
50
51
  - 🔧 **JSON output** — pipe-friendly `--json` flag for all commands
51
52
 
52
53
  ---
@@ -161,6 +162,19 @@ Uses a multiplicative efficiency model with recency weighting, recent form blend
161
162
  uv run korb --ligaid 12345 top -n 5
162
163
  ```
163
164
 
165
+ ### `skill` — Print AI skill prompts
166
+
167
+ ```bash
168
+ # List available skills
169
+ uv run korb skill --list
170
+
171
+ # Print a specific skill prompt
172
+ uv run korb skill analysis
173
+ uv run korb skill prediction
174
+ ```
175
+
176
+ Ships two built-in skills: `analysis` (team deep-dive) and `prediction` (league top-N forecast).
177
+
164
178
  ### `--download` — Fetch fresh data before any command
165
179
 
166
180
  ```bash
@@ -192,18 +206,19 @@ $ uv run korb --help
192
206
 
193
207
  usage: korb [-h] [--version] [--results RESULTS] [--schedule SCHEDULE]
194
208
  [--json] [--ligaid LIGAID] [--download]
195
- {standings,team,schedule,predict,top,download} ...
209
+ {standings,team,schedule,predict,top,download,skill} ...
196
210
 
197
211
  Basketball league analysis tools
198
212
 
199
213
  positional arguments:
200
- {standings,team,schedule,predict,top,download}
214
+ {standings,team,schedule,predict,top,download,skill}
201
215
  standings Display league standings
202
216
  team Display results for a team
203
217
  schedule Display game schedule
204
218
  predict Predict final standings
205
219
  top Show top teams from standings
206
220
  download Download results & schedule HTML
221
+ skill Print a skill prompt or list available skills
207
222
 
208
223
  options:
209
224
  -h, --help show this help message and exit
@@ -22,6 +22,7 @@ A zero-dependency Python CLI that parses HTML from the **DBB** (Deutscher Basket
22
22
  - 🔮 **Predictions** — efficiency-model-based final standings forecast
23
23
  - 🥇 **Top N** — quick leaderboard with ASCII bar chart
24
24
  - 📥 **Download** — fetch fresh HTML data directly from basketball-bund.net
25
+ - 🤖 **Skills** — built-in AI skill prompts for team analysis & league prediction
25
26
  - 🔧 **JSON output** — pipe-friendly `--json` flag for all commands
26
27
 
27
28
  ---
@@ -136,6 +137,19 @@ Uses a multiplicative efficiency model with recency weighting, recent form blend
136
137
  uv run korb --ligaid 12345 top -n 5
137
138
  ```
138
139
 
140
+ ### `skill` — Print AI skill prompts
141
+
142
+ ```bash
143
+ # List available skills
144
+ uv run korb skill --list
145
+
146
+ # Print a specific skill prompt
147
+ uv run korb skill analysis
148
+ uv run korb skill prediction
149
+ ```
150
+
151
+ Ships two built-in skills: `analysis` (team deep-dive) and `prediction` (league top-N forecast).
152
+
139
153
  ### `--download` — Fetch fresh data before any command
140
154
 
141
155
  ```bash
@@ -167,18 +181,19 @@ $ uv run korb --help
167
181
 
168
182
  usage: korb [-h] [--version] [--results RESULTS] [--schedule SCHEDULE]
169
183
  [--json] [--ligaid LIGAID] [--download]
170
- {standings,team,schedule,predict,top,download} ...
184
+ {standings,team,schedule,predict,top,download,skill} ...
171
185
 
172
186
  Basketball league analysis tools
173
187
 
174
188
  positional arguments:
175
- {standings,team,schedule,predict,top,download}
189
+ {standings,team,schedule,predict,top,download,skill}
176
190
  standings Display league standings
177
191
  team Display results for a team
178
192
  schedule Display game schedule
179
193
  predict Predict final standings
180
194
  top Show top teams from standings
181
195
  download Download results & schedule HTML
196
+ skill Print a skill prompt or list available skills
182
197
 
183
198
  options:
184
199
  -h, --help show this help message and exit
@@ -3,4 +3,4 @@
3
3
  Target: DBB Version ≤11.50.0-623b018 (legacy JSP platform).
4
4
  """
5
5
 
6
- __version__ = "0.2.2"
6
+ __version__ = "0.2.4"
@@ -25,6 +25,7 @@ from .schedule import (
25
25
  parse_schedule,
26
26
  print_schedule,
27
27
  )
28
+ from .skills import SKILL_MAP, get_skill_text
28
29
  from .standings import calculate_standings, print_table
29
30
  from .team import get_team_results, print_bars, print_metrics, print_results
30
31
 
@@ -352,6 +353,22 @@ def cmd_download(args: argparse.Namespace) -> None:
352
353
  _download(args.ligaid)
353
354
 
354
355
 
356
+ def cmd_skill(args: argparse.Namespace) -> None:
357
+ """Handle 'skill' subcommand."""
358
+ if args.list_skills:
359
+ for name, filename in SKILL_MAP.items():
360
+ print(f" {name:12s} {filename}")
361
+ return
362
+ if args.name is None:
363
+ print("Error: pass a skill name or --list", file=sys.stderr)
364
+ sys.exit(1)
365
+ try:
366
+ print(get_skill_text(args.name))
367
+ except ValueError as exc:
368
+ print(f"Error: {exc}", file=sys.stderr)
369
+ sys.exit(1)
370
+
371
+
355
372
  def main() -> None:
356
373
  """Entry point for CLI."""
357
374
  parser = argparse.ArgumentParser(
@@ -478,6 +495,25 @@ def main() -> None:
478
495
  )
479
496
  p_dl.set_defaults(func=cmd_download)
480
497
 
498
+ p_sk = subs.add_parser(
499
+ "skill",
500
+ help="Print a skill prompt or list available skills",
501
+ )
502
+ p_sk.add_argument(
503
+ "name",
504
+ nargs="?",
505
+ default=None,
506
+ help="Skill name: analysis or prediction",
507
+ )
508
+ p_sk.add_argument(
509
+ "--list",
510
+ "-l",
511
+ action="store_true",
512
+ dest="list_skills",
513
+ help="List available skill names and descriptions",
514
+ )
515
+ p_sk.set_defaults(func=cmd_skill)
516
+
481
517
  args = parser.parse_args()
482
518
 
483
519
  # Pre-command download hook
@@ -182,10 +182,6 @@ def extract_league_info(html: str) -> LeagueInfo:
182
182
  return LeagueInfo(name=name, number=number)
183
183
 
184
184
 
185
- # Backward-compatible alias
186
- extract_league_name = extract_league_info
187
-
188
-
189
185
  def read_games(filepath: str) -> tuple[list[Game], LeagueInfo]:
190
186
  """Read all valid games from HTML results file.
191
187
 
@@ -126,19 +126,19 @@ class _HTMLScheduleParser(HTMLParser):
126
126
 
127
127
 
128
128
  def parse_schedule(html_file: str) -> tuple[list[ScheduledGame], LeagueInfo]:
129
- """Parse HTML file into scheduled games, sorted newest first.
129
+ """Parse HTML file into scheduled games, sorted chronologically.
130
130
 
131
131
  Args:
132
132
  html_file: Path to HTML schedule file.
133
133
 
134
134
  Returns:
135
- Tuple of (games sorted by date descending, league_info).
135
+ Tuple of (games sorted by date ascending, league_info).
136
136
  """
137
137
  content = read_file_safe(html_file)
138
138
  league_info = extract_league_info(content)
139
139
  parser = _HTMLScheduleParser()
140
140
  parser.feed(content)
141
- parser.games.sort(key=lambda g: g.date, reverse=True)
141
+ parser.games.sort(key=lambda g: g.date)
142
142
  return parser.games, league_info
143
143
 
144
144
 
@@ -97,5 +97,4 @@ Keep it brief (caption-like) and avoid bullet points.
97
97
 
98
98
  ## Output
99
99
 
100
- Return: (1) the markdown table and (2) the explanation paragraph.
101
-
100
+ Return: (1) the markdown table and (2) the explanation paragraph.
@@ -66,11 +66,11 @@ uv run korb --json --ligaid <LIGA_ID> team "<TEAM_NAME>"
66
66
 
67
67
  Important: in JSON mode, the CLI returns **all matching games** and does **not** apply `--last-k` slicing or `--metrics` printing logic.
68
68
 
69
- So to emulate last 5 games”:
69
+ So to emulate "last 5 games":
70
70
 
71
71
  1. Take `team_json["results"]`.
72
72
  2. Treat it as **newest-first**.
73
- 3. Use the first 5 items as the last 5”. If there are fewer than 5 games, use all available.
73
+ 3. Use the first 5 items as the "last 5". If there are fewer than 5 games, use all available.
74
74
 
75
75
  Compute:
76
76
 
@@ -92,7 +92,7 @@ uv run korb --json --ligaid <LIGA_ID> schedule --pending
92
92
  uv run korb --json --ligaid <LIGA_ID> predict
93
93
  ```
94
94
 
95
- From `predict_json["standings"]` extract the teams **predicted rank** as `index_in_list + 1`.
95
+ From `predict_json["standings"]` extract the team's **predicted rank** as `index_in_list + 1`.
96
96
 
97
97
  ---
98
98
 
@@ -117,5 +117,4 @@ Write the paragraph in the selected `LANGUAGE`.
117
117
 
118
118
  ## Output
119
119
 
120
- Return the paragraph directly. Do **not** save to a file.
121
-
120
+ Return the paragraph directly. Do **not** save to a file.
@@ -0,0 +1,28 @@
1
+ """Skill definitions shipped with the korb package."""
2
+
3
+ from importlib.resources import files
4
+
5
+ SKILL_MAP: dict[str, str] = {
6
+ "analysis": "SKILL_TEAM_ANALYSIS.md",
7
+ "prediction": "SKILL_LEAGUE_TOP_N_ANALYSIS.md",
8
+ }
9
+
10
+
11
+ def get_skill_text(name: str) -> str:
12
+ """Return the markdown text of a named skill.
13
+
14
+ Args:
15
+ name: Skill key from SKILL_MAP (e.g. "analysis", "prediction").
16
+
17
+ Returns:
18
+ The full markdown content of the skill file.
19
+
20
+ Raises:
21
+ ValueError: If *name* is not in SKILL_MAP.
22
+ """
23
+ if name not in SKILL_MAP:
24
+ raise ValueError(
25
+ f"unknown skill: {name!r}. Choose from: {', '.join(SKILL_MAP)}"
26
+ )
27
+ filename = SKILL_MAP[name]
28
+ return files(__package__).joinpath(filename).read_text(encoding="utf-8")
@@ -4,7 +4,13 @@ import argparse
4
4
 
5
5
  import pytest
6
6
 
7
- from korb.__main__ import cmd_predict, cmd_schedule, cmd_standings, cmd_team
7
+ from korb.__main__ import (
8
+ cmd_predict,
9
+ cmd_schedule,
10
+ cmd_skill,
11
+ cmd_standings,
12
+ cmd_team,
13
+ )
8
14
 
9
15
 
10
16
  def _make_args(**kwargs) -> argparse.Namespace:
@@ -167,3 +173,34 @@ class TestCmdPredict:
167
173
  cmd_predict(args)
168
174
  captured = capsys.readouterr()
169
175
  assert "Predicted" in captured.out
176
+
177
+
178
+ class TestCmdSkill:
179
+ def test_analysis_skill(self, capsys):
180
+ args = _make_args(name="analysis", list_skills=False)
181
+ cmd_skill(args)
182
+ out = capsys.readouterr().out
183
+ assert "Team Analysis" in out
184
+
185
+ def test_prediction_skill(self, capsys):
186
+ args = _make_args(name="prediction", list_skills=False)
187
+ cmd_skill(args)
188
+ out = capsys.readouterr().out
189
+ assert "League Prediction" in out
190
+
191
+ def test_list_skills(self, capsys):
192
+ args = _make_args(name=None, list_skills=True)
193
+ cmd_skill(args)
194
+ out = capsys.readouterr().out
195
+ assert "analysis" in out
196
+ assert "prediction" in out
197
+
198
+ def test_no_args(self):
199
+ args = _make_args(name=None, list_skills=False)
200
+ with pytest.raises(SystemExit):
201
+ cmd_skill(args)
202
+
203
+ def test_invalid_skill(self):
204
+ args = _make_args(name="nonexistent", list_skills=False)
205
+ with pytest.raises(SystemExit):
206
+ cmd_skill(args)
@@ -4,7 +4,6 @@ import gzip
4
4
  import zlib
5
5
  from unittest.mock import MagicMock, patch
6
6
 
7
-
8
7
  from korb.__main__ import _DELAY_MAX, _DELAY_MIN, _HEADERS, _read_response
9
8
 
10
9
 
@@ -46,7 +45,6 @@ class TestHeaders:
46
45
  assert _HEADERS["DNT"] == "1"
47
46
 
48
47
 
49
-
50
48
  def _fake_response(body: bytes, encoding: str = "") -> MagicMock:
51
49
  """Build a mock HTTP response with the given body & encoding."""
52
50
  resp = MagicMock()
@@ -90,7 +88,6 @@ class TestReadResponse:
90
88
  assert _read_response(resp) == raw
91
89
 
92
90
 
93
-
94
91
  class TestDownload:
95
92
  """Verify _download sends correct headers and sleeps between requests."""
96
93
 
@@ -120,9 +117,7 @@ class TestDownload:
120
117
  # Use real tmp_path for file writes
121
118
  dest_a = tmp_path / "ergebnisse.html"
122
119
  dest_b = tmp_path / "spielplan.html"
123
- mock_liga.__truediv__ = MagicMock(
124
- side_effect=[dest_a, dest_b]
125
- )
120
+ mock_liga.__truediv__ = MagicMock(side_effect=[dest_a, dest_b])
126
121
  mock_root.__truediv__ = MagicMock(return_value=mock_liga)
127
122
  mock_liga.mkdir = MagicMock()
128
123
 
@@ -158,9 +153,7 @@ class TestDownload:
158
153
 
159
154
  dest_a = tmp_path / "ergebnisse.html"
160
155
  dest_b = tmp_path / "spielplan.html"
161
- mock_liga.__truediv__ = MagicMock(
162
- side_effect=[dest_a, dest_b]
163
- )
156
+ mock_liga.__truediv__ = MagicMock(side_effect=[dest_a, dest_b])
164
157
  mock_root.__truediv__ = MagicMock(return_value=mock_liga)
165
158
  mock_liga.mkdir = MagicMock()
166
159
 
@@ -66,7 +66,7 @@ class TestParseSchedule:
66
66
  def test_sorting_by_date(self, spielplan_path):
67
67
  games, _ = parse_schedule(spielplan_path)
68
68
  for i in range(len(games) - 1):
69
- assert games[i].date >= games[i + 1].date
69
+ assert games[i].date <= games[i + 1].date
70
70
 
71
71
  def test_missing_file_raises(self, tmp_path):
72
72
  with pytest.raises(SystemExit):
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes