first ruggedy draft
This commit is contained in:
250
apps/local-agent/mousehold_agent/cli.py
Normal file
250
apps/local-agent/mousehold_agent/cli.py
Normal file
@@ -0,0 +1,250 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from .csv_importer import read_transaction_csv
|
||||
from .fints_connector import (
|
||||
account_to_dict,
|
||||
config_from_args,
|
||||
date_range_from_args,
|
||||
display_accounts,
|
||||
display_transactions,
|
||||
fetch_transactions,
|
||||
list_accounts,
|
||||
)
|
||||
from .http import post_json
|
||||
from .imap_importer import fetch_imap_observations
|
||||
from .server import run as run_local_agent_server
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(prog="mousehold-agent")
|
||||
parser.add_argument("--api-url", default="http://127.0.0.1:8000")
|
||||
subcommands = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
register = subcommands.add_parser("register")
|
||||
register.add_argument("--household-id", required=True)
|
||||
register.add_argument("--owner-user-id", required=True)
|
||||
register.add_argument("--display-name", default="Local connector agent")
|
||||
|
||||
csv_sync = subcommands.add_parser("sync-csv")
|
||||
csv_sync.add_argument("--connection-id", required=True)
|
||||
csv_sync.add_argument("--household-id", required=True)
|
||||
csv_sync.add_argument("--source-type", choices=["bank_csv", "paypal_csv"], required=True)
|
||||
csv_sync.add_argument("--path", type=Path, required=True)
|
||||
csv_sync.add_argument("--source-account-id")
|
||||
csv_sync.add_argument("--owner-user-id")
|
||||
|
||||
imap_sync = subcommands.add_parser("sync-imap")
|
||||
imap_sync.add_argument("--connection-id", required=True)
|
||||
imap_sync.add_argument("--household-id", required=True)
|
||||
imap_sync.add_argument("--owner-user-id")
|
||||
imap_sync.add_argument("--source-account-id")
|
||||
imap_sync.add_argument("--limit", type=int, default=25)
|
||||
|
||||
demo = subcommands.add_parser("sync-demo")
|
||||
demo.add_argument("--connection-id", required=True)
|
||||
demo.add_argument("--household-id", required=True)
|
||||
demo.add_argument("--owner-user-id")
|
||||
demo.add_argument("--source-account-id")
|
||||
|
||||
serve = subcommands.add_parser("serve")
|
||||
serve.add_argument("--host", default="127.0.0.1")
|
||||
serve.add_argument("--port", type=int, default=8765)
|
||||
|
||||
fints_accounts = subcommands.add_parser("fints-accounts")
|
||||
_add_fints_common_args(fints_accounts)
|
||||
fints_accounts.add_argument("--with-balances", action="store_true")
|
||||
fints_accounts.add_argument("--json", action="store_true")
|
||||
|
||||
fints_transactions = subcommands.add_parser("fints-transactions")
|
||||
_add_fints_common_args(fints_transactions)
|
||||
_add_fints_transaction_args(fints_transactions)
|
||||
fints_transactions.add_argument("--json", action="store_true")
|
||||
|
||||
fints_sync = subcommands.add_parser("sync-fints")
|
||||
_add_fints_common_args(fints_sync)
|
||||
_add_fints_transaction_args(fints_sync)
|
||||
fints_sync.add_argument("--connection-id", required=True)
|
||||
fints_sync.add_argument("--household-id", required=True)
|
||||
fints_sync.add_argument("--owner-user-id")
|
||||
fints_sync.add_argument("--source-account-id")
|
||||
fints_sync.add_argument("--display", action="store_true")
|
||||
|
||||
args = parser.parse_args()
|
||||
if args.command == "register":
|
||||
result = post_json(
|
||||
f"{args.api_url}/local-agent/register",
|
||||
{
|
||||
"household_id": args.household_id,
|
||||
"owner_user_id": args.owner_user_id,
|
||||
"display_name": args.display_name,
|
||||
},
|
||||
)
|
||||
elif args.command == "sync-csv":
|
||||
observations = read_transaction_csv(
|
||||
args.path,
|
||||
household_id=args.household_id,
|
||||
source_type=args.source_type,
|
||||
source_account_id=args.source_account_id,
|
||||
owner_user_id=args.owner_user_id,
|
||||
)
|
||||
result = _sync(args.api_url, args.connection_id, observations)
|
||||
elif args.command == "sync-imap":
|
||||
observations = fetch_imap_observations(
|
||||
household_id=args.household_id,
|
||||
owner_user_id=args.owner_user_id,
|
||||
source_account_id=args.source_account_id,
|
||||
limit=args.limit,
|
||||
)
|
||||
result = _sync(args.api_url, args.connection_id, observations)
|
||||
elif args.command == "sync-demo":
|
||||
result = _sync(
|
||||
args.api_url,
|
||||
args.connection_id,
|
||||
_demo_observations(args.household_id, args.owner_user_id, args.source_account_id),
|
||||
)
|
||||
elif args.command == "serve":
|
||||
run_local_agent_server(host=args.host, port=args.port)
|
||||
return
|
||||
elif args.command == "fints-accounts":
|
||||
accounts = list_accounts(config_from_args(args), include_balances=args.with_balances)
|
||||
if args.json:
|
||||
result = accounts
|
||||
else:
|
||||
display_accounts(accounts)
|
||||
return
|
||||
elif args.command == "fints-transactions":
|
||||
start_date, end_date = date_range_from_args(args)
|
||||
fetched = fetch_transactions(
|
||||
config_from_args(args),
|
||||
iban=args.iban,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
include_pending=args.include_pending,
|
||||
)
|
||||
if args.json:
|
||||
result = {
|
||||
"account": account_to_dict(fetched.account),
|
||||
"observations": fetched.observations,
|
||||
}
|
||||
else:
|
||||
display_transactions(fetched.observations)
|
||||
return
|
||||
elif args.command == "sync-fints":
|
||||
start_date, end_date = date_range_from_args(args)
|
||||
fetched = fetch_transactions(
|
||||
config_from_args(args),
|
||||
iban=args.iban,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
include_pending=args.include_pending,
|
||||
household_id=args.household_id,
|
||||
source_account_id=args.source_account_id,
|
||||
owner_user_id=args.owner_user_id,
|
||||
)
|
||||
if args.display:
|
||||
display_transactions(fetched.observations)
|
||||
result = _sync(args.api_url, args.connection_id, fetched.observations)
|
||||
else:
|
||||
raise AssertionError(args.command)
|
||||
print(json.dumps(result, indent=2, sort_keys=True))
|
||||
|
||||
|
||||
def _sync(api_url: str, connection_id: str, observations: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
return post_json(
|
||||
f"{api_url}/observations/sync",
|
||||
{
|
||||
"connection_id": connection_id,
|
||||
"observations": observations,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _add_fints_common_args(parser: argparse.ArgumentParser) -> None:
|
||||
parser.add_argument("--profile", default="default")
|
||||
parser.add_argument("--state-dir", default="~/.config/mousehold/fints")
|
||||
parser.add_argument("--no-state", action="store_true")
|
||||
parser.add_argument("--no-bootstrap", action="store_true")
|
||||
parser.add_argument("--blz", help="Bank identifier/BLZ. Env fallback: MOUSEHOLD_FINTS_BLZ")
|
||||
parser.add_argument("--server", help="FinTS endpoint URL. Env fallback: MOUSEHOLD_FINTS_SERVER")
|
||||
parser.add_argument("--bank-user-id", help="FinTS login/user ID. Env fallback: MOUSEHOLD_FINTS_USER_ID")
|
||||
parser.add_argument("--customer-id", help="Optional customer ID. Env fallback: MOUSEHOLD_FINTS_CUSTOMER_ID")
|
||||
parser.add_argument("--pin", help="FinTS PIN. Prefer prompt or MOUSEHOLD_FINTS_PIN over shell history.")
|
||||
parser.add_argument("--pin-env", default="MOUSEHOLD_FINTS_PIN")
|
||||
parser.add_argument("--tan-mechanism", help="Optional TAN mechanism. Env fallback: MOUSEHOLD_FINTS_TAN_MECHANISM")
|
||||
parser.add_argument("--tan-medium", help="Optional TAN medium name. Env fallback: MOUSEHOLD_FINTS_TAN_MEDIUM")
|
||||
parser.add_argument("--product-id", help="Registered FinTS product ID. Env fallback: MOUSEHOLD_FINTS_PRODUCT_ID")
|
||||
parser.add_argument(
|
||||
"--product-version",
|
||||
help="Optional product version. Env fallback: MOUSEHOLD_FINTS_PRODUCT_VERSION",
|
||||
)
|
||||
|
||||
|
||||
def _add_fints_transaction_args(parser: argparse.ArgumentParser) -> None:
|
||||
parser.add_argument("--iban", help="IBAN to fetch. Defaults to the first account returned by the bank.")
|
||||
parser.add_argument("--days", type=int, default=30)
|
||||
parser.add_argument("--start-date", help="YYYY-MM-DD")
|
||||
parser.add_argument("--end-date", help="YYYY-MM-DD")
|
||||
parser.add_argument("--include-pending", action="store_true")
|
||||
|
||||
|
||||
def _demo_observations(
|
||||
household_id: str,
|
||||
owner_user_id: str | None,
|
||||
source_account_id: str | None,
|
||||
) -> list[dict[str, Any]]:
|
||||
today = date.today().isoformat()
|
||||
return [
|
||||
{
|
||||
"household_id": household_id,
|
||||
"source_type": "bank_csv",
|
||||
"source_account_id": source_account_id,
|
||||
"owner_user_id": owner_user_id,
|
||||
"source_reference": "demo-bank-001",
|
||||
"provider_transaction_id": "demo-bank-001",
|
||||
"amount": "42.50",
|
||||
"currency": "EUR",
|
||||
"event_date": today,
|
||||
"merchant": "REWE",
|
||||
"description": "Groceries",
|
||||
"event_type": "expense",
|
||||
"status": "confirmed",
|
||||
},
|
||||
{
|
||||
"household_id": household_id,
|
||||
"source_type": "paypal_email",
|
||||
"source_account_id": source_account_id,
|
||||
"owner_user_id": owner_user_id,
|
||||
"source_reference": "demo-paypal-email-001",
|
||||
"message_id": "demo-paypal-email-001",
|
||||
"amount": "18.99",
|
||||
"currency": "EUR",
|
||||
"event_date": today,
|
||||
"due_date": today,
|
||||
"merchant": "PayPal Merchant",
|
||||
"description": "PayPal purchase announced by email",
|
||||
"event_type": "promised_payment",
|
||||
"status": "outstanding",
|
||||
},
|
||||
{
|
||||
"household_id": household_id,
|
||||
"source_type": "klarna_email",
|
||||
"source_account_id": source_account_id,
|
||||
"owner_user_id": owner_user_id,
|
||||
"source_reference": "demo-klarna-email-001",
|
||||
"message_id": "demo-klarna-email-001",
|
||||
"amount": "84.00",
|
||||
"currency": "EUR",
|
||||
"event_date": today,
|
||||
"due_date": today,
|
||||
"merchant": "Klarna Shop",
|
||||
"description": "Klarna pay-later obligation",
|
||||
"event_type": "promised_payment",
|
||||
"status": "outstanding",
|
||||
},
|
||||
]
|
||||
Reference in New Issue
Block a user