From 33081523a152f70f1d72736c35069a50d8eb93e2 Mon Sep 17 00:00:00 2001 From: Connor Rhodes Date: Wed, 29 Apr 2026 02:17:48 +0000 Subject: [PATCH] Add log_expense.py script with validation; simplify skill --- log-work-expense/SKILL.md | 43 ++++++----------- log-work-expense/scripts/log_expense.py | 62 +++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 29 deletions(-) create mode 100644 log-work-expense/scripts/log_expense.py diff --git a/log-work-expense/SKILL.md b/log-work-expense/SKILL.md index fbb6a7f..f9370ed 100644 --- a/log-work-expense/SKILL.md +++ b/log-work-expense/SKILL.md @@ -5,51 +5,36 @@ description: Log work expenses into MongoDB. Use when the user sends receipt ima # Log Work Expense -Log work expenses into the `wip.work_expenses` collection in MongoDB. +Log work expenses into `wip.work_expenses` via `scripts/log_expense.py`. ## Trigger Phrases -- "log this expense" -- "record this receipt" -- "add this to my work expenses" -- "expense entry" -- "mileage for XYZ" -- "meals for XYZ" +- "log this expense", "record this receipt", "add this to my work expenses" +- "mileage for XYZ", "meals for XYZ" ## Steps -1. **Classify images** — If the user sends multiple images of different types (e.g. receipts AND odometer photos), use the vision workaround from TOOLS.md to classify each image as either `"receipt"` or `"odometer"`. This is the ONLY reason to analyze images — to determine which type they are so they get grouped correctly. Do not extract amounts, dates, or other details from images. +1. **Classify images** — Use the vision workaround from TOOLS.md to classify each image as `"receipt"` or `"odometer"`. Do not extract amounts or other details from images. -2. **Determine type and account from the user's message** — The user will tell you the expense type (e.g. "mileage", "meals", "mileage and meals") and the account (e.g. "LTISD"). If either is unclear, ask. Do not infer type or account from images. +2. **Determine type and account** — The user provides the expense type and account (e.g. "meals and mileage for LTISD"). Ask if unclear. 3. **Upload images to S2** — Upload all images to S2 and collect the URLs. -4. **Insert into MongoDB** — Group images by type and insert documents: +4. **Insert via script** — Run the script once **per entry**: -```json -{ - "files": ["https://s2.connorrhodes.com/agent/{sha256}.{ext}"], - "date": "YYYY-MM-DD", - "type": "meal", - "account": "LTISD", - "note": "Context from user's message", - "status": "todo" -} +```bash +uv run --with pymongo ~/notes/skills/log-work-expense/scripts/log_expense.py "" [s2_url_2] ``` -- **files**: Array of S2 URLs. 1 item for meal receipts, 2 items for mileage (start/end odometer). -- **date**: Today's date, formatted as YYYY-MM-DD. -- **type**: Expense type from the user's message (e.g. `"meal"`, `"mileage"`). -- **account**: Account from the user's message (e.g. `"LTISD"`). -- **note**: The context the user provided (e.g. "meals during LTISD CBR onsite"). -- **status**: Always `"todo"`. +- **Meal receipts**: Run once per receipt (1 file each). +- **Mileage**: Run once with both odometer URLs (2 files). -If the user sends both mileage and meals, create two separate entries — one for each type. +The script enforces file counts and rejects invalid input. -5. **Confirm** — Reply with a brief summary of what was logged. +5. **Confirm** — Brief summary of what was logged. ## Notes -- Use `uv run --with pymongo` for MongoDB scripts. -- Use the S2 upload endpoint from TOOLS.md for file uploads. - Use the vision workaround from TOOLS.md for image classification only. +- Use the S2 upload endpoint from TOOLS.md for file uploads. +- The script hardcodes MongoDB connection details — do not pass credentials. diff --git a/log-work-expense/scripts/log_expense.py b/log-work-expense/scripts/log_expense.py new file mode 100644 index 0000000..4b6ac80 --- /dev/null +++ b/log-work-expense/scripts/log_expense.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Log work expenses to MongoDB (wip.work_expenses). + +Usage: + python log_expense.py [s2_url_2] + + type: "meal" or "mileage" + account: e.g. "LTISD" + date: YYYY-MM-DD + note: free-text context (quote if it has spaces) + s2_url: one or more S2 URLs of uploaded files + +Rules enforced: + - meal: exactly 1 file per invocation + - mileage: exactly 2 files per invocation (start + end odometer) +""" + +import sys +from pymongo import MongoClient + +def main(): + args = sys.argv[1:] + if len(args) < 5: + print("Usage: log_expense.py [s2_url_2]") + sys.exit(1) + + exp_type = args[0] + account = args[1] + date = args[2] + note = args[3] + files = args[4:] + + if exp_type not in ("meal", "mileage"): + print(f"Error: type must be 'meal' or 'mileage', got '{exp_type}'") + sys.exit(1) + + if exp_type == "meal" and len(files) != 1: + print(f"Error: meal entries must have exactly 1 file, got {len(files)}") + sys.exit(1) + + if exp_type == "mileage" and len(files) != 2: + print(f"Error: mileage entries must have exactly 2 files (start + end odometer), got {len(files)}") + sys.exit(1) + + client = MongoClient("mongodb://root:3wwfoUjyk2E2zWELXFlLuHqfw1ALlOp4pb2H5Vq3TImbMIHL2h1u8Jej2mjzCPl@docdb.connorrhodes.com:35563") + db = client["wip"] + col = db["work_expenses"] + + doc = { + "files": files, + "date": date, + "type": exp_type, + "account": account, + "note": note, + "status": "todo" + } + + result = col.insert_one(doc) + print(f"Inserted {exp_type} for {account} on {date}: {result.inserted_id}") + +if __name__ == "__main__": + main()