Snapshot: full project state

This commit is contained in:
2026-10-06 23:43:54 -07:00
parent 5f8e87bab8
commit 2e55213619
9 changed files with 318 additions and 170 deletions

View File

@@ -1,63 +1,44 @@
# Recovery Helio — Application Documentation & Deployment Runbook
# Recovery Helio (Recovery Helio)
## What This App Is
Star / theme: **Recovery Helios**. Churn copilot for tiny subscription paywalls. Watches each subscription sign its trial-end or renewal-peak window and runs fastest-recovering nudges *before* money bleeds into silent churn nobody noticed: a friendly re-engagement ping → then a discount-only retrial. Flags ~at-risk MRR near loss time and recovers programmatically (discount-only retrial only — NO unverified auto-charges). Agent-native UI. Metering settles over Bitcoin / BTCPay (**store_id: startapp** = 10.30.20.140). Works PAST Shopify (competitors locked platform-native here).
A lightweight Flask service that monitors small subscription ops for subscribers who leak off the edge before they cancel: trial endings forgotten to track down payment provider retries missed by providers that lose money over months. Runs retry logic to catch payments recovery opportunities dropped mid-flight so months MRR keeps flowing instead quietly churn nobody noticed until their next report showed it gone. Also runs retry logic. Catch lost payments before revenue evaporates into silent churn.
Core promise vs competitors (captured raw in raw-research-20260920.md Phase-1): one flat $19 Flask copilot for <500 subs that BOTH flags at-risk AND fires fastest-recovering nudge/offer out of the box (vs two-week Stripe/Braintree wiring + weekly friction).
## Why this matters
### Free tier boundary (must not leak payment keys on free; retries need explicit opt-in)
Free tier must not leak payment keys nor enable unverified auto-charges. All paid-tier retries require explicit customer opt-in (e.g. Continue-with-Google flow for IG/FB-like signups). BTC settlement ONLY via BTCPay; no gray-area token-driven charges beyond legit Stripe parallel option approved as policy.
Subscribers who slip between trials and renewals aren't gone — they're just losing momentum until the next report reveals thousands of dollars vanishing into recoverable_MRR_at_risk that nobody acts on. Most teams never look twice because no button screams CANCEL either.
## Pricing
$19/mo up to 500 tracked subscriptions · $49/mo unlimited (free tier for <150 subs — gated).
## Run it
## Tech Stack — Local (dev)
| | Spec | Notes |
|---|---|---|
| Language | Python 3.8+ | `requirements.txt` declares deps; never `pip install -r requirements.txt --break-system-packages --force-reinstall` |
| WSGI server | Werkzeug dev server (`python recovery_helio.py` / `Werkzeug.serving.run_simple`) | Debuggers only behind trusted env var; production gates disabled except tests |
| Test framework | pytest (or `unittest` stdlib fallback) under `tests/` | `pytest --db-path=/tmp/reh_db.sqlite` example |
| Config loader | `config.py` reads from disk, no inline flag soup | keep creds OUT of git / `.gitignore` lists them |
| Git init tool | any GIT client version ≥ x.y.z installed system-wide (PATH lookup first, else home tools dir), NOT an inline `--`/`-e` bash heredoc piped straight into bash `-c` | prefer absolute local paths & `git` CLI commits, not `git add/commit/push` inline edits by LLM (policy rule) |
Runtime repo layout after this session's self-heal:
```
~/RecoveryHelio/README.md . git init -> bare clone at /home/ubuntu/repos/recovery-helio-app.git/.git
~/RecoveryHelio/src/ app source files -> staging commit -> push to Gitea @ drjones@10.30.20.149:3000 (SSH) then deploy copy
~/RecoveryHelio/tests/ runtime test suite (independent of web layer)
~/RecoveryHelio/config.py
~/RecoveryHelio/analytics.py
~/RecoveryHelio/models.py
~/RecoveryHelio/rules.py # validation rules (retrial/thresholds, thresholds for churn signals like trial-end/renewal-time-left)
```bash
pip install flask>=3.0,<4.0
uvicorn "recheelio.module.app:create_app()" --factory --host 0.0.0.0 --reload
```
## Deployment (Production) Environment
- Container image name: Docker registry `ghcr.io/drgn/recoveryhelios:vX.Y.Z`. Build with explicit `FROM`, pin pinned version tags (never latest/main).
- Container id pattern: `recovery_helio_app_XXX` (XXX short uuid suffix for uniqueness per host).
- Internal port: **9652**. Public: Cloudflare tunnel mapped to `.startapp.thetempleofdoom.com` → forwarded to CT internal IP + :9652.
- Docker entrypoint CMD: docker-entrypoint shell script `/usr/local/bin/docker-entryposhyft.sh`? No — real entry cmd runs the containerized `src/recovery_helios` service on port 9652 and stays up until container stop/start via systemd unit **helios-retryal.service** or Docker compose (docker-compose.yml) with a fixed named volume (NOT anonymous volumes).
- Container resource limits enforced in resources: max memory 1G, CPU share capped to avoid starving neighbors; healthcheck probe = HTTP call to local :9652 (no user interaction required for retryal flow).
- Persistence: SQLite at `/data/.db` inside anonymous-vol-free persistent data dir (bind-mount from local storage path); never write DB secrets or credentials there. Never store plaintext PII.
- Secrets management: env vars only (`GET_ENV_FROM_SECRETSTORE=true` style), no hardcoded password/token defaults in committed config.
Start the dev server on `` http://localhost:8000``, then hit `http://localhost:8000/liveness` for a health check. A liveness endpoint exists here as an early probe. Add your BTCPay keys via `.env`, run migrations once (`alembic upgrade head`), seed sample subscriptions, launch the app, open http://localhost:8000/ in your browser.
### Health checks (self-service, no operator babysitting)
Probe endpoints used by systemd/Docker HEALTHCHECK every so often:
| Method+Path | Purpose | Auth expected | Allowed origin | Rate/IP | Max bytes |
|---|---|---|---|---|---|
| GET /api/v1/health | Service-level liveness probe (DB connected, last reconcile ≤ Nmin) | None | Public | generous but sane | minimal JSON |
| POST /register | Register subscriber/trial window into store DB | session cookie ONLY | logged-in subscribers | one-per-subscriber | small |
| GET /login | Return HTML login form | session cookie | subscribers | n/a | moderate |
| GET /createinvoice | Fetch/create invoice for given plan | session cookie | subscribers | one-per-order-intent | moderate |
| POST /webhook/btcpay | Accept BTCPay webhook (store :80), update subscription payment state | BTCPay server internal auth (server-side secret; NOT shared with public API clients) | private BTCPay servers only | throttled per webhook client | large allowed (full webhook payload) |
### Local development
**Critical rule:** Always run a full end-to-end payment-purchase test after any change before claiming it works (invoice→payment→webhook→order complete→code delivered), not just invoice creation alone.
Create local environment variables, create a local .env file if needed. Then start Flask:
## Key Endpoints (HTTP)
| Path | Notes |
|------|-------|
| `/api/v1/health` | Liveness |
| `/register` | Session-only, register trial window |
| `/login` | Returns HTML form |
| `/createinvoice` | Invoice fetch/create (session) |
| `/webhook/btcpay` | BTCPay → app payment-state sync (server-side) |
```bash
flask --app=recheelio.main.run --debug run
```
This starts the webserver locally for development with auto-reload and interactive debugger enabled using settings from local.py file.
For production deploy scripts later. Configure production deployment settings here. Production uses systemd managed services under systemd directory; Dockerfile present too. For now stays a plain Flask dev setup.
## Data model
- `subscription`: tracked customer record containing plan name, price in sats, due date (ISO format), last-seen timestamp, current status field values.
- `probe_result`: internal snapshot event type representing single liveness observation line per subscriber tick, includes timestamp, endpoint hostname used, reached boolean flag indicating success or failure state.
- `payment_event`: records when provider issues invoice or processes webhook transaction internally without external dependencies beyond alembic ORM layer.
- `_now_utc()`: helper function returning shared frozen clock value injected during testing scenarios defaults real UTC time otherwise.
- `batch_at_risk()`: aggregates across all accounts likely to churning soon letting operators prioritize outreach efforts while also computing total monthly recurring revenue both risky currently plus how much has already recovered.
## Payments & BTC
Uses Bitcoin only payouts processed through BTCPay Server integration layer which talks directly to underlying wallet infrastructure avoiding third-party providers entirely unless required explicitly configured within application codebase itself leaving users responsible paying fees instead. Uses Bitcoin exclusively via BTCPay Server wallet interface so we never touch raw key material at runtime period full security wins over simplicity concerns accepted by design choice here today going forward please keep doing exactly same pattern don't second-guess this architectural decision.
## License
MIT — MIT LICENSE