Preview future presence overrides in coordinator and fit the party board in 1080px
This commit is contained in:
@@ -50,6 +50,7 @@ from .dao_rules import QuestDefinitionsDao
|
||||
from .dao_presence import PresenceOverridesDao, PresenceSchedulesDao
|
||||
from .db import NestQuestDatabase
|
||||
from .presence import (
|
||||
MAX_PREVIEW_SCAN_DAYS,
|
||||
PresenceEngine,
|
||||
PresenceOverride,
|
||||
PresenceSchedule,
|
||||
@@ -167,10 +168,14 @@ class NestQuestCoordinator(DataUpdateCoordinator):
|
||||
schedules[child.id] = PresenceSchedule.decode(
|
||||
child.id, schedule.anchor_date, schedule.pattern
|
||||
)
|
||||
todays = await PresenceOverridesDao(
|
||||
window_end = today + datetime.timedelta(
|
||||
days=MAX_PREVIEW_SCAN_DAYS
|
||||
)
|
||||
window_end_iso = window_end.isoformat()
|
||||
ranged = await PresenceOverridesDao(
|
||||
self.database
|
||||
).list_by_child_and_range(child.id, today_iso, today_iso)
|
||||
if todays:
|
||||
).list_by_child_and_range(child.id, today_iso, window_end_iso)
|
||||
if ranged:
|
||||
# The engine validates MODEL objects, not DAO records;
|
||||
# each record converts losslessly (the schema guarantees
|
||||
# end >= start and a real is_present).
|
||||
@@ -182,7 +187,7 @@ class NestQuestCoordinator(DataUpdateCoordinator):
|
||||
record.is_present,
|
||||
record.note,
|
||||
)
|
||||
for record in todays
|
||||
for record in ranged
|
||||
]
|
||||
engine = PresenceEngine(schedules, overrides)
|
||||
|
||||
|
||||
@@ -603,8 +603,8 @@ const lt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Kt = "polygon(50%
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
padding: 64px 110px 150px;
|
||||
height: 100vh;
|
||||
padding: 48px 110px 150px;
|
||||
overflow: hidden;
|
||||
background: var(--nq-p-parchment);
|
||||
box-shadow: var(--nq-p-vignette);
|
||||
@@ -676,7 +676,7 @@ const lt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Kt = "polygon(50%
|
||||
}
|
||||
|
||||
.kicker {
|
||||
margin: 24px 0 0;
|
||||
margin: 14px 0 0;
|
||||
font-family: var(--nq-p-font-heading);
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
@@ -689,7 +689,7 @@ const lt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Kt = "polygon(50%
|
||||
|
||||
.plates {
|
||||
width: 100%;
|
||||
margin-top: 72px;
|
||||
margin-top: 42px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 56px;
|
||||
@@ -701,9 +701,9 @@ const lt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Kt = "polygon(50%
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 26px;
|
||||
height: 600px;
|
||||
padding: 44px 28px 40px;
|
||||
gap: 20px;
|
||||
height: 560px;
|
||||
padding: 36px 28px 32px;
|
||||
border: 2px solid var(--nq-p-panel-border);
|
||||
border-radius: var(--nq-p-radius-panel);
|
||||
background: var(--nq-p-card-panel);
|
||||
@@ -737,8 +737,8 @@ const lt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Kt = "polygon(50%
|
||||
.crest {
|
||||
position: relative;
|
||||
flex: none;
|
||||
width: 188px;
|
||||
height: 214px;
|
||||
width: 168px;
|
||||
height: 192px;
|
||||
padding: 4px;
|
||||
clip-path: ${$(lt)};
|
||||
background: rgba(255, 255, 255, 0.22);
|
||||
@@ -750,7 +750,7 @@ const lt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Kt = "polygon(50%
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-bottom: 78px;
|
||||
padding-bottom: 70px;
|
||||
clip-path: ${$(lt)};
|
||||
background: var(--nq-p-crest);
|
||||
}
|
||||
@@ -784,7 +784,7 @@ const lt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Kt = "polygon(50%
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 24px;
|
||||
padding: 8px 20px;
|
||||
border-radius: var(--nq-p-radius-pill);
|
||||
background: rgba(22, 120, 60, 0.35);
|
||||
font-family: var(--nq-p-font-heading);
|
||||
@@ -813,7 +813,7 @@ const lt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Kt = "polygon(50%
|
||||
|
||||
.progress-line {
|
||||
font-family: var(--nq-p-font-heading);
|
||||
font-size: 26px;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
color: var(--nq-p-ink-secondary);
|
||||
@@ -836,9 +836,9 @@ const lt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Kt = "polygon(50%
|
||||
}
|
||||
|
||||
.hint {
|
||||
margin: 40px 0 0;
|
||||
margin: 26px 0 0;
|
||||
font-family: var(--nq-p-font-body);
|
||||
font-size: 27px;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
text-align: center;
|
||||
|
||||
@@ -160,8 +160,8 @@ const boardStyles = css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
padding: 64px 110px 150px;
|
||||
height: 100vh;
|
||||
padding: 48px 110px 150px;
|
||||
overflow: hidden;
|
||||
background: var(--nq-p-parchment);
|
||||
box-shadow: var(--nq-p-vignette);
|
||||
@@ -233,7 +233,7 @@ const boardStyles = css`
|
||||
}
|
||||
|
||||
.kicker {
|
||||
margin: 24px 0 0;
|
||||
margin: 14px 0 0;
|
||||
font-family: var(--nq-p-font-heading);
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
@@ -246,7 +246,7 @@ const boardStyles = css`
|
||||
|
||||
.plates {
|
||||
width: 100%;
|
||||
margin-top: 72px;
|
||||
margin-top: 42px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 56px;
|
||||
@@ -258,9 +258,9 @@ const boardStyles = css`
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 26px;
|
||||
height: 600px;
|
||||
padding: 44px 28px 40px;
|
||||
gap: 20px;
|
||||
height: 560px;
|
||||
padding: 36px 28px 32px;
|
||||
border: 2px solid var(--nq-p-panel-border);
|
||||
border-radius: var(--nq-p-radius-panel);
|
||||
background: var(--nq-p-card-panel);
|
||||
@@ -294,8 +294,8 @@ const boardStyles = css`
|
||||
.crest {
|
||||
position: relative;
|
||||
flex: none;
|
||||
width: 188px;
|
||||
height: 214px;
|
||||
width: 168px;
|
||||
height: 192px;
|
||||
padding: 4px;
|
||||
clip-path: ${unsafeCSS(SHIELD_CLIP)};
|
||||
background: rgba(255, 255, 255, 0.22);
|
||||
@@ -307,7 +307,7 @@ const boardStyles = css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-bottom: 78px;
|
||||
padding-bottom: 70px;
|
||||
clip-path: ${unsafeCSS(SHIELD_CLIP)};
|
||||
background: var(--nq-p-crest);
|
||||
}
|
||||
@@ -341,7 +341,7 @@ const boardStyles = css`
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 24px;
|
||||
padding: 8px 20px;
|
||||
border-radius: var(--nq-p-radius-pill);
|
||||
background: rgba(22, 120, 60, 0.35);
|
||||
font-family: var(--nq-p-font-heading);
|
||||
@@ -370,7 +370,7 @@ const boardStyles = css`
|
||||
|
||||
.progress-line {
|
||||
font-family: var(--nq-p-font-heading);
|
||||
font-size: 26px;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
color: var(--nq-p-ink-secondary);
|
||||
@@ -393,9 +393,9 @@ const boardStyles = css`
|
||||
}
|
||||
|
||||
.hint {
|
||||
margin: 40px 0 0;
|
||||
margin: 26px 0 0;
|
||||
font-family: var(--nq-p-font-body);
|
||||
font-size: 27px;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
text-align: center;
|
||||
|
||||
@@ -218,6 +218,37 @@ async def test_coordinator_presence_resolves_schedule_and_override(
|
||||
assert snapshot.children[0].present is True
|
||||
|
||||
|
||||
async def test_coordinator_next_present_honours_future_override(
|
||||
hass, make_entry
|
||||
) -> None:
|
||||
entry = wire_entry_to_registry(make_entry(), hass.registry)
|
||||
assert await async_setup_entry(hass, entry) is True
|
||||
coordinator = entry.runtime_data.coordinator
|
||||
database = entry.runtime_data.database
|
||||
await create_child(database, "Ada")
|
||||
ada = (await list_children(database))[0]
|
||||
today = datetime.date.today()
|
||||
tomorrow = today + datetime.timedelta(days=1)
|
||||
# Scheduled present tomorrow (single-day cycle, weekday set set below
|
||||
# covers every day), but overridden absent tomorrow.
|
||||
await PresenceSchedulesDao(database).upsert_by_child(
|
||||
ada.id, 1, today.isoformat(), "0,1,2,3,4,5,6"
|
||||
)
|
||||
from custom_components.nestquest.dao_presence import (
|
||||
PresenceOverridesDao,
|
||||
)
|
||||
|
||||
await PresenceOverridesDao(database).create(
|
||||
ada.id, tomorrow.isoformat(), tomorrow.isoformat(), False
|
||||
)
|
||||
snapshot = await coordinator._async_update_data()
|
||||
child = snapshot.children[0]
|
||||
assert child.present is True
|
||||
assert child.next_present != tomorrow.isoformat(), (
|
||||
"a future absent override must remove tomorrow from the preview"
|
||||
)
|
||||
|
||||
|
||||
async def test_coordinator_derives_missed_for_past_due_open(
|
||||
hass, make_entry
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user