Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions addon-edge/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 0.0.0-edge.26

- Let people with task configuration permission set optional base points when creating a task.
- Keep the household default for title-only creation and accept localized decimal input.
- Explain how to correct a fractional amount when the current allocation requires whole base points.

## 0.0.0-edge.21

- Use singular units when reviewing an intake of exactly one item in English and Spanish.
Expand Down
6 changes: 4 additions & 2 deletions addon-edge/DOCS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Installation and operation

Edge 0.0.0-edge.23 packages the immutable runtime declared in
[artifact.json](artifact.json). Home Assistant can read the same due-today and
Edge 0.0.0-edge.26 packages the immutable runtime declared in
[artifact.json](artifact.json). People with task configuration permission can set
optional base points when creating a task. Leaving the field empty uses the
household default. Home Assistant can read the same due-today and
overdue task counts shown on Overview through a dedicated installation grant.
Enable the [native deadline sensors](../docs/tasks/ha-deadline-sensors.md) using
the optional `ha_deadline_digest` setting. The calendar uses its own
Expand Down
8 changes: 4 additions & 4 deletions addon-edge/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# The Supervisor builds this folder. Fetch only the reviewed immutable runtime and UI.
FROM ghcr.io/astral-sh/uv:0.11.29@sha256:eb2843a1e56fd9e30c7276ce1a52cba86e64c7b385f5e3279a0e08e02dd058fc AS uv
FROM python:3.14.6-slim-trixie@sha256:7bec7ddcddeff7975d6ba9b4be7dd6f6b2f55e7491539145e2978f7f97ce9144 AS source
ARG DOMUS_SOURCE_COMMIT=b72b5e40c0c552ae597ce77c95447c12a8a40cc4
ARG DOMUS_SOURCE_SHA256=52bb0b1a109e4cd51a321168ffdefd7d1d56eeda2910e24df28902c0b2145b75
ARG DOMUS_SOURCE_COMMIT=691496c299ff371763e445acb9d36b9623a81404
ARG DOMUS_SOURCE_SHA256=535c0b95a2db96fe3ebc927596f4e74f6eaadb7b641f2a674c23e672489f0d3b
ADD https://codeload.github.com/vypdev/domus/tar.gz/${DOMUS_SOURCE_COMMIT} /tmp/source.tar.gz
RUN printf '%s /tmp/source.tar.gz\n' "$DOMUS_SOURCE_SHA256" | sha256sum --check - \
&& mkdir /app \
Expand All @@ -24,9 +24,9 @@ RUN uv sync --frozen --no-dev --package domus-server --no-editable
RUN .venv/bin/python -c "from pathlib import Path; import domus; from domus.entrypoints.ui_bundle import load_bundle; assert load_bundle(Path(domus.__file__).parent / 'ui') is not None"

FROM python:3.14.6-slim-trixie@sha256:7bec7ddcddeff7975d6ba9b4be7dd6f6b2f55e7491539145e2978f7f97ce9144
ARG BUILD_VERSION=0.0.0-edge.23
ARG BUILD_VERSION=0.0.0-edge.26
ARG BUILD_ARCH
ARG DOMUS_SOURCE_COMMIT=b72b5e40c0c552ae597ce77c95447c12a8a40cc4
ARG DOMUS_SOURCE_COMMIT=691496c299ff371763e445acb9d36b9623a81404
LABEL io.hass.type="app" \
io.hass.version="${BUILD_VERSION}" \
io.hass.arch="${BUILD_ARCH}" \
Expand Down
3 changes: 2 additions & 1 deletion addon-edge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
Development preview of Domus installed as a Home Assistant app. Open it through
HA's Open Web UI using your existing Home Assistant session.

This preview adds optional Home Assistant sensors for the same due-today and
This preview lets people with task configuration permission set optional base
points when creating a task. It also includes Home Assistant sensors for the same due-today and
overdue task counts shown on Overview.

This candidate uses SQLite schema 31. Keep a
Expand Down
32 changes: 16 additions & 16 deletions addon-edge/artifact.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"schemaVersion": 1,
"version": "0.0.0-edge.23",
"sourceCommit": "b72b5e40c0c552ae597ce77c95447c12a8a40cc4",
"sourceArchiveSHA256": "52bb0b1a109e4cd51a321168ffdefd7d1d56eeda2910e24df28902c0b2145b75",
"version": "0.0.0-edge.26",
"sourceCommit": "691496c299ff371763e445acb9d36b9623a81404",
"sourceArchiveSHA256": "535c0b95a2db96fe3ebc927596f4e74f6eaadb7b641f2a674c23e672489f0d3b",
"storageSchema": 31,
"wheelSHA256": "3933b2d56ca64d8b5a23462cd53950e74466a0fa05d9a295b10dadc3953aa98f",
"wheelSHA256": "5bb060c69c0b2caf9b9f8d99ecff0b56e44a3096f6339582816f5dd52c5bf089",
"pythonFiles": {
"domus/__init__.py": "7182c88b92234b4328f94497f4e70222d4390d8093e4b62893dfa78ac5530ac6",
"domus/application/__init__.py": "1e8c0c87bf2a8ca947112543070583c0cc55d9d40b56eab0d86ed59a4ec1e82c",
Expand Down Expand Up @@ -42,13 +42,13 @@
"domus/application/member_profile_ports.py": "0e041d1923997b6c4894f185cfaad3912a396703373ece9a6e90a829e9aafc39",
"domus/application/member_source_ports.py": "619b1e6eff75ea2ed84b41f268959a99ffa864f6c78bc8d096b52aa5d57d38dd",
"domus/application/point_allocation_ports.py": "9ecbf2d76d7c97df71ff4872e0e66e86bc4fcc6f279849e20adfad7aecc47fb6",
"domus/application/point_allocations.py": "20199fb0d877de279a6b95418fef88cf8983bd5c4437fb67fed67cf178932517",
"domus/application/point_allocations.py": "140f86b2689d03ff41df01fc59b48ad17456811424cbc742522d7619362d3292",
"domus/application/points.py": "a2a660198151d5b65a62b57cb90b35a82a28f38a9a3b7b214b49d6cf6cf0294e",
"domus/application/points_ports.py": "8392b778007d4c55bc55498d38c959d6dc1d4eeaa1dc501cf4d4928ce09f4ac0",
"domus/application/policy_commands.py": "bc588b5b5feb72fd5cb57d7b4a10ef5a05ae48da5ec042fd569142ce50fedeb6",
"domus/application/ports.py": "9a7d2e39c07896faf6799789971174687a7290035ac6283af2f8b699a2ed45d0",
"domus/application/recovery_commands.py": "2b8c198cde97f0213ec1612ea32bce5e00637aab99862fe08b59418d3c64af49",
"domus/application/recurrence_commands.py": "eb2544947770e456c780b3e30026ff63dd8a7f821344429f85a619e34e319a58",
"domus/application/recurrence_commands.py": "4d91c498ced40c335b8a081719a33dde97ab081a050ab352c3f1fac83e6f1447",
"domus/application/recurrence_ports.py": "651d86417a20810c198aba0cd48129de6396db185278c01836366575027d6c24",
"domus/application/recurrence_review.py": "c4d2a60551f774936662cb9244d227ea52a71a5e48fff0eadfbea4d1cc8b0e8b",
"domus/application/recurrence_review_target.py": "e3c25fd2f9462769817acf739742117c1fadf95927d4efc3360c2e9a168e2dc2",
Expand All @@ -57,7 +57,7 @@
"domus/application/relative_lifecycle.py": "e52a3077da1dff9dd7577dd9847579e0f4c346632798c2081b1d043f8a291b69",
"domus/application/relative_materialization.py": "1271838d37bbe5e64cc940189d3168b18a6ca7f9ad4a64ff810e61f42f61bc55",
"domus/application/relative_recurrence.py": "f0d595e9cbf2022977c7940c3b4fc50dd6f242b3dfa615415099f639fa11394f",
"domus/application/relative_recurrence_commands.py": "3ecf7c819a7ec79c43fd57ff5557c5aef6bd7cb572d920a5248c451d9aad3030",
"domus/application/relative_recurrence_commands.py": "159d12b30f9aa02ca7374fa03d41f39daeb7a86243c4c669ec2ef029a2df3aab",
"domus/application/relative_state_review.py": "da62fe52f0de100fc7263c1d17a54b3183c996a61b3c357dcde90a505fdff30a",
"domus/application/reschedule_review.py": "5d36a7ba163bb7b31e5fc3c90cb6ffff69a8792bdaa736a1929e2be797e5539b",
"domus/application/restoration.py": "32a301e9d733f453526dbba70dbe99c036d6485a4c9bc8483d4f52456534a6eb",
Expand All @@ -73,8 +73,8 @@
"domus/application/snapshots.py": "c4f0862f590d222711018de3b23a7155f5702375ce6ece7dd0e2170c86a22ca4",
"domus/application/task_authorization.py": "abe17920d3131a5e1e27f9e2eff95ba152cd73463ec6cd9deca619c9009a9610",
"domus/application/task_category_commands.py": "24544f80b3a8b9d0b42757a2df8d03b706337d5594bdcfcfd92128830f2ab36e",
"domus/application/task_commands.py": "ebcf93308cf7e099e87b204a736d64ea937b1c49682aec348eabf71210e79c7a",
"domus/application/task_creation.py": "a293bff1581f36439b4ebfd91dfc1d6c4aa7bf130a309bf464f0e81eed5cd8cd",
"domus/application/task_commands.py": "70c9ea6bee4045853e1553073892b0c5cb084409add9ee5ab21e633d474d60d9",
"domus/application/task_creation.py": "9f1344b45e705b21f811c770b2c58f61058772465618fde03f8fa163ed1399b5",
"domus/application/task_events.py": "8586629b5f0322e45e299d15a8c21a2a834343d86feea0278d70e9d15c491d6a",
"domus/application/task_facts.py": "a6ba841a719c8b8b4f4556d0381cccb52f40365f6e3539c8aca7bdd1c13cb935",
"domus/application/task_material_commands.py": "bc0715ce0ed15a4807b773fda634945037bdc877ccc8417df77e6a34f1125ba6",
Expand All @@ -83,7 +83,7 @@
"domus/application/task_material_views.py": "1f4e1b52c17064b73e3c6502b38fc578a7589d68044279312c783b656e5aa5bf",
"domus/application/task_materials.py": "caa132a7b16fca293770deba3da149cf9bda2e24d4719abe9de39470d686698e",
"domus/application/task_ports.py": "165f04fc639310da0b742c8c2d58b2eadd459142720fdc138df384f34e53e35a",
"domus/application/task_queries.py": "b9b320d080dcb699266cc6c33d978f199e1ecc0ca1ca9a70c02516b89ffc54a9",
"domus/application/task_queries.py": "a2ac362fd7b62cd83518ff7c8d8889d1e0d16ec50d9c02a8b6c78774902a69c8",
"domus/application/task_requests.py": "2e9ead8f5458d45335ec55bad50f5f7e12b42ee9b00ff54634f3ff681b320897",
"domus/application/task_reviews.py": "dcc288cda6cdcf5073bad4f24133b9d3496c0b0cf1272e8328e6df38129f3328",
"domus/composition/__init__.py": "a902d8faf7dd19da6829fb730d81e32d34ec38ee6cfafd676966a95d0ad31366",
Expand All @@ -109,7 +109,7 @@
"domus/domain/member_profiles.py": "2790ca55a7b37a390d006165a44520085e1ff513783b9cf1a7b681435b8ae4d2",
"domus/domain/member_sources.py": "b5e9d19d501d3d5dd5caf5b4ce945d4c3ff71b1967e92bd8933f26661a416c01",
"domus/domain/nominal_recurrence.py": "2c3678f220a311b8b08e6cad0d146a1a68135b5fd2a5c0d8ab9ed809fe47d8c1",
"domus/domain/point_allocation.py": "1c27b3dbb262093d5e4b37dd35ea3b824d7b0c817cf3ea243c4135d25901e433",
"domus/domain/point_allocation.py": "69768d52023194bad248cee0635532b9be1b5f81328cc9e1d8cbde3a709b4c81",
"domus/domain/points.py": "e6a8fa845920f28270f1d7c0ed2f81542b60573fb1be95c4e16e152e4d531413",
"domus/domain/recovery.py": "0bbe731961d4481e8002382ab0c9948fd415534b0cbbc03bdf0a0e35676f0cb0",
"domus/domain/recurrence_frontier.py": "d0b1150e2bdbe855413c353e11222a649d3dcd2408e4eba18fcea09a3f4d5ad3",
Expand Down Expand Up @@ -161,9 +161,9 @@
"domus/entrypoints/recurrence_dto.py": "094e6f64e1f963bfb053e830b214eed1189258e4f0eb8ff15ddba1f059649a1a",
"domus/entrypoints/recurrence_http.py": "bb60e6fad11b09efa7fe97924451d85919fecce9df105e06a1999fc82bd2e1cc",
"domus/entrypoints/reward_http.py": "503e46ccc8b26758799a229b75939085e3295bdfcdbf2cb9d137db95e3644737",
"domus/entrypoints/schedule_creation_http.py": "f5bc369e5135229e9eb62ad1ac1046c3fb2d0c9c53e6acad6bd61631c2a7042e",
"domus/entrypoints/schedule_creation_http.py": "2a3c3ea682f978eb2767dc8808a218c312861f31079cc2c76dbc3847ddec3197",
"domus/entrypoints/task_category_http.py": "e5443015f5e5cb2684eb6807e0a3bac5116703b5611508281afd7d1addee9672",
"domus/entrypoints/task_dto.py": "df12654a0917a06742ea4605f0aca552d339df610ecb0f7467f69aa89b5a7c9d",
"domus/entrypoints/task_dto.py": "10657d1df0180bf05e71d1f38ac3214d2cea34c8822f50ea25ab331b9c014049",
"domus/entrypoints/task_http.py": "5189ade4f7ab546ba84d0b88a82b02bf7e53cc436bf312920ed6839761aa0810",
"domus/entrypoints/task_response.py": "d2f4c0795aee429fc318b32495601031c559fa38af1d7da63b4735f7e6eb5d13",
"domus/entrypoints/ui_bundle.py": "beae3d4a7a57375ee195cca81efccdbc899d9db4286029d53eabaf30de6e14f7",
Expand Down Expand Up @@ -226,9 +226,9 @@
"domus/infrastructure/task_materials.py": "d5d2934e36f290c7bf44b473392f014eab8749f1f3b3d26a3402079e65d2818f"
},
"uiFiles": {
".vite/manifest.json": "d3e5d88e254b44f115434200d7c06e3786503ddd3dcc54159b12cfa478a0e8d3",
".vite/manifest.json": "734a1f5ba77da03257115ade8cb5f680b19febd79c853f95a3fa51993e0f2cdb",
"assets/index-CjJnHZzV.css": "0f2c8c2e31576c0b64fe60373150f09390dafb896df6c07777b2a220ad430e48",
"assets/index-DOfU-Xei.js": "58c0a0583f61ddb56f7990abdb08988801cb12ab8cd261424507f6a1d6489a84",
"index.html": "640506dad66475ec8db78b827e96c250a8ce3fbacbc4c2f77aa1173266c51f7d"
"assets/index-CrrA8YHS.js": "c32342fbb48c4d5e7266a3a6272e77d6a4479290440c4e801887750470b99bb2",
"index.html": "1198c1ad6b3475ec0c03041f145522f4454e7782aec020a2779204a4bea2f399"
}
}
2 changes: 1 addition & 1 deletion addon-edge/config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Domus Edge
version: "0.0.0-edge.23"
version: "0.0.0-edge.26"
slug: domus_edge
description: Development preview of the local Domus household application
url: https://github.com/vypdev/domus
Expand Down
15 changes: 10 additions & 5 deletions backend/src/domus/application/point_allocations.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,22 @@
ALLOCATION_ACTION = "points.allocation.update"


def household_allocation(unit: UnitOfWork, home: str, base: int) -> tuple[AllocationPolicy, int]:
default = unit.point_allocations.setting(home, None)
if default is None:
return AllocationPolicy(precision=2 if base % 100 else 0), 0
assert default.policy is not None
return default.policy, default.revision


def effective_allocation(
unit: UnitOfWork, home: str, definition_id: str, base: int
) -> tuple[AllocationPolicy, int, str | None]:
override = unit.point_allocations.setting(home, definition_id)
if override is not None and override.policy is not None:
return override.policy, override.revision, definition_id
default = unit.point_allocations.setting(home, None)
if default is None:
return AllocationPolicy(precision=2 if base % 100 else 0), 0, None
assert default.policy is not None
return default.policy, default.revision, None
policy, revision = household_allocation(unit, home, base)
return policy, revision, None


def resolve_allocation(
Expand Down
7 changes: 4 additions & 3 deletions backend/src/domus/application/recurrence_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from domus.application.ports import Clock, Identifiers, Transactions, UnitOfWork
from domus.application.schedule_creation_policy import require_schedule_creation_policy
from domus.application.task_authorization import TaskAccess, TaskCommandAuthorization
from domus.application.task_creation import TaskCreation
from domus.application.task_creation import TaskCreation, task_creation_result
from domus.application.task_material_ports import TaskInventory
from domus.application.task_requests import CreateTask
from domus.domain.errors import IntegrationUnavailable, InvalidInput
Expand Down Expand Up @@ -114,7 +114,7 @@ def apply(unit: UnitOfWork) -> Change:
)
unit.recurrence.save_schedule(schedule, 0)
unit.recurrence.save_progress(progress, 0)
result = JsonDocument.from_value(
result = task_creation_result(
{
"schedule_id": schedule.schedule_id,
"definition_id": item.definition_id,
Expand All @@ -123,7 +123,8 @@ def apply(unit: UnitOfWork) -> Change:
"state": schedule.state.value,
"processing": "pending",
"first_occurrence_id": None,
}
},
item,
)
detail = JsonDocument.from_value(
{
Expand Down
7 changes: 4 additions & 3 deletions backend/src/domus/application/relative_recurrence_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from domus.application.recurrence_ports import RecurrenceTimes
from domus.application.schedule_creation_policy import require_schedule_creation_policy
from domus.application.task_authorization import TaskAccess, TaskCommandAuthorization
from domus.application.task_creation import TaskCreation
from domus.application.task_creation import TaskCreation, task_creation_result
from domus.application.task_material_ports import TaskInventory
from domus.application.task_requests import CreateTask
from domus.domain.errors import IntegrationUnavailable, InvalidInput
Expand Down Expand Up @@ -136,7 +136,7 @@ def apply(unit: UnitOfWork) -> Change:
)
unit.recurrence.save_relative_schedule(schedule, 0)
unit.recurrence.save_relative_progress(progress, 0)
result = JsonDocument.from_value(
result = task_creation_result(
{
"schedule_id": schedule.schedule_id,
"definition_id": item.definition_id,
Expand All @@ -145,7 +145,8 @@ def apply(unit: UnitOfWork) -> Change:
"state": schedule.state.value,
"processing": "pending",
"first_occurrence_id": None,
}
},
item,
)
detail = JsonDocument.from_value(
{
Expand Down
6 changes: 4 additions & 2 deletions backend/src/domus/application/task_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
TaskCommandAuthorization,
)
from domus.application.task_category_commands import require_active_category
from domus.application.task_creation import TaskCreation
from domus.application.task_creation import TaskCreation, task_creation_result
from domus.application.task_facts import (
current_cycle,
definition_result,
Expand Down Expand Up @@ -294,7 +294,9 @@ def apply(unit: UnitOfWork, access: TaskAccess) -> Change:
)
work = item.new_occurrence(self.identifiers.new(), item.created_at)
unit.tasks.save_occurrence(work, 0)
return _change("task.created", request, _result(work))
return _change(
"task.created", request, task_creation_result(_result(work).decode(), item)
)

return self._execute(
key,
Expand Down
14 changes: 14 additions & 0 deletions backend/src/domus/application/task_creation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,27 @@
from collections.abc import Callable
from dataclasses import dataclass

from domus.application.point_allocations import household_allocation
from domus.application.ports import Clock, Identifiers, UnitOfWork
from domus.application.task_authorization import TaskAccess, TaskMaterialAccess
from domus.application.task_category_commands import require_active_category
from domus.application.task_material_commands import TaskMaterialCommands
from domus.application.task_material_ports import MissingTaskInventory, TaskInventory
from domus.application.task_requests import CreateTask
from domus.domain.errors import IntegrationUnavailable
from domus.domain.storage import JsonDocument, JsonObject
from domus.domain.task_values import TaskRules
from domus.domain.tasks import HouseholdTaskPolicy, TaskDefinition


def task_creation_result(result: JsonObject, definition: TaskDefinition) -> JsonDocument:
"""Preserve the explicit base amount in the original creation receipt."""
amount = definition.overrides.point_value_minor
return JsonDocument.from_value(
result if amount is None else {**result, "point_value_minor": amount}
)


@dataclass
class TaskCreation:
clock: Clock
Expand Down Expand Up @@ -42,6 +52,10 @@ def materials(
def definition(
self, unit: UnitOfWork, access: TaskAccess, household_id: str, request: CreateTask
) -> TaskDefinition:
amount = request.overrides.point_value_minor
if amount is not None:
allocation, _ = household_allocation(unit, household_id, amount)
allocation.require_representable_base(amount)
category = (
None
if request.content.category_id is None
Expand Down
5 changes: 4 additions & 1 deletion backend/src/domus/application/task_queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class TaskWorkspaceView:
can_record_for_others: bool
can_manage_points: bool
schedule_policy: TimePolicy | None = None
can_configure: bool = False


@dataclass(frozen=True, slots=True)
Expand Down Expand Up @@ -731,12 +732,13 @@ def workspace(self) -> TaskWorkspaceView:
unit, self.household_id, Permission.TASKS_MANAGE
)
)
catalog = category_catalog(unit, TaskAccess(self.human), self.household_id)
return TaskWorkspaceView(
self.household_id,
actor.member.member_id,
can_create,
can_review,
category_catalog(unit, TaskAccess(self.human), self.household_id),
catalog,
_possible(
lambda: TaskAccess(self.human).require(
unit, self.household_id, Permission.TASKS_RECORD_FOR_OTHERS
Expand All @@ -750,6 +752,7 @@ def workspace(self) -> TaskWorkspaceView:
schedule_creation_policy(unit, self.household_id, self.schedule_data_identity)
if can_create and self.schedule_data_identity is not None
else None,
can_configure=catalog.can_manage,
)

def _operation_access(self, unit: UnitOfWork) -> None:
Expand Down
Loading
Loading