API Reference
Generated from the duty_teller package. The following subpackages and modules are included.
Configuration
duty_teller.config
Load configuration from environment (e.g. .env via python-dotenv).
BOT_TOKEN is not validated on import; call require_bot_token() in the entry point when running the bot.
Settings
dataclass
Injectable settings built from environment. Used in tests or when env is overridden.
Source code in duty_teller/config.py
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 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | |
from_env()
classmethod
Build Settings from current environment (same logic as module-level variables).
Returns:
| Type | Description |
|---|---|
Settings
|
Settings instance with all fields populated from env. |
Source code in duty_teller/config.py
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | |
can_access_miniapp(username)
Check if username is allowed to open the calendar Miniapp.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
username
|
str
|
Telegram username (with or without @; case-insensitive). |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if in ALLOWED_USERNAMES or ADMIN_USERNAMES. |
Source code in duty_teller/config.py
172 173 174 175 176 177 178 179 180 181 182 | |
can_access_miniapp_by_phone(phone)
Check if phone (set via /set_phone) is allowed to open the Miniapp.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
phone
|
str | None
|
Raw phone string or None. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if normalized phone is in ALLOWED_PHONES or ADMIN_PHONES. |
Source code in duty_teller/config.py
185 186 187 188 189 190 191 192 193 194 195 196 197 | |
is_admin(username)
Check if Telegram username is in ADMIN_USERNAMES.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
username
|
str
|
Telegram username (with or without @; case-insensitive). |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if in ADMIN_USERNAMES. |
Source code in duty_teller/config.py
160 161 162 163 164 165 166 167 168 169 | |
is_admin_by_phone(phone)
Check if phone is in ADMIN_PHONES.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
phone
|
str | None
|
Raw phone string or None. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if normalized phone is in ADMIN_PHONES. |
Source code in duty_teller/config.py
200 201 202 203 204 205 206 207 208 209 210 | |
normalize_phone(phone)
Return phone as digits only (spaces, +, parentheses, dashes removed).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
phone
|
str | None
|
Raw phone string or None. |
required |
Returns:
| Type | Description |
|---|---|
str
|
Digits-only string, or empty string if None or empty. |
Source code in duty_teller/config.py
23 24 25 26 27 28 29 30 31 32 33 34 | |
require_bot_token()
Raise SystemExit with a clear message if BOT_TOKEN is not set.
Call from the application entry point (e.g. main.py or duty_teller.run) so the process exits with a helpful message instead of failing later.
Raises:
| Type | Description |
|---|---|
SystemExit
|
If BOT_TOKEN is empty. |
Source code in duty_teller/config.py
213 214 215 216 217 218 219 220 221 222 223 224 225 | |
API (FastAPI and auth)
duty_teller.api
HTTP API for the calendar Mini App: duties, calendar events, and static webapp.
duty_teller.api.app
FastAPI app: /api/duties, /api/calendar-events, personal ICS, and static webapp at /app.
get_personal_calendar_ical(token, session=Depends(get_db_session))
Return ICS calendar with only the subscribing user's duties. No Telegram auth; access is by secret token in the URL.
Source code in duty_teller/api/app.py
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | |
duty_teller.api.dependencies
FastAPI dependencies: DB session, Miniapp auth (initData/allowlist), date validation.
fetch_duties_response(session, from_date, to_date)
Load duties in range and return as DutyWithUser list for API response.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
from_date
|
str
|
Start date YYYY-MM-DD. |
required |
to_date
|
str
|
End date YYYY-MM-DD. |
required |
Returns:
| Type | Description |
|---|---|
list[DutyWithUser]
|
List of DutyWithUser (id, user_id, start_at, end_at, full_name, event_type). |
Source code in duty_teller/api/dependencies.py
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 | |
get_authenticated_username(request, x_telegram_init_data, session)
Return identifier for miniapp auth (username or full_name or id:...); empty if skip-auth.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Request
|
FastAPI request (client host for private-IP bypass). |
required |
x_telegram_init_data
|
str | None
|
Raw X-Telegram-Init-Data header value. |
required |
session
|
Session
|
DB session (for phone allowlist lookup). |
required |
Returns:
| Type | Description |
|---|---|
str
|
Username, full_name, or "id: |
str
|
or private IP and no initData. |
Raises:
| Type | Description |
|---|---|
HTTPException
|
403 if initData missing/invalid or user not in allowlist. |
Source code in duty_teller/api/dependencies.py
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | |
get_db_session()
Yield a DB session for the request; closed automatically by FastAPI.
Source code in duty_teller/api/dependencies.py
86 87 88 89 | |
get_validated_dates(request, from_date=Query(..., description='ISO date YYYY-MM-DD', alias='from'), to_date=Query(..., description='ISO date YYYY-MM-DD', alias='to'))
Validate from/to date query params; use Accept-Language for error messages.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Request
|
FastAPI request (for Accept-Language). |
required |
from_date
|
str
|
Start date YYYY-MM-DD. |
Query(..., description='ISO date YYYY-MM-DD', alias='from')
|
to_date
|
str
|
End date YYYY-MM-DD. |
Query(..., description='ISO date YYYY-MM-DD', alias='to')
|
Returns:
| Type | Description |
|---|---|
tuple[str, str]
|
(from_date, to_date) as strings. |
Raises:
| Type | Description |
|---|---|
HTTPException
|
400 if format invalid or from_date > to_date. |
Source code in duty_teller/api/dependencies.py
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | |
require_miniapp_username(request, x_telegram_init_data=None, session=Depends(get_db_session))
FastAPI dependency: require valid Miniapp auth; return username/identifier.
Raises:
| Type | Description |
|---|---|
HTTPException
|
403 if initData missing/invalid or user not in allowlist. |
Source code in duty_teller/api/dependencies.py
92 93 94 95 96 97 98 99 100 101 102 103 104 | |
duty_teller.api.telegram_auth
Validate Telegram Web App initData and extract user username.
validate_init_data(init_data, bot_token, max_age_seconds=None)
Validate Telegram Web App initData and return username if valid.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
init_data
|
str
|
Raw initData string from tgWebAppData. |
required |
bot_token
|
str
|
Bot token (must match the bot that signed the data). |
required |
max_age_seconds
|
int | None
|
Reject if auth_date older than this; None to disable. |
None
|
Returns:
| Type | Description |
|---|---|
str | None
|
Username (lowercase, no @) or None if validation fails. |
Source code in duty_teller/api/telegram_auth.py
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | |
validate_init_data_with_reason(init_data, bot_token, max_age_seconds=None)
Validate initData signature and return user id, username, reason, and lang.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
init_data
|
str
|
Raw initData string from tgWebAppData. |
required |
bot_token
|
str
|
Bot token (must match the bot that signed the data). |
required |
max_age_seconds
|
int | None
|
Reject if auth_date older than this; None to disable. |
None
|
Returns:
| Type | Description |
|---|---|
int | None
|
Tuple (telegram_user_id, username, reason, lang). reason is one of: "ok", |
str | None
|
"empty", "no_hash", "hash_mismatch", "auth_date_expired", "no_user", |
str
|
"user_invalid", "no_user_id". lang is from user.language_code normalized |
str
|
to 'ru' or 'en'; 'en' when no user. On success: (user.id, username or None, |
tuple[int | None, str | None, str, str]
|
"ok", lang). |
Source code in duty_teller/api/telegram_auth.py
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 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | |
duty_teller.api.calendar_ics
Fetch and parse external ICS calendar; in-memory cache with 7-day TTL.
get_calendar_events(url, from_date, to_date)
Fetch ICS from URL and return events in the given date range.
Uses in-memory cache with TTL 7 days. Recurring events are skipped. On fetch or parse error returns an empty list.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
URL of the ICS calendar. |
required |
from_date
|
str
|
Start date YYYY-MM-DD. |
required |
to_date
|
str
|
End date YYYY-MM-DD. |
required |
Returns:
| Type | Description |
|---|---|
list[dict]
|
List of dicts with keys "date" (YYYY-MM-DD) and "summary". Empty on error. |
Source code in duty_teller/api/calendar_ics.py
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | |
duty_teller.api.personal_calendar_ics
Generate ICS calendar containing only one user's duties (for subscription link).
build_personal_ics(duties_with_name)
Build a VCALENDAR (ICS) with one VEVENT per duty.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
duties_with_name
|
list[tuple[Duty, str]]
|
List of (Duty, full_name). full_name is available for DESCRIPTION; SUMMARY is taken from event_type (duty/unavailable/vacation). |
required |
Returns:
| Type | Description |
|---|---|
bytes
|
ICS file content as bytes (UTF-8). |
Source code in duty_teller/api/personal_calendar_ics.py
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 | |
Database
duty_teller.db
Database layer: SQLAlchemy models, Pydantic schemas, repository, init.
Base
Bases: DeclarativeBase
Declarative base for all models.
Source code in duty_teller/db/models.py
7 8 9 10 | |
Duty
Bases: Base
Single duty/unavailable/vacation slot (UTC start_at/end_at, event_type).
Source code in duty_teller/db/models.py
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | |
DutyCreate
Bases: DutyBase
Duty creation payload.
Source code in duty_teller/db/schemas.py
40 41 42 43 | |
DutyInDb
Bases: DutyBase
Duty as stored in DB (includes id).
Source code in duty_teller/db/schemas.py
46 47 48 49 50 51 | |
DutyWithUser
Bases: DutyInDb
Duty with full_name and event_type for calendar display.
event_type: only these values are returned; unknown DB values are mapped to "duty" in the API.
Source code in duty_teller/db/schemas.py
54 55 56 57 58 59 60 61 62 63 | |
User
Bases: Base
Telegram user and display name; may have telegram_user_id=None for import-only users.
Source code in duty_teller/db/models.py
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | |
UserCreate
Bases: UserBase
User creation payload including Telegram user id.
Source code in duty_teller/db/schemas.py
17 18 19 20 | |
UserInDb
Bases: UserBase
User as stored in DB (includes id and telegram_user_id).
Source code in duty_teller/db/schemas.py
23 24 25 26 27 28 29 | |
delete_duties_in_range(session, user_id, from_date, to_date)
Delete all duties of the user that overlap the given date range.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
user_id
|
int
|
User id. |
required |
from_date
|
str
|
Start date YYYY-MM-DD. |
required |
to_date
|
str
|
End date YYYY-MM-DD. |
required |
Returns:
| Type | Description |
|---|---|
int
|
Number of duties deleted. |
Source code in duty_teller/db/repository.py
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | |
get_duties(session, from_date, to_date)
Return duties overlapping the given date range with user full_name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
from_date
|
str
|
Start date YYYY-MM-DD. |
required |
to_date
|
str
|
End date YYYY-MM-DD. |
required |
Returns:
| Type | Description |
|---|---|
list[tuple[Duty, str]]
|
List of (Duty, full_name) tuples. |
Source code in duty_teller/db/repository.py
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | |
get_engine(database_url)
Return cached SQLAlchemy engine for the given URL (one per process).
Source code in duty_teller/db/session.py
42 43 44 45 46 47 48 49 50 51 52 53 | |
get_or_create_user(session, telegram_user_id, full_name, username=None, first_name=None, last_name=None)
Get or create user by Telegram user ID.
On create, name fields come from Telegram. On update: username is always synced; full_name, first_name, last_name are updated only if name_manually_edited is False (otherwise existing display name is kept).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
telegram_user_id
|
int
|
Telegram user id. |
required |
full_name
|
str
|
Display full name. |
required |
username
|
str | None
|
Telegram username (optional). |
None
|
first_name
|
str | None
|
Telegram first name (optional). |
None
|
last_name
|
str | None
|
Telegram last name (optional). |
None
|
Returns:
| Type | Description |
|---|---|
User
|
User instance (created or updated). |
Source code in duty_teller/db/repository.py
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 | |
get_or_create_user_by_full_name(session, full_name)
Find user by exact full_name or create one (for duty-schedule import).
New users have telegram_user_id=None and name_manually_edited=True.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
full_name
|
str
|
Exact full name to match or set. |
required |
Returns:
| Type | Description |
|---|---|
User
|
User instance (existing or newly created). |
Source code in duty_teller/db/repository.py
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | |
get_session(database_url)
Create a new session from the factory for the given URL.
Source code in duty_teller/db/session.py
65 66 67 | |
get_session_factory(database_url)
Return cached session factory for the given URL (one per process).
Source code in duty_teller/db/session.py
56 57 58 59 60 61 62 | |
init_db(database_url)
Create all tables from SQLAlchemy metadata.
Prefer Alembic migrations for schema changes in production.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
database_url
|
str
|
SQLAlchemy database URL. |
required |
Source code in duty_teller/db/__init__.py
51 52 53 54 55 56 57 58 59 60 | |
insert_duty(session, user_id, start_at, end_at, event_type='duty')
Create a duty record.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
user_id
|
int
|
User id. |
required |
start_at
|
str
|
Start time UTC, ISO 8601 with Z (e.g. 2025-01-15T09:00:00Z). |
required |
end_at
|
str
|
End time UTC, ISO 8601 with Z. |
required |
event_type
|
str
|
One of "duty", "unavailable", "vacation". Default "duty". |
'duty'
|
Returns:
| Type | Description |
|---|---|
Duty
|
Created Duty instance. |
Source code in duty_teller/db/repository.py
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 | |
session_scope(database_url)
Context manager that yields a session; rolls back on exception, closes on exit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
database_url
|
str
|
SQLAlchemy database URL. |
required |
Yields:
| Type | Description |
|---|---|
Session
|
Session instance. Caller must not use it after exit. |
Source code in duty_teller/db/session.py
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | |
set_user_phone(session, telegram_user_id, phone)
Set or clear phone for user by Telegram user id.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
telegram_user_id
|
int
|
Telegram user id. |
required |
phone
|
str | None
|
Phone string or None to clear. |
required |
Returns:
| Type | Description |
|---|---|
User | None
|
Updated User or None if not found. |
Source code in duty_teller/db/repository.py
453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 | |
update_user_display_name(session, telegram_user_id, full_name, first_name=None, last_name=None)
Update display name and set name_manually_edited=True.
Use from API or admin when name is changed manually; subsequent get_or_create_user will not overwrite these fields.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
telegram_user_id
|
int
|
Telegram user id. |
required |
full_name
|
str
|
New full name. |
required |
first_name
|
str | None
|
New first name (optional). |
None
|
last_name
|
str | None
|
New last name (optional). |
None
|
Returns:
| Type | Description |
|---|---|
User | None
|
Updated User or None if not found. |
Source code in duty_teller/db/repository.py
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | |
duty_teller.db.models
SQLAlchemy ORM models for users and duties.
Base
Bases: DeclarativeBase
Declarative base for all models.
Source code in duty_teller/db/models.py
7 8 9 10 | |
CalendarSubscriptionToken
Bases: Base
One active calendar subscription token per user; token_hash is unique.
Source code in duty_teller/db/models.py
34 35 36 37 38 39 40 41 42 43 44 | |
Duty
Bases: Base
Single duty/unavailable/vacation slot (UTC start_at/end_at, event_type).
Source code in duty_teller/db/models.py
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | |
GroupDutyPin
Bases: Base
Stores which message to update in each group for the pinned duty notice.
Source code in duty_teller/db/models.py
65 66 67 68 69 70 71 | |
User
Bases: Base
Telegram user and display name; may have telegram_user_id=None for import-only users.
Source code in duty_teller/db/models.py
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | |
duty_teller.db.schemas
Pydantic schemas for API request/response and validation.
CalendarEvent
Bases: BaseModel
External calendar event (e.g. holiday) for a single day.
Source code in duty_teller/db/schemas.py
66 67 68 69 70 | |
DutyBase
Bases: BaseModel
Duty fields: user_id, start_at, end_at (UTC ISO 8601 with Z).
Source code in duty_teller/db/schemas.py
32 33 34 35 36 37 | |
DutyCreate
Bases: DutyBase
Duty creation payload.
Source code in duty_teller/db/schemas.py
40 41 42 43 | |
DutyInDb
Bases: DutyBase
Duty as stored in DB (includes id).
Source code in duty_teller/db/schemas.py
46 47 48 49 50 51 | |
DutyWithUser
Bases: DutyInDb
Duty with full_name and event_type for calendar display.
event_type: only these values are returned; unknown DB values are mapped to "duty" in the API.
Source code in duty_teller/db/schemas.py
54 55 56 57 58 59 60 61 62 63 | |
UserBase
Bases: BaseModel
Base user fields (full_name, username, first/last name).
Source code in duty_teller/db/schemas.py
8 9 10 11 12 13 14 | |
UserCreate
Bases: UserBase
User creation payload including Telegram user id.
Source code in duty_teller/db/schemas.py
17 18 19 20 | |
UserInDb
Bases: UserBase
User as stored in DB (includes id and telegram_user_id).
Source code in duty_teller/db/schemas.py
23 24 25 26 27 28 29 | |
duty_teller.db.session
SQLAlchemy engine and session factory.
Engine and session factory are cached globally per process. Only one DATABASE_URL is effectively used for the process lifetime. Using a different URL later (e.g. in tests with in-memory SQLite) would still use the first engine. To use a different URL in tests, set env (e.g. DATABASE_URL) before the first import of this module, or clear _engine and _SessionLocal in test fixtures. Prefer session_scope() for all callers so sessions are always closed and rolled back on error.
get_engine(database_url)
Return cached SQLAlchemy engine for the given URL (one per process).
Source code in duty_teller/db/session.py
42 43 44 45 46 47 48 49 50 51 52 53 | |
get_session(database_url)
Create a new session from the factory for the given URL.
Source code in duty_teller/db/session.py
65 66 67 | |
get_session_factory(database_url)
Return cached session factory for the given URL (one per process).
Source code in duty_teller/db/session.py
56 57 58 59 60 61 62 | |
session_scope(database_url)
Context manager that yields a session; rolls back on exception, closes on exit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
database_url
|
str
|
SQLAlchemy database URL. |
required |
Yields:
| Type | Description |
|---|---|
Session
|
Session instance. Caller must not use it after exit. |
Source code in duty_teller/db/session.py
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | |
duty_teller.db.repository
Repository: get_or_create_user, get_duties, insert_duty, get_current_duty, group_duty_pins.
create_calendar_token(session, user_id)
Create a new calendar subscription token for the user.
Any existing tokens for this user are removed. The raw token is returned only once (not stored in plain text).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
user_id
|
int
|
User id. |
required |
Returns:
| Type | Description |
|---|---|
str
|
Raw token string (e.g. for URL /api/calendar/ical/{token}.ics). |
Source code in duty_teller/db/repository.py
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 | |
delete_duties_in_range(session, user_id, from_date, to_date)
Delete all duties of the user that overlap the given date range.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
user_id
|
int
|
User id. |
required |
from_date
|
str
|
Start date YYYY-MM-DD. |
required |
to_date
|
str
|
End date YYYY-MM-DD. |
required |
Returns:
| Type | Description |
|---|---|
int
|
Number of duties deleted. |
Source code in duty_teller/db/repository.py
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | |
delete_group_duty_pin(session, chat_id)
Remove the pinned duty message record for the chat (e.g. when bot leaves group).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
chat_id
|
int
|
Telegram chat id. |
required |
Source code in duty_teller/db/repository.py
427 428 429 430 431 432 433 434 435 | |
get_all_group_duty_pin_chat_ids(session)
Return all chat_ids that have a pinned duty message.
Used to restore update jobs on bot startup.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
Returns:
| Type | Description |
|---|---|
list[int]
|
List of chat ids. |
Source code in duty_teller/db/repository.py
438 439 440 441 442 443 444 445 446 447 448 449 450 | |
get_current_duty(session, at_utc)
Return the duty and user active at the given UTC time (event_type='duty').
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
at_utc
|
datetime
|
Point in time (timezone-aware or naive UTC). |
required |
Returns:
| Type | Description |
|---|---|
tuple[Duty, User] | None
|
(Duty, User) or None if no duty at that time. |
Source code in duty_teller/db/repository.py
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 | |
get_duties(session, from_date, to_date)
Return duties overlapping the given date range with user full_name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
from_date
|
str
|
Start date YYYY-MM-DD. |
required |
to_date
|
str
|
End date YYYY-MM-DD. |
required |
Returns:
| Type | Description |
|---|---|
list[tuple[Duty, str]]
|
List of (Duty, full_name) tuples. |
Source code in duty_teller/db/repository.py
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | |
get_duties_for_user(session, user_id, from_date, to_date)
Return duties for one user overlapping the date range.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
user_id
|
int
|
User id. |
required |
from_date
|
str
|
Start date YYYY-MM-DD. |
required |
to_date
|
str
|
End date YYYY-MM-DD. |
required |
Returns:
| Type | Description |
|---|---|
list[tuple[Duty, str]]
|
List of (Duty, full_name) tuples. |
Source code in duty_teller/db/repository.py
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 | |
get_group_duty_pin(session, chat_id)
Get the pinned duty message record for a chat.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
chat_id
|
int
|
Telegram chat id. |
required |
Returns:
| Type | Description |
|---|---|
GroupDutyPin | None
|
GroupDutyPin or None. |
Source code in duty_teller/db/repository.py
390 391 392 393 394 395 396 397 398 399 400 | |
get_next_shift_end(session, after_utc)
Return the end_at of the current or next duty (event_type='duty').
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
after_utc
|
datetime
|
Point in time (timezone-aware or naive UTC). |
required |
Returns:
| Type | Description |
|---|---|
datetime | None
|
End datetime (naive UTC) or None if no current or future duty. |
Source code in duty_teller/db/repository.py
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 | |
get_or_create_user(session, telegram_user_id, full_name, username=None, first_name=None, last_name=None)
Get or create user by Telegram user ID.
On create, name fields come from Telegram. On update: username is always synced; full_name, first_name, last_name are updated only if name_manually_edited is False (otherwise existing display name is kept).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
telegram_user_id
|
int
|
Telegram user id. |
required |
full_name
|
str
|
Display full name. |
required |
username
|
str | None
|
Telegram username (optional). |
None
|
first_name
|
str | None
|
Telegram first name (optional). |
None
|
last_name
|
str | None
|
Telegram last name (optional). |
None
|
Returns:
| Type | Description |
|---|---|
User
|
User instance (created or updated). |
Source code in duty_teller/db/repository.py
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 | |
get_or_create_user_by_full_name(session, full_name)
Find user by exact full_name or create one (for duty-schedule import).
New users have telegram_user_id=None and name_manually_edited=True.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
full_name
|
str
|
Exact full name to match or set. |
required |
Returns:
| Type | Description |
|---|---|
User
|
User instance (existing or newly created). |
Source code in duty_teller/db/repository.py
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | |
get_user_by_calendar_token(session, token)
Find user by calendar subscription token.
Uses constant-time comparison to avoid timing leaks.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
token
|
str
|
Raw token from URL. |
required |
Returns:
| Type | Description |
|---|---|
User | None
|
User or None if token is invalid or not found. |
Source code in duty_teller/db/repository.py
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 | |
get_user_by_telegram_id(session, telegram_user_id)
Find user by Telegram user ID.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
telegram_user_id
|
int
|
Telegram user id. |
required |
Returns:
| Type | Description |
|---|---|
User | None
|
User or None if not found. Does not create a user. |
Source code in duty_teller/db/repository.py
13 14 15 16 17 18 19 20 21 22 23 | |
insert_duty(session, user_id, start_at, end_at, event_type='duty')
Create a duty record.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
user_id
|
int
|
User id. |
required |
start_at
|
str
|
Start time UTC, ISO 8601 with Z (e.g. 2025-01-15T09:00:00Z). |
required |
end_at
|
str
|
End time UTC, ISO 8601 with Z. |
required |
event_type
|
str
|
One of "duty", "unavailable", "vacation". Default "duty". |
'duty'
|
Returns:
| Type | Description |
|---|---|
Duty
|
Created Duty instance. |
Source code in duty_teller/db/repository.py
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 | |
save_group_duty_pin(session, chat_id, message_id)
Save or update the pinned duty message for a chat.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
chat_id
|
int
|
Telegram chat id. |
required |
message_id
|
int
|
Message id to pin/update. |
required |
Returns:
| Type | Description |
|---|---|
GroupDutyPin
|
GroupDutyPin instance (created or updated). |
Source code in duty_teller/db/repository.py
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 | |
set_user_phone(session, telegram_user_id, phone)
Set or clear phone for user by Telegram user id.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
telegram_user_id
|
int
|
Telegram user id. |
required |
phone
|
str | None
|
Phone string or None to clear. |
required |
Returns:
| Type | Description |
|---|---|
User | None
|
Updated User or None if not found. |
Source code in duty_teller/db/repository.py
453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 | |
update_user_display_name(session, telegram_user_id, full_name, first_name=None, last_name=None)
Update display name and set name_manually_edited=True.
Use from API or admin when name is changed manually; subsequent get_or_create_user will not overwrite these fields.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
telegram_user_id
|
int
|
Telegram user id. |
required |
full_name
|
str
|
New full name. |
required |
first_name
|
str | None
|
New first name (optional). |
None
|
last_name
|
str | None
|
New last name (optional). |
None
|
Returns:
| Type | Description |
|---|---|
User | None
|
Updated User or None if not found. |
Source code in duty_teller/db/repository.py
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | |
Services
duty_teller.services
Service layer: business logic and orchestration.
delete_pin(session, chat_id)
Remove the pinned message record for the chat (e.g. when bot leaves).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
chat_id
|
int
|
Telegram chat id. |
required |
Source code in duty_teller/services/group_duty_pin_service.py
107 108 109 110 111 112 113 114 | |
format_duty_message(duty, user, tz_name, lang='en')
Build the text for the pinned duty message.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
duty
|
Duty instance or None. |
required | |
user
|
User instance or None. |
required | |
tz_name
|
str
|
Timezone name for display (e.g. Europe/Moscow). |
required |
lang
|
str
|
Language code for i18n ('ru' or 'en'). |
'en'
|
Returns:
| Type | Description |
|---|---|
str
|
Formatted message string; "No duty" if duty or user is None. |
Source code in duty_teller/services/group_duty_pin_service.py
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 | |
get_all_pin_chat_ids(session)
Return all chat_ids that have a pinned duty message.
Used to restore update jobs on bot startup.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
Returns:
| Type | Description |
|---|---|
list[int]
|
List of chat ids. |
Source code in duty_teller/services/group_duty_pin_service.py
131 132 133 134 135 136 137 138 139 140 141 142 | |
get_duty_message_text(session, tz_name, lang='en')
Get current duty from DB and return formatted message text.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
tz_name
|
str
|
Timezone name for display. |
required |
lang
|
str
|
Language code for i18n. |
'en'
|
Returns:
| Type | Description |
|---|---|
str
|
Formatted duty message or "No duty" if none. |
Source code in duty_teller/services/group_duty_pin_service.py
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | |
get_message_id(session, chat_id)
Return message_id for the pinned duty message in this chat.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
chat_id
|
int
|
Telegram chat id. |
required |
Returns:
| Type | Description |
|---|---|
int | None
|
Message id or None if no pin record. |
Source code in duty_teller/services/group_duty_pin_service.py
117 118 119 120 121 122 123 124 125 126 127 128 | |
get_next_shift_end_utc(session)
Return next shift end as naive UTC datetime for job scheduling.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
Returns:
| Type | Description |
|---|---|
datetime | None
|
Next shift end (naive UTC) or None. |
Source code in duty_teller/services/group_duty_pin_service.py
84 85 86 87 88 89 90 91 92 93 | |
run_import(session, result, hour_utc, minute_utc)
Run duty-schedule import: delete range per user, insert duty/unavailable/vacation.
For each entry: get_or_create_user_by_full_name, delete_duties_in_range for the result date range, then insert duties (handover time in UTC), unavailable (all-day), and vacation (consecutive ranges).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
result
|
DutyScheduleResult
|
Parsed duty schedule (start_date, end_date, entries). |
required |
hour_utc
|
int
|
Handover hour in UTC (0-23). |
required |
minute_utc
|
int
|
Handover minute in UTC (0-59). |
required |
Returns:
| Type | Description |
|---|---|
tuple[int, int, int, int]
|
Tuple (num_users, num_duty, num_unavailable, num_vacation). |
Source code in duty_teller/services/import_service.py
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 81 82 83 84 | |
save_pin(session, chat_id, message_id)
Save or update the pinned duty message record for a chat.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
chat_id
|
int
|
Telegram chat id. |
required |
message_id
|
int
|
Message id to store. |
required |
Source code in duty_teller/services/group_duty_pin_service.py
96 97 98 99 100 101 102 103 104 | |
duty_teller.services.import_service
Import duty schedule: delete range, insert duties/unavailable/vacation. Accepts session.
run_import(session, result, hour_utc, minute_utc)
Run duty-schedule import: delete range per user, insert duty/unavailable/vacation.
For each entry: get_or_create_user_by_full_name, delete_duties_in_range for the result date range, then insert duties (handover time in UTC), unavailable (all-day), and vacation (consecutive ranges).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
result
|
DutyScheduleResult
|
Parsed duty schedule (start_date, end_date, entries). |
required |
hour_utc
|
int
|
Handover hour in UTC (0-23). |
required |
minute_utc
|
int
|
Handover minute in UTC (0-59). |
required |
Returns:
| Type | Description |
|---|---|
tuple[int, int, int, int]
|
Tuple (num_users, num_duty, num_unavailable, num_vacation). |
Source code in duty_teller/services/import_service.py
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 81 82 83 84 | |
duty_teller.services.group_duty_pin_service
Group duty pin: current duty message text, next shift end, pin CRUD. All accept session.
delete_pin(session, chat_id)
Remove the pinned message record for the chat (e.g. when bot leaves).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
chat_id
|
int
|
Telegram chat id. |
required |
Source code in duty_teller/services/group_duty_pin_service.py
107 108 109 110 111 112 113 114 | |
format_duty_message(duty, user, tz_name, lang='en')
Build the text for the pinned duty message.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
duty
|
Duty instance or None. |
required | |
user
|
User instance or None. |
required | |
tz_name
|
str
|
Timezone name for display (e.g. Europe/Moscow). |
required |
lang
|
str
|
Language code for i18n ('ru' or 'en'). |
'en'
|
Returns:
| Type | Description |
|---|---|
str
|
Formatted message string; "No duty" if duty or user is None. |
Source code in duty_teller/services/group_duty_pin_service.py
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 | |
get_all_pin_chat_ids(session)
Return all chat_ids that have a pinned duty message.
Used to restore update jobs on bot startup.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
Returns:
| Type | Description |
|---|---|
list[int]
|
List of chat ids. |
Source code in duty_teller/services/group_duty_pin_service.py
131 132 133 134 135 136 137 138 139 140 141 142 | |
get_duty_message_text(session, tz_name, lang='en')
Get current duty from DB and return formatted message text.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
tz_name
|
str
|
Timezone name for display. |
required |
lang
|
str
|
Language code for i18n. |
'en'
|
Returns:
| Type | Description |
|---|---|
str
|
Formatted duty message or "No duty" if none. |
Source code in duty_teller/services/group_duty_pin_service.py
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | |
get_message_id(session, chat_id)
Return message_id for the pinned duty message in this chat.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
chat_id
|
int
|
Telegram chat id. |
required |
Returns:
| Type | Description |
|---|---|
int | None
|
Message id or None if no pin record. |
Source code in duty_teller/services/group_duty_pin_service.py
117 118 119 120 121 122 123 124 125 126 127 128 | |
get_next_shift_end_utc(session)
Return next shift end as naive UTC datetime for job scheduling.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
Returns:
| Type | Description |
|---|---|
datetime | None
|
Next shift end (naive UTC) or None. |
Source code in duty_teller/services/group_duty_pin_service.py
84 85 86 87 88 89 90 91 92 93 | |
save_pin(session, chat_id, message_id)
Save or update the pinned duty message record for a chat.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
DB session. |
required |
chat_id
|
int
|
Telegram chat id. |
required |
message_id
|
int
|
Message id to store. |
required |
Source code in duty_teller/services/group_duty_pin_service.py
96 97 98 99 100 101 102 103 104 | |
Handlers
duty_teller.handlers
Expose a single register_handlers(app) that registers all handlers.
register_handlers(app)
Register all Telegram handlers (commands, import, group pin, error handler) on the application.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
Application
|
python-telegram-bot Application instance. |
required |
Source code in duty_teller/handlers/__init__.py
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
duty_teller.handlers.commands
Command handlers: /start, /help; /start registers user.
calendar_link(update, context)
async
Handle /calendar_link: send personal ICS URL (private chat only; user must be in allowlist).
Source code in duty_teller/handlers/commands.py
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | |
help_cmd(update, context)
async
Handle /help: send list of commands (admins see import_duty_schedule).
Source code in duty_teller/handlers/commands.py
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | |
set_phone(update, context)
async
Handle /set_phone [number]: set or clear phone (private chat only).
Source code in duty_teller/handlers/commands.py
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 81 82 83 84 85 86 87 88 | |
start(update, context)
async
Handle /start: register user in DB and send greeting.
Source code in duty_teller/handlers/commands.py
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 | |
duty_teller.handlers.import_duty_schedule
Import duty-schedule: /import_duty_schedule (admin only). Two steps: handover time -> JSON file.
handle_duty_schedule_document(update, context)
async
Handle uploaded JSON file: parse duty-schedule and run import.
Source code in duty_teller/handlers/import_duty_schedule.py
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 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | |
handle_handover_time_text(update, context)
async
Handle text message when awaiting handover time (e.g. 09:00 Europe/Moscow).
Source code in duty_teller/handlers/import_duty_schedule.py
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | |
import_duty_schedule_cmd(update, context)
async
Handle /import_duty_schedule: start two-step import (admin only); asks for handover time.
Source code in duty_teller/handlers/import_duty_schedule.py
19 20 21 22 23 24 25 26 27 28 29 30 | |
duty_teller.handlers.group_duty_pin
Pinned duty message in groups: handle bot add/remove, schedule updates at shift end.
my_chat_member_handler(update, context)
async
Handle bot added to or removed from group: send/pin duty message or delete pin record.
Source code in duty_teller/handlers/group_duty_pin.py
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | |
pin_duty_cmd(update, context)
async
Handle /pin_duty: pin the current duty message in the group (reply to bot's message).
Source code in duty_teller/handlers/group_duty_pin.py
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | |
restore_group_pin_jobs(application)
async
Restore scheduled pin-update jobs for all chats that have a pinned message (on startup).
Source code in duty_teller/handlers/group_duty_pin.py
189 190 191 192 193 194 195 196 | |
update_group_pin(context)
async
Job callback: refresh pinned duty message and schedule next update at shift end.
Source code in duty_teller/handlers/group_duty_pin.py
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | |
duty_teller.handlers.errors
Global error handler: log exception and notify user.
error_handler(update, context)
async
Global error handler: log exception and reply with generic message if possible.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
update
|
Update | None
|
Update that caused the error (may be None). |
required |
context
|
DEFAULT_TYPE
|
Callback context. |
required |
Source code in duty_teller/handlers/errors.py
14 15 16 17 18 19 20 21 22 23 24 25 26 27 | |
Importers
duty_teller.importers
Importers for duty data (e.g. duty-schedule JSON).
duty_teller.importers.duty_schedule
Parser for duty-schedule JSON format. No DB access.
DutyScheduleEntry
dataclass
One person's schedule: full_name and three lists of dates by event type.
Source code in duty_teller/importers/duty_schedule.py
13 14 15 16 17 18 19 20 | |
DutyScheduleParseError
Bases: Exception
Invalid or missing fields in duty-schedule JSON.
Source code in duty_teller/importers/duty_schedule.py
32 33 34 35 | |
DutyScheduleResult
dataclass
Parsed duty schedule: start_date, end_date, and per-person entries.
Source code in duty_teller/importers/duty_schedule.py
23 24 25 26 27 28 29 | |
parse_duty_schedule(raw_bytes)
Parse duty-schedule JSON into DutyScheduleResult.
Expects meta.start_date (YYYY-MM-DD) and schedule (array). For each schedule item: name (required), duty string with ';' separator; index i = start_date + i days. Cell values: в/В/б/Б => duty, Н => unavailable, О => vacation; rest ignored.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw_bytes
|
bytes
|
UTF-8 encoded JSON bytes. |
required |
Returns:
| Type | Description |
|---|---|
DutyScheduleResult
|
DutyScheduleResult with start_date, end_date, and entries (per-person dates). |
Raises:
| Type | Description |
|---|---|
DutyScheduleParseError
|
On invalid JSON, missing/invalid meta or schedule, or invalid item fields. |
Source code in duty_teller/importers/duty_schedule.py
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 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | |