crm-observation.py: script Python Playwright pour phase 2 observation 7j (doctrine 62) · API fetch + screenshot + report JSON + Blade alert si delta<500 apres J+2 · daily 9h cron installé · intent WEVIA observe_crm_pipeline wire

This commit is contained in:
Opus-Yacine
2026-04-17 14:33:00 +02:00
parent 933d1560a3
commit 0599cbceb7

158
crm-observation.py Executable file
View File

@@ -0,0 +1,158 @@
#!/usr/bin/env python3
"""
CRM Pipeline Daily Observer — doctrine 62 Phase 2 (observation J+1 to J+7)
Runs daily at 9am via cron. Uses Playwright to screenshot + fetch API + validate delta.
Alerts via Blade queue if delta < 500/day.
"""
import json, os, sys, time, urllib.request, urllib.parse, datetime
from pathlib import Path
LOG_FILE = "/var/log/weval/crm-observation.log"
SCREENSHOTS_DIR = "/var/www/html/api/crm-observation-screenshots"
BLADE_KEY = "BLADE2026"
DELTA_ALERT_THRESHOLD = 500
API_STATUS = "https://weval-consulting.com/api/crm-pipeline-live.php?action=status"
PAGE_URL = "https://weval-consulting.com/crm-pipeline-live.html"
os.makedirs(SCREENSHOTS_DIR, exist_ok=True)
Path(LOG_FILE).parent.mkdir(parents=True, exist_ok=True)
def log(msg):
line = f"[{datetime.datetime.now().isoformat()}] {msg}\n"
print(line, end="")
try:
with open(LOG_FILE, "a") as f: f.write(line)
except Exception as e:
print(f"log write err: {e}")
def fetch_api():
"""Fetch API status + parse delta"""
try:
r = urllib.request.urlopen(API_STATUS, timeout=10)
d = json.loads(r.read().decode())
return d
except Exception as e:
log(f"API_FETCH_ERR {e}")
return None
def playwright_screenshot():
"""Take screenshot of dashboard via playwright"""
try:
from playwright.sync_api import sync_playwright
except ImportError:
log("PLAYWRIGHT_NOT_INSTALLED")
return None
ts = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
screenshot = f"{SCREENSHOTS_DIR}/crm-pipeline-{ts}.png"
try:
with sync_playwright() as p:
browser = p.chromium.launch(headless=True, args=["--no-sandbox", "--disable-dev-shm-usage"])
ctx = browser.new_context(viewport={"width": 1400, "height": 900}, ignore_https_errors=True)
page = ctx.new_page()
page.goto(PAGE_URL, wait_until="networkidle", timeout=30000)
time.sleep(2) # let JS render
page.screenshot(path=screenshot, full_page=True)
browser.close()
log(f"SCREENSHOT_OK {screenshot}")
return screenshot
except Exception as e:
log(f"PLAYWRIGHT_ERR {e}")
return None
def send_blade_alert(msg, cmd=None):
"""Push alert to Blade queue for Windows-side notification"""
try:
data = urllib.parse.urlencode({
"k": BLADE_KEY,
"name": "CRM Observation Alert",
"cmd": cmd or f"Write-Host 'CRM ALERT: {msg}'; New-BurntToastNotification -Text 'WEVAL CRM', '{msg}' -ErrorAction SilentlyContinue",
"type": "powershell",
"priority": "high"
}).encode()
req = urllib.request.Request(
"https://weval-consulting.com/api/blade-task-queue.php?k=BLADE2026&action=add",
data=data, method="POST"
)
r = urllib.request.urlopen(req, timeout=10)
d = json.loads(r.read().decode())
log(f"BLADE_ALERT_SENT {d.get('task_id', '?')}")
return True
except Exception as e:
log(f"BLADE_ALERT_ERR {e}")
return False
def main():
log("=== DAILY OBSERVATION START ===")
# 1. Fetch API
data = fetch_api()
if not data or not data.get("ok"):
log("API_UNAVAILABLE")
send_blade_alert("API crm-pipeline-live DOWN")
return 1
total = data.get("send_contacts_total", 0)
delta_today = data.get("delta_today", 0)
runs_ok = data.get("runs_ok_24h", 0)
runs_err = data.get("runs_err_24h", 0)
last_age = data.get("last_run_age", "?")
cron_status = data.get("cron_status", "?")
log(f"STATS total={total} delta_today={delta_today} ok_24h={runs_ok} err_24h={runs_err} last={last_age} cron={cron_status}")
# 2. Playwright screenshot
screenshot = playwright_screenshot()
# 3. Decision: alert or pass
alert_triggered = False
alert_reasons = []
if cron_status != "active":
alert_reasons.append(f"cron_status={cron_status} (not active)")
alert_triggered = True
if runs_err > runs_ok and runs_ok + runs_err > 5:
alert_reasons.append(f"err>ok ({runs_err}>{runs_ok})")
alert_triggered = True
# Day-N check: after J+2, if delta still 0 → alert
day_since_reactivation = (datetime.datetime.now() - datetime.datetime(2026, 4, 17, 14, 23)).days
if day_since_reactivation >= 2 and delta_today < DELTA_ALERT_THRESHOLD:
alert_reasons.append(f"delta_today={delta_today} < {DELTA_ALERT_THRESHOLD} (day {day_since_reactivation} after reactivation)")
alert_triggered = True
if alert_triggered:
msg = f"Day{day_since_reactivation}: {' | '.join(alert_reasons)}"
log(f"ALERT {msg}")
send_blade_alert(msg)
else:
log(f"OK day{day_since_reactivation}: delta={delta_today}, runs ok/err={runs_ok}/{runs_err}")
# 4. Write daily report JSON
report = {
"ts": datetime.datetime.now().isoformat(),
"day_since_reactivation": day_since_reactivation,
"total": total,
"delta_today": delta_today,
"runs_ok_24h": runs_ok,
"runs_err_24h": runs_err,
"last_run_age": last_age,
"cron_status": cron_status,
"alert_triggered": alert_triggered,
"alert_reasons": alert_reasons,
"screenshot": screenshot,
}
report_file = f"/var/www/html/api/crm-observation-latest.json"
try:
with open(report_file, "w") as f:
json.dump(report, f, indent=2)
log(f"REPORT_SAVED {report_file}")
except Exception as e:
log(f"REPORT_ERR {e}")
log("=== END ===\n")
return 0 if not alert_triggered else 2
if __name__ == "__main__":
sys.exit(main())