Files
AutoACCT/scripts/append_row.py
Knowit 6110ef4bc2 Initial commit: bookkeeping skill
Receipt-image to Google Sheets expense logger with HKD conversion.
Includes SKILL.md, categories/schema reference, config template,
and Python scripts for FX conversion (frankfurter.app) and Sheets append.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 15:03:06 +08:00

81 lines
2.2 KiB
Python
Executable File

#!/usr/bin/env python3
"""
append_row.py — append one expense row to the configured Google Sheet.
Reads config from ../config.json.
Reads a single JSON object from stdin. Keys (all optional; missing -> ""):
date, merchant, category, amount, currency,
amount_hkd, fx_rate, fx_date, payment_method,
line_items, raw_ocr, note, receipt
`logged_at` is set automatically to now (UTC, ISO-8601).
Prints the updated range on success; exits non-zero on failure.
"""
from __future__ import annotations
import json
import os
import sys
from datetime import datetime, timezone
from pathlib import Path
from google.oauth2.service_account import Credentials
from googleapiclient.discovery import build
CONFIG_PATH = Path(__file__).resolve().parent.parent / "config.json"
COLUMNS = [
"date", "merchant", "category", "amount", "currency",
"amount_hkd", "fx_rate", "fx_date", "payment_method",
"line_items", "raw_ocr", "note", "receipt", "logged_at",
]
def load_config() -> dict:
if not CONFIG_PATH.exists():
sys.exit(
f"config.json not found at {CONFIG_PATH}. "
f"Copy config.example.json to config.json and fill it in."
)
cfg = json.loads(CONFIG_PATH.read_text())
cfg["service_account_path"] = os.path.expanduser(cfg["service_account_path"])
return cfg
def main() -> int:
cfg = load_config()
row = json.loads(sys.stdin.read())
row.setdefault(
"logged_at",
datetime.now(timezone.utc).isoformat(timespec="seconds"),
)
values = [str(row.get(col, "")) for col in COLUMNS]
creds = Credentials.from_service_account_file(
cfg["service_account_path"],
scopes=["https://www.googleapis.com/auth/spreadsheets"],
)
svc = build("sheets", "v4", credentials=creds, cache_discovery=False)
resp = (
svc.spreadsheets()
.values()
.append(
spreadsheetId=cfg["sheet_id"],
range=f"{cfg['worksheet']}!A1",
valueInputOption="USER_ENTERED",
insertDataOption="INSERT_ROWS",
body={"values": [values]},
)
.execute()
)
updated = resp.get("updates", {}).get("updatedRange", "?")
print(f"OK {updated}")
return 0
if __name__ == "__main__":
sys.exit(main())