Communitygithub.com

OrrZwebner/conference-reimbursement

Prepares a travel-expense reimbursement claim (החזר הוצאות נסיעה) for an Israeli university after a conference trip abroad: collects receipts from the trip's receipts folder and from email (flight and add-ons, travel insurance, hotel, registration, conference dinner, poster printing, ground transport), saves each as a named PDF, and builds the Hebrew right-to-left expense spreadsheet from its bundled claim template, converting to shekels at Bank of Israel representative rates (שער יציג) and comp

What is conference-reimbursement?

conference-reimbursement is a Claude Code agent skill that prepares a travel-expense reimbursement claim (החזר הוצאות נסיעה) for an Israeli university after a conference trip abroad: collects receipts from the trip's receipts folder and from email (flight and add-ons, travel insurance, hotel, registration, conference dinner, poster printing, ground transport), saves each as a named PDF, and builds the Hebrew right-to-left expense spreadsheet from its bundled claim template, converting to shekels at Bank of Israel representative rates (שער יציג) and comp.

Works with✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/OrrZwebner/conference-reimbursement/tree/HEAD/skills/conference-reimbursement

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

Conference expense reimbursement

Produce two spreadsheets from the same rows, with the same totals, and save every receipt they cite in <conference folder>/החזרים/קבלות/:

  • SENT file <conference folder>/החזרים/<full name> הוצאות.xlsx — the only file that goes to finance. Column B סעיף = budget-form category; receipts sheet; bank block; rates. No budget-vs-actual comparison.
  • INTERNAL file <conference folder>/החזרים/<full name> הוצאות - פנימי.xlsx — the user's local copy, never attached or sent. Column B השירות = what each item was (e.g. "קפה", "רכבת שדה–עיר"), a סעיף category column after הערות, and the budget comparison table.

Both are Hebrew and right-to-left; talk to the user in English. The layout comes from the bundled assets/claim-template.xlsx (labels only); the user never supplies a spreadsheet.

Profile (full name and bank details). Read ~/.config/conference-reimbursement/profile.json ({"full_name": "...", "bank": {"בנק": "...", "סניף": "...", "מספר חשבון": "..."}}). If it is missing or incomplete, ask for the missing fields with the AskUserQuestion tool (the user types the values as their own answer), then save the file with permissions 600. Keep it only there — outside every repository and project folder. Never commit it, never print the bank details in chat or in the report (say "bank details: from profile"), and copy them only into the scratchpad spec's bank key.

Ask every question to the user through the AskUserQuestion tool — never in prose — one decision per question, recommended option first.

Pick the mode

  • Mode A — full claim. The approved budget form PDF, the bank details (profile), the receipts folder and mailbox access are all available. Run steps 1–7.
  • Mode B — partial inputs. One or more of those is missing. Run the same steps and leave the missing part out, never invent it:
    • user declines to give bank details → omit bank; the script writes empty labelled cells and warns "bank details missing — fill by hand"; report it;
    • no mailbox access → use only the files the user puts in קבלות/; report that email was not searched; at step 7 print the email body instead of drafting it;
    • no budget form → omit budget; the INTERNAL file is built without the budget table and there is no comparison with approved amounts.

List every Mode B gap in the report under "to complete by hand".

1. Gather inputs (read-only)

  1. Approved budget. Open the travel budget form PDF in the conference folder (*טופס נסיעות*). Read it yourself; forms differ by institution. Record each approved line in USD (per diem אש״ל, flight טיסה, hotel, insurance, registration, other) and the trip dates.
  2. Profile. Load the full name and bank details as described above (ask only the first time).
  3. Physical receipts. List קבלות/ and read every image (convert HEIC first, step 3). Record date, provider, amount, currency, card last digits and number of diners.
  4. Email receipts. Search the mailbox over booking date → return date plus about two weeks, and the user's travel label or folder. Run the per-category queries together in one turn — read references/email-sources.md first. Look beyond what the user listed: finding unnoticed expenses is part of the job; propose each one found.
  5. Exclude other trips. Open every finance transfer and every flight booking in the window and match conference, route and dates. Record each exclusion for the report.

2. Ask the policy decisions

Ask only those that apply, one decision per question, recommended option first. These defaults come from one past claim; they are suggestions, not institutional rules.

  • Shared hotel booking → own room only (Recommended) / whole booking.
  • Shared meals → full bill / own share only. Flag whichever is chosen for the clerk.
  • Exchange rate → Bank of Israel representative rate of the return day, one rate per currency (Recommended) / the rate of each receipt's date.
  • Shared items (a poster invoice covering two people) → claim the user's share, with a note.
  • Add-ons bought with the flight (seat, bag, cover) → inside the flight line (Recommended).

3. Save receipts as PDFs

  1. Email attachments. If the mail connector returns message text but not attachments, first re-check whether it can now fetch them; if not, download through browser automation (open the message, click the attachment's download icon, then move the file from ~/Downloads). Without browser automation, ask the user to download them or drop them into קבלות/.
  2. Emails with no attachment (order confirmations) → print to PDF.
  3. Name each file <what> <provider> <document number>.pdf, in Hebrew.
  4. HEIC photos → add a PDF copy next to the original, never modify the original. macOS: sips -s format pdf IN.heic --out IN.pdf. Elsewhere use Pillow with pillow-heif if installed, otherwise ask the user to export the photo as PDF or JPEG. For the spreadsheet's receipts sheet, also convert every photo and screenshot to a JPEG named by its receipt base name, in the scratchpad (never over the original): sips -s format jpeg -s formatOptions 70 -Z 1400 IN --out "<receipt base name>.jpg".
  5. Read every saved PDF. Take the amount from the document, not the email text. If they differ, use the document and flag the difference.

4. Build the spreadsheet

  1. Fetch the Bank of Israel representative rates (boi.org.il) for the chosen date for every foreign currency used. Put the date in rates.label: שער יציג בנק ישראל, <dd.mm.yyyy>. Never reuse a rate from memory or from an earlier claim.

  2. Write the spec JSON in the scratchpad. Schema, layout and Mode B behaviour: references/spec-format.md; synthetic worked example: references/example-spec.json. Set bank from the profile. Leave template out: the bundled template is the default.

  3. Row rules:

    • one row per receipt, sorted by date;
    • the amount in exactly one key: its original currency (usd, eur, or an extra code that is also in rates) or ils;
    • receipt = file base name(s) in קבלות/, several joined with +;
    • note explains anything a clerk could question (shared bill, half invoice, item split out of a hotel invoice);
    • line = a budget-line name spelled exactly as in budget; column B of the SENT file shows it (category only, never "coffee" or "dinner" — clerks see approval categories), and the INTERNAL file's סעיף column and budget table use it;
    • service = a short Hebrew description of the item ("קפה", "ארוחת ערב", "רכבת שדה–עיר"); shown only in column B of the INTERNAL file (omitted → the category is shown);
    • meals, including the conference dinner → the per-diem line;
    • restaurant charges on a hotel invoice → their own meal row, hotel row reduced.
    • receipt_images = the JPEGs from step 3 (this trip's photos and screenshots only). They go on sheet קבלות, 5 per row, each with a caption cell holding its file base name. Emailed PDFs are not embedded; they go in the zip at step 7.
  4. Run:

    python3 -I "${CLAUDE_PLUGIN_ROOT}/skills/conference-reimbursement/scripts/build_claim.py" spec.json
    

    One run writes both files. It prints JSON with output (SENT), output_internal, total_ILS, total_USD and warnings. Fix every warning about budget lines; carry the Mode B warnings into the report. output_internal defaults to the SENT path with - פנימי before .xlsx; set it in the spec only to put the local copy elsewhere.

  5. The script refuses to overwrite either output unless "overwrite": true. Never set it on a file the user edited by hand; write a new name or ask.

5. Verify

Done means run and checked. Read references/pitfalls.md first.

  1. Run the checks:

    python3 -I "${CLAUDE_PLUGIN_ROOT}/skills/conference-reimbursement/scripts/check_claim.py" spec.json
    
    • total / total_internal: the script's Python total equals the סה״כ בש״ח cell of each file after LibreOffice recalculation (to 0.01 ₪), so both files carry the same total;
    • sent_no_budget: the SENT file holds no budget comparison table;
    • receipts: every cited file exists in קבלות/;
    • budget: every row's line is a budget line and the INTERNAL file's budget table actual total equals total_ILS / USD rate.

    Report a not_run check as not run, never as passed.

  2. Independent audit: start a subagent with only the SENT spreadsheet and the receipts folder — not your conclusions. Ask it to check every row's amount, currency, date and file against the receipt and to list anything a finance clerk might question. Fix real errors; report the rest.

6. Report

In this order:

  1. Both paths (SENT, to attach; INTERNAL, local only), row count, total ₪ and ≈ USD versus the approved total.
  2. Receipts added (and which were found by searching beyond the user's list).
  3. Rates used, with source and date.
  4. Over-budget lines, as a table: line, approved, actual, difference.
  5. Discrepancies between sources (email versus document).
  6. Exclusions (items belonging to other trips).
  7. Clerk flags (from references/pitfalls.md).
  8. To complete by hand (Mode B gaps, script warnings).
  9. Decisions made without asking.

7. Draft the email to the finance contact

Read references/email-draft.md first; follow it in order. Use and attach only the SENT file (output, <full name> הוצאות.xlsx); never attach, upload or quote the INTERNAL - פנימי file — email_summary.py refuses it. In short:

  1. Read the user's previous claim thread with the finance contact (Sent mail, about two years back, replies included) and copy its style and subject pattern.
  2. Ask: bank details in or out; budget or rate lines (default: neither); BCC a co-traveller (default: no; never invent an address).
  3. scripts/zip_ereceipts.py spec.json → קבלות אלקטרוניות.zip of the e-receipt PDFs (UTF-8 names; never the macOS zip command). Photos and screenshots are in sheet קבלות instead.
  4. scripts/email_summary.py spec.json … → the RTL HTML body ("היי ," / "מה שלומך? :)" … "תודה רבה!"), the summary table with a קבלה column saying where each receipt is, and the bank details read from the spreadsheet.
  5. Check its total_ILS equals the build's; mention any rounding_diff.
  6. Gmail create_draft with htmlBody first. Then attach the SENT spreadsheet and the zip through Claude in Chrome (file_upload on the draft's compose file input) and confirm both chips — the connector takes attachments only as base64 in the call, and update_draft drops them. Never send. Give the user the draft link.

Formulas

  • Row: ILS_i = amount_i × rate[cur_i], or the ₪ amount itself.
  • Total: T = Σ ILS_i; in dollars T / rate[USD].
  • Per budget line ℓ (INTERNAL file): actual_ℓ = Σ_{line_i = ℓ} ILS_i / rate[USD]; diff_ℓ = approved_ℓ − actual_ℓ, negative means over budget. Σ_ℓ actual_ℓ = T / rate[USD] when every row's line is in the budget.

Example: rates USD 4.00, EUR 4.50; registration $500, hotel €600, taxi ₪200 → 2,000 + 2,700 + 200 = 4,900 ₪ = $1,225.

Judgment calls

  • Hotel's own invoice beats the booking-site confirmation (it is the tax document).
  • Bank details come from the local profile into the spec's bank key; the script writes them by label. Ask for them only through AskUserQuestion, never in prose, and never echo them.
  • An earlier partial reimbursement that belongs to this trip → mark those rows as already claimed.
  • The user's institution's rules decide policy questions; ask rather than assert a rule.

References

  • references/email-sources.md — per-category mailbox queries and what to check (step 1.4).
  • references/spec-format.md — spec keys, both files' layouts, script output, Mode B behaviour (step 4).
  • references/example-spec.json — synthetic complete spec (step 4).
  • references/pitfalls.md — failures seen before and the clerk-flag checklist (steps 5–6).
  • references/email-draft.md — the email draft: previous thread, wording, attachments (step 7).

Related Skills