Add log-work-expense skill to skill index
This commit is contained in:
parent
e667cf0e39
commit
ac1b4f83f3
4 changed files with 161 additions and 0 deletions
|
|
@ -34,6 +34,7 @@ description: Master index of all skills in your robot assistant system. Your ass
|
||||||
| "polish my LinkedIn post," "clean up my LinkedIn draft," "turn this into a LinkedIn post," "edit my LinkedIn post" | **linkedin-post** |
|
| "polish my LinkedIn post," "clean up my LinkedIn draft," "turn this into a LinkedIn post," "edit my LinkedIn post" | **linkedin-post** |
|
||||||
| "use mongodb," "query my database," "add to mongo," "mongo," "database," "docdb" | **mongodb** |
|
| "use mongodb," "query my database," "add to mongo," "mongo," "database," "docdb" | **mongodb** |
|
||||||
| "add this to my book list," "add to my books," "what's on my reading list," "show me my to-read books" | **book-list** |
|
| "add this to my book list," "add to my books," "what's on my reading list," "show me my to-read books" | **book-list** |
|
||||||
|
| "log this expense," "record this receipt," "add this to my work expenses," "mileage for XYZ," "meals for XYZ" | **log-work-expense** |
|
||||||
| "prep for this interview," "prep a tech screen," "prepare for this technical interview," "tech screen prep," "interview prep," "organize my interview feedback," "write up my interview notes," "generate scorecard feedback" | **prep-tech-screen** |
|
| "prep for this interview," "prep a tech screen," "prepare for this technical interview," "tech screen prep," "interview prep," "organize my interview feedback," "write up my interview notes," "generate scorecard feedback" | **prep-tech-screen** |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -166,6 +167,12 @@ description: Master index of all skills in your robot assistant system. Your ass
|
||||||
**File:** `skills/book-list/SKILL.md`
|
**File:** `skills/book-list/SKILL.md`
|
||||||
**Dependencies:** mongodb skill (for database operations)
|
**Dependencies:** mongodb skill (for database operations)
|
||||||
|
|
||||||
|
### Log Work Expense
|
||||||
|
**Purpose:** Log work expenses (meals, mileage, etc.) into MongoDB (`wip.work_expenses`). Classifies receipt/odometer images via vision model, uploads to S2, and inserts via script.
|
||||||
|
**Triggers:** "log this expense," "record this receipt," "add this to my work expenses," "mileage for XYZ," "meals for XYZ"
|
||||||
|
**File:** `skills/log-work-expense/SKILL.md`
|
||||||
|
**Dependencies:** mongodb skill (for database operations), S2 upload endpoint, `classify_image.py` and `log_expense.py` scripts
|
||||||
|
|
||||||
### Prep Tech Screen
|
### Prep Tech Screen
|
||||||
**Purpose:** Two-phase skill for Verkada Enterprise SE peer technical interviews. Phase 1 prepares interview questions by reading the candidate's resume, cross-referencing the Peer Tech Screen Guide, and producing a structured meeting note with checkboxed questions. Phase 2 organizes post-interview feedback into the Greenhouse scorecard format by pulling from the interviewer's voice dictation, meeting notes, and interview transcript (in priority order).
|
**Purpose:** Two-phase skill for Verkada Enterprise SE peer technical interviews. Phase 1 prepares interview questions by reading the candidate's resume, cross-referencing the Peer Tech Screen Guide, and producing a structured meeting note with checkboxed questions. Phase 2 organizes post-interview feedback into the Greenhouse scorecard format by pulling from the interviewer's voice dictation, meeting notes, and interview transcript (in priority order).
|
||||||
**Triggers:** "prep for this interview," "prep a tech screen," "prepare for this technical interview," "tech screen prep," "interview prep," "organize my interview feedback," "write up my interview notes," "generate scorecard feedback"
|
**Triggers:** "prep for this interview," "prep a tech screen," "prepare for this technical interview," "tech screen prep," "interview prep," "organize my interview feedback," "write up my interview notes," "generate scorecard feedback"
|
||||||
|
|
|
||||||
39
log-work-expense/SKILL.md
Normal file
39
log-work-expense/SKILL.md
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
---
|
||||||
|
name: log-work-expense
|
||||||
|
description: Log work expenses into MongoDB. Use when the user sends receipt images or odometer photos along with context, or says things like "log this expense", "record this receipt", "mileage for XYZ", "meals for XYZ", or provides any expense details for work reimbursement tracking. Triggers on any message that includes expense context (account, type, images of receipts or odometers).
|
||||||
|
---
|
||||||
|
|
||||||
|
# Log Work Expense
|
||||||
|
|
||||||
|
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"
|
||||||
|
- "mileage for XYZ", "meals for XYZ"
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. **Classify images** — Run each image through `scripts/classify_image.py <path>` to determine if it is a `receipt` or `odometer`. The script outputs exactly one word.
|
||||||
|
|
||||||
|
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 via script** — Run the script once **per entry**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run --with pymongo ~/notes/skills/log-work-expense/scripts/log_expense.py <type> <account> <date> "<note>" <s2_url_1> [s2_url_2]
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Meal receipts**: Run once per receipt (1 file each).
|
||||||
|
- **Mileage**: Run once with both odometer URLs (2 files).
|
||||||
|
|
||||||
|
The script enforces file counts and rejects invalid input.
|
||||||
|
|
||||||
|
5. **Confirm** — Brief summary of what was logged.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- Use `classify_image.py` for image classification — it calls the vision model and returns only "receipt" or "odometer".
|
||||||
|
- Use the S2 upload endpoint from TOOLS.md for file uploads.
|
||||||
|
- The log script hardcodes MongoDB connection details — do not pass credentials.
|
||||||
53
log-work-expense/scripts/classify_image.py
Normal file
53
log-work-expense/scripts/classify_image.py
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Classify an image as 'receipt' or 'odometer'.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python classify_image.py <image_path>
|
||||||
|
|
||||||
|
Prints exactly one word: "receipt" or "odometer"
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import base64
|
||||||
|
import requests
|
||||||
|
|
||||||
|
API_KEY = "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"
|
||||||
|
MODEL = "google/gemini-2.5-flash-lite"
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
print("Usage: classify_image.py <image_path>", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
path = sys.argv[1]
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
b64 = base64.b64encode(f.read()).decode()
|
||||||
|
|
||||||
|
ext = path.rsplit(".", 1)[-1].lower()
|
||||||
|
mime = "image/jpeg" if ext in ("jpg", "jpeg") else "image/png"
|
||||||
|
|
||||||
|
resp = requests.post(
|
||||||
|
"https://openrouter.ai/api/v1/chat/completions",
|
||||||
|
headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"},
|
||||||
|
json={
|
||||||
|
"model": MODEL,
|
||||||
|
"messages": [{"role": "user", "content": [
|
||||||
|
{"type": "text", "text": "Classify this image. Reply with exactly one word: receipt or odometer. Nothing else."},
|
||||||
|
{"type": "image_url", "image_url": {"url": f"data:{mime};base64,{b64}"}}
|
||||||
|
]}],
|
||||||
|
"max_tokens": 10
|
||||||
|
},
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
|
||||||
|
text = resp.json()["choices"][0]["message"]["content"].strip().lower()
|
||||||
|
if "receipt" in text:
|
||||||
|
print("receipt")
|
||||||
|
elif "odometer" in text:
|
||||||
|
print("odometer")
|
||||||
|
else:
|
||||||
|
print(f"unknown: {text}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
62
log-work-expense/scripts/log_expense.py
Normal file
62
log-work-expense/scripts/log_expense.py
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Log work expenses to MongoDB (wip.work_expenses).
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python log_expense.py <type> <account> <date> <note> <s2_url_1> [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 <type> <account> <date> <note> <s2_url_1> [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()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue