A clean weekly family planner with custom child names & school classes (e.g. Ann: 1A, Berit: 6B, Matthew: 10C) and AI-powered extraction of school week schedules (PDF/images) and activities.
-
Weekly Family Dashboard (Web):
- 7-day responsive grid (Monday through Sunday).
- Categorized by 🎒 Pack & Bring (gym kit, swimming gear, outdoor trip items), 📅 Activities (trainings, matches, appointments), 📝 Homework & Tasks, and 📌 Notes.
- "Pack for Tomorrow" header banner: Displays what needs to be packed into backpacks for the next day.
- Filter by family member or child (with school class indicators).
- Manage kids and family members (add, rename, update school classes).
- Add, edit, check off, and delete items directly.
- Document archive: every uploaded plan is stored on disk, auto-tagged with the child's name and week number — browse, view, re-tag and delete at
/documents. - Standing school schedules: one permanent timetable per child (e.g. the full school-day layout), kept separate from the weekly plan — upload, replace, view and delete at
/schedules.
-
Ingestion Channels:
- Apple Share Sheet (Shortcut): Select a schedule PDF in Mail, Safari, or Files on iPhone/iPad/Mac and tap "Share to Week Planner".
- Email Forwarding (IMAP Bot): Forward any school email with PDF attachments to a dedicated email inbox; background service processes it automatically.
- Direct Drag-and-Drop: Upload PDF or image schedules directly via the Web UI.
- Natural-language command bar: Type straight into the Web UI — e.g.
Sigrid har sjakktrening hver onsdag 18:00-19:50— and the AI turns it into structured calendar items matched to your family members.
-
Local AI on CPU (llama.cpp in Docker):
- Runs
llama.cpp:serverinside Docker on CPU (no GPU/CUDA or Nvidia driver setup needed on the host). - Uses 4-bit quantized
Qwen 2.5 7B(or3B), with high accuracy in parsing complex tabular schedules and dates into strict JSON. - OCR stage: set
OCR_ENABLED=truein.envto transcribe PDF uploads and photo uploads through a dedicated vision model (any OpenAI-compatible endpoint, e.g. llama.cpp, vLLM/SGLang servingzai-org/GLM-OCR) pointed at byOCR_BASE_URL(defaulthttp://localhost:8080/v1) withOCR_MODEL(defaultglm-ocr). PDFs are sent whole in one call when the endpoint ingests PDFs natively (OCR_PDF_NATIVE=true, the z.ai API); for endpoints that only accept images (llama.cpp rejectsdata:application/pdf), setOCR_PDF_NATIVE=falseand the pages are rendered to 150 DPI PNG (max 8) and transcribed one per page. Photos are sent as PNG. The OCR model is prompted with the canonicalOCRtrigger (GLM-OCR is prompt-limited) and produces markdown; the extraction LLM above then structures that text. When OCR is disabled or fails, PDFs fall back to their digital text layer (scanned pages then yield no text).
- Runs
-
Apple Calendar Subscription (.ics):
- Provides a live subscription feed (
/api/calendar.ics) for native iOS/macOS Calendar, syncing all activities and gear reminders onto lock screens, Apple Watch, and CarPlay.
- Provides a live subscription feed (
All you need is standard Docker (or Podman) with its Compose provider installed:
# Start the full stack in the background:
docker compose up -dukeplan-llm: Runsghcr.io/ggml-org/llama.cpp:serverwith--hf-repo ${LLM_HF_REPO:-bartowski/Qwen2.5-7B-Instruct-GGUF:Q4_K_M}on port 8089.ukeplan-app: Runs the web application and API on port 8000.
The application will be accessible at http://localhost:8000 (or your host's local network IP).
The same docker-compose.yml works with Podman unchanged — it uses only standard Compose features. Just swap the command:
podman compose up -d(On older setups, the Python podman-compose package also works: podman-compose up -d.)
One note: the host.docker.internal:host-gateway entry in the compose file is a Docker idiom used only so the app can reach an OCR endpoint running on the host (disabled by default via OCR_ENABLED=false). Modern rootless Podman adds host.docker.internal to the container automatically, so nothing needs changing. If an older Python podman-compose errors on that line, delete the extra_hosts entry (the hostname still resolves) or point OCR_BASE_URL at http://host.containers.internal:8080/v1 instead.
Every AI call (document parsing and natural-language commands) has a read timeout of 300 seconds, configurable with AI_TIMEOUT_SECONDS (env or .env):
AI_TIMEOUT_SECONDS=600This is the budget for waiting on the AI endpoint's response — it matters most when you point AI_BASE_URL at a remote or shared endpoint you do not control, where a long week-plan document can queue behind other traffic. Raise it if you see 504 responses or ReadTimeout in the app logs; the connect timeout stays at 10 seconds so an unreachable endpoint still fails fast.
A timeout is reported as 504 Gateway Timeout in the web UI and in the ingest log, and the document is kept under Documents so you can retry it. Note the endpoint may still be working on the request after the client gives up — raising the timeout is usually better than retrying, which doubles the load.
Open http://<your-host-ip>:8000/setup/apple-shortcut in Safari on your iPhone or Mac:
- Shortcut: One-tap download of a ready-made, signed "Share to Week Planner" iOS Share Sheet shortcut (imported in Files, no Mac required). Manual step-by-step instructions are provided as a fallback if the download is unavailable.
- Calendar: One-click subscription URL for Apple Calendar.
To enable the IMAP forwarding bot, set the following environment variables (or create a .env file):
IMAP_ENABLED=true
IMAP_HOST=imap.gmail.com
IMAP_PORT=993
IMAP_USER=your-family-planner@gmail.com
IMAP_PASSWORD=your-app-password
IMAP_POLL_INTERVAL_SECONDS=120The app automatically removes old data so the database and upload folder do not grow forever. A background task (at startup and then on an interval) deletes:
- Calendar items whose date is older than the retention window. Recurring-weekly items are standing entries and are never auto-removed.
- Uploaded documents (PDFs/photos) created before the retention window, including their file on disk.
Configure via environment variables (or .env):
ITEM_RETENTION_WEEKS=12
ITEM_PURGE_INTERVAL_SECONDS=86400
ITEM_AUTOPURGE=trueSet ITEM_AUTOPURGE=false to keep everything.
