fix(projekt-matching): final-review fixes — safe redeploy, poison-mail hardening, plan deviations note

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
tlg
2026-07-09 17:15:39 +02:00
parent 1863ce087c
commit 9ca7767208
6 changed files with 154 additions and 73 deletions

View File

@@ -83,53 +83,60 @@ def run_ingest(cfg, mailbox=None, dispatch=None, espo=None, resolve=None):
resolve = resolve or mailparse.canonical_url
summary = {"mails": 0, "projects": 0,
**{k: 0 for k in SUMMARY_KEYS}}
for uid in box.unchecked_uids():
msg = box.fetch(uid)
subject = decode_subject(msg)
html, text = mailparse.bodies(msg)
items = [] if mailparse.is_own_mail(subject) else \
mailparse.split_projects(html, text)
if not items:
box.flag_checked(uid)
continue
summary["mails"] += 1
results = []
for item in items:
summary["projects"] += 1
result = dict(item)
try:
for uid in box.unchecked_uids():
try:
result["canonical"] = resolve(item["url"])
dup = crm.find_opportunity_by_link(result["canonical"])
if dup:
result["status"] = "duplicate"
else:
result.update(send(result))
except Exception as exc: # noqa: BLE001
result.update(status="failed", error=str(exc))
if result.get("status") not in SUMMARY_KEYS:
result.update(
status="failed",
error=result.get("error")
or f"unbekannter Status: {result.get('status')!r}")
results.append(result)
summary[result["status"]] += 1
failed = [r for r in results if r["status"] == "failed"]
if failed:
lines = "\n\n".join(
f"- {f.get('title', '?')}\n"
f" {f.get('canonical', f.get('url', '?'))}\n"
f" Fehler: {f.get('error', '?')}" for f in failed)
try:
mailer.send_mail(
cfg.imap_user, cfg.imap_password, cfg.alert_to,
f"[Projekt-Match-Fehler] Freelancermap — "
f"{len(failed)} Projekt(e)",
"Folgende Projekte konnten nicht verarbeitet "
"werden:\n\n" + lines + "\n")
except Exception: # noqa: BLE001
summary["alertFailed"] = summary.get("alertFailed", 0) + 1
box.move_to_trash(uid)
box.close()
return summary
msg = box.fetch(uid)
subject = decode_subject(msg)
html, text = mailparse.bodies(msg)
except Exception: # noqa: BLE001
box.flag_checked(uid) # nie wieder anfassen, Pipeline läuft weiter
summary["unreadable"] = summary.get("unreadable", 0) + 1
continue
items = [] if mailparse.is_own_mail(subject) else \
mailparse.split_projects(html, text)
if not items:
box.flag_checked(uid)
continue
summary["mails"] += 1
results = []
for item in items:
summary["projects"] += 1
result = dict(item)
try:
result["canonical"] = resolve(item["url"])
dup = crm.find_opportunity_by_link(result["canonical"])
if dup:
result["status"] = "duplicate"
else:
result.update(send(result))
except Exception as exc: # noqa: BLE001
result.update(status="failed", error=str(exc))
if result.get("status") not in SUMMARY_KEYS:
result.update(
status="failed",
error=result.get("error")
or f"unbekannter Status: {result.get('status')!r}")
results.append(result)
summary[result["status"]] += 1
failed = [r for r in results if r["status"] == "failed"]
if failed:
lines = "\n\n".join(
f"- {f.get('title', '?')}\n"
f" {f.get('canonical', f.get('url', '?'))}\n"
f" Fehler: {f.get('error', '?')}" for f in failed)
try:
mailer.send_mail(
cfg.imap_user, cfg.imap_password, cfg.alert_to,
f"[Projekt-Match-Fehler] Freelancermap — "
f"{len(failed)} Projekt(e)",
"Folgende Projekte konnten nicht verarbeitet "
"werden:\n\n" + lines + "\n")
except Exception: # noqa: BLE001
summary["alertFailed"] = summary.get("alertFailed", 0) + 1
box.move_to_trash(uid)
return summary
finally:
box.close()
finally:
release_lock(lock)