تفاصيل العمل

Core workflow

Lead uploads source report -> system preserves source evidence -> extracts and validates financial data -> maps it to the correct project/category -> manager reviews -> approved values become official -> project/site/company results aggregate automatically.

The PostgreSQL database is the source of truth. Uploaded files are kept as evidence, Excel workbooks are also an import source, and reports are exported as Excel. No file is used as the application database.

Architecture

Part Technology

Frontend Next.js 16, React 19, TypeScript

Backend FastAPI on Python 3.12, SQLAlchemy 2.x, Alembic migrations

Database PostgreSQL 17

Excel extraction and reports openpyxl (deterministic)

HTTPS, production only Caddy 2.11

Packaging Docker Compose

Tests and CI pytest, Vitest, GitHub Actions

The browser talks only to the Next.js application. Next.js keeps the FastAPI access token in an HTTP-only cookie and calls FastAPI server-to-server. Financial and identity authorization is enforced by FastAPI.

There are two Compose files:

docker-compose.yml docker-compose.prod.yml

Purpose Local development Production, on one server

Reachable from outside Frontend 3000 and backend 8000, plain HTTP Caddy only: 443 (HTTPS) and 80 (redirects to HTTPS)

Settings .env from .env.example; development defaults allowed .env from .env.production.example; secrets required, no defaults

APP_ENV development production

Evidence files ./storage (git-ignored) MK_STORAGE_DIR, outside the checkout

Local development

cp .env.example .env. Its development defaults, such as the change-me database password, are for your own machine only.

docker compose up --build. The backend applies the database migrations every time it starts.

Create the first administrator, once per database: docker compose exec backend python -m app.scripts.seed_identity. It asks for an email, a name and a password of at least 12 characters.

Open http://localhost:3000. The FastAPI docs are at http://localhost:8000/doc... (development only: with APP_ENV=production they are not served).

A write (sign-in, upload, approval, admin change) from a page at any origin other than PUBLIC_ORIGIN is refused. PUBLIC_ORIGIN defaults to http://localhost:3000; if you open the app at another address, set it in .env. The session cookie is not Secure here, because local traffic is plain HTTP (SESSION_COOKIE_SECURE=false).

Checks

The CI workflows (.github/workflows/) run these checks:

Backend (backend/):

install from requirements-dev.lock (see Dependencies);

ruff check app tests alembic;

alembic upgrade head;

pytest -q -rs, against a PostgreSQL 17 service;

a Docker image build.

Frontend (frontend/):

npm ci;

npm run lint, npm run typecheck, npm run test and npm run build;

a Docker image build, then a smoke test: the image starts, the sign-in page loads, and the server does not run as root.

The backend test suite empties every application table in the database at DATABASE_URL. Run it only against a disposable test database; CI uses one named mk_test. Three tests need the real purchase workbook, which is never committed. Without it they skip; MK_PURCHASE_SAMPLE points them at a local copy.

Production deployment

Status: rehearsed end to end in a sandbox on 5 October 2026; not deployed anywhere yet. The runbook, docs/PRODUCTION_DEPLOYMENT.md, covers the owner's decisions, the first deployment, the checks after each deployment, updates and rollback, and operations. In short:

Services: docker-compose.prod.yml runs Caddy, the frontend, the backend and PostgreSQL. Only Caddy publishes ports. The backend and PostgreSQL are on an internal network with no route to the internet.

HTTPS: Caddy obtains and renews the certificate for MK_DOMAIN automatically. It uses Let's Encrypt, registered to the email in MK_TLS; MK_TLS=internal is only for rehearsals without a public domain.

Settings: copy .env.production.example to .env, which is never committed. Compose refuses to start until all of these are set, and nothing falls back to development values:

MK_DOMAIN and MK_TLS;

MK_STORAGE_DIR, MK_UID and MK_GID;

SECRET_KEY and POSTGRES_PASSWORD.

.env also sets COMPOSE_FILE, so plain docker compose commands and the backup scripts use the production stack.

Production mode: the backend runs with APP_ENV=production. It refuses to start on a default or short SECRET_KEY or the change-me database password, and serves no API docs. The session cookie is Secure, and writes must come from https://<MK_DOMAIN>;.

Health checks and restarts:

The checks are: backend GET /health, frontend GET /login, and PostgreSQL pg_isready. /health shows the backend process is up; it does not query the database.

Each service starts once the service it depends on is healthy: database → backend → frontend → Caddy.

Every service restarts unless stopped, and every container's log is rotated (10 MB × 5 files).

Migrations run on every backend start and are forward-only, so take a backup before deploying a new version.

Evidence files live in MK_STORAGE_DIR, an absolute path outside the checkout. The backend runs as MK_UID:MK_GID, the account that owns that directory and runs the backups.

Uploaded evidence

Accepted types: .xlsx, .xlsm, .pdf, .docx, .png, .jpg and .jpeg. A file's content must match the signature for its extension.

Size: the backend refuses files over 25 MiB with 413. In production, Caddy also stops any request body larger than 30 MB, with 413.

Storage: files are stored under a generated name, with their SHA-256 recorded, and written read-only (mode 0440).

Downloads of an original file need the review permission. The file is sent as an attachment, typed by its validated extension, with X-Content-Type-Options: nosniff.

Backups and restore

Backup: ops/backup/backup.sh writes a backup set: a consistent database dump, exactly the evidence files it refers to, and a manifest. Before reporting success, it checks every evidence file against its recorded SHA-256. The application can keep running while it does.

Restore: ops/backup/restore.sh restores a set into a new database and an empty directory, then checks the schema revision, the row counts and the evidence hashes. It never touches the live system.

In production, run both scripts as the MK_UID account, not with sudo. Evidence files are readable only by their owner and group.

Not decided yet: where backups go, how often they run, how long they are kept, and how they are encrypted. Nothing is scheduled.

Details: docs/BACKUP_AND_RESTORE.md and the backups section of the runbook.

Security

Authorization: roles and permissions are enforced by FastAPI, not just hidden in the UI.

Sessions:

The access token is kept in the mk_access_token cookie: HttpOnly, SameSite=Lax, and Secure in production.

It expires after ACCESS_TOKEN_EXPIRE_MINUTES (default 480).

Signing out revokes it on the server.

Passwords:

stored as bcrypt hashes;

12 to 72 characters;

after an administrator resets a password, the user can do nothing else until they change it.

Sign-in throttling:

Limits: 5 failed attempts per account, or 30 per public client address, within 15 minutes. Over a limit, sign-in returns 429 with Retry-After.

The client address comes from the connection or a configured trusted proxy, never from a header the client sends.

Cross-site requests: a write whose Origin is not PUBLIC_ORIGIN is refused, sign-in and sign-out included. Sign-in accepts JSON only.

Production hardening:

no API docs and no X-Powered-By or Server header;

Caddy adds HSTS, X-Content-Type-Options: nosniff, X-Frame-Options: DENY, a frame-ancestors 'none' CSP, Referrer-Policy and Permissions-Policy.

Containers: the frontend runs as the unprivileged node user and cannot change its own files. In production, the backend runs as MK_UID:MK_GID.

Never commit .env, production secrets, uploaded evidence, database dumps or backup sets.

Dependencies

Builds and CI install exactly the locked versions:

Lockfile Used by Installed with

frontend/package-lock.json the frontend image and Frontend CI npm ci

backend/requirements.lock the backend image pip install --require-hashes -r requirements.lock, then pip install --no-deps .

backend/requirements-dev.lock Backend CI and local development (adds pytest, ruff and the test clients) pip install --require-hashes -r requirements-dev.lock, then pip install --no-deps -e .

The backend locks carry hashes, so pip refuses any other version and any altered package. The base images (python:3.12-slim, node:24-alpine, postgres:17-alpine, caddy:2.11-alpine) use floating tags, so a rebuild can pick up a newer patch release.

To change dependencies:

Frontend: edit frontend/package.json, run npm install in frontend/, and commit the updated package-lock.json.

Backend: edit backend/pyproject.toml, then regenerate both locks in backend/ with uv. Add --upgrade to move to newer versions.

uv pip compile pyproject.toml --universal --python-version 3.12 --generate-hashes -o requirements.lock

uv pip compile pyproject.toml --extra dev --universal --python-version 3.12 --generate-hashes -o requirements-dev.lock

Milestones

M1: Core Financial Control Engine

M2: Identity, Authentication & RBAC

M3: Master Data & Administration

M4: Core Web Application (dashboard metrics still open: which figures to show is not decided)

M5: Additional Financial Source Ingestion

M6: Business Calculation and Allocation Rules

M7: Advanced Management Reporting (period-filtered reporting)

M8: More Resilient Purchase-Workbook Parsing

M9: Purchase Summary Review and Approval Workflow

M10: Go-live readiness: single purchase path, review audit trail, mapping administration, session security, backup and restore. Open business questions are listed in docs/MILESTONE_10.md.

See the milestone documents under docs/ for guarantees, scope boundaries, and engineering decisions.

Rent reminders, a separate page that emails management before each apartment's rent is due, and how to switch them on: docs/RENT_REMINDERS.md.

The platform's name and the report sites' names, and how to upgrade an existing install to them: docs/NAMING_UPDATE.md.

بطاقة العمل

اسم المستقل
عدد الإعجابات
0
تاريخ الإضافة
تاريخ الإنجاز
المهارات