feat(pwa): optimize Mila app icons with safe-zone padding to prevent Android/iOS zoom crop

This commit is contained in:
Moritz Graf
2026-10-12 08:25:27 +02:00
parent f5b5deaae7
commit 191c8e9143
11 changed files with 316 additions and 3 deletions
+84 -2
View File
@@ -30,6 +30,10 @@ from zoneinfo import ZoneInfo
import duckdb
SCRIPT_DIR = Path(__file__).resolve().parent
if str(SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(SCRIPT_DIR))
logger = logging.getLogger("datastore")
def generate_ulid() -> str:
@@ -225,6 +229,34 @@ class DatastoreEngine:
restingHeartRate INTEGER,
raw JSON
);
CREATE OR REPLACE VIEW v_growth AS
SELECT
id,
subject,
actor,
start,
start AT TIME ZONE 'Europe/Berlin' AS local_time,
CAST(start AT TIME ZONE 'Europe/Berlin' AS DATE) AS log_date,
CAST(json_extract(payload, '$.weight_kg') AS DOUBLE) AS weight_kg,
CAST(json_extract(payload, '$.height_cm') AS DOUBLE) AS height_cm,
CAST(json_extract(payload, '$.head_circ_cm') AS DOUBLE) AS head_circ_cm,
note,
created_at
FROM events
WHERE kind = 'growth.measurement' AND deleted_at IS NULL;
CREATE OR REPLACE VIEW v_growth_daily AS
SELECT
subject,
log_date,
ROUND(AVG(weight_kg), 3) AS weight_kg,
ROUND(AVG(height_cm), 1) AS height_cm,
ROUND(AVG(head_circ_cm), 1) AS head_circ_cm,
COUNT(*) AS measurement_count
FROM v_growth
GROUP BY subject, log_date
ORDER BY log_date DESC;
""")
def _load_all_data(self):
@@ -387,6 +419,13 @@ class DatastoreEngine:
elif kind.startswith("sleep."):
if p1.get("type") == p2.get("type"):
return True
elif kind.startswith("growth."):
w1, w2 = p1.get("weight_kg"), p2.get("weight_kg")
h1, h2 = p1.get("height_cm"), p2.get("height_cm")
match_w = (w1 is not None and w2 is not None and abs(float(w1) - float(w2)) < 0.001)
match_h = (h1 is not None and h2 is not None and abs(float(h1) - float(h2)) < 0.001)
if match_w or match_h:
return True
return False
def _is_duplicate_event(
@@ -870,6 +909,11 @@ class DatastoreEngine:
if fee:
res["feeding"] = fee[0]
# 6. Growth latest measurement
grw = self.query(f"SELECT * FROM v_growth WHERE subject = '{subject}' ORDER BY start DESC LIMIT 1")
if grw:
res["growth_latest"] = grw[0]
return res
def find_places_near(
@@ -952,6 +996,44 @@ class DatastoreEngine:
"bottle_count": daily[0],
"total_consumed_ml": daily[1],
}
elif kind.startswith("growth"):
row = self.con.execute(
"SELECT weight_kg, height_cm, head_circ_cm FROM v_growth WHERE subject = ? AND log_date = ? ORDER BY start DESC LIMIT 1",
[subject, date_str],
).fetchone()
if row:
w_kg, h_cm, hc_cm = row[0], row[1], row[2]
p_row = self.con.execute("SELECT birthdate FROM people WHERE id = ?", [subject]).fetchone()
bdate = p_row[0] if p_row and p_row[0] else datetime.date(2026, 3, 14)
if isinstance(bdate, str):
bdate = datetime.date.fromisoformat(bdate)
age_days = (target_date - bdate).days
age_months = max(0.0, age_days / 30.4375)
try:
from who_growth import format_growth_feedback, calculate_percentile
fb_text = format_growth_feedback(w_kg, h_cm, age_months, date_str)
fb_res: Dict[str, Any] = {
"day_total_formatted": fb_text,
"age_months": round(age_months, 2),
}
if w_kg is not None:
pw = calculate_percentile("weight", w_kg, age_months)
fb_res["weight_kg"] = w_kg
fb_res["weight_percentile"] = pw["percentile"]
fb_res["weight_percentile_str"] = pw["percentile_str"]
if h_cm is not None:
ph = calculate_percentile("height", h_cm, age_months)
fb_res["height_cm"] = h_cm
fb_res["height_percentile"] = ph["percentile"]
fb_res["height_percentile_str"] = ph["percentile_str"]
return fb_res
except Exception as e:
logger.warning(f"Error computing growth feedback: {e}")
return {
"day_total_formatted": f"Wachstum ({date_str}): Gewicht {w_kg} kg, Größe {h_cm} cm",
"weight_kg": w_kg,
"height_cm": h_cm,
}
return {}
# --------------------------------------------------------------------------
@@ -1013,11 +1095,11 @@ def run_mcp_server():
TOOLS = [
{
"name": "datastore_add_event",
"description": "Log an event into the structured datastore (e.g. nutrition.intake, sleep.session, feeding.bottle). Computes all macros and totals deterministically.",
"description": "Log an event into the structured datastore (e.g. nutrition.intake, sleep.session, feeding.bottle, growth.measurement). Computes all macros, totals, and WHO percentiles deterministically.",
"inputSchema": {
"type": "object",
"properties": {
"kind": {"type": "string", "description": "e.g. 'nutrition.intake', 'sleep.session', 'feeding.bottle'"},
"kind": {"type": "string", "description": "e.g. 'nutrition.intake', 'sleep.session', 'feeding.bottle', 'growth.measurement'"},
"subject": {"type": "string", "description": "'moritz', 'amelie', or 'mila'"},
"actor": {"type": "string", "description": "'agent:koch', 'agent:hebamme', 'moritz', 'amelie'"},
"payload": {"type": "object", "description": "Detailed payload matching the kind schema"},