1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
"""
Sprint point capacity: per-user weekly story point capacity.
PyGitWeb has no database, so this follows the same JSON-file pattern as
auth_config.py and settings_config.py: persisted at ~/.pygitweb/capacity.json
(or PYGITWEB_CAPACITY_CONFIG).
"""
from __future__ import annotations
import json
import os
from datetime import date
from pathlib import Path
from pydantic import BaseModel, Field
DEFAULT_CAPACITY_CONFIG_PATH = Path.home() / ".pygitweb" / "capacity.json"
DEFAULT_WEEKLY_CAPACITY_POINTS = 20
def capacity_config_path() -> Path:
explicit = os.environ.get("PYGITWEB_CAPACITY_CONFIG", "").strip()
if explicit:
return Path(explicit).expanduser()
return DEFAULT_CAPACITY_CONFIG_PATH
def iso_week_key(d: date) -> str:
"""ISO week identifier, e.g. 2026-W38."""
year, week, _ = d.isocalendar()
return f"{year:04d}-W{week:02d}"
def current_iso_week() -> str:
return iso_week_key(date.today())
class UserCapacity(BaseModel):
default_points: int = DEFAULT_WEEKLY_CAPACITY_POINTS
weeks: dict[str, int] = Field(default_factory=dict)
class CapacityConfig(BaseModel):
users: dict[str, UserCapacity] = Field(default_factory=dict)
def load_capacity_config() -> CapacityConfig:
path = capacity_config_path()
if not path.is_file():
return CapacityConfig()
try:
return CapacityConfig.model_validate_json(path.read_text(encoding="utf-8"))
except ValueError:
return CapacityConfig()
def write_capacity_config(config: CapacityConfig) -> None:
path = capacity_config_path()
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(config.model_dump(mode="json"), indent=2) + "\n", encoding="utf-8")
def get_user_week_capacity(username: str, iso_week: str) -> int:
"""Points capacity for a user in a given ISO week (falls back to their default, then the global default)."""
config = load_capacity_config()
user = config.users.get(username)
if user is None:
return DEFAULT_WEEKLY_CAPACITY_POINTS
return user.weeks.get(iso_week, user.default_points)
def set_user_week_capacity(username: str, iso_week: str, points: int) -> CapacityConfig:
config = load_capacity_config()
user = config.users.get(username) or UserCapacity()
user.weeks[iso_week] = points
config.users[username] = user
write_capacity_config(config)
return config