#!/usr/bin/env python3 """MeLi questions SLA sweep for a tenant. Lists unanswered MercadoLibre buyer questions older than N hours (the ones that hurt the seller's response score / reputation), oldest first, so the operator can answer them before the SLA is blown. Auto-answering is separate and OPT-IN: * It only runs when the tenant has `meli_auto_answer_questions = true`. * `--auto-answer` sends the high-confidence, template-based answers built by services/question_ai (never free-form LLM text) through marketplace_external_service.auto_answer_questions(). * Without `--auto-answer` the script only reports; it never sends anything. Note: MercadoLibre returns the question status as UNANSWERED / ANSWERED. The service now normalizes it to lowercase on upsert, and the queries here are case-insensitive either way. Usage (from /home/Autopartes): python3 scripts/meli_questions_sla.py --tenant 31 python3 scripts/meli_questions_sla.py --tenant 31 --max-age-hours 48 python3 scripts/meli_questions_sla.py --tenant 31 --auto-answer python3 scripts/meli_questions_sla.py --tenant 31 --json Suggested systemd timer (per tenant), once an hour: ExecStart=/usr/bin/python3 /home/Autopartes/scripts/meli_questions_sla.py \ --tenant 31 --max-age-hours 24 """ import argparse import json import os import sys sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'pos')) def _load_env(): env_file = '/etc/nexus/pos.env' if os.environ.get('MASTER_DB_URL') or not os.path.exists(env_file): return for line in open(env_file): line = line.strip() if not line or line.startswith('#') or '=' not in line: continue key, value = line.split('=', 1) os.environ.setdefault(key.strip(), value.strip().strip('"').strip("'")) def main(): ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument('--tenant', type=int, required=True, help='tenant id (e.g. 31 = Rached)') ap.add_argument('--max-age-hours', type=float, default=24, help='age threshold in hours (default 24)') ap.add_argument('--limit', type=int, default=20, help='max questions to auto-answer when --auto-answer is used') ap.add_argument('--auto-answer', action='store_true', help='send high-confidence answers (requires meli_auto_answer_questions = true)') ap.add_argument('--json', action='store_true', help='print the full result as JSON') args = ap.parse_args() _load_env() from tenant_db import get_tenant_conn from services import marketplace_external_service as meli conn = get_tenant_conn(args.tenant) try: stale = meli.list_stale_questions(conn, max_age_hours=args.max_age_hours) cfg = meli.get_meli_config(conn) result = { "tenant": args.tenant, "max_age_hours": args.max_age_hours, "auto_answer_enabled": bool(cfg.get("meli_auto_answer_questions")), "stale_count": len(stale), "stale": stale, } if args.auto_answer: if not cfg.get("meli_auto_answer_questions"): result["auto_answer"] = { "enabled": False, "reason": "meli_auto_answer_questions is not enabled for this tenant", } else: result["auto_answer"] = meli.auto_answer_questions(conn, limit=args.limit) finally: conn.close() if args.json: print(json.dumps(result, indent=2, ensure_ascii=False, default=str)) return 0 print(f"[tenant {args.tenant}] questions SLA — unanswered older than " f"{args.max_age_hours}h: {result['stale_count']}") if not stale: print(" Nothing overdue.") for q in stale: age = q.get('age_hours') print(f" q#{q['id']} ({age}h) {q['buyer_nickname'] or 'comprador'}: " f"{(q['question_text'] or '')[:70]!r} — item {q['external_item_id']}") if args.auto_answer: aa = result.get("auto_answer") or {} if aa.get("enabled") is False: print(f"\n auto-answer not run: {aa.get('reason')}") else: print(f"\n auto-answer: answered={aa.get('answered')} " f"skipped={aa.get('skipped')}") else: print("\n (report-only; re-run with --auto-answer to send high-confidence " "answers when meli_auto_answer_questions = true)") return 0 if __name__ == '__main__': sys.exit(main())