Skip to content

Commit c98bc0f

Browse files
authored
Release 1.4.0 (#86)
* Refactor Bakalari timetable calendar coordinator-backed Replace per-entity API client with BakalariCoordinator. Calendar entities read timetable data from the coordinator's shared snapshot, rebuild their event cache on coordinator updates, and request coordinator refreshes instead of performing direct HTTP calls. Move timetable conversion and utility helpers into the calendar module. * Refactor Bakalari integration to separate coordinators - Split original coordinator into three: marks, messages, and timetable - Introduce ChildrenIndex class as a shared source of truth for children - Update async_setup_entry to create and register all three coordinators - Modify calendar entity to use timetable coordinator and cache events from coordinator data - Add messages-specific coordinator with independent update interval and seen-message tracking - Add timetable-specific coordinator with independent longer update interval - Adapt sensors, entities, websocket commands, and services to use the new coordinators - Remove direct API calls from calendar and sensors; data comes from coordinators - Add ChildrenIndex for consistent child handling across coordinators and entities - Update tests to use the new coordinators and children indexing classes * Annotate marks and messages as new; add events and services - Add `is_new` flag to marks and messages with in-memory per-child cache - Fire `bakalari_new_mark` and `bakalari_new_message` events on new items - Introduce services to mark marks/messages as seen and refresh data - Add WebSocket API support for marks, messages, and timetable queries - Document sensors, attributes, API, and example automations in README - Simplify marks coordinator by removing unused legacy code and variables - Update sensors to rely directly on main coordinators without legacy resolvers * Bump version of API * Fix version in const.py * Add service to sign all marks for a child Added service call for frontend to support singnign marks. Added api_call to API for singing marks. * Rename sign_all_marks methods to async_sign_marks for consistency * Refactor Bakalari setup to improve structure and concurrency - Split async_setup_entry into smaller helper functions for devices, services, and WebSocket registration - Run initial coordinator refreshes concurrently using asyncio.gather - Use a single SW_VERSION constant for software version info - Clean up coordinator and entity code to reflect new version handling * Rename sign_all_marks to async_sign_marks and update service handler accordingly * Refresh marks coordinator after signing marks * Bump API version to 0.9.0 * Update README, update CHANGELOG
1 parent 537f7e0 commit c98bc0f

22 files changed

+1414
-724
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
pip install -e .
2424
# HA runtime pro testy (minimální):
2525
pip install homeassistant==2025.9.4
26-
pip install async-bakalari-api==0.7.0
26+
pip install async-bakalari-api==0.9.0
2727
- name: Ruff (lint)
2828
run: ruff check .
2929
- name: Ruff (format)

CHANGELOG.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,34 @@
1-
# # Changelog
1+
## Changelog
2+
3+
## v1.4.0
4+
5+
## ✨ Nové funkce
6+
7+
- **Introduce `confirmed` filed to marks** (#83) @schizza
8+
- zavádí novou funkci pro podpis známek
9+
- nový atribut u známky - `confirmed`, který označuje, zda je zpráva přečtená
10+
11+
## 🧹 Refaktoring / Údržba
12+
13+
- **Bump API version to 0.9.0** (#84) @schizza
14+
- zvednuta verze pro API endpoint na 0.9.0
15+
16+
- **Refactor Bakalari integration to separate coordinators** (#82) (#80) @schizza
17+
- Rozdělení společného koordinátoru na vlasní koordinatory pro každý modul
18+
- seznam dětí je nyní společný pro všechny entity přes `ChildrenIndex`
19+
- Proveden update `async_setup_entry` pro každý koordinator zvlášť
20+
- `Kalendář` nyní používá vlastní koordinator a kešuje data z koordinatoru
21+
- Odstraněno přímé volání API z kalendáře, data se nyní využívají z koordinatoru
22+
- Každý koordinator má nyní vlastní interval aktualizace
23+
- Předělány entity, senzory, websocket a services na nové koordinatory
24+
25+
---
26+
## 📦 Technické
27+
- Verze integrace: `v1.4.0`
28+
- Vyžaduje API verzi `0.9.0+`
29+
- Minimální verze Home Assistant: `2025.9+`
30+
- Předchozí tag: `v1.3.1`
31+
- Autoři přispěli: @schizza
232

333
## v1.3.1
434

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ PYTEST := $(PYTHON) -m pytest
1111

1212
# Verze / cesty
1313
HA_VERSION := 2025.9.4
14-
BAKALARI_VERSION := 0.7.0
14+
BAKALARI_VERSION := 0.9.0
1515
HA_CONFIG := ./config
1616
COMPONENT_PATH := custom_components/bakalari
1717

README.md

Lines changed: 216 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,6 @@ Custom komponenta pro Home Assistant, založená na [async-bakalari-api3](https:
1414
- původní senzor `all_marks` již drží jen metadata pro Lovelace kartu
1515
- obsah metadat a co lze z tohoto senzoru získat viz níže.
1616

17-
Od verze 1.1.0 jsou již senzory migrovány pod `DeviceRegistry`
18-
- nově je každé dítě jako separátní `DeviceRegistry` (zařízení v HUBu) s jednotlivými senzory
19-
- `uid` senzoru se nezměnilo, ale změnil se název senzoru - nyní dědí jméno z `DeviceRegistry`
20-
- nově jsou tedy názvy senzorů takto: `sensor.<device_name>_<sensor_name>`
21-
- kde `<device_name>` je jméno dítěte + škola
22-
- `friendly_name` je složen z `<sensor_name> - <short_name>`, tedy např. `Rozvrh - Jan`
23-
24-
- staré senzory se již neaktualizují a nebudou generovány při odebrání a znovupřidání integrace.
25-
26-
## ⚠️ ***Po aktualizaci na verzi 1.1.0+ je tedy nutné změnit názvy senzorů v kartách v Lovelace***
27-
2817
## Instalace (HACS)
2918

3019
1. V HACS → **Integrations** → menu (⋮) → **Custom repositories**
@@ -44,7 +33,9 @@ Od verze 1.1.0 jsou již senzory migrovány pod `DeviceRegistry`
4433
- tento senzor stahuje rozvrh na aktuální týden +- 7 dní
4534

4635
- Známky
47-
- každý předmět má nyní svůj vlastní senzro
36+
- každý předmět má nyní svůj vlastní senzor
37+
- lze podepisovat známky - buď jednotlivě u každé známky nebo hromadně v záhlaví Lovelace karty\
38+
u nepodepsné známky se zobrazí ikona podpisu, která je proklikávací.
4839
- původní senzor `all_marks` udržuje pouze metadata pro Lovelace kartu
4940
- ze školního serveru se již stahují všechny známky, zrušen limit 30 posledních
5041
- známky jsou agregované per-předmět a per-child
@@ -81,6 +72,135 @@ summary:
8172
total_non_point_marks: "105"
8273
```
8374
75+
## Anotované známky (is_new) a události
76+
77+
- Každá známka je při zpracování anotovaná příznakem `is_new`. Ten je `true`, pokud kombinace (dítě, id známky) ještě nebyla v interní cache integrace.
78+
- Tento příznak používají senzory pro výpočet počtu nových známek a agregace po předmětech.
79+
- Cache „seen“ je in-memory. Po restartu HA se nově načtené známky dočasně považují za nové, dokud je integrace nevyhodnotí a neodpálí události. Pokud potřebuješ trvalé chování přes restarty, je vhodné použít automatizace (viz níže) a/nebo budoucí perzistenci.
80+
81+
### Událost `bakalari_new_mark`
82+
83+
- Při objevení nové známky integrace vyvolá událost `bakalari_new_mark` na Event Busu.
84+
- Payload obsahuje atributy známky dle Bakalářů (např. `id`, `date`, `subject_id`, `subject_abbr`, `subject_name`, `caption`, `theme`, `mark_text`, `is_points`, …).
85+
86+
### Příklad automatizace (oznámení o nové známce)
87+
88+
```yaml
89+
alias: Bakaláři – nová známka (notifikace)
90+
description: Odeslat push notifikaci při nové známce
91+
mode: parallel
92+
trigger:
93+
- platform: event
94+
event_type: bakalari_new_mark
95+
condition: []
96+
action:
97+
- service: notify.mobile_app_telefon
98+
data:
99+
title: "Nová známka – {{ trigger.event.data.subject_abbr or trigger.event.data.subject_name }}"
100+
message: >-
101+
{{ (trigger.event.data.date | as_datetime).strftime('%-d. %-m. %Y') if trigger.event.data.date else '' }}
102+
{{ trigger.event.data.caption or 'Hodnocení' }}:
103+
{{ trigger.event.data.mark_text }}
104+
data:
105+
url: /lovelace/bakalari
106+
```
107+
108+
## Události pro zprávy (Komens)
109+
110+
- Zprávy jsou také anotované příznakem `is_new`. Integrace udržuje in-memory cache již „viděných“ zpráv per dítě.
111+
- Při objevení nové zprávy se odpálí událost `bakalari_new_message`.
112+
113+
Payload:
114+
```yaml
115+
child_key: <kompozitní klíč dítěte>
116+
message: <plný objekt zprávy z Bakalářů>
117+
```
118+
119+
Příklad automatizace (notifikace):
120+
```yaml
121+
alias: Bakaláři – nová zpráva (notifikace)
122+
mode: parallel
123+
trigger:
124+
- platform: event
125+
event_type: bakalari_new_message
126+
action:
127+
- service: notify.mobile_app_telefon
128+
data:
129+
title: "Nová zpráva – {{ trigger.event.data.message.subject or trigger.event.data.message.title }}"
130+
message: >-
131+
{{ (trigger.event.data.message.date | as_datetime).strftime('%-d. %-m. %Y') if trigger.event.data.message.date else '' }}
132+
{{ trigger.event.data.message.preview or trigger.event.data.message.title }}
133+
data:
134+
url: /lovelace/bakalari
135+
```
136+
137+
## Služby
138+
139+
- `bakalari.mark_as_seen`
140+
- Parametry: `mark_id` (povinné), `child_key` (volitelné – pokud není, použije se první dítě).
141+
- Popis: Označí známku jako „viděnou“ a potlačí její opětovné hlášení jako novou.
142+
143+
- `bakalari.refresh`
144+
- Popis: Okamžitě obnoví data známek (jinak běží podle intervalu).
145+
146+
- `bakalari.mark_message_as_seen`
147+
- Parametry: `message_id` (povinné), `child_key` (volitelné).
148+
- Popis: Označí zprávu jako „viděnou“ a potlačí její opětovné hlášení jako novou.
149+
150+
- `bakalari.refresh_messages`
151+
- Popis: Okamžitě obnoví data zpráv.
152+
153+
- `bakalari.refresh_timetable`
154+
- Popis: Okamžitě obnoví data rozvrhu.
155+
156+
Příklady volání:
157+
```yaml
158+
service: bakalari.mark_as_seen
159+
data:
160+
mark_id: "m123"
161+
child_key: "john.doe@school|12345"
162+
```
163+
164+
```yaml
165+
service: bakalari.mark_message_as_seen
166+
data:
167+
message_id: "msg-abc"
168+
```
169+
170+
## WebSocket API
171+
172+
Využij v Dev Tools → WebSocket, nebo z vlastních frontend karet.
173+
174+
- `bakalari/get_marks`
175+
- Payload: `config_entry_id` (string), `child_key` (volitelné), `limit` (volitelné, default 50)
176+
- Výsledek: `{ "items": [ ... ] }` – plochý seznam známek s `is_new`.
177+
178+
- `bakalari/get_messages`
179+
- Payload: `config_entry_id` (string), `child_key` (volitelné), `limit` (volitelné, default 50)
180+
- Výsledek: `{ "items": [ ... ] }` – seznam zpráv s `is_new`.
181+
182+
- `bakalari/get_timetable`
183+
- Payload: `config_entry_id` (string), `child_key` (volitelné), `limit` (volitelné, default 3)
184+
- Výsledek: `{ "items": [ ... ] }` – seznam týdnů rozvrhu (aktuální, +1 týden, -1 týden).
185+
186+
Příklad požadavku/odpovědi:
187+
```json
188+
{ "id": 1, "type": "bakalari/get_marks", "config_entry_id": "<entry_id>", "limit": 25 }
189+
```
190+
```json
191+
{ "id": 1, "type": "result", "success": true, "result": { "items": [ /* ... */ ] } }
192+
```
193+
194+
## Intervaly dotazování
195+
196+
- Známky: klíč `scan_interval` (sekundy), výchozí 900 s. Probíhá s jitterem ±10 % kvůli omezení špiček.
197+
- Zprávy: klíč `scan_interval_messages` (sekundy), výchozí 3600 s. Také s jitterem ±10 %.
198+
- Rozvrh: klíč `scan_interval_timetable` (sekundy), výchozí 21600 s (6 h). Také s jitterem ±10 %.
199+
200+
Poznámky:
201+
- Intervaly se aplikují per koordinátor. Pokud nejsou klíče v options přítomné, použijí se výchozí hodnoty.
202+
- Po ručním volání služeb `refresh*` se naplánuje další cyklus opět podle intervalu.
203+
84204
## Karty pro Lovelace jsou nyní instalovány přes HACS ve vlastím [repozitáři](https://github.com/schizza/bakalari-ha-frontend).
85205

86206
- v HACS přidej repozitář `https://github.com/schizza/bakalari-ha-frontend`
@@ -90,14 +210,97 @@ summary:
90210
- Zprávy `type: custom:bakalari-messages-card`
91211
- více informací o kartách najdete v [repozitáři](https://github.com/schizza/bakalari-ha-frontend)
92212

213+
## Přehled senzorů a atributů
214+
215+
Níže je stručný přehled vytvářených entit a jejich stavů/atributů.
216+
217+
- Nové známky – `sensor.Nové známky - <dítě>`
218+
- Stav: počet nových známek (počítá se podle příznaku is_new u každé známky)
219+
- Atributy:
220+
- `child_key`: identifikátor dítěte
221+
- `recent`: posledních několik známek (každá položka nese alespoň: `id`, `date`, `subject_id`, `subject_abbr`, `subject_name`, `caption`, `theme`, `mark_text` nebo `points_text`, `is_points`, případně `weight/coef/coefficient`, a hlavně `is_new`)
222+
- `total_marks_cached`: kolik známek je právě v cache koordinátoru
223+
224+
- Poslední známka – `sensor.Poslední známka - <dítě>`
225+
- Stav: krátký text „<předmět> <známka>“, např. „M 1“
226+
- Atributy:
227+
- `child_key`: identifikátor dítěte
228+
- `last`: poslední známka (stejná struktura položky jako výše)
229+
230+
- Známky podle předmětu – `sensor.Známky <ABBR> - <dítě>` (dynamicky generované per předmět)
231+
- Stav: počet známek v daném předmětu
232+
- Atributy:
233+
- `child_key`: identifikátor dítěte
234+
- `subject_key`: interní klíč předmětu (id nebo zkratka či jméno)
235+
- `subject`: agregované statistiky pro předmět:
236+
- `count`, `new_count`, `numeric_count`, `non_numeric_count`
237+
- `avg` (aritmetický průměr), `wavg` (vážený průměr)
238+
- `last_text`, `last_date` (poslední známka a datum)
239+
- `recent`: poslední známky pro daný předmět (položky se stejnou strukturou jako výše)
240+
241+
- Pomocný index – `sensor.Všechny známky - <dítě>`
242+
- Stav: počet předmětů pro dané dítě (celkem)
243+
- Atributy:
244+
- `friendly_names`: seznam názvů předmětů
245+
- `mapping_names`: mapování ID → `{ name, abbr }`
246+
- `sensor_map`: mapování `subject_key` → `entity_id` příslušné senzorové entity
247+
- `summary`: souhrnné statistiky ze zdroje (např. `wavg`, `avg`, `subjects`, `total_marks`, `total_point_marks`, `total_non_point_marks`)
248+
249+
- Zprávy – `sensor.Zprávy - <dítě>`
250+
- Stav: počet zpráv v cache
251+
- Atributy:
252+
- `child_key`: identifikátor dítěte
253+
- `messages`: seznam zpráv (každá zpráva je anotovaná `is_new`)
254+
- `total_messages_cached`: počet zpráv v cache
255+
256+
- Rozvrh – `sensor.Rozvrh - <dítě>`
257+
- Stav: počet dostupných týdnů rozvrhu v cache
258+
- Atributy: obsahuje zestručněné informace o rozvrhu dle dostupných dat koordinátoru rozvrhu
259+
260+
- Rozvrh – kalendářová entita `calendar.Rozvrh - <dítě>`
261+
- Zobrazuje jednotlivé hodiny v daném období (aktuální týden, +1 a -1 týden)
262+
- Vlastnosti událostí: `start`, `end`, `summary` (předmět), `description` (učitel, skupiny, téma, změny), `location` (učebna)
263+
264+
Poznámky:
265+
- Každá známka i zpráva je anotovaná příznakem `is_new` (in-memory per dítě). Po restartu HA se nové položky mohou dočasně chovat jako „nové“, dokud proběhne první diff.
266+
- Pro potlačení „novosti“ lze použít služby `bakalari.mark_as_seen` a `bakalari.mark_message_as_seen` (viz výše).
267+
268+
## Datové modely v koordinátorech
269+
270+
Tyto struktury najdeš v `coordinator.data`. Klíče jsou stabilní API pro senzory, frontend karty a automatizace.
271+
272+
- Koordinátor známek
273+
- `subjects_by_child`: mapování `child_key` → `{ <subject_id>: { id, name, abbr }, ... }`
274+
- `marks_by_child`: mapování `child_key` → `list[mark]` – už anotované položky s `is_new`
275+
- `marks_flat_by_child`: mapování `child_key` → `list[mark]` – „surové“ položky (bez `is_new`), používá se pro diff a eventy
276+
- `summary`: mapování `child_key` → souhrnné statistiky (např. `wavg`, `avg`, `subjects`, `total_marks`, `total_point_marks`, `total_non_point_marks`)
277+
- `school_year`: `{ start: ISO date, end_exclusive: ISO date }`
278+
- `last_sync_ok`: boolean
279+
- Položka `mark` typicky obsahuje: `id`, `date`, `subject_id`, `subject_abbr`, `subject_name`, `caption`, `theme`, `mark_text` nebo `points_text`, `is_points`, případně `weight/coef/coefficient`, a v anotované větvi navíc `is_new`.
280+
281+
- Koordinátor zpráv (Komens)
282+
- `messages_by_child`: mapování `child_key` → `list[message]` – každá zpráva má `is_new`
283+
- `last_sync_ok`: boolean
284+
- Položka `message` je slovník dle API (např. `id`/`message_id`, `title`/`subject`, `date`/`created`, `preview`, `from`, `to`, …) + `is_new`.
285+
286+
- Koordinátor rozvrhu
287+
- `timetable_by_child`: mapování `child_key` → `list[week]` – aktuální týden, +1 týden, -1 týden
288+
- `permanent_timetable_by_child`: mapování `child_key` → „permanentní“ rozvrh (pokud škola poskytuje)
289+
- `timetable_window_dates`: mapování `child_key` → `list[ISO date]` – jaké dny byly načteny
290+
- `last_sync_ok`: boolean
291+
292+
Doporučení:
293+
- Pro zobrazování použij `marks_by_child` (již obsahuje `is_new`), pro odvozování novinek v automatizacích se spolehni na události `bakalari_new_mark` a `bakalari_new_message`.
294+
- Per-předmět agregace a další statistiky pro senzory poskytuje helper `aggregate_marks_for_child` a jsou k dispozici v atributech senzorů dle výše uvedeného přehledu.
295+
93296
## Screenshot
94297

95298
![Screenshot](https://raw.githubusercontent.com/schizza/bakalari-ha/refs/heads/main/docs/screenshot.png)
96299

97300
## Požadavky
98301

99302
- Home Assistant `2025.9.4+`
100-
- PyPI: `async-bakalari-api==0.7.0`
303+
- PyPI: `async-bakalari-api==0.9.0`
101304

102305
## Licence
103306

bump-bakalari.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[tool.bumpversion]
2-
current_version = "0.7.0"
2+
current_version = "0.9.0"
33
commit = false
44
tag = false
55

0 commit comments

Comments
 (0)