freightdesk-data/append_load.py
2026-05-17 20:14:49 +00:00

108 lines
4.6 KiB
Python
Executable file

#!/usr/bin/env python3
"""Append a new freight record and regenerate CSV/summary outputs.
Usage:
python3 append_load.py new_record.json
The input JSON should be a single record object with fields matching the ledger schema.
"""
import csv
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent
LEDGER_PATH = ROOT / 'truck_freight_ledger.json'
CSV_PATH = ROOT / 'truck_freight_ledger.csv'
SUMMARY_PATH = ROOT / 'truck_freight_summary.md'
FIELDNAMES = [
'id','date','status','vehicle','from','via','to','shipper','load_type','item','deliveries',
'freight_charged','advance_received','paid_to_driver','commission','driver_freight',
'pending_from_shipper','pending_to_driver','notes'
]
def join_list(v):
if v is None:
return ''
if isinstance(v, list):
return ' | '.join(str(x) for x in v)
return str(v)
def load_ledger():
return json.loads(LEDGER_PATH.read_text())
def write_csv(records):
with CSV_PATH.open('w', newline='') as f:
writer = csv.DictWriter(f, fieldnames=FIELDNAMES)
writer.writeheader()
for r in records:
row = {k: '' for k in FIELDNAMES}
for k in ['id','date','status','vehicle','from','to','shipper','load_type','item','freight_charged','advance_received','paid_to_driver','commission','driver_freight','pending_from_shipper','pending_to_driver','notes']:
row[k] = r.get(k, '') if r.get(k, '') is not None else ''
row['via'] = join_list(r.get('via'))
row['deliveries'] = join_list(r.get('deliveries'))
writer.writerow(row)
def write_summary(records):
def total(key):
s = 0
for r in records:
v = r.get(key)
if isinstance(v, (int, float)):
s += v
return s
num_records = len(records)
settled = sum(1 for r in records if r.get('status') == 'settled')
pending_or_partial = sum(1 for r in records if 'pending' in (r.get('status') or '') or r.get('status') == 'partial')
lines = []
lines.append('# Truck Freight Ledger Summary')
lines.append('')
lines.append('Overview')
lines.append(f'- Records: {num_records}')
lines.append(f'- Settled loads: {settled}')
lines.append(f'- Pending/partial loads: {pending_or_partial}')
lines.append('')
lines.append('Totals')
lines.append(f'- Freight charged: {total("freight_charged")}')
lines.append(f'- Advance received: {total("advance_received")}')
lines.append(f'- Paid to driver: {total("paid_to_driver")}')
lines.append(f'- Commission: {total("commission")}')
lines.append(f'- Pending from shipper: {total("pending_from_shipper")}')
lines.append(f'- Pending to driver: {total("pending_to_driver")}')
lines.append('')
lines.append('Record snapshot')
lines.append('| Date | Vehicle | Shipper | Route | Freight | Advance | Paid driver | Commission | Pending shipper | Status |')
lines.append('|---|---|---|---|---:|---:|---:|---:|---:|---|')
for r in sorted(records, key=lambda x: (x.get('date') or '', x.get('vehicle') or '', x.get('id') or '')):
route = ''.join([p for p in [r.get('from'), r.get('to')] if p])
if r.get('via'):
route = f"{r.get('from')} via {join_list(r.get('via'))}{r.get('to')}"
lines.append(
f"| {r.get('date','')} | {r.get('vehicle','') or '-'} | {r.get('shipper','') or '-'} | {route or '-'} | {r.get('freight_charged','') if r.get('freight_charged') is not None else '-'} | {r.get('advance_received','') if r.get('advance_received') is not None else '-'} | {r.get('paid_to_driver','') if r.get('paid_to_driver') is not None else '-'} | {r.get('commission','') if r.get('commission') is not None else '-'} | {r.get('pending_from_shipper','') if r.get('pending_from_shipper') is not None else '-'} | {r.get('status','') or '-'} |"
)
SUMMARY_PATH.write_text('\n'.join(lines) + '\n')
def main():
if len(sys.argv) != 2:
print('Usage: python3 append_load.py new_record.json', file=sys.stderr)
raise SystemExit(2)
new_record = json.loads(Path(sys.argv[1]).read_text())
ledger = load_ledger()
ledger.setdefault('records', []).append(new_record)
ledger['records'] = sorted(ledger['records'], key=lambda x: (x.get('date') or '', x.get('vehicle') or '', x.get('id') or ''))
LEDGER_PATH.write_text(json.dumps(ledger, indent=2, ensure_ascii=False) + '\n')
write_csv(ledger['records'])
write_summary(ledger['records'])
print(f"Appended record {new_record.get('id', '(no id)')} and regenerated CSV + summary.")
if __name__ == '__main__':
main()