Add Apprise notifications to quality-scan (self-hosted apprise server); dashboard ready

This commit is contained in:
2026-08-08 09:05:25 +10:00
parent 11e79b7066
commit b7d7ef38b4
3 changed files with 80 additions and 1 deletions

42
apprise_helper.py Normal file
View File

@@ -0,0 +1,42 @@
"""photo-pipeline: Apprise notification helper.
Posts to the self-hosted Apprise API server (fans out to all configured targets).
Usage: notify("Batch 3 done: 212 photos, 79 near-dups, 3 candidates")
The Apprise server holds the target config; we just POST title/body.
"""
import os
import subprocess
APPRISE_URL = os.environ.get("APPRISE_URL", "https://apprise.lab.audasmedia.com.au/notify")
def notify(title: str, body: str, tag: str = None) -> bool:
"""Send a notification via the Apprise API server. Returns success."""
import json
payload = {"title": title, "body": body}
if tag:
payload["tag"] = tag
r = subprocess.run(
["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}",
"-X", "POST", APPRISE_URL,
"-H", "Content-Type: application/json",
"-d", json.dumps(payload)],
capture_output=True, text=True, timeout=30,
)
ok = r.stdout.strip() in ("204", "200")
print(f"[apprise] {title}: HTTP {r.stdout.strip()}")
return ok
if __name__ == "__main__":
# CLI test: python apprise.py "title" "body"
import sys
t = sys.argv[1] if len(sys.argv) > 1 else "photo-pipeline test"
b = sys.argv[2] if len(sys.argv) > 2 else "Notification test from photo-pipeline"
ok = notify(t, b)
print("sent" if ok else "FAILED")

View File

@@ -72,3 +72,18 @@ deployments:
name: photo-pool name: photo-pool
work_queue_name: null work_queue_name: null
job_variables: {} job_variables: {}
- name: import
version: null
tags: [photo-pipeline]
description: "Import staged photos into Immich via server-bundled CLI (checksum dedup)"
schedule: null
flow_name: null
entrypoint: immich_import.py:immich_import
parameters:
src_dir: /mnt/data/01_keep
dry_run: true
work_pool:
name: photo-pool
work_queue_name: null
job_variables: {}

View File

@@ -87,13 +87,15 @@ def _move(p: Path, dest_root: Path, src_root: Path):
@flow(name="photo-quality-scan") @flow(name="photo-quality-scan")
def quality_scan(base_dir: str, move: bool = False): def quality_scan(base_dir: str, move: bool = False, notify: bool = True):
"""Audit image quality with CleanVision; classify into keep/review/delete.""" """Audit image quality with CleanVision; classify into keep/review/delete."""
audit = audit_folder(base_dir) audit = audit_folder(base_dir)
print(f"Issue summary: {audit['summary']}") print(f"Issue summary: {audit['summary']}")
result = classify_and_sort(base_dir, audit["per_image"], move=move) result = classify_and_sort(base_dir, audit["per_image"], move=move)
print(f"Verdicts: {result['counts']}") print(f"Verdicts: {result['counts']}")
return {"audit": audit["summary"], **result} return {"audit": audit["summary"], **result}
if notify:
notify_result(result["counts"], audit["summary"], base_dir)
if __name__ == "__main__": if __name__ == "__main__":
@@ -101,3 +103,23 @@ if __name__ == "__main__":
d = sys.argv[1] if len(sys.argv) > 1 else "/tmp/cvtest" d = sys.argv[1] if len(sys.argv) > 1 else "/tmp/cvtest"
move = "--move" in sys.argv move = "--move" in sys.argv
quality_scan(d, move=move) quality_scan(d, move=move)
@task
def notify_result(counts: dict, summary: list, base_dir: str):
"""Send batch summary via Apprise."""
import apprise_helper
hard = counts.get("delete_candidate", 0)
soft = counts.get("review", 0)
keep = counts.get("keep", 0)
flagged = [s for s in summary if s["num_images"] > 0]
lines = "; ".join(f"{s[issue_type]}: {s[num_images]}" for s in flagged) or "none"
body = (
f"Scanned: {base_dir}\n"
f"Keep: {keep} | Review: {soft} | Delete-candidates: {hard}\n"
f"Issues: {lines}\n"
f"Review: http://192.168.20.13:8092/review"
)
apprise_helper.notify("📸 photo-pipeline batch complete", body)
return True