{"id":51481502,"url":"https://github.com/dholbach/my-practice","last_synced_at":"2026-07-07T02:01:20.361Z","repository":{"id":365203774,"uuid":"1271011819","full_name":"dholbach/my-practice","owner":"dholbach","description":"Self-hosted practice management software for therapy, coaching, etc","archived":false,"fork":false,"pushed_at":"2026-06-30T12:58:43.000Z","size":4386,"stargazers_count":5,"open_issues_count":20,"forks_count":2,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-30T13:05:41.882Z","etag":null,"topics":["django","dsgvo","gdpr","healthcare","postgresql","practice-management","psychotherapy","self-hosted"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dholbach.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-16T08:51:18.000Z","updated_at":"2026-06-30T12:58:45.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dholbach/my-practice","commit_stats":null,"previous_names":["dholbach/my-practice"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/dholbach/my-practice","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dholbach%2Fmy-practice","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dholbach%2Fmy-practice/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dholbach%2Fmy-practice/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dholbach%2Fmy-practice/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dholbach","download_url":"https://codeload.github.com/dholbach/my-practice/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dholbach%2Fmy-practice/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35211553,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-07T02:00:07.222Z","response_time":90,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["django","dsgvo","gdpr","healthcare","postgresql","practice-management","psychotherapy","self-hosted"],"created_at":"2026-07-07T02:01:18.688Z","updated_at":"2026-07-07T02:01:20.302Z","avatar_url":"https://github.com/dholbach.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# my-practice\n\n[![CI](https://github.com/dholbach/my-practice/actions/workflows/ci.yml/badge.svg)](https://github.com/dholbach/my-practice/actions/workflows/ci.yml)\n[![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)\n[![Django 6](https://img.shields.io/badge/Django-6.0-092E20?logo=django\u0026logoColor=white)](https://www.djangoproject.com/)\n[![Python 3.14](https://img.shields.io/badge/Python-3.14-3776AB?logo=python\u0026logoColor=white)](https://www.python.org/)\n\n**Self-hosted practice management for independent, private-pay practices** — therapy, coaching, and similar.  \nInvoicing, session tracking, notes, analytics — on your own hardware, under your own control.\n\n\u003e **Pre-release.** The code is production-grade — 945 tests, used daily since early 2026. But it has only been tested on one setup (Germany, GLS Bank, Berlin public holidays, single practitioner). No stable API is promised and there are no support guarantees. Issues and PRs welcome.\n\n---\n\n## The story behind this\n\nMy practice for Body Psychotherapy and Somatic Experiencing (HPG) is in Berlin — I also offer Coaching. Over the years I'd built a collection of manual processes to run the practice: a spreadsheet for sessions and billing, another for tracking open cases, a folder of templates for invoices, calendar exports for capacity planning, a notes system for supervision preparation, separate tools for financial overviews at tax time — and a whole set of recurring todos to glue it all together.\n\nIt all *worked*, more or less. But by late 2025 the system had grown unwieldy. Each task touched multiple tools, context-switching was constant, and the mental overhead of maintaining everything was starting to feel like a second job.\n\nIn December 2025 I sat down and started over. I had Django experience from a previous career in software, so the choice of stack came naturally. What surprised me was how much building this with AI assistance taught me about modern development practices — things I'd missed in the years between my software career and my practice work. I'm glad to share that here, both the code and the approach.\n\nThe name is a little self-conscious. \"my practice\" isn't meant as a possessive — it's the question I kept asking myself while building: *what is my actual practice in doing this work?* What are my real workflows, what makes them feel natural, what am I doing when I sit with a client and then later sit with the paperwork?\n\n---\n\n## Why self-hosted?\n\nThe hard requirement, before anything else: **client data does not leave my machine.** No third-party cloud, no SaaS backend, no \"encrypted in transit\" hand-waving. Personal and health data (Art. 9 GDPR) stays on hardware I control, with sensitive notes encrypted at rest under a key only I hold.\n\nBuilding it this way forced me to think explicitly about questions that any private practice handling personal data should be able to answer — and which off-the-shelf SaaS quietly answers *for* you, invisibly. The notes I made along the way are in the repo.\n\nFork it, rename it, make it yours.\n\n---\n\n## Not a developer? These resources are still for you\n\nYou don't need to run the app to get value from this project. Running a private practice means handling Art. 9 health data under GDPR — and most of what that requires, SaaS tools handle silently on your behalf, without you ever seeing how. Building this system meant answering those questions explicitly. The documents below are usable as standalone references:\n\n**[DPIA template](docs/operations/DPIA-template.md)**  \nWhen you systematically process Art. 9 health data, GDPR Art. 35 requires a Data Protection Impact Assessment before you start. Many solo practitioners don't know they need one. This is a filled-in template for a single-practitioner psychotherapy practice — adapt it to your setup and you're most of the way there.\n\n**[Emergency access planning](docs/guides/EMERGENCY_ACCESS_PLANNING.md)**  \nWhat happens to your clients and your data if you're suddenly unavailable — illness, accident, incapacitation? Your clients need continuity of care; your practice needs an administrative handover. This guide walks through a concrete model for solo practitioners: who can reach clients, who can access records, and what the legal boundaries are.\n\n**[Backup and § 147 AO retention](docs/guides/BACKUP_SETUP.md)**  \nGerman law requires keeping business records — including invoices — for ten years (§ 147 AO). That's not a technical problem; it's a compliance question every practice has to answer, regardless of what software they use. The backup guide explains the strategy used here; the questions it raises apply universally.\n\n**[Clinical data security](docs/guides/CLINICAL_DATA_SECURITY.md)**  \nSession notes and client profiles are the most sensitive data in a practice. This guide explains the two-key model used here — full-disk encryption plus a separate Fernet key for clinical content — what each layer protects against, and the questions every practitioner should be able to answer about wherever their data lives.\n\n**[Client privacy notices](docs/operations/PRIVACY_NOTICE_CLIENTS_DE.md)**  \nReady-to-adapt privacy notices (DE + EN) for informing clients about data processing under GDPR Art. 13. Most practices need these and most don't have them written down.\n\n**[Record of processing activities](docs/operations/DATA_REGISTER.md)**  \nA Verzeichnis von Verarbeitungstätigkeiten as required under GDPR Art. 30 — the register of what data you hold, why, how long, and who has access. Filled in for a solo psychotherapy practice.\n\n---\n\n## Screenshots\n\n\u003e Screenshots taken with `./dev.py manage seed_sample_data` — all names are fictional.\n\n### Dashboard\n![Dashboard](docs/screenshots/dashboard.png)\n\n### Invoice detail\n![Invoice detail](docs/screenshots/invoice-detail.png)\n\n### Analytics\n![Analytics](docs/screenshots/analytics.png)\n\n### Client detail\n![Client detail](docs/screenshots/client-detail.png)\n\n### Batch invoicing\n![Batch invoicing](docs/screenshots/batch-invoicing.png)\n\n---\n\n## What it does\n\n**Billing**\n- Client management with per-client hourly rates\n- Session-linked invoice items — bill directly from session records\n- Batch invoicing: one screen to draft a whole month's invoices\n- PDF generation (bilingual DE/EN, your logo + signature)\n- Email via Proton Bridge; payment status tracking\n\n**Sessions \u0026 clinical**\n- Session log with protocol notes, therapist reflection, mood tags\n- Intake questionnaire and treatment contract PDFs\n- Clinical notes encrypted with Fernet (separate key from disk encryption)\n- Client document upload and tagging\n\n**Analytics**\n- Revenue trends, year-over-year comparison, tax-year summary\n- Capacity utilisation vs. target hours (Berlin public holidays aware)\n- Client inquiry pipeline with funnel view and conversion rates\n\n**Integrations**\n- Google Calendar import — maps events to sessions, detects reschedules and cancellations\n- Bank statement CSV import with auto-matching to invoices (GLS Bank semicolon-delimited format; other banks will need format adaption)\n- Operational backup checklist with completion tracking\n\nFull feature list: [docs/FEATURES.md](docs/FEATURES.md)\n\n---\n\n## Architecture\n\nA conventional Django 6 monolith backed by PostgreSQL (psycopg3, async-capable), ~950 tests, no external services required beyond the database. A few non-obvious choices:\n\n**Encrypted clinical fields**  \nSession notes and sensitive client data use a custom [`EncryptedTextField`](app/my_practice/fields.py) backed by Fernet symmetric encryption (`cryptography` library). The key lives in `FERNET_KEY` (env var), separate from the LUKS full-disk-encryption key — two independent keys, two independent attack surfaces. A plain SQL `SELECT` on an encrypted column returns `gAAAAA...` ciphertext; the ORM is the only path to plaintext. Trade-off: no `.filter()`/`.exclude()` on encrypted columns — navigate clinical records by client, date, and tags instead.\n\n**Why PostgreSQL**  \nPerformance indexes on the invoice/client foreign keys (migrations 0013, 0014), and psycopg3's async driver for Django's async views. The load of a single-practitioner practice doesn't require it, but it keeps the deployment realistic and the door open for async features.\n\n**Pattern layer**  \nViews stay thin by delegating to builder classes (`AnalyticsDashboardBuilder`, `FinancialListContextBuilder`) and filter helpers (`InvoiceFilterHelper`). Models, views, and utils are split into domain-focused modules under `app/my_practice/{models,views,utils}/`. The full pattern catalogue — including the builder API, CRUD mixins, and query helpers — is in [`CLAUDE.md`](CLAUDE.md). It was written as AI-assistant instructions but reads as a practical architecture reference for anyone contributing to the codebase.\n\n---\n\n## Quick start\n\n### Image-based (recommended for self-hosters)\n\n**Requirements**: Docker with the Compose plugin, Python 3.\n\n```bash\ncurl -O https://raw.githubusercontent.com/dholbach/my-practice/main/prod.py\nchmod +x prod.py\n./prod.py setup\n```\n\n`setup` generates secrets, pulls the image, starts the stack, and walks you through creating a login and configuring your practice.\n\n### Source-based (for developers)\n\n**Requirements**: Docker with the Compose plugin, Git, Python 3 (for `dev.py`).\n\n```bash\ngit clone https://github.com/dholbach/my-practice.git\ncd my-practice\n./dev.py start --build      # builds image, starts PostgreSQL + Django\n```\n\nApp is at **http://localhost:8000**. First run:\n\n```bash\n./dev.py manage createsuperuser\n./dev.py manage seed_sample_data   # optional: loads fictional demo data\n```\n\nFull walkthrough including real-use setup: [docs/guides/GETTING_STARTED.md](docs/guides/GETTING_STARTED.md)\n\n---\n\n## Keeping up to date\n\nSee [UPGRADING.md](UPGRADING.md) for the standard upgrade procedure and any version-specific steps.\n\n---\n\n## Status and limitations\n\n- **Daily driver** — used in production for one practice since early 2026; ~945 tests\n- **German UI** — the interface is in German. Full i18n via Django `{% trans %}` is on the roadmap — see [P-039](docs/projects/todo/P-039_I18N.md). If you'd like to help translate, PRs are very welcome.\n- **One practice, one setup** — multi-practice support exists in the data model but the UX assumes a solo practitioner\n- **Some parts are mine** — the backup checklist items, bank CSV parser, and treatment contract template reflect my specific setup; see [CUSTOMISATION.md](docs/guides/CUSTOMISATION.md)\n- **AGPL-3.0** — Using it for your own practice: no restrictions. Offering it as a service to others: AGPL applies.\n\n---\n\n## Development\n\n`dev.py` is the single entry point:\n\n```bash\n./dev.py test            # Django + JS tests\n./dev.py quality         # ruff format, ruff lint, tests\n./dev.py shell           # Django shell\n./dev.py logs -f         # follow container logs\n./dev.py restart --force # full restart (reloads .env)\n```\n\nReference: `./dev.py --help` or [docs/operations/SCRIPTS.md](docs/operations/SCRIPTS.md)\n\n---\n\n## Documentation\n\n| | |\n|---|---|\n| [UPGRADING.md](UPGRADING.md) | Upgrade procedure and breaking-change notes |\n| [GETTING_STARTED.md](docs/guides/GETTING_STARTED.md) | First-run walkthrough |\n| [FEATURES.md](docs/FEATURES.md) | Full feature list |\n| [CUSTOMISATION.md](docs/guides/CUSTOMISATION.md) | What to replace before using for your own practice |\n| [BACKUP_SETUP.md](docs/guides/BACKUP_SETUP.md) | Backup and restore |\n| [EMERGENCY_ACCESS_PLANNING.md](docs/guides/EMERGENCY_ACCESS_PLANNING.md) | What happens to the data if you're unavailable |\n| [CLINICAL_DATA_SECURITY.md](docs/guides/CLINICAL_DATA_SECURITY.md) | How sensitive data is encrypted at rest |\n| [SECURITY.md](SECURITY.md) | Responsible-disclosure policy — how to report a vulnerability privately |\n| [docs/operations/SECURITY.md](docs/operations/SECURITY.md) | Security model and deployment notes |\n| [DPIA-template.md](docs/operations/DPIA-template.md) | Data protection impact assessment template |\n| [CODE_STRUCTURE.md](docs/architecture/CODE_STRUCTURE.md) | Codebase patterns |\n| [CLAUDE.md](CLAUDE.md) | Builder classes, view mixins, query helpers — AI instructions that double as an architecture reference |\n| [PROJECTS.md](PROJECTS.md) | Roadmap and backlog |\n\n---\n\n## License\n\n[GNU Affero General Public License v3.0](LICENSE) (AGPL-3.0)\n\nCopyright (C) 2026 Daniel Holbach\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdholbach%2Fmy-practice","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdholbach%2Fmy-practice","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdholbach%2Fmy-practice/lists"}