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).
  • Weekly Jira review – a separate Streamlit page shows one likely task per day, its Jira activity, and editable module/project/use-case/phase recommendations.
  • Excel export – fills the provided workbook template with every day's complete net stamped time after legal break deductions. Optional Azure OpenAI classification is used when configured; a local deterministic fallback remains available.

Project layout

stempelbot/
    Stempelbot.py      # Streamlit UI
    cli.py             # `stempelbot` entry point → runs `streamlit run Stempelbot.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
    jira_client.py     # Jira Cloud activity retrieval and normalization
    weekly_report.py   # Weekly aggregation and task recommendations
    excel_export.py    # Template-preserving XLSX export
    pages/             # Weekly Jira & Excel Streamlit page
    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). The core stamping variables shown first are required; Jira, Azure OpenAI, and workbook overrides are optional:

# 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_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

# Optional: weekly Jira reporting (Jira Cloud API token authentication)
# Use the tenant origin only; browser paths are normalized automatically.
JIRA_URL=https://your-company.atlassian.net
JIRA_EMAIL=you@example.com
JIRA_API_TOKEN=your-jira-api-token
# Optional for scoped API tokens; normally discovered automatically after a 401.
# JIRA_CLOUD_ID=00000000-0000-0000-0000-000000000000
# Optional custom JQL. {start} and {end} are replaced with ISO dates; end is exclusive.
# JIRA_JQL=project in (ABC, XYZ) AND updated >= "{start}" AND updated < "{end}"
# JIRA_TIMEOUT_SEC=30
# JIRA_VERIFY_TLS=true

# Optional: improve the daily classification with an Azure OpenAI deployment
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com
AZURE_OPENAI_API_KEY=your-azure-openai-key
AZURE_OPENAI_DEPLOYMENT=gpt-5.6-luna
# AZURE_OPENAI_API_VERSION=2025-04-01-preview

# Optional workbook defaults
TIMETRACKING_NAME=Firstname Lastname
# TIMETRACKING_TEMPLATE_FILE=C:\path\to\template.xlsx
# TIMETRACKING_EXPORT_LOCATION=C:\path\to\weekly\exports

⚠️ Credentials are sent to the coeo portal from your local machine. Keep the .env file out of version control.

Creating a Jira API token

This integration uses an Atlassian API token, not your Jira password. The token is used with your Atlassian account email through HTTP Basic authentication and only reads Jira data.

  1. Sign in to Atlassian account security.

  2. Select Create API token.

  3. Choose either:

    • API token (unscoped/classic) for the simplest setup; or
    • API token with scopes to restrict the token to Jira read access.
  4. Give it a descriptive name such as stempelbot, select an expiration date, and—if creating a scoped token—select Jira as the app and grant the read scopes listed below.

  5. Create and immediately copy the token. Atlassian only displays it once.

  6. Add your Jira tenant origin, Atlassian account email, and token to .env:

    JIRA_URL=https://your-company.atlassian.net
    JIRA_EMAIL=you@example.com
    JIRA_API_TOKEN=paste-the-token-here
    

For a scoped token, grant read access for:

  • your current user/profile;
  • issues and issue details;
  • issue changelogs;
  • comments;
  • worklogs;
  • projects, users, fields, statuses, and issue types used by search/JQL.

In Atlassian's scope selector these are typically represented by scopes such as read:me, read:issue:jira, read:issue-details:jira, read:issue.changelog:jira, read:comment:jira, read:issue-worklog:jira, read:project:jira, read:user:jira, read:field:jira, read:status:jira, read:issue-type:jira, and read:jql:jira. Atlassian may group or rename individual granular scopes over time; no write or administration scope is required. If offered, the classic Jira scope read:jira-work covers the required Jira work data.

The Jira user must also have normal Jira permissions to browse the relevant projects and view their comments and worklogs. An API token cannot grant access that the user does not already have.

For scoped tokens, the app automatically discovers the tenant Cloud ID and switches to Atlassian's scoped-token gateway. JIRA_CLOUD_ID normally does not need to be set.

If authentication fails:

  • verify that JIRA_EMAIL is the email belonging to the account that created the token;
  • set JIRA_URL to the tenant origin, not a board or project URL;
  • check that the token has not expired or been revoked;
  • confirm that the scoped token includes the Jira read scopes above; and
  • restart the Streamlit process after changing .env.

Usage

Start the Streamlit app via the Poetry script:

poetry run stempelbot

This is equivalent to streamlit run stempelbot/Stempelbot.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

  1. Pick a time in the Auto-Stempel Planer section.
  2. Click "Hinzufügen" – the entry is written to autostamps.json.
  3. 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.

Weekly Jira & Excel export

Open Weekly Jira & Excel in Streamlit's page navigation, choose any date in the desired ISO week, and click Load Jira activity & recommendations. The page:

  1. loads issues created, assigned, reported, or worklogged by the configured Jira user;
  2. retains authored comments, changelog entries, worklogs, and relevant assigned updates;
  3. calculates net time from complete local stamp pairs for each day;
  4. recommends one workbook classification and one short task summary per day;
  5. allows every classification and summary to be edited before exporting the workbook.

When TIMETRACKING_EXPORT_LOCATION is configured, the workbook is saved directly to that directory. If it is unset or empty, the page provides a browser download button instead.

The full net duration for a day is placed on exactly one row, so no stamped minute is split or omitted. Incomplete historical stamp pairs are not guessed: the open pair is ignored and visibly flagged for review. Jira and Azure credentials are optional for normal stamping; without Jira, the weekly page still loads local time with editable fallback rows. Azure receives only the selected week's normalized Jira metadata and net minutes, and is skipped entirely when its settings are absent.

The output is a new in-memory .xlsx; the source template is never overwritten. Dropdowns are restored as portable Excel validations after export, and formulas/styles, merged cells, sheet names, and print layout are retained. Formula recalculation is requested when the generated workbook is opened in Excel.

Both regular and scoped Atlassian API tokens are supported. Regular tokens use the tenant REST URL. If that URL returns HTTP 401, the client discovers the tenant Cloud ID and retries through https://api.atlassian.com/ex/jira/{cloudId} as required for scoped tokens. Set JIRA_CLOUD_ID only if automatic discovery is unavailable.

How stamping works

CoeoClient in client.py performs two HTTP POSTs against https://mitarbeiterinfo.coeo.de:

  1. POST /ppeiiefdna_login with httpd_username / httpd_password.
  2. POST /index.php with c=zeiterfassung&homeoffice=1 to 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_SEC after SMART_TIME_VERIFY_INITIAL_DELAY seconds, the UI flags the stamp as unverified.

Each verification performs one SmartTime check. That check waits up to 30 seconds for the asynchronously rendered login form or complete dashboard values; it does not retry a failed verification.

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 the AutoStampService (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.
S
Description
No description provided
Readme
351 KiB
Languages
Python 100%