Posted on
Artificial Intelligence

Automating Back Office Tasks with Artificial Intelligence

Author
  • User
    linuxbash
    Posts by this author
    Posts by this author

Automating Back Office Tasks with Artificial Intelligence (Bash-First on Linux)

If your back office is drowning in PDFs, spreadsheets, and repetitive email replies, you’re not alone. The hidden cost of “swivel‑chair” work—copying values from invoices, sorting documents, drafting routine responses—adds up fast. The good news: AI can handle a surprising amount of this unstructured, boring work, and you can orchestrate it from the Linux command line you already know.

This guide shows how to use AI from Bash to:

  • Triage and route documents automatically

  • Extract structured data (e.g., invoice fields) into clean CSV

  • Draft human‑quality replies you only need to approve

  • Produce daily digests of activity logs

You’ll get actionable, copy‑paste‑ready scripts that run locally (Ollama) or against a cloud API (OpenAI‑compatible), with install instructions for apt, dnf, and zypper.


Why this works (and why now)

  • LLMs are finally good at unstructured text. They can read PDFs (after OCR), understand context, and emit clean JSON for your pipelines.

  • Linux shines at glue work. Tools like inotifywait, jq, and cron make it easy to watch folders, parse responses, and schedule routines.

  • Privacy and control are possible. Run models locally with Ollama for sensitive data, or swap to a cloud API when you need more accuracy or scale.

  • Real ROI. Automating even 30–60 minutes/day of manual triage or data entry pays back quickly.


Prerequisites and installation

You’ll need a few standard CLI tools. Choose the package manager for your distro:

  • Ubuntu/Debian (apt):
sudo apt update
sudo apt install -y curl jq inotify-tools poppler-utils ocrmypdf tesseract-ocr tesseract-ocr-eng cron bsd-mailx
  • Fedora/RHEL (dnf):
sudo dnf install -y curl jq inotify-tools poppler-utils ocrmypdf tesseract tesseract-langpack-eng cronie mailx
  • openSUSE (zypper):
sudo zypper install -y curl jq inotify-tools poppler-tools ocrmypdf tesseract-ocr tesseract-ocr-traineddata-english cron mailx

Enable cron (so scheduled jobs can run):

  • Ubuntu/Debian:
sudo systemctl enable --now cron
  • Fedora/RHEL:
sudo systemctl enable --now crond
  • openSUSE:
sudo systemctl enable --now cron

Optional: local AI with Ollama (recommended for privacy):

curl -fsSL https://ollama.com/install.sh | sh
ollama pull llama3

Environment options (pick one backend):

  • Local (Ollama):
export LLM_BACKEND=ollama
export OLLAMA_MODEL=llama3
  • Cloud (OpenAI‑compatible Chat Completions):
export LLM_BACKEND=openai
export OPENAI_API_KEY=sk-yourkey
export OPENAI_MODEL=gpt-4o-mini
export OPENAI_BASE_URL=https://api.openai.com

Security tip: Store secrets in a dedicated .env with chmod 600, and source it in your scripts.


Reusable Bash helper: ask an LLM

Drop this into ~/backoffice/bin/llm.sh and chmod +x it. It abstracts a local Ollama or OpenAI‑compatible call and safely escapes prompt text.

#!/usr/bin/env bash
set -euo pipefail

ask_llm() {
  # Usage: ask_llm "prompt text" "system text" ["json"|"plain"]
  local prompt="${1:-}"
  local system="${2:-You are a precise assistant. Keep answers concise.}"
  local mode="${3:-plain}"

  if [[ "${LLM_BACKEND:-ollama}" == "ollama" ]]; then
    local fmt=""
    [[ "$mode" == "json" ]] && fmt='"format":"json",'
    curl -sS http://localhost:11434/api/chat \
      -H 'Content-Type: application/json' \
      -d "{
        \"model\":\"${OLLAMA_MODEL:-llama3}\",
        \"messages\":[
          {\"role\":\"system\",\"content\":$(
            jq -Rs . <<< "$system"
          )},
          {\"role\":\"user\",\"content\":$(
            jq -Rs . <<< "$prompt"
          )}
        ],
        $fmt
        \"stream\": false
      }" | jq -r '.message.content'
  else
    local resp_fmt=''
    [[ "$mode" == "json" ]] && resp_fmt=',"response_format":{"type":"json_object"}'
    curl -sS "${OPENAI_BASE_URL:-https://api.openai.com}/v1/chat/completions" \
      -H "Authorization: Bearer ${OPENAI_API_KEY:?Set OPENAI_API_KEY}" \
      -H "Content-Type: application/json" \
      -d "{
        \"model\":\"${OPENAI_MODEL:-gpt-4o-mini}\",
        \"temperature\": 0.2
        $resp_fmt,
        \"messages\":[
          {\"role\":\"system\",\"content\":$(
            jq -Rs . <<< "$system"
          )},
          {\"role\":\"user\",\"content\":$(
            jq -Rs . <<< "$prompt"
          )}
        ]
      }" | jq -r '.choices[0].message.content'
  fi
}

Source it in other scripts:

source "$HOME/backoffice/bin/llm.sh"

Actionable automation 1: Auto‑triage PDFs into folders

Watch a directory for new PDFs, OCR/parse them, and let the LLM classify and rename files. This removes a ton of manual sorting.

1) Prepare folders:

mkdir -p ~/backoffice/{inbox,processed,errors,classified/{invoice,receipt,contract,statement,po,other}}

2) Save as ~/backoffice/bin/triage.sh:

#!/usr/bin/env bash
set -euo pipefail
source "$HOME/backoffice/bin/llm.sh"

INBOX="$HOME/backoffice/inbox"
PROCESSED="$HOME/backoffice/processed"
ERRORS="$HOME/backoffice/errors"
DEST="$HOME/backoffice/classified"

extract_text() {
  local pdf="$1" outtxt="$2"
  local tmp_pdf
  tmp_pdf="$(mktemp --suffix=.pdf)"
  # Sidecar text; skip OCR if text exists
  if ocrmypdf --sidecar "$outtxt" --skip-text "$pdf" "$tmp_pdf" >/dev/null 2>&1; then
    :
  else
    # Fallback: plain text extraction
    pdftotext -layout "$pdf" "$outtxt" || true
  fi
  rm -f "$tmp_pdf"
}

classify_and_move() {
  local pdf="$1"
  local base; base="$(basename "$pdf" .pdf)"
  local txt; txt="$(mktemp)"
  trap 'rm -f "$txt"' EXIT

  extract_text "$pdf" "$txt"
  local content; content="$(cat "$txt")"

  local sys="You classify business documents. Allowed categories: invoice, receipt, contract, statement, purchase_order, other.
Return strict JSON with:
  category: one of [invoice, receipt, contract, statement, po, other] (use 'po' for purchase_order),
  tags: array of 1-5 lowercase short tags,
  suggested_filename: lowercase slug with optional ISO date (YYYY-MM-DD) and a short title. Don't include file extension."
  local prompt="Document text:
---
$content
---

Return only JSON."

  local resp
  if ! resp="$(ask_llm "$prompt" "$sys" json)"; then
    echo "LLM error; moving to $ERRORS" >&2
    mv -f "$pdf" "$ERRORS/$(basename "$pdf")"
    return
  fi

  # Validate JSON
  if ! echo "$resp" | jq . >/dev/null 2>&1; then
    echo "Invalid JSON; moving to $ERRORS" >&2
    mv -f "$pdf" "$ERRORS/$(basename "$pdf")"
    return
  fi

  local cat fname
  cat="$(echo "$resp" | jq -r '.category // "other"')"
  # normalize purchase_order -> po
  [[ "$cat" == "purchase_order" ]] && cat="po"
  fname="$(echo "$resp" | jq -r '.suggested_filename // "'"$base"'"')"
  [[ -z "$fname" || "$fname" == "null" ]] && fname="$base"

  local target_dir="$DEST/$cat"
  mkdir -p "$target_dir"
  mv -f "$pdf" "$target_dir/$fname.pdf"
  echo "Filed $(basename "$pdf") -> $target_dir/$fname.pdf"
}

mkdir -p "$INBOX" "$PROCESSED" "$ERRORS" "$DEST"

# One-shot over existing files (idempotent)
shopt -s nullglob
for f in "$INBOX"/*.pdf "$INBOX"/*.PDF; do
  classify_and_move "$f"
done

# Watch for new PDFs
command -v inotifywait >/dev/null 2>&1 || {
  echo "inotifywait not found; install inotify-tools" >&2; exit 1; }

inotifywait -m -e close_write --format '%f' "$INBOX" | while read -r file; do
  case "$file" in
    *.pdf|*.PDF) classify_and_move "$INBOX/$file" ;;
    *) : ;;
  esac
done

3) Run it:

bash ~/backoffice/bin/triage.sh

Result: New PDFs dropped into ~/backoffice/inbox get OCR’d, classified, renamed, and filed into ~/backoffice/classified/....


Actionable automation 2: Extract invoice data into CSV

Turn messy invoices into clean records your finance tools can ingest.

1) Save as ~/backoffice/bin/invoice_extract.sh:

#!/usr/bin/env bash
set -euo pipefail
source "$HOME/backoffice/bin/llm.sh"

SRC_DIR="$HOME/backoffice/classified/invoice"
DATA_DIR="$HOME/backoffice/data"
OUT_CSV="$DATA_DIR/invoices.csv"

mkdir -p "$DATA_DIR"
[[ -f "$OUT_CSV" ]] || echo "vendor,invoice_no,issue_date,due_date,currency,total,filename" > "$OUT_CSV"

extract_text_quick() {
  local pdf="$1" outtxt="$2"
  # Fast path: try pdftotext first
  if ! pdftotext -layout "$pdf" "$outtxt" >/dev/null 2>&1; then
    # Fallback to OCR sidecar
    local tmp_pdf
    tmp_pdf="$(mktemp --suffix=.pdf)"
    ocrmypdf --sidecar "$outtxt" --skip-text "$pdf" "$tmp_pdf" >/dev/null 2>&1 || true
    rm -f "$tmp_pdf"
  fi
}

for pdf in "$SRC_DIR"/*.pdf; do
  [[ -e "$pdf" ]] || continue
  tmp="$(mktemp)"; trap 'rm -f "$tmp"' EXIT
  extract_text_quick "$pdf" "$tmp"
  content="$(cat "$tmp")"

  sys="You extract invoice fields and return strict JSON with keys:
vendor (string), invoice_no (string), issue_date (YYYY-MM-DD), due_date (YYYY-MM-DD or null),
currency (ISO code like USD/EUR), total (number).
Infer missing fields conservatively; use null when uncertain. Return only JSON."
  prompt="Invoice text:
---
$content
---"

  if ! json="$(ask_llm "$prompt" "$sys" json)"; then
    echo "LLM error on $pdf" >&2
    continue
  fi

  # Coerce to CSV row
  row="$(echo "$json" | jq -r --arg f "$(basename "$pdf")" \
    '[.vendor,.invoice_no,.issue_date, ( .due_date // "" ), .currency, ( .total // "" ), $f] | @csv')"

  echo "$row" >> "$OUT_CSV"
  echo "Appended $(basename "$pdf") -> $OUT_CSV"
done

2) Run it after triage:

bash ~/backoffice/bin/invoice_extract.sh

You’ll get a growing ~/backoffice/data/invoices.csv you can feed into accounting or BI tools.


Actionable automation 3: Draft email replies from templates

Generate drafts you review and send—perfect for routine customer queries or status updates.

1) Prepare folders:

mkdir -p ~/backoffice/drafts/inbox ~/backoffice/drafts/outbox

2) Drop a plain text customer message into ~/backoffice/drafts/inbox/request-123.txt.

3) Create ~/backoffice/bin/reply_drafter.sh:

#!/usr/bin/env bash
set -euo pipefail
source "$HOME/backoffice/bin/llm.sh"

INBOX="$HOME/backoffice/drafts/inbox"
OUTBOX="$HOME/backoffice/drafts/outbox"

sys="You are a professional customer support agent.
Write friendly, concise, and accurate replies. Use British English.
Include a clear subject line and numbered next steps when appropriate.
If information is missing, ask 1-2 specific questions."

for f in "$INBOX"/*.txt; do
  [[ -e "$f" ]] || continue
  prompt="$(cat "$f")"
  draft="$(ask_llm "$prompt" "$sys" plain)"

  out="$OUTBOX/$(basename "${f%.txt}").md"
  {
    echo "$draft"
    echo
    echo "<!-- Review before sending. Save subject on first line prefixed by 'Subject: ' -->"
  } > "$out"

  echo "Drafted: $out"
done

4) Review the Markdown drafts in outbox/, edit as needed, then optionally send with mailx (replace addresses):

to="customer@example.com"
file="~/backoffice/drafts/outbox/request-123.md"
subject="$(grep -m1 -E '^Subject:' "$file" | sed 's/^Subject:[[:space:]]*//')"
body="$(sed '/^Subject:/d' "$file")"
echo "$body" | mailx -s "$subject" "$to"

Note: On Debian/Ubuntu, mailx comes from bsd-mailx. You may need to configure an MTA (e.g., msmtp, postfix) or use your org’s relay.


Actionable automation 4: Daily digest via cron

Summarize logs or ticket updates into a clean daily report.

1) Create directories and a sample log:

mkdir -p ~/backoffice/updates ~/backoffice/reports
echo "[10:04] Ticket #842 escalated; awaiting vendor ETA
[12:17] 18 invoices posted
[15:32] Payroll export validated" > ~/backoffice/updates/$(date +%F).log

2) Save as ~/backoffice/bin/digest.sh:

#!/usr/bin/env bash
set -euo pipefail
source "$HOME/backoffice/bin/llm.sh"

UPDATES="$HOME/backoffice/updates"
OUTDIR="$HOME/backoffice/reports"
today="$(date +%F)"
log="$UPDATES/$today.log"
out="$OUTDIR/daily-$today.md"

[[ -f "$log" ]] || { echo "No log for $today"; exit 0; }

sys="You are an operations analyst. Summarize the day's updates into a brief Markdown report with:

- bullets grouped by theme,

- notable risks/blockers,

- action items with owners if present."
content="$(cat "$log")"

report="$(ask_llm "$content" "$sys" plain)"
echo -e "# Daily Digest ($today)\n\n$report" > "$out"
echo "Wrote $out"

3) Schedule on weekdays at 17:00:

crontab -e

Add:

0 17 * * 1-5 LLM_BACKEND=ollama OLLAMA_MODEL=llama3 bash $HOME/backoffice/bin/digest.sh >> $HOME/backoffice/logs/cron.log 2>&1

For OpenAI‑compatible:

0 17 * * 1-5 LLM_BACKEND=openai OPENAI_API_KEY=... OPENAI_MODEL=gpt-4o-mini OPENAI_BASE_URL=https://api.openai.com bash $HOME/backoffice/bin/digest.sh >> $HOME/backoffice/logs/cron.log 2>&1

Create the logs dir if desired:

mkdir -p ~/backoffice/logs

Real‑world tips

  • Start local, then scale: Try Ollama for private data. If accuracy is insufficient, switch to a stronger cloud model by flipping env vars.

  • Keep a paper trail: Save LLM JSON outputs alongside source docs. It helps with audits and troubleshooting.

  • Validate aggressively: Always check JSON with jq. On parse errors, route to errors/ for manual review.

  • Guard secrets: Never hardcode API keys. Use .env, environment variables, and least‑privilege.

  • Iterate prompts: Small wording changes can improve consistency (e.g., “Return strict JSON with these keys only”).


Conclusion and next step (CTA)

You now have a practical Bash toolkit to:

  • File and rename PDFs automatically

  • Extract invoice data into clean CSV

  • Draft accurate customer replies

  • Produce daily operational digests

Pick one workflow and wire it up today—start with the triage script, then add extraction and summaries. Once you see the time saved, expand to new document types and teams.

If you’d like a follow‑up post with systemd units, containerization, or a full logging/metrics setup, let me know which workflow you’re automating and what model/backend you’re using.