Koch Laboratory

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:

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:

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.

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:

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.