#!/usr/bin/env python3 """Audit 待审查 and 任务交付 directories against tracker. Usage: python3 ~/.hermes/skills/legal/contract-pass-workflow/references/cleanup-audit.py Prints a matrix showing which files are: - In tracker (and their status/age/cleaned flag) - NOT in tracker (orphans that will never be auto-cleaned) - Should be cleaned (>24h + completed + cleaned=false) Does NOT delete anything. Pure diagnostic. """ import json, os, subprocess from datetime import datetime, timezone, timedelta BJT = timezone(timedelta(hours=8)) now = datetime.now(BJT) TRACKER = os.path.expanduser('~/.hermes/data/contract-tracker.json') BASE = os.path.expanduser('~/nextcloud/data/data/doro/files/Doro合同审查任务') 待审查 = os.path.join(BASE, '待审查') 任务交付 = os.path.join(BASE, '任务交付') def nc_ls(path): """List files in a Nextcloud-managed directory (needs sudo).""" r = subprocess.run(['sudo', 'ls', path], capture_output=True, text=True) return [f for f in r.stdout.strip().split('\n') if f] if r.stdout.strip() else [] def file_age_hours(path): """Get file age in hours from mtime.""" r = subprocess.run(['sudo', 'stat', '-c', '%Y', path], capture_output=True, text=True) if r.stdout.strip(): mtime = int(r.stdout.strip()) return (now - datetime.fromtimestamp(mtime, tz=BJT)).total_seconds() / 3600 return -1 def main(): with open(TRACKER) as f: tracker = json.load(f) # Build lookup: filename -> list of tracker records lookup = {} for c in tracker['contracts']: for key in ['delivered_filename', 'original_filename', 'converted_filename']: fn = c.get(key, '') if fn: lookup.setdefault(fn, []).append(c) orphans = [] for label, directory in [('待审查', 待审查), ('任务交付', 任务交付)]: print(f"\n{'='*70}") print(f" {label} ({directory})") print(f"{'='*70}") files = nc_ls(directory) if not files: print(" (empty)") continue for fn in sorted(files): records = lookup.get(fn, []) age = file_age_hours(os.path.join(directory, fn)) is_companion = any(k in fn for k in ['审查意见', '合同流程单']) if records: for r in records: ts = r.get('xlsx_updated_at', '') h = (now - datetime.fromisoformat(ts)).total_seconds() / 3600 if ts else -1 should = r['status'] == 'completed' and h > 24 flag = '🔴 SHOULD_CLEAN' if should else '⏳ waiting' print(f" {fn}") print(f" seq={r['seq']} | {h:.0f}h | cleaned={r.get('cleaned')} | {flag}") else: tag = '📋 COMPANION_ORPHAN' if is_companion else '⚠️ NOT_IN_TRACKER' print(f" {fn}") print(f" {tag} | age={age:.0f}h") orphans.append((label, fn, age)) if orphans: print(f"\n{'='*70}") print(f" ORPHANS SUMMARY: {len(orphans)} files not tracked") print(f"{'='*70}") for label, fn, age in orphans: print(f" [{label}] {fn} ({age:.0f}h old)") if __name__ == '__main__': main()