A web application for importing and analyzing ODS (OpenOffice Calc) timesheet files. Built for Project Managers and Team Leads to understand team workload and estimate task sizing.
make build # build Docker images
make run # start at http://localhost:8080
make stop # stop containers (data is preserved)
make clean # stop + delete database volume- Open http://localhost:8080
- Go to Import → drag & drop or click to upload an
.odstimesheet file - Go to Reports → select a report type, set date range, apply filters → Generate Report
Re-uploading the same file replaces its previous data (upsert by file hash). The database persists across container restarts.
| Report | Description |
|---|---|
| By Person | Total hours per team member |
| By Project | Total hours per project |
| By Work Type | Hours breakdown: management / investigation / implementation / documentation |
| Task Size | S/M/L/XL classification based on hours per task per person |
| Periodic | Monthly or weekly pivot table (person or project rows) |
| Utilization | Workload % by hours and task count |
All reports support: date range, project filter, person filter, work type filter, and an option to exclude [Meta] Management entries (calls/meetings).
| Size | Hours | Work Days |
|---|---|---|
| S | ≤ 8h | ≤ 1 day |
| M | ≤ 16h | ≤ 2 days |
| L | ≤ 40h | ≤ 5 days |
| XL | > 40h | > 5 days |
The importer processes sheets whose names match a month+year pattern:
- English:
March 2026,Mar 2026 - Ukrainian:
Березень 2026
A Legend sheet (name contains "legend") is optional. When present, its first two columns
(nick, full_name) override the names found in timesheet rows.
Other sheets (summary, config, etc.) are ignored.
The importer auto-detects column positions from header names (case-insensitive, Ukrainian and English aliases). If no header row is found, columns are inferred from cell content automatically.
Required columns: date, hours, assignee.
Optional columns: project, role, work_type, task_name, description, artifacts, celoxis_key.
All three formats are supported:
| Format | Example |
|---|---|
[nick] Full Name |
[alice] Alice Smith |
Full Name [nick] |
Alice Smith [alice] |
| Plain name (no brackets) | Alice Smith → nick auto-derived as alice_smith |
| Format | Example |
|---|---|
DD.MM.YYYY |
15.03.2026 — most common in UA/EU |
YYYY-MM-DD |
2026-03-15 — ISO 8601 |
DD/MM/YYYY |
15/03/2026 |
MM/DD/YYYY |
03/15/2026 — US format |
DD-MM-YYYY |
15-03-2026 |
DD.MM.YY |
15.03.26 — two-digit year |
Month DD, YYYY |
March 15, 2026 |
DD Month YYYY |
15 March 2026 |
| Excel serial number | numeric cell value |
Various YYYY/… variants |
2026/03/15, 2026/15/03 |
If a task cell contains an embedded hyperlink (LibreOffice clickable link), the URL is extracted
automatically. Plain-text URLs (https://...) and Phabricator-style refs (T123) in the task
name are also extracted and stored separately — the URL is stripped from the display name.
Project name is resolved in this order per row:
- Explicit
projectcolumn value - Content-based detection on the row
- Sheet name — only if it is not a month-period name
- ODS filename stem
# Backend (requires Python 3.10+)
cd backend && pip install -r requirements.txt
uvicorn main:app --reload --port 8000
# Frontend (requires Node 20+)
cd frontend && npm install
npm run dev # runs at http://localhost:5173, proxies /api/ to localhost:8000- Backend: Python 3.10+, FastAPI, SQLAlchemy 2, SQLite, odfpy
- Frontend: React 18, Vite 5, TypeScript, Tailwind CSS 3, Recharts, Zustand
- Deployment: Docker Compose, nginx (reverse proxy + SPA routing)