odoo-timesheets/AGENTS.md
Jef Roosens 267ad5b1b5
feat(cli): add flexible positional day argument
- Add parse_date_arg() to utils.py supporting YYYY-MM-DD, MM-DD, and
  DD-MM formats with either - or / as separator
- Add AmbiguousDateError for two-part dates valid as both MM-DD and DD-MM
- Replace --day flag with a positional optional argument (defaults to today)
- Remove old _parse_date() helper from cli.py
2026-06-02 09:31:04 +02:00

4.5 KiB

Timesheets — Agent Guide

Project overview

A Python CLI tool that parses markdown pipe-delimited timesheet tables and exports them to CSV for import into Odoo (or similar tools). It also supports a human-readable summary view and can fetch notes directly from Joplin.

Package layout

timesheets/
├── pyproject.toml                  # package metadata, entry point, dev dependencies
├── AGENTS.md
└── src/timesheets/
    ├── cli.py        # argument parsing, main() entry point
    ├── parser.py     # markdown table parsing, aggregation, date filtering
    ├── projects.py   # project_map.json loading and key resolution
    ├── output.py     # CSV writing and summary printing
    ├── joplin.py     # Joplin API integration (notebook traversal, note fetching)
    └── utils.py      # shared low-level helpers (duration parsing, formatting, etc.)

Tests live in tests/, one file per source module:

tests/
├── test_utils.py
├── test_parser.py
├── test_projects.py
├── test_output.py
└── test_joplin.py

Package manager — uv

All dependency management and script execution is done via uv. Do not use pip or python directly.

Task Command
Install / sync dependencies uv sync
Add a runtime dependency uv add <package>
Add a dev-only dependency uv add --dev <package>
Run the CLI uv run timesheets <args>
Run any Python script uv run python <script>

CLI usage

# Print CSV to stdout (date defaults to today)
uv run timesheets --input input.md

# Specify a day (positional, accepts YYYY-MM-DD, MM-DD, or DD-MM; - or / separator)
uv run timesheets 2026-05-22 --input input.md
uv run timesheets 05-22 --input input.md

# Write CSV to a file
uv run timesheets --input input.md -o output.csv

# Override the day (accepts YYYY-MM-DD, MM-DD, or DD-MM; - or / separator)
uv run timesheets --input input.md --day 2026-05-22
uv run timesheets --input input.md --day 05-22

# Use a specific project map file
uv run timesheets --input input.md --map /path/to/project_map.json

# Print a human-readable summary instead of CSV
uv run timesheets --input input.md --summary

# Read from stdin
cat input.md | uv run timesheets --input -

# Fetch today's entries from Joplin (token via env var)
JOPLIN_TOKEN=your_token uv run timesheets --joplin

# Fetch entries for a specific day from Joplin
uv run timesheets 2026-05-22 --joplin --token your_token

The --joplin flag and the file input argument are mutually exclusive. When --joplin is used, only entries matching the target day (positional arg, or today) are returned, filtered by the # ... YYYY-MM-DD day heading in the note.

The API token can be provided via:

  • --token <token> CLI flag
  • JOPLIN_TOKEN environment variable

project_map.json is auto-discovered in the current working directory if --map is not provided.


Joplin notebook structure

The --joplin flag expects the following notebook hierarchy in Joplin:

Work/
└── Timesheets/
    └── YYYY/
        └── YYYY - WNN/         ← notebook per week
            └── YYYY - WNN      ← note with the same title as the notebook

The note body contains one markdown table per day, each preceded by a heading of the form # <weekday> - YYYY-MM-DD.


Testing

The test suite uses pytest with pytest-cov for coverage reporting.

# Run all tests
uv run pytest

# Run with coverage report
uv run pytest --cov

# Run a specific test file
uv run pytest tests/test_parser.py

# Run a specific test
uv run pytest tests/test_parser.py::TestParseTable::test_empty_input

Rules for adding or changing functionality

  1. Always update or add tests when introducing new behaviour or modifying existing behaviour. Tests live in the tests/ file that corresponds to the module being changed (e.g. changes to parser.pytests/test_parser.py).

  2. Run the full test suite before finishing and confirm it passes with no failures:

    uv run pytest --cov
    
  3. Do not reduce coverage. Every new function or branch should have at least one test covering the happy path. Edge cases and error paths should be covered where the logic is non-trivial.

  4. cli.py is intentionally excluded from unit tests — it is thin glue code. All logic worth testing belongs in the other modules.

  5. Joplin integration tests in test_joplin.py must mock ClientApi — do not require a live Joplin instance.