Laborbuch — administrator manual
Laboratory profile, projects, users and 2FA, storno, translations, week closes, backups, licence, updates.
The administrator manual for a Laborbuch instance — configuration, users, week closes, backups and the licence. Day-to-day work is covered by the user manual.
The admin panel
The panel lives at /admin/ (same account and 2FA as the journal — the panel cannot be opened bypassing 2FA). Accounts with the “staff” right have access; full configuration is done by the superuser created during installation.
First configuration — Site settings
Site settings → Laboratory profile adapts the system to how you work:
| Setting | Meaning |
|---|---|
| Git/GitHub integration | Repositories, commit sync and commit anchors — the IT/hardware profile; disable in a lab without code |
| File anchors (uploads) | Attaching evidence files to entries |
| File anchor mode | Upload — files stored in the instance; Hash-only — the file stays with the user, the system stores only its SHA-256 fingerprint (trade secrets never reach the server) |
Projects
Projects → Add: name, slug, Vorhaben code (mapping to the BSFZ application), a description demarcating R&D from routine. Two people lists:
- Members — add entries and see the project’s data.
- Observers — see entries and reports (teamwork) but without other people’s hours (masked “—”) and without the right to add entries.
A new account automatically becomes a member of every active project, and a new project receives all existing accounts. Excluding someone from a project is a deliberate removal from the Members list — a person removed once is never re-added.
With Git enabled, add Repositories to the project (owner/name format; the access token is set in the instance configuration as GITHUB_TOKEN). Commits sync every 15 minutes; manually: the dashboard button or the Sync commits from GitHub admin action.
A fine-grained token covers repositories of a single resource owner, so an organisation repository is invisible to a personal account’s token (GitHub then answers 404, not 403). Such a repository gets its own token in the Access token field — issued for the organisation (Resource owner = organisation, selected repository, Contents: Read-only permission); the organisation must allow access via fine-grained PATs. Empty field = the global token. The Check GitHub access action verifies the setup without fetching commits.
Users and 2FA
Users → Add: login and password, then on the account page permissions (“staff” for admin access) and role groups. You declare the 2FA method per account — at the bottom of the user page:
- 2FA — TOTP (app / hardware card): add a device; for apps (e.g. Google Authenticator) the key is generated automatically — the QR code is on the device’s page in the “TOTP devices” section; for a hardware card paste its seed into
keyand correct clock drift withdrift/tolerance. - 2FA — code by e-mail: add a device; empty
email= the account’s address. Requires configured mail (EMAIL_* in the instance configuration). - An account without a device logs in with the password alone.
The licence limit counts active accounts: with the limit exhausted you cannot add or activate an account — deactivating one (“active” unchecked) frees a seat without losing its entry history.
Accounting parameters (a section on the account page) hold the personal weekly cap. An empty field = the instance-wide cap, 40 h by default, i.e. the Eigenleistung rule under § 3 (3) FZulG. For an employee, enter their contractual hours: an employee’s hours are eligible in the amount actually worked, so a half-time position means 20 h, and an employee’s overtime is not “R&D above the cap”. The cap set here also shows on that person’s dashboard — in the week bar and as the chart line.
Which Vorhaben count toward the cap. Only hours on Vorhaben designated by the administrator count toward the weekly cap: Vorhaben → BSFZ status (editable straight from the list, column “Counts toward cap”). Before filing the application, give the Vorhaben in it the status “Submitted”. After the Bescheid, set certified ones to “Certified” and rejected ones to “Rejected”: their hours stop counting toward the cap, which frees room in the same week for “R&D over cap” on certified Vorhaben. Work on other projects and on “not submitted” Vorhaben does not count toward the cap. As long as no Vorhaben is “Submitted” or “Certified”, the cap covers all projects combined. An entry without a split by Vorhaben (e.g. added from the dashboard) counts if its “Vorhaben” field contains the code of a counting Vorhaben; an entry with a split counts by the sum of its allocations to such Vorhaben.
Entries, corrections (storno) and translations
Entries in the admin: the full list with filters. Entries of open weeks can be corrected; closed ones (🔒) are permanently read-only.
- Storno: add a new entry with negative hours and point at the corrected one in the
correctsfield. The original stays — the correction is explicit, as in accounting. - Translations: with
TRANSLATE_AUTO_LANGSset (e.g.de), every new or corrected entry is translated automatically in the background right after saving. Gaps (older entries, a temporary API outage) are filled by the report itself when generated in that language — in batches; in bulk:manage.py translate_entries --lang de. Manually: select entries → action Translate into German / English / Polish (Claude). A translation is a separate record (the original untouched), created once, and can be corrected by hand under Entry translations. RequiresANTHROPIC_API_KEY; every translation is a paid API call — but only one per entry and language. - Anchors (section within an entry): commits, files (hash computed automatically), mtime, event log, other.
Closing the week — the working rhythm
The week is closed with a button in the dashboard — the “Open weeks” tile shows the oldest open week and a “Close 2026-W29” button. No server login required.
Visible to accounts with the R&D Lead role (permission core.add_weekclose) and to superusers. Researchers add entries but do not close weeks — closing locks every author’s work.
Three barriers the button enforces:
- a week still in progress cannot be closed — the rest of the work has yet to happen,
- weeks close in order, oldest first: closing W30 while W29 is open would leave a gap in the chain of seals,
- closing is irreversible — which is why the button asks for confirmation.
The same operation from the console, should you need it (e.g. in a migration script):
docker compose exec web python manage.py close_week 2026-W29
Closing: builds the canonical record of the week’s entries → computes SHA-256 → timestamps it via BeatTime (Ed25519 signature) and OpenTimestamps (Bitcoin anchoring) → locks the entries (🔒). Proofs are under Week closes. The OTS stamp matures after Bitcoin confirmation — upgrade_ots refreshes the proofs.
Entries added after the close. Closing locks editing of the entries it covered — but it does not block adding a new entry whose work date falls in that week (regime “reconstruction”). Such an entry is not covered by the seal: it is absent from the canonical record and from the timestamp, and it will never get there. The report says so explicitly — the “covered by seal” column in the CSV, plus the “outside the week’s seal” marker and a warning above the table in HTML/PDF.
Rule of thumb: close a week once it is fully written up — not earlier, but not indefinitely later either. An open week is entries without a stamp; a week closed too early is entries outside the seal. If you write entries a few weeks in arrears, simply close with the same lag.
If no stamp is produced (no network, service temporarily unreachable): the week is still closed, hashed and locked, but carries the status “closed” rather than “stamped” — the auditor’s report shows exactly that state, because the status is derived from the actual proofs, not from the attempt. Nothing to do: the autopilot retries the stamp on its next pass until it succeeds. BeatTime and OpenTimestamps are independent time anchors — one success is enough.
Autopilot — what runs by itself
A loop inside the container (every 15 minutes by default) does everything outstanding. No cron on the server, no configuration — it ships with the image.
| What | When |
|---|---|
| fetch commits from GitHub | every pass |
| missing stamps on closed weeks | every pass, ≥ 1 h between attempts |
| mature the proofs: OTS → Bitcoin confirmation, BeatTime weekly signature | every pass |
entry translations (TRANSLATE_AUTO_LANGS) |
every pass, in batches of 50 |
What the autopilot does not do: close weeks. Closing locks entries irreversibly, and someone may still be adding catch-up work to a just-ended week — so a human makes that call with the button.
Show what is outstanding without doing anything:
docker compose exec web python manage.py autopilot --dry-run
Report for the tax office application
The Tax office application tab (/reports/fa/) compiles the hours for the Forschungszulage application, filed in ELSTER after the end of the fiscal year — one per year, covering all Vorhaben with a BSFZ certificate. The report does not change the records.
What to set in the admin panel before filing:
| Where | What |
|---|---|
| Vorhaben → BSFZ status | “Certified” for Vorhaben with a certificate — only these enter the summary |
| Vorhaben → Certificate / decision reference | the reference from the BSFZ certificate; the report shows it for each Vorhaben |
| Vorhaben → Vorhaben start (per application) | the start date stated in the BSFZ application; it determines the 20 % overhead flat rate |
| Users → account → Accounting parameters → Eigenleistung | tick for a sole proprietor or partner; leave empty for employees |
What the report calculates. Hours from the allocation of entries to certified Vorhaben, with the personal weekly cap applied chronologically: hours above the cap drop off at the end of the week, and the report lists them. Entries “outside R&D” and “R&D over cap”, and hours on Vorhaben without a certificate, do not enter the summary — the report shows them separately, for information. For persons with Eigenleistung the report estimates the amount at the statutory rates: €40/h until 27.03.2024, €70/h from 28.03.2024, €100/h from 2026; plus the 20 % overhead flat rate (§ 3(3b) FZulG) for Vorhaben begun after 31.12.2025; a 25 % rate, 10 percentage points higher for SMEs from 28.03.2024 (switch in the filters). For employees the report shows hours only — the basis is their salary from payroll. The tax office’s Bescheid decides the amount.
Checks before filing sit at the top of the report: open weeks (close them before filing — the seal shows the record has not been changed), hours trimmed by the cap, entries without allocation to Vorhaben (not included — add the allocation in the entry), days over 20 h, Vorhaben without a start date or certificate reference. When everything is in order, the report shows “All checks passed”.
Export: filter by fiscal year or From/To dates, PDF and CSV (; separator, decimal comma), PL/DE/EN versions. Every opening of the report is logged in Read accesses (audit).
In the ELSTER application you give, for each Vorhaben, the certificate reference and the hours from the table. The increased SME rate requires an application with a declaration of SME status under the EU definition (including linked enterprises), and the application also asks about other funding for the same costs. Keep the report, the auditor report and the evidence anchors of the entries (commits, files) as evidence — the tax office may request them.
Backups
Admin home → Database backups: create a backup (no downtime), a dated list, Download (keep a copy off the server!), Restore (automatically saves the prior state as pre-restore-…) and Delete. The backup covers the database; evidence files in media/ need a separate directory copy. All operations land in the audit journal.
Read-access audit
Read accesses (audit) — an append-only register: who opened reports, downloaded evidence files, created and restored backups, and when. Records can be neither changed nor deleted.
The licence
The license.json file lives in the installation directory. Its state shows on the dashboard (customer, plan, seat limit, validity). After expiry the instance switches to read-only with full export — data is never held hostage. Renewal: swap in the new file (no restart).
Updates
cd laborbuch && docker compose pull && docker compose up -d
Database migrations run automatically on start. Images are signed (cosign). Create a backup before major updates.