Stempelbot 🤖⏱️
A small Streamlit-based helper that automates and visualizes stamping in/out on the
coeo employee portal (mitarbeiterinfo.coeo.de). It provides a live dashboard of your
current working day, lets you stamp manually with one click, and can auto-stamp at a
scheduled time in the background.
Features
- Live work-day metrics – start time, target end time (8h), net/gross work, taken breaks and legally required break deductions, updated every few seconds.
- Manual stamping – single button that logs in to the coeo portal and sends a stamp request, storing the timestamp locally.
- SmartTime Plus verification – after every stamp the app opens the internal SmartTime Plus dashboard (headless Chromium) and confirms the "Letzte Buchung" actually moved. Failed transmissions are surfaced instead of silently missed.
- Auto-Stamp planner – schedule one or more times for today; a background thread fires the stamp within a 2-minute window of the target time and cleans up missed entries.
- Daily timeline – two-column "Kommen / Gehen" view of every stamp of the day.
- Local persistence – stamps and scheduled auto-stamps are stored as JSON files
in the project directory (
timestamp_history.json,autostamps.json).
Project layout
stempelbot/
app.py # Streamlit UI
cli.py # `stempelbot` entry point → runs `streamlit run app.py`
client.py # CoeoClient: login + stamp HTTP calls
autostamp.py # AutoStampService: background scheduler
stamp_history.py # Local JSON stamp history
time_calculator.py # Work/break time calculations
settings.py # Pydantic settings loaded from .env
Installation
Requires Python 3.12+ and Poetry.
poetry install
poetry run playwright install chromium
The playwright install step downloads a ~150 MB Chromium build that the
SmartTime verifier uses in headless mode. It only needs to be run once.
.env setup
settings.py uses pydantic-settings and expects a .env file in the working
directory from which you start the app (typically the project root). All variables
are required:
# Credentials for https://mitarbeiterinfo.coeo.de
USERNAME=your.user
PASSWORD=your-password
# Password for the internal SmartTime Plus web app (username is USERNAME).
# Used to verify that stamps actually reached the backend.
SMART_TIME_PASSWORD=your-smarttime-password
# Optional overrides:
# SMART_TIME_URL=https://zeiterfassung/
# SMART_TIME_VERIFY_INITIAL_DELAY=1 # seconds to wait before the first check
# SMART_TIME_VERIFY_GRACE_PERIOD=30 # total polling budget in seconds
# SMART_TIME_VERIFY_POLL_INTERVAL=3 # seconds between polls
# SMART_TIME_VERIFY_TOLERANCE_SEC=90 # allowed drift between stamp & backing time
# Paths (relative or absolute) to the JSON files used for local persistence.
# The files will be created on first write.
AUTOSTAMP_FILE=autostamps.json
STAMPHISTORY_FILE=timestamp_history.json
# Python logging level, e.g. DEBUG / INFO / WARNING / ERROR
LOG_LEVEL=INFO
⚠️ Credentials are sent to the coeo portal from your local machine. Keep the
.envfile out of version control.
Usage
Start the Streamlit app via the Poetry script:
poetry run stempelbot
This is equivalent to streamlit run stempelbot/app.py. Any extra CLI arguments
are forwarded to Streamlit, e.g.:
poetry run stempelbot --server.port 8502
Then open the URL Streamlit prints (default: http://localhost:8501).
Manual stamp
Click "Jetzt Stempeln" – the client logs in and posts the stamp request; on
success the current time is appended to timestamp_history.json.
Auto-Stamp
- Pick a time in the Auto-Stempel Planer section.
- Click "Hinzufügen" – the entry is written to
autostamps.json. - A background thread (
AutoStampService) checks every 30 seconds and fires the stamp when the current time is within a 2-minute window of the target. Missed entries (older than 2 minutes) are cleaned up automatically.
Because the scheduler runs inside the Streamlit process, the app must be running for auto-stamps to fire.
Daily view
The "Heutiger Verlauf" section lists all of today's stamps split into Kommen (even index) and Gehen (odd index) columns.
How stamping works
CoeoClient in client.py performs two HTTP POSTs against
https://mitarbeiterinfo.coeo.de:
POST /ppeiiefdna_loginwithhttpd_username/httpd_password.POST /index.phpwithc=zeiterfassung&homeoffice=1to toggle the stamp.
The session cookie from step 1 is reused for step 2. HTTP 200 is treated as success.
Because a 200 from the coeo portal does not guarantee the stamp reached the
downstream time-tracking system, SmartTimeClient (see smarttime_client.py)
then uses a headless Chromium (Playwright) to log in to SmartTime Plus
(https://zeiterfassung/), navigate to the dashboard, and read the
"Letzte Buchung" value. If it doesn't match the sent stamp within
SMART_TIME_VERIFY_TOLERANCE_MIN after SMART_TIME_VERIFY_DELAY seconds, the
UI flags the stamp as unverified.
SmartTime Plus is a GWT app (all traffic is text/x-gwt-rpc with per-build
strong-name hashes), so we drive the DOM instead of trying to speak the RPC
protocol. A persistent Chromium profile in ~/.stempelbot/smarttime-profile
keeps session cookies between runs so subsequent verifications skip the login
step.
SmartTime Abgleich panel
Below the daily timeline there's a "SmartTime Abgleich" panel with a "Jetzt prüfen" button that compares the last local stamp with the value reported by SmartTime Plus and highlights any mismatch.
Notes
- Time calculations (net work, break deductions, target end time) live in
time_calculator.py. - Streamlit caches the
CoeoClient(lru_cache) and theAutoStampService(st.cache_resource) so login state and the scheduler thread survive reruns. - This project targets the internal coeo portal and is not affiliated with coeo.