Files
truenas-stacks/home/minerva-dashboard/app/services/store.py
Fizzlepoof 2db6a23a2c
Some checks failed
secret-guardrails / artifact-secret-scan (push) Has been cancelled
secret-guardrails / gitleaks (push) Has been cancelled
home: add minerva dashboard scaffold
2026-05-25 15:40:26 +00:00

173 lines
6.9 KiB
Python

from __future__ import annotations
import json
from copy import deepcopy
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from uuid import uuid4
DEFAULT_STATE = {
'profile': {
'id': 'manndra',
'display_name': 'Manndra',
'role': 'Primary household user',
'persona': 'Minerva',
'house_style': 'Ravenclaw common room',
'tone': 'Elegant, magical, useful',
'primary_color_story': 'Midnight blue, parchment, and warm bronze',
'notes': 'Seeded as the primary user for the Minerva household dashboard scaffold.',
},
'agenda': [
{
'id': 'agenda-1',
'title': 'Morning check-in',
'time_label': '8:30 AM',
'detail': 'Review the day, any errands, and which household lane needs attention first.',
},
{
'id': 'agenda-2',
'title': 'After-lunch reset',
'time_label': '1:30 PM',
'detail': 'Tidy loose notes, review reminders, and confirm dinner direction.',
},
{
'id': 'agenda-3',
'title': 'Evening wind-down',
'time_label': '8:00 PM',
'detail': 'Wrap tomorrow notes, shortlist meals, and leave one clean next action.',
},
],
'reminders': [
{'id': 'reminder-1', 'title': 'Check school and household messages', 'urgency': 'today'},
{'id': 'reminder-2', 'title': 'Restock one comfort-snack shelf item', 'urgency': 'this week'},
{'id': 'reminder-3', 'title': 'Set aside one graceful task for future Minerva automation', 'urgency': 'someday'},
],
'lists': [
{
'id': 'list-1',
'title': 'Errands',
'items': [
{'label': 'Pick up household basics', 'done': False},
{'label': 'Confirm any school supply gaps', 'done': False},
{'label': 'Check gift / occasion queue', 'done': False},
],
},
{
'id': 'list-2',
'title': 'House reset',
'items': [
{'label': 'Clear one surface', 'done': True},
{'label': 'Run a fast room sweep', 'done': False},
{'label': 'Stage tomorrow launch pad', 'done': False},
],
},
],
'notes': [
{
'id': 'note-1',
'title': 'Minerva welcome',
'body': 'This front page should feel calm, capable, and immediately useful from a phone.',
'category': 'welcome',
'source': 'seed',
'pinned': True,
'created_at': '2026-05-23T09:00:00Z',
},
{
'id': 'note-2',
'title': 'Household rhythm',
'body': 'Keep the wording practical: what matters now, what can wait, and what would make the day easier.',
'category': 'guidance',
'source': 'seed',
'pinned': False,
'created_at': '2026-05-23T10:15:00Z',
},
],
'links': [
{'id': 'link-1', 'label': 'Calendar', 'url': '#', 'note': 'Placeholder until live integration is chosen.'},
{'id': 'link-2', 'label': 'School hub', 'url': '#', 'note': 'Quick route for classes, due dates, and messages.'},
{'id': 'link-3', 'label': 'Household notes', 'url': '#', 'note': 'Pin common references in one predictable place.'},
],
'meal_ideas': [
{'id': 'meal-1', 'title': 'Easy comfort dinner', 'detail': 'One low-friction weeknight fallback with leftovers.'},
{'id': 'meal-2', 'title': 'Snack board night', 'detail': 'Small-prep option for a busier day.'},
{'id': 'meal-3', 'title': 'Tomorrow prep idea', 'detail': 'Something that makes the next day easier.'},
],
'shortcuts': [
{'id': 'shortcut-1', 'label': 'Quick note', 'hint': 'Capture the thing before it evaporates.'},
{'id': 'shortcut-2', 'label': 'Meal spark', 'hint': 'Write one dinner idea without overthinking it.'},
{'id': 'shortcut-3', 'label': 'Errand add', 'hint': 'Drop a list item while already on the move.'},
],
'quick_actions': [
{'id': 'action-1', 'label': 'Capture note', 'detail': 'Fast jot for reminders, ideas, and school fragments.'},
{'id': 'action-2', 'label': 'Review day', 'detail': 'One-tap habit: scan agenda, reminders, and lists.'},
{'id': 'action-3', 'label': 'Choose dinner lane', 'detail': 'Decide between comfort, fast, or leftovers.'},
],
}
class JSONStore:
def __init__(self, state_dir: Path) -> None:
self.state_dir = Path(state_dir)
self.state_path = self.state_dir / 'state.json'
self.state_dir.mkdir(parents=True, exist_ok=True)
if not self.state_path.exists():
self._write(deepcopy(DEFAULT_STATE))
def _read(self) -> dict[str, Any]:
return json.loads(self.state_path.read_text())
def _write(self, payload: dict[str, Any]) -> None:
self.state_path.write_text(json.dumps(payload, indent=2, sort_keys=True))
def dashboard_summary(self) -> dict[str, Any]:
state = self._read()
notes = self._sorted_notes(state['notes'])
return {
'profile': state['profile'],
'counts': {
'agenda': len(state['agenda']),
'reminders': len(state['reminders']),
'lists': len(state['lists']),
'notes': len(notes),
'links': len(state['links']),
'meal_ideas': len(state['meal_ideas']),
'shortcuts': len(state['shortcuts']),
'quick_actions': len(state['quick_actions']),
},
'agenda': state['agenda'],
'reminders': state['reminders'],
'lists': state['lists'],
'notes': notes,
'links': state['links'],
'meal_ideas': state['meal_ideas'],
'shortcuts': state['shortcuts'],
'quick_actions': state['quick_actions'],
}
def add_quick_note(self, payload: dict[str, Any]) -> dict[str, Any]:
state = self._read()
saved = {
'id': uuid4().hex,
'title': payload['title'].strip(),
'body': payload['body'].strip(),
'category': payload.get('category', 'note').strip() or 'note',
'source': payload.get('source', 'quick-add').strip() or 'quick-add',
'pinned': bool(payload.get('pinned', False)),
'created_at': datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace('+00:00', 'Z'),
}
state['notes'].append(saved)
self._write(state)
return saved
@staticmethod
def _sorted_notes(notes: list[dict[str, Any]]) -> list[dict[str, Any]]:
return sorted(
notes,
key=lambda item: (
not item.get('pinned', False),
item.get('created_at', ''),
),
reverse=False,
)[::-1]