89 lines
3.2 KiB
Python
89 lines
3.2 KiB
Python
#!/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()
|