← Tous les manuels · Portail
↗ Ouvrir l'app

MANUAL — Dofus Market

Rôle

Dashboard d'analyse du marché d'un serveur Dofus Retro privé. Stocke dans une

base SQLite toutes les données de prix scrapées (cartes, items d'équipement,

ressources HDV) et offre une interface web sécurisée pour explorer les prix,

l'historique, les vendeurs et les variations. Remplace l'ancien export Google

Sheets.

URL / accès

  • - Public: https://dofus-market.panelbay.com (HTTPS, Let's Encrypt)
  • - Login par mot de passe (page /login). Identifiant unique par mot de passe.
  • - Local: http://127.0.0.1:8355
  • Service / infra

  • - Service systemd: dofus-market.service (ExecStart uvicorn, port 8355)
  • - Repertoire: /root/workspace/apps/dofus-market/
  • import (import_existing.py), schema.sql, .env (secrets, chmod 600).

  • - Stack: Python 3.12, FastAPI + uvicorn (déjà installés en global), SQLite stdlib,
  • Chart.js (CDN) côté front. Pas de venv.

    Base de données (schéma)

    Table observations : une ligne = un prix observé.

    `obs_date, category(cards|items|resources), item_name, price, qty(1/10/100),

    seller, map_x, map_y, stats, res_category, source_file, ingested_at, dedup_key`.

    dedup_key (hash) rend les imports/ingestions idempotents (pas de doublon).

    Table ingestions : journal des imports (audit).

    Endpoints API

    Lecture (cookie de session requis):

  • - GET /api/summary — KPIs + last_ingest (derniere ingestion source=agent : ts,
  • rows_seen, rows_added, note) + today_ingest (rows_added, batches du jour) : badge

    "Direct" du dashboard, rafraichi toutes les 30 s cote front.

  • - GET /api/items?category=&search=&sort=&qty=&res_cat= — liste agrégée
  • (dernier/min/moy/max). res_cat (ressources uniquement) filtre par

    sous-catégorie HDV (Aile, Cuir, Poudre...). La ligne renvoyée porte res_category.

  • - GET /api/res-categories — sous-catégories de ressources HDV avec le nombre
  • d'objets distincts ([{category, n_items}], trié décroissant). Alimente les

    chips de catégories de l'onglet Ressources.

  • - GET /api/craft?only_priced= — recettes de craft croisées avec les prix marché
  • (coût de craft, prix de vente, marge, ROI, couverture ingrédients). Alimente

    l'onglet Craft. Voir backend/recipes.py + recipes_schema.sql.

  • - GET /api/item?name=&category= — détail + séries temporelles + vendeurs.
  • - GET /api/dofus — marche des Dofus regroupe par type puis par JET. Chaque
  • type (Cawotte, Kaliptus...) : stat (caracteristique dominante lue dans

    stats), n_obs, jet_min/jet_max, et jets[] (par palier de jet :

    min/avg/med/max, n offres, last_date, offers[] = 10 moins chers).

    Le jet est extrait de stats via _JET_RE ("+30 Sagesse", "+18 en

    prospection", "+19 aux coups critiques"). Alimente l'onglet Dofus.

  • - GET /api/movers?category=&qty= — plus fortes variations.
  • - GET /api/last-run — résumé de la DERNIÈRE run + produits intéressants. La run
  • est reconstituée par rupture temporelle sur ingested_at (le direct envoie un

    source_file par MINUTE, donc inexploitable comme identifiant ; on regroupe les

    lots séparés de moins de RUN_GAP_MINUTES=30 min). Renvoie found, la période

    (start/end/duration_min), les compteurs (n_obs, n_items, n_sellers,

    n_maps, n_dofus, by_category, maps) et deals[] = bonnes affaires (offre

    de la run ≥35% sous son prix marché historique élagué, robust_mean calculée

    HORS run, min 5 relevés d'historique, remise plafonnée à 90% pour écarter les

    erreurs OCR de prix ; champs price/market/discount/saving/seller/map/

    stats/history). Constantes dans analytics.py (DEAL_*). Alimente le

    panneau permanent en HAUT du dashboard (rafraîchi toutes les 30 s).

    Synchro des fichiers du bot vers le PC (auto-updater, token DOFUS_SYNC_TOKEN, header X-Sync-Token ou Bearer):

  • - GET /sync/manifest — liste des fichiers synchronisables de
  • Dofus-Bot-Nouveau/scraping-manuel/ (racine only) : {version, count, files:[{name,size,sha256,mode}]}.

    mode = overwrite (code/routes/templates, à écraser sur le PC) ou seed

    (maps-retro.json / liens-maps.json / sorties-apprises.json / dashboard.json /

    faux_marchands.json : écrits seulement s'ils manquent, jamais un état appris).

    Exclus du manifest (écrits par le bot en run) : route-observee.txt, .live_sent.json,

    *.log, sous-dossiers Data/IMG/debug. Logique dans backend/sync.py.

  • - GET /sync/file?name=<nom> — contenu brut d'un fichier (anti path-traversal :
  • racine de scraping-manuel/ uniquement). Client PC : Dofus-Bot-Nouveau/bot-sync/.

    Icones d'items (vrai art du jeu, token X-Sync-Token):

  • - GET /sync/item-names — noms distincts en base + slug + categorie (pour le scraper PC).
  • - GET /sync/icons — slugs des icones deja stockees (frontend/item-icons/*.png).
  • - POST /sync/icon?name=<nom> — corps = image brute (PNG/GIF/JPG/WEBP) ; normalisee en
  • PNG <=80px par PIL et stockee frontend/item-icons/<slug>.png (servie via /static).

    Le slug (accents retires, " - No.X" des cartes retire) est IDENTIQUE cote serveur

    (sync.slug), front (slug() dans index.html) et le peupleur, sinon les images ne se

    retrouvent pas.

    SOURCE DES ICONES (depuis 2026-09-12) : backend/populate_icons.py, cote SERVEUR,

    depuis l'API DofusDude (https://api.dofusdu.de, art Ankama Dofus 3). Construit une

    table slug->icone a partir des listes completes (equipment/resources/consumables,

    ~10 700 slugs), puis telecharge ce qui manque. Rejouable (--limit N, --force).

    Matching EXACT sur slug uniquement (le fallback /search renvoie de FAUX items -> ne

    jamais l'utiliser). Couverture actuelle : items 69%, resources 46%, cards 4% (les

    "Paquet de cartes" Retro n'existent pas chez Ankama), total ~567/1613. Les non

    couverts (items Retro supprimes/renommes dans le Dofus moderne) tombent sur le repli

    onerror du front (pas d'image, aucune casse).

    ANCIENNE source ABANDONNEE : Dofus-Bot-Nouveau/recup-images/ (recup_images.py via

    moon-bot). moon-bot n'heberge AUCUN sprite (juste des avatars-lettres) et son schema

    d'URL /items/<slug>/ renvoie 404 partout : cul-de-sac. Le VPS est de toute facon

    bloque en IP sur moon-bot.

    Agent PC piloté par l'AIOS (canal de commandes, token X-Sync-Token):

  • - GET /agent/next — l'agent PC récupère le prochain ordre (poll ~2 s) ; marque l'agent en ligne.
  • - POST /agent/report — l'agent renvoie log / status / result (stockés en mémoire, agent_hub.py).
  • - POST /agent/command body {action, args} — l'AIOS dépose un ordre. Actions : sync,
  • run + args.name (lance une action de la LISTE BLANCHE de l'agent = un .bat précis)

    + args.cli optionnel (options sûres passées au .bat, filtrées par _safe_cli), stop, ping.

    Le serveur ne relaie qu'un NOM (+ options allowlistées), jamais du shell (allowlist dans agent.json côté PC).

  • - GET /agent/status?events=N — en ligne ? action en cours ? derniers logs/résultats.
  • Helper AIOS : python3 lib/dofus_bot.py status|logs|sync|run <nom>|stop.

    Agent PC : Dofus-Bot-Nouveau/agent/ (aios_agent.py + Agent-AIOS.bat + agent.json).

    Pilotage agent depuis le dashboard (authentifié par SESSION, pas par token) :

  • - GET /api/agent/status et POST /api/agent/command = mêmes actions que /agent/*
  • mais protégées par le cookie de session (le navigateur ne voit jamais le token de

    synchro). Alimentent le panneau "Agent du bot" en haut du dashboard (statut en ligne,

    boutons Synchroniser / Foire / Place marchande / Foire rapide / Lecteur / Stop, logs

    en direct, refresh 3 s).

    Ingestion (token Bearer séparé, pour l'agent local):

  • - POST /api/ingest body {category, data, source_file?}
  • header Authorization: Bearer <DOFUS_INGEST_TOKEN>.

    data = même forme que les JSON scrapés (liste de relevés).

    Public: GET /health, GET /login.

    Sécurité

  • - Mot de passe admin: haché PBKDF2-SHA256 dans .env (DOFUS_ADMIN_PASSWORD_HASH),
  • jamais en clair. Session = cookie signé HMAC (DOFUS_SESSION_SECRET), Secure + HttpOnly.

  • - Ingestion protégée par DOFUS_INGEST_TOKEN (distinct du login).
  • - Synchro bot protégée par DOFUS_SYNC_TOKEN (distinct aussi ; lecture seule des fichiers du bot).
  • - .env en chmod 600, hors Git.
  • - Réinitialiser le mot de passe: régénérer un hash PBKDF2 (600k iter) et remplacer
  • DOFUS_ADMIN_PASSWORD_HASH, puis systemctl restart dofus-market.

    Alimenter la base

  • - Historique existant importé depuis /root/workspace/Data/*.json via
  • python3 backend/import_existing.py (idempotent).

  • - En continu: l'agent local Windows lit Data/*.json et POST vers /api/ingest
  • avec le token Bearer. Voir integrations/local-agent/.

  • - EN DIRECT (depuis 2026-09-06, remplace l'agent) : le lanceur du bot
  • (/root/workspace/Dofus-Bot-Nouveau/scraping-manuel/dashboard_live.py, fil

    LiveSender) POST chaque vendeur lu vers /api/ingest dans les 5 s, lots de 50,

    source_file = live <date heure>. Config cote PC : dashboard.json (URL + token).

    Intégrations

  • - Aucune dépendance Google (remplace l'export Sheets de l'ancien bot).
  • - integrations/local-agent/ : agent Python (stdlib only) qui tourne sur le PC
  • Windows du bot. Il détecte les fichiers dofus_cards_* / dofus_items_* /

    hdv_ressources_* nouveaux ou modifiés et les envoie à /api/ingest

    (catégories cards / items / resources). Envoi incrémental via empreinte sha1

    (.agent_sync_state.json), idempotent côté serveur. Modes: envoi unique,

    --watch (surveillance continue), --full (renvoi complet). Le token réel

    n'est PAS stocké ici (voir agent_config.example.json), il vit dans

    backend/.env (DOFUS_INGEST_TOKEN) et dans la copie de travail

    /root/workspace/Scripts/agent_config.json. Ne modifie PAS le bot de scraping.

    Pièges

  • - Le cookie de session est Secure → ne marche qu'en HTTPS (donc via l'URL
  • publique, pas en http://IP:8355 dans un navigateur; curl fonctionne).

  • - Les prix sont en kamas, entiers. Les ressources ont 3 lots (x1/x10/x100) =
  • 3 séries distinctes par objet.

    CHANGELOG

  • - 2026-09-24 (5) : VRAIE CAUSE des gels du bot (piles capturees par le chien de garde) : le tuyau stdout vers l'agent, pas l'OCR. 2e lancement de la reprise (bg_task d3635faf, 14h27) : potion, zaap, colonne -14 en transit, guichet, entree dans le parc, transit -9,-38, puis plus une ligne relayee apres « (-9, -38) -> (-10, -38) : direction 'gauche' » (14h31). Chien de garde interne declenche a 14h37 (300 s), debug_pnj/watchdog_20260924-142723.txt remonte : etape reelle = « Scraping map (-10, -38)... » (le bot AVAIT continue), et 3 fils bloques dans scraping_manuel.py:192 flush (ManuelWorker via _consume du sous-scraper, NetReader, NetInterp) = le tuyau stdout est plein, plus personne ne le vide. Cause : aios_agent.py lance le .bat en text=True SANS encodage (cp1252 strict sous Windows, d'ou les « â†' » dans les logs) ; la ligne [ESC-CHECK] OCR: '...' de -9,-38 (texte OCR garbage, deterministe sur cette map) contient un octet invalide en cp1252 -> UnicodeDecodeError dans readline -> fil _stream mort -> tuyau plein -> gel de tout fil qui ecrit (les 12h09, 15/09 « Raoulita » et 13/09 « capture reseau morte » sont le meme mecanisme). Correctifs : aios_agent.py 2.14 (Popen encoding="utf-8", errors="replace" ; _stream en try/except qui continue de vider le tuyau quoi qu'il arrive) ; scraping_manuel._Tee : ecriture/flush console au mieux (un tuyau casse par un redemarrage d'agent ne fait plus planter le bot, le fichier log garde tout). Le timeout OCR et le chien de garde restent (utiles). Veilleur : --attach avait un t_launch faux (started_at efface par les rapports d'etat periodiques) -> heure du « Lancement : foire-reprise » retrouvee dans les evenements. Run stoppee + tuer-bot, systemctl restart dofus-market (fix agent_hub actif), synchro agent 2.14, 3e lancement de la reprise (bg_task 1d26241b, 14h42) : reconnexion ecran=ingame immediate (repli coordonnees OK), map de reprise auto = -12,-38 (-10,-38 et -11,-38 avaient ete faites par la 2e run avant le gel), « deja dans l'enclos : pas de potion ni de zaap », scraping immediat. 1re tentative sur -12,-38 : 0/8 vendeurs de la liste MEMOIRE lus -> passage de main -> le veilleur a relance (tuer-bot + reconnexion + reprise -12,-38) ; 2e tentative : 2/8, tour complet, liste venue de la memoire -> map COMPLETE (1 vendeur present, Bresaola), puis -12,-39 (zaap, transit), -11,-39... La ligne [ESC-CHECK] OCR: 'vue 4 << -' est relayee sans tuer le lecteur : correctif agent 2.14 valide en conditions reelles.
  • - 2026-09-24 (4) : Reprise de la Foire + chien de garde interne du bot + OCR borne (gel du scrap marchand). Relecture des 800 derniers evenements de l'agent : le gel de 12h09 n'a PAS eu lieu sur -10,-38 mais sur -9,-38, map COMPLETE (6/6 vendeurs lus). Derniere ligne « Boutique encore ouverte (reseau+ecran) : Echap (essai 1/3) », puis plus rien ; en temps normal la ligne suivante est [ESC-CHECK] OCR: '...' (Move_between_maps.pressEscape -> _is_escape_menu_open : capture BitBlt + pytesseract). Le fil de travail s'est donc fige dans cette capture/OCR (tesseract = sous-process lance SANS timeout). Les 2 relevés « -10,-38 » de 12h09 en base sont un rattachement de map errone, pas un scrape de cette map ; 13 maps restaient a faire (-10,-38 .. -12,-40). Correctifs bot (scraping-manuel/, synchro OK, agent 2.13) : (1) watchdog_bot.py, chien de garde interne : pouls = ecritures stdout (_Tee.write -> tick) ; bot muet SM_WATCHDOG_STALL s (defaut 300) -> piles de TOUS les fils via faulthandler dans debug_pnj/watchdog_<horodatage>.txt (table nom->id des fils incluse), STOP_EVT, puis arret force code 3 apres 20 s de grace (minuteur programme AVANT tout, un print peut bloquer) ; filet C faulthandler.dump_traceback_later(exit=True) re-arme toutes les 5 s (tue a 480 s meme si le GIL est tenu, code 1) ; desarme + fichier supprime sur fin normale ; auto-test python watchdog_bot.py --selftest (valide sur le VPS : dump + code 3). (2) OCR borne Move_between_maps._ocr_text : timeout SM_OCR_TIMEOUT (8 s) si le pytesseract installe le supporte, exception -> lecture vide, jamais de blocage ; applique aux 3 appels (menu Echap, coordonnees x2). (3) Mode reprise --resume-from x,y / SM_RESUME_FROM (scraping_manuel.py) : les maps de la route AVANT cette map sont traversees sans scraping ([REPRISE] ... transit), [REPRISE] Map de reprise atteinte puis scraping normal ; route lue en tete de worker ; perso deja dans l'enclos de la Foire sur une map de la route (2 lectures votees identiques) -> ni potion ni zaap (bloques dans l'enclos) ; HUD masque au demarrage -> ensure_booth_closed (Echap puis croix). (4) Scraping-Foire-Reprise.bat (route-foire-auto.txt, SM_NET_DEBUG=1, defaut SM_RESUME_FROM=-10,-38 sans argument), action whitelistee foire-reprise, option CLI --resume-from (regex -?\d{1,3},-?\d{1,3}) dans aios_agent.py. Cote AIOS : lib/dofus_foire_reprise.py : reconnexion sure ([RC] ... EN JEU ; mot de passe demande -> arret propre), map de reprise = 1re map de la route avec < 3 vendeurs releves aujourd'hui (ou --from x,y), run foire-reprise --resume-from x,y, veille (agent hors ligne 3 min, silence 8 min, jeu deconnecte 4 min, [PASSE-LA-MAIN], plafond 1 h par lancement) ; run morte -> pull des watchdog_*.txt (diagnostic dans le bilan), tuer-bot + reconnexion, relance depuis la meme map puis en SAUTANT la map fautive au 2e echec (--no-skip l'interdit), --max-launches 3 ; bilan maps faites/manquantes + releves + extrait des piles. Silencieux (--notify opt-in). Lancement : python3 lib/bg_task.py launch --title "Reprise Foire Dofus" --timeout 7200 -- 'python3 /root/aios/lib/dofus_foire_reprise.py' 1er lancement (bg_task 14f541cd, 14h20) : run morte en code 2 = argparse prenait la valeur -10,-38 pour une option (« expected one argument ») ; corrige dans main() (recollage --resume-from=x,y avant parse_args), synchro, 2e lancement bg_task d3635faf 14h26 : reconnexion OK, potion -> Astrub -> zaap Plaines Rocheuses -> colonne -14 en transit [REPRISE] ... transit, chien de garde actif. Bug annexe corrige : reconnexion._screen rendait unknown x12 avec le perso EN JEU (mot « Coordonnees » rate par l'OCR, captures rc_auto_unknown_*.png) -> repli nav.read_map_once() (lecture des coordonnees) quand aucun autre ecran ne correspond ; lib/dofus_foire_reprise.py ne lit plus que les lignes de log POSTERIEURES au lancement (fresh_lines, champ ts/action des evenements) pour ne jamais confondre un vieux [RC] ... EN JEU ou [PASSE-LA-MAIN] avec la run courante. BUG BACKEND trouve dans la foulee (14h29) : agent_hub.report remettait running a vide sur N'IMPORTE QUEL resultat (sync, pull, refus « occupé par ... ») jusqu'au rapport d'etat suivant de l'agent (~35 s) ; une synchro de reconnexion.py pendant la run a fait croire au veilleur que la run etait finie (ses ordres tuer-bot/reconnexion/relance ont ete refuses « occupé », la run du bot n'a pas ete touchee). Corrige : running n'est efface que par le resultat de l'action en cours (hors refus « occup... ») ; systemctl restart dofus-market a faire apres la run en cours (file d'ordres en memoire, DB intacte). lib/dofus_foire_reprise.py : fin de run = resultat pour foire-reprise posterieur au lancement (jamais un refus), sinon running vide pendant 7 sondages (70 s) ; nouveau --attach (se raccrocher a une run foire-reprise deja en cours, puis enchainer relance/saut). Sauvegardes : .tmp/{scraping_manuel.py,Move_between_maps.py,aios_agent.py,reconnexion.py,agent_hub.py}.bak-20260924b. PIEGE : Move_between_maps.py est en CRLF ; une edition via io.open (newlines universels) le convertit en LF, a restaurer avant synchro.
  • - 2026-09-24 (3) : Scrap marchand 24/09 : partiel (2367 releves) + bot fige recupere + action tuer-bot (agent 2.12). Run scrap-general (bg_task 3bd67dcd, 11h47) : Place Marchande Bonta OK (29 vendeurs, 2 min, lignes SANS coordonnees en base : map_x/map_y NULL, map_id 4259), Village Amakna OK (81 vendeurs, 6 maps, 6 min), Foire 17/30 maps (94 vendeurs) puis le bot s'est FIGE sur -10,-38 apres « Boutique encore ouverte (reseau+ecran) : Echap (essai 1/3) » (plus aucune ligne de log, meme pas [NET] ; -9/-10,-38 = maps deja connues pour la fenetre d'achat a 2 panneaux, cf. ensure_booth_closed 16/09). Le veilleur lib/dofus_scrap_marchand.py a stoppe la run apres 10 min de silence (12h19), rc 1. Le jeu s'est ensuite deconnecte pour inactivite. CONSTAT : stop de l'agent tue le cmd.exe mais le python scraping_manuel.py (PID 14752) a survecu 40 min, fenetre « Bot - controle » encore a l'ecran (incident shell=True du 13/09, encore d'actualite). AJOUT : action whitelistee tuer-bot -> scraping-manuel/Tuer-Bot.bat (PowerShell : Stop-Process des processus dont la ligne de commande contient scraping_manuel.py ou combat_run.py, hors $PID/powershell ; jamais l'agent ni le jeu), aios_agent.py VERSION agent-2.12 (sync -> relance auto OK). Sequence de recuperation validee : run tuer-bot -> run reconnexion (Ok, login, serveur, perso Lama : OK) -> run sortir-foire. FIX veilleur : game_silent() acceptait seulement des lignes « aucun paquet » consecutives alors que le lecteur entrelace « capture armee » : corrige (toutes lignes [NET] + au moins une alerte). A CORRIGER cote bot : le gel apres l'Echap d'ensure_booth_closed (candidats : _close_options_menu/_options_menu_open, _booth_open_on_screen grab, _net_up_to_date) ; ajouter un chien de garde interne (thread) qui log l'etape en cours toutes les 60 s.
  • - 2026-09-24 (2) : Lanceur permanent du scrap marchand complet : lib/dofus_scrap_marchand.py (remplace les scripts jetables .tmp/scrap_marchand_watch.py). Lance l'action scrap-general (Scrap-General.bat : Place Marchande Bonta -> Places Marchandes Village Amakna -> Foire, 3 appels scraping_manuel.py separes) et veille jusqu'a la fin reelle avec 4 garde-fous (regle automation-liveness-guard) : agent hors ligne > 3 min, aucune nouvelle ligne de log > 10 min (stop), jeu deconnecte = que des lignes [NET] aucun paquet Dofus depuis pendant > 4 min (stop), plafond global 2h30. Bilan imprime avec les releves marchands ajoutes en base par zone (Bonta / Village / Foire, via map_y), code retour 1 si echec ou 0 releve. Silencieux (--notify opt-in). Options : --no-launch (veiller une run deja lancee), --action <nom> (une seule route), --timeout. Usage normal : python3 lib/bg_task.py launch --title "Scrap marchand Dofus" --timeout 10800 -- 'python3 /root/aios/lib/dofus_scrap_marchand.py'. Contexte : le run du 22/09 (bg_task c67b2ce7) a tourne 68 min A VIDE (0 releve marchand en base, logs = uniquement des alertes de silence reseau) avant que l'agent passe hors ligne : le jeu etait deconnecte et rien ne coupait la run ; d'ou le garde-fou « jeu deconnecte ». Premier run avec ce lanceur : 24/09 11h47 (bg_task 3bd67dcd). Prerequis rappeles par le .bat : potion Bonta (slot 1833,1032) ET potion de rappel Astrub (template) dans la barre d'action ; les routes Village/Foire partent de rappel 4,-19.
  • - 2026-09-24 : HDV alchimiste : retour a la route Milice (potion Bonta -> Milice -33,-57 -> zaapi alch -> HDV -33,-55). scraping-manuel/Scraping-HDV-Alch.bat repasse sur route-hdv-alch.txt avec SM_ZAAPI_ANCHOR=1160,301 + SM_POTION_XY=1833,1032 (meme structure que les 7 autres HDV, regle depart Milice du 13/09 ; l'economie de potion du 15/09 s'applique via zaapi-anchors.json). Cause : la variante « zaapi au sol » du 15/09 (route-hdv-alch-onmap.txt, ancre fixe 835,370) ne marchait que depuis la map -32,-50 ; dans l'enchainement de tous les HDV (lib/dofus_hdv_chain.py, alch en 8e apres forgemage) elle tombait en timeout 3 fois sur 3 (17, 18 et 22/09), soit ~40 min perdues par run. Ancienne version sauvegardee cote AIOS (.tmp/Scraping-HDV-Alch.bat.bak-20260924). Synchronisee sur le PC (sync : 1 fichier). VALIDE en live le 24/09 (bg_task edfbc329, alch en 8e position apres forgemage) : HDV ouvert au 1er essai, 118 objets lus, 270 releves, 9 min 30 au lieu de 40 min de timeout. Enchainement complet 8/8 OK en 1 h 06 (2816 releves, 1115 objets, 50 categories). LIMITE connue (pre-existante, deja 0 le 15/09) : a l'HDV alch, les categories « Potion d'oubli de metier / de sort / de maitrise » sont lues 0 : le menu du jeu classe l'apostrophe AVANT l'espace (Potion d'oubli... avant Potion de forgemagie) alors que pnj_talk._norm supprime l'apostrophe (« potiondoubli » apres « potiondeforgemagie »), donc apres « Potion de forgemagie » le bot doit REMONTER dans le menu, ce que la regle « ordre monotone » ne fait pas -> selection ratee, 0 clic. Piste : trier avec une cle qui garde l'apostrophe comme caractere < espace, ou scraper dans l'ordre exact renvoye par l'enumeration. Rappel : enchainement « tous les HDV » = python3 lib/bg_task.py launch --title "Scrap tous HDV Dofus (8)" --timeout 18000 -- 'python3 /root/aios/lib/dofus_hdv_chain.py hdv-ressources hdv-boucher hdv-bucheron hdv-mineur hdv-paysan hdv-pecheur hdv-forgemage hdv-alch' (les scripts .tmp/hdv_scrape_all.py du 22/09 plafonnaient a 25 min/HDV, trop court pour Ressources qui prend ~27 min). A surveiller : le 22/09 Boucher etait « termine » mais 0 releve Viande en base (echec silencieux, cause inconnue).
  • - 2026-09-16 : Fix lag / crash du dashboard (demande Matt). Cause : la recherche (#q) appelait loadTable() à CHAQUE frappe (un appel API /api/items + une réinjection innerHTML de la table entière par lettre), et les onglets volumineux rendaient toutes leurs lignes d'un coup (Ressources 3087, Items 1902 objets distincts) -> DOM énorme, re-render à répétition -> lag puis crash de l'onglet. Correctifs (frontend/index.html, statique, pas de restart, un simple refresh navigateur suffit) : (1) helper debounce(fn,ms) (après api()) ; recherche items debouncée 250 ms, recherches Âmes/Banque/Craft/Ventes debouncées 200 ms (.oninput). (2) Plafond RENDER_CAP=400 : loadTable ne rend que les 400 premières lignes + bandeau ".showall" « N sur M affichés » avec bouton Tout afficher (state.showAll) ; state.showAll remis à false sur changement de recherche/tri/catégorie/quantité/rareté/sous-catégorie ressources. CSS .showall. Aucun changement backend/API. Syntaxe JS validée (node --check).
  • - 2026-09-15 (suite) : Départ HDV à deux cas (économie de potion, demande Matt) + vendeur tailleur calibré. Logique dans scraping-manuel/scraping_manuel.py worker() (bloc recall_first) : au lancement d'un run HDV (route avec rappel + zaapi), CAS 1 = si le perso est DÉJÀ à Bonta sur une map dont on connaît le zaapi (zaapi-anchors.json : {"x,y":"sx,sy"}, seed -33,-57=Milice 1160,301 et -33,-55=HDV alch 1468,368), on force SM_ZAAPI_ANCHOR sur cette map et on prend son zaapi SANS potion ; CAS 2 = map inconnue/hors Bonta -> ensure_start_at_zaap (potion Bonta -> Milice -33,-57) puis ancre Milice. Helpers route_has_zaapi(), _load_zaapi_anchors(), _bonta_zaapi_anchor(coords). La table grandit au fil des maps taguées via Clic-Guide ; s'applique à TOUS les HDV Bonta (le cas 1 ne se déclenche que sur les maps listées). Vendeur tailleur SM_HDV_NPC_XY calibré 978,463 -> 980,441 (Clic-Guide). Fichiers synchronisés (overwrite) : scraping_manuel.py, route-hdv-tailleur.txt, Scraping-HDV-Tailleur.bat, zaapi-anchors.json. Testé en logique (3 cas) ; validation live = sync + run HDV.
  • - 2026-09-15 : Onglet "Capes (capture)" + toggle ON/OFF pour piloter ce que le bot scrape (demande Matt). But : choisir item par item ce que le scrap HDV releve ou ignore. (1) Backend analytics.py : capes() liste les capes deja capturees (distinct listings_v items LIKE '%cape%' avec prix) enrichies de leurs jets de base (ref_jets_for -> base_jets.json xixou), nb d'offres, dernier prix, min, et l'etat capture. Config persistee data/capture_config.json {"ignore":[noms d'origine...]} (dedup par forme normalisee) via set_capture(name, capture) / capture_ignore_list(). (2) Endpoints (app.py) : GET /api/capes (session), POST /api/capture-toggle body {name, capture} (session), GET /sync/capture-config (token sync) = {"ignore":[...]} lue par le bot. (3) Front (index.html) : onglet capes (groupe Equipement) renderCapes/drawCapes : table icone-less avec toggle-switch CSS .sw, jets de base en puces .jchip, recherche, filtre "seulement ignorees", boutons Tout capturer / Tout ignorer. (4) Bot : bot-sync/aios_sync.py _sync_capture_config() ecrit capture-ignore.json dans scraping-manuel a chaque sync ; scraping-manuel/pnj_talk.py _load_capture_ignore() (fichier ou SM_CAPTURE_IGNORE_FILE) -> set normalise passe a scrape_hdv_category_net(..., ignore_names=) qui SAUTE toute ligne dont _norm(nom) est dans la liste (match nom EXACT). Une liste d'ignore non vide force le scrape net (name-aware) au lieu du replay par position. Teste cote serveur (toggle -> /sync/capture-config reflete). ACTIVATION cote bot : necessite un sync puis un run HDV pour valider en live. Backend redemarre.
  • - 2026-09-14 : Nouvelle action sortir-foire : sortir le perso de l'enclos de la Foire du Troll + rappel. Dans l'enclos de la Foire la potion de rappel est BLOQUEE par le jeu ; il faut d'abord ressortir. Procedure (dictee par Matt) depuis -11,-37 a l'interieur : (1) se placer/verifier -11,-37 ; (2) 1er clic de sortie vers le BAS (sud) = le perso marche vers la porte ; (3) attente ~5 s ; (4) 2e clic de sortie (bas) = franchit -> changement de map ; (5) map changee -> potion de rappel (Astrub 4,-19). GARDE-FOU principal (etape la plus dangereuse) : si apres le 4 la map n'a PAS change, le 1er clic n'a pas franchi l'enclos -> on REPREND au 1er clic (boucle bornee SM_FOIRE_EXIT_TRIES, defaut 4). Confirmation finale de la sortie = la potion de rappel FONCTIONNE (refusee tant qu'on est dans l'enclos). Garde-fou serveur (_server_guard) a chaque clic, clics humanises, aucun scraping. Implementation : scraping_manuel.py exit_foire() (+ helpers _foire_locate_exit/_foire_walk_to, branche SM_SORTIR_FOIRE=1 en tete de worker()) ; lanceur Sortir-Foire.bat ; action whitelistee sortir-foire -> Sortir-Foire.bat (agent/aios_agent.py). Reglages : SM_FOIRE_EXIT_DIR (bas), SM_FOIRE_EXIT_TRIES (4), SM_FOIRE_WALK_WAIT (5 s), SM_FOIRE_CROSS_TIMEOUT (12 s). Testee en simulation (28/28 verifs : nominal, retry-depuis-etape-2, abandon borne, coupure serveur, rappel non confirme, marche depuis une autre map, coords illisibles) PUIS en live : perso parti de -9,-37 -> marche jusqu'a -11,-37 -> sortie -> arrive au zaap d'Astrub (4,-19). Lancer : python3 lib/dofus_bot.py run sortir-foire.
  • - 2026-09-13 (suite 5) : Fiche objet (clic depuis Craft ou ailleurs) : jets de reference + jets constates en ligne (demande Matt). (1) Backend : analytics.ref_jets_for(name) lit data/base_jets.json (max de base par stat, source xixou) et rend une liste ordonnee {stat, label FR, max} ; ajoute au retour de item_detail sous ref_jets. Nouveau _JET_LABELS/_JET_ORDER (libelles FR + ordre d'affichage). (2) Front (renderItemModal) : bloc "Jets de référence (normaux)" (roll de base max, puces via formatJets) affiche AU-DESSUS de la liste ; la table des annonces s'appelle desormais "Annonces constatées (prix + jets)" et affiche les jets EN LIGNE (formatJets(s.stats) au lieu du tag repliable jetsCell), limite portee a 120 annonces. CSS .refjets/.jline/table.sellt. Backend redemarre.
  • - 2026-09-13 (suite 4) : Panneau "Derniere run" repare + onglet Accueil supprime. (1) BUG : analytics.last_run() melangeait des ingested_at avec fuseau (offset-aware) et sans (naive) -> TypeError a la soustraction de dates -> GET /api/last-run renvoyait une 500 -> le panneau "Derniere run + produits interessants" ne s'affichait JAMAIS (front : if(!d) return). Corrige dans _parse_ts (normalisation naive/UTC systematique). Verifie : found=True, 71 bonnes affaires sur la run 21:56-22:09. (2) UI (demande Matt) : l'onglet Accueil est retire de CATS ; on ouvre l'accueil en cliquant le logo "DofusMarket" en haut a gauche (onclick=setCat('accueil')). L'etat cat='accueil' reste la vue par defaut au chargement et la cible du clic logo ; render()/#home inchanges. Backend redemarre.
  • - 2026-09-13 (suite 3) : Jets reels des HDV re-ingeres (Matt avait raison). La capture du scrap complet des 6 HDV (run_20260913-154141.pcap) contenait les messages EHl = le detail de CHAQUE annonce avec ses JETS (l ancien ingest ne gardait que EHP = prix MOYEN sans jets). Nouveau captures-combat/ingest_hdv_jets.py : parse les EHl (decode via lecteur_reseau.decode_effects + EFFECT_NAMES etendue : 174=Initiative, 240-244=Res fixes ; effet 623=ame exclu), ecrit une observation par annonce avec jets. 18710 annonces ingerees, ancienne source EHP scrap-hdv-items-20260913 supprimee. recipes.price_index : prix vente VERIFIE (jets basiques, non-exo, non-premium) prioritaire ; repli prix de reference sans jets plafonne DM_REF_PRICE_CAP (defaut 1.5M) via _accept_listing. Champ sell_source (marche/ref/lot) ajoute au rapport + badge ref. au front. Resultat : 1186 crafts price-es dont 1092 VERIFIES (vs 273), fausses marges multi-M eliminees (L Epee Nice masquee, Az tech 478k, Ceinture Bitoufale 666k).
  • - 2026-09-13 (suite 2) : Onglet Craft nettoye (regle Matt). (1) Prix de reference d un item calcule SANS les annonces exo (is_exo = jet PA/PM) : un objet crafte a des jets basiques, les exos gonflaient le prix de vente. Filtre dans recipes.price_index (resultat ET ingredients). (2) craft_report ne renvoie plus les recettes sans prix de reference de l objet resultat (sell_price None -> ligne masquee) ; les ingredients sans prix restent toleres. 576 annonces exo exclues, 279 crafts affiches.
  • - 2026-09-13 (suite) : Nav a plat + page d'accueil + filtre prix banque. (1) La nav
  • a 2 niveaux (groupes -> sous-onglets) est remplacee par UNE rangee d'onglets de categorie a

    plat en haut de page (renderTabs liste tout CATS dans #tabs ; #groups, setGroup,

    GROUPS/GROUP_OF ne sont plus utilises). (2) Nouvel onglet Accueil (cat='accueil',

    defaut au chargement) : le bloc "derniere run + produits interessants", les KPIs et la

    collection sont deplaces dans #home et ne s'affichent QUE sur l'accueil (render() masque

    #home sur les pages de categorie). (3) Onglet Banque : champ prix unitaire min qui masque

    les objets sous ce seuil ; les totaux (valeur globale, quantite, chips par categorie) sont

    RECALCULES cote client sur l'ensemble retenu, donc filtrer change la valeur globale affichee

    (la valeur totale complete reste rappelee "sur X au total"). Tout est cote frontend/index.html

    (servi depuis le disque : un simple refresh suffit, pas de restart).

  • - 2026-09-13 : Onglet "Ma banque" (inventaire du coffre valorise). Nouveau: table
  • banque_stock (item_name PK, qty, fiche, updated_at) creee dans db.init_db() (migration

    idempotente) ; script backend/banque_ingest.py qui lit data/agent-debug/banque-inventaire.jsonl

    (rapatrie du bot) et remplit la table en dedoublonnant par nom (garde la DERNIERE ligne a qty

    valide, car le jsonl est en mode append entre deux runs) ; analytics.banque() croise le stock

    avec le prix HDV le moins cher a l'unite (offre la moins chere du dernier jour, lot x1/x10/x100 le

    plus avantageux) ; endpoint GET /api/banque (auth) renvoie items valorises + total_value +

    by_category ; onglet front "Ma banque" (groupe Coffre) avec cartes de synthese, chips par

    sous-categorie, tableau triable. Premiere ingestion : 723 objets, qty 270 501, valeur ~27,16 M

    kamas. Reingerer apres un nouveau scan : python3 backend/banque_ingest.py puis

    systemctl restart dofus-market. Le scan cote bot : action banque-inventaire (voir bot).

  • - 2026-09-12 (soir, suite) : Premiere extinction reelle + 2 pieges. (1) Eteindre.bat avait une
  • parenthese dans un echo a l'interieur d'un bloc if (...) : cmd ferme le bloc trop tot

    (". etait inattendu.", code 255) MAIS la ligne shutdown precedente s'execute quand meme : le

    PC s'est eteint alors que l'agent renvoyait "ok: False". Corrige (plus de parenthese dans les

    blocs cmd ; regle : jamais de (/) dans un echo sous if (...)). Le .bat corrige part au

    prochain demarrage de l'agent (do_sync initial). (2) La file d'ordres (agent_hub.py) est EN

    MEMOIRE, sans endpoint d'annulation : des ordres deposes pendant que le PC est eteint

    s'executent au reboot (ici un 2e eteindre aurait re-eteint le PC). Purge = `systemctl restart

    dofus-market` (perd seulement l'historique d'evenements, pas la DB). A retenir avant tout

    run eteindre : verifier pending = 0 ensuite, et ne jamais renvoyer un ordre a un agent

    hors ligne sans purger.

  • - 2026-09-12 (soir) : Extinction du PC a distance via l'agent. Agent aios_agent.py 2.9 :
  • deux actions dans DEFAULT_ACTIONS, eteindre (Eteindre.bat = shutdown /s /t 60,

    delai en secondes surchargeable en argument CLI, message affiche a l'ecran) et

    annuler-extinction (Annuler-Extinction.bat = shutdown /a, code 0 meme si rien n'est

    en attente, erreur 1116 = normal). Usage : python3 lib/dofus_bot.py run eteindre (toujours

    avec la confirmation de Matt) puis run annuler-extinction dans la minute pour annuler.

    Le delai de 60 s laisse l'agent renvoyer son log avant la coupure. Limites : l'agent doit

    tourner sur le PC (pas lance au demarrage de Windows) ; aucun rallumage a distance (il

    faudrait du Wake-on-LAN). Pourquoi : Matt voulait eteindre son ordi depuis l'AIOS ; le

    pont agent existant evite tout tunnel/SSH supplementaire. Test OK le 12/09 (annulation).

  • - 2026-09-13 (11) : Lecteur reseau du bot : plus de retard. Incident run foire 024bfbe2 (le
  • lecteur avait 45 s de retard, faux BLOCAGE F0, 5 Echap). Cote bot (scraping-manuel/) :

    lecteur_reseau.py reecrit en 3 fils (capture brute -> interpretation -> ecriture), filtre

    noyau BPF limite a la connexion Dofus (re-arme apres 45 s sans paquet), temoins backlog /

    lag / drained(), copie brute Data/captures/run_*.pcap relue en fin de run

    (finalize() -> reconcile_from_pcap). scraping_manuel.py : _net_up_to_date() avant F0

    et A3, l'ecran tranche si le lecteur est en retard ou contredit 2 fois un ecran propre.

    Agent aios_agent.py 2.7 : option CLI --maps N autorisee (run foire --maps 2 = run

    courte de test) ; stdin=DEVNULL (le pause final des .bat rendait l'action "en cours"

    pour toujours) ; journal envoye en LOTS ({"type":"log_batch","lines":[...]}, un POST

    toutes les 0,25 s au lieu d'un par ligne : le tuyau stdout du bot ne bloque plus, c'etait

    la 2e source de retard du lecteur, 7 s mesures sur la run test). Serveur : agent_hub.report

    eclate un log_batch en evenements log, /agent/report repond {"ok":true,"batch":true}.

    sync.py : maps-provenance.json et maps-a-verifier.json passent en seed-only (ecrits

    par le bot). Moteur : suppression de l'Echap aveugle apres le scraping (ouvrait le menu

    options, 1er deplacement rate), menu options ferme par template avant tout deplacement.

    Regles : section N de Dofus-Bot-Nouveau/REGLES-BOT-PROPOSITION.md.

  • - 2026-09-13 (12) : Run foire 21:29 (arretee par Matt a 21:43). (a) Detour de 3 maps
  • (-11,-37 -> haut au lieu de droite) : _dirs_to classait un chemin appris en transit avant la

    voisine directe ; corrige (voisine d'abord). (b) A -10,-38 la capture reseau est morte en

    silence (45 s sans paquet, boutique jamais lue) : _capture_loop rouvre le handle apres 8 s

    de silence. (c) Donnees de la run recuperees : pcap run_20260912-212903.pcap relu sur le VPS

    (reconcile_from_pcap, +18 releves) puis ingestion directe db.ingest_payload : +252 lignes

    cartes, +22 lignes items. Pull : --dir "ressources-IMG-Data (1)/Data" (relatif au dossier

    bot DofusRetroBot, pas a scraping-manuel).

  • - 2026-09-12 (10) : Coût de craft récursif sur le dashboard. recipes.py :
  • resolve_price() valorise un ingrédient par son prix marché (x1), sinon par le prix

    unitaire d'un lot x10/x100 (source: lot), sinon par le COÛT DE CRAFT de sa propre

    recette (récursif, anti-cycle, profondeur <= 6, source: craft). /api/craft renvoie en

    plus derived / n_derived. Couverture : 1 775 / 2 197 recettes à coût complet (81 %,

    contre ~51 % le matin), dont 315 grâce à un intermédiaire déduit ; 278 avec marge (prix de

    vente connu). Onglet Craft : coût préfixé « ≈ » + badge quand un intermédiaire est déduit,

    info-bulles listant les ingrédients déduits / manquants. Service redémarré.

  • - 2026-09-12 (9) : Règles A1/A2/A3 implémentées (validées par Matt). lecteur_reseau.py :
  • provenance par id (maps-provenance.json : manuel / ocr / reseau-verifie / route / estime,

    confirm), ancre = dernière position vérifiée + hops_since_anchor (changements de map

    comptés au réseau), set_anchor, confirm_map, is_verified, entrée manuelle jamais

    écrasée. scraping_manuel.py : _ocr_plausible (OCR accepté seulement si distance <=

    maps traversées depuis l'ancre), verify_position appelée avant scraping et avant chaque

    mouvement (confirme, corrige, ou verification manuelle), _request_manual_verification

    (capture debug_sorties/verif_map_<id>_est_<x>_<y>.png + maps-a-verifier.json + arrêt

    propre). Côté AIOS : lib/dofus_map_verif.py check (remonte captures + attente) et

    set <id> <x> <y> (écrit maps-corrections.json + sync). Règles complètes proposées :

    workspace/Dofus-Bot-Nouveau/REGLES-BOT-PROPOSITION.md.

  • - 2026-09-12 (8) : Incident Foire : dérive de position -14,-43 -> -14,-35, cause + 4 correctifs.
  • Chronologie (journal PC scraping-manuel-log.txt, capture echec_-14_-40_bas_memoire.png

    montrant « Coordonnées -14,-37 » quand le bot se croit sur -14,-40) : à 18:23:04 la map

    change PENDANT le scraping de -14,-43 (id 3255, autres marchands) sans que la route

    le voie ; puis chaque nouvelle map est « apprise » par estimation (position supposée

    + 1) -> ids 3256..3259 enregistrés faux (décalés d'1 puis 2), et read_coords_voted

    préfère le réseau (donc la table fausse) à l'OCR écran qui affichait la vérité ;

    enfin les clics de sortie (marche > 12 s sur les grandes maps) déclenchaient des

    changements de map « que plus personne n'attendait » -> re-clics -> descente jusqu'à

    -35. Correctifs : (1) scraping_manuel.py worker : si l'id de map change pendant

    run_scraper, alerte + resynchronisation (réseau connu, sinon OCR x2 identiques) ou

    arrêt propre ; (2) _wait_coords_change : fenêtre de stabilisation 1,5 s comptant les

    doubles changements, et contre-vérification OCR (2 lectures identiques, <= 3 maps)

    avant tout apprentissage par estimation ; (3) clic de sortie touche A : timeout 12 -> 18 s ;

    (4) lecteur_reseau.load_tables applique maps-corrections.json (poussé par sync) :

    ids 3250..3262 = -14,-47..-35 (contigus, vérifiés). Données : les relevés Foire de

    18:23 à 18:33 sont rattachés à une map décalée (-43 = réellement -42, etc.) ; prix

    corrects, seule l'attribution de map est fausse.

  • - 2026-09-12 (7) : Rattrapage Os + Etoffe réussi (hdv-ressources-ici, ONLY_CATS) :
  • Os 97/104 objets (263 relevés), Etoffe 46/48 (111 relevés). Couverture recettes :

    équipements 65 % -> 87 % (1 351/1 546), toutes recettes 51 % -> 81 % (1 774/2 197).

    Le menu OCR liste maintenant 22 catégories (Os inclus) ; Etoffe reste absente de

    l'énumération mais est sélectionnée par nom via EXTRA_CATS (matching tolérant).

  • - 2026-09-12 (6) : Données manquantes : 3 correctifs bot + analyse équipements.
  • (a) lecteur_reseau.py _HDV_PRICE_RE : le champ flags accepte tout sauf ;

    (les consommables portent leurs EFFETS dans la ligne de prix, ex. 6e#4b##0d0+75 :

    rejetés avant -> "Viande/Poisson comestible" lus 0, 100 % de parasites). (b)

    pnj_talk.py : la catégorie Os (2 lettres) était jetée par le filtre OCR du

    menu (len >= 3) et Etoffe mal matchée ; à l'ouverture le serveur annonce 23

    types (paquet ECK), le bot n'en visitait que 21. Ajout _HDV_SHORT_CATS,

    _cat_match tolérant (ratio 0.8, noms >= 5), et modes SM_HDV_ONLY_CATS (ne

    scraper que ces catégories) / SM_HDV_EXTRA_CATS (ajouter aux énumérées). (c)

    Nouveaux bats/actions : hdv-ressources (one-click potion -> zaapi "ressources" ->

    HDV -32,-57, vendeur 783,512, EXTRA_CATS=Os,Etoffe, NET_DEBUG) et

    hdv-ressources-ici (rattrapage ONLY_CATS=Os,Etoffe). Le dump réseau est lu via

    pull --dir debug_pnj --glob net-debug.log. Analyse complète des trous côté

    équipements : workspace/Dofus-Bot-Nouveau/ANALYSE-DATA-EQUIPEMENTS-2026-09-12.md

    (65 % des recettes d'équipement ont un coût calculable, 3 % une marge : aucun HDV

    d'équipement jamais scrapé ; prochaine étape = HDV équipements/bijoux/armes, avec

    lecteur à faire évoluer pour garder toutes les annonces (uid) d'un même gid).

  • - 2026-09-12 (5) : HDV pêcheur + boucher ajoutés. Pêcheur : zaapi pech depuis
  • Milice -33,-57 → HDV -35,-55, vendeur PNJ SM_HDV_NPC_XY=815,642. Boucher : zaapi

    bouc → HDV -30,-50, vendeur SM_HDV_NPC_XY=1298,650 (position trouvée par

    enregistrement du clic réel via Enregistrer-HDV.bat puis pull de

    ../Data/records/<session>/events.jsonl : le signal net.hdv_count passant de 0 à

    positif isole les 2 clics d'ouverture). Bats/actions : hdv-pecheur[-recon|-ici],

    hdv-boucher[-recon|-ici]. Relevés en base : pêcheur (Poisson, Poisson vidé),

    boucher (Viande 21 objets, Viande conservée 16). BUG connu : les sous-catégories

    « comestible » (Poisson comestible 42, Viande comestible 39) sont annoncées mais lues

    0 (100% des clics = PARASITES) : la lecture réseau des lignes de ces catégories

    échoue, à corriger dans scraping_manuel.py (les autres catégories passent avec ~1

    parasite). ~81 objets comestibles encore manquants.

  • - 2026-09-12 (4) : Re-scrape correct de tous les HDV + fermeture auto du HDV.
  • Avec le correctif (3), re-scrape complet un par un (relevés live sur le dashboard) :

    alchimiste 124, mineur 160, bûcheron 82, runes forgemagie 51, paysan 44, « 0

    parasite » sur quasiment toutes les catégories. PIÈGE de navigation identifié et

    corrigé : le scrape laissait le HDV OUVERT, dont la fenêtre masque l'affichage des

    coordonnées (coin haut-droit) → au run suivant read_coords_voted échoue → potion

    de rappel abandonnée → navigation en échec. FIX : scraping_manuel.py ferme

    désormais le HDV automatiquement en fin de scrape (appelle reconnexion.do_close_hdv,

    clic souris « Fermer » 1741,798), désactivable via SM_HDV_NO_CLOSE=1. Permet

    d'enchaîner les HDV (et un futur cron balayant tous les métiers) sans close-hdv

    manuel entre chaque. Rappel navigation : chaque route métier part de rappel -33,-57

    (potion Bonta) → zaapi (onglet Hôtels de vente ; Ateliers pour le forgemage) → HDV.

  • - 2026-09-12 (3) : PRIX HDV DÉBLOQUÉS (le vrai correctif). Le "mur de capture
  • réseau" du (2) n'était PAS un problème de capture : le dump net-debug.log

    (mode SM_NET_DEBUG=1, désormais écrit dans debug_pnj/ = seul dossier pullable,

    et purgé à chaque run) a prouvé que le serveur répond à CHAQUE clic avec la ligne

    de prix, mais fragmentée (préfixe EHl<gid>| amputé au réassemblage → le

    fragment commence par les derniers chiffres du gid, ex

    4|26138;77#1##;7,0;85,0;993,0;1524). Surtout, le champ <flags> (ex 77#1##,

    9e#a##) est presque toujours REMPLI, donc il n'y a pas de ;; : l'ancienne

    regex _HDV_PRICE_RE = ";;..." ratait 51/54 runes (0 en prod), et comme la ligne

    de prix n'était jamais parsée, la moyenne (EHP fragmentée) ne l'était pas non plus.

    CORRECTIF dans lecteur_reseau.py : nouvelle regex

    ;[0-9a-fA-F#]*;(x1)?;(x10)?;(x100)?;(gid)$ (queue fiable, gid ancré en fin,

    flags rempli OU vide → rétro-compatible ancien format ; lots vides gérés) +

    nouvelle branche EHm+<uid>|<gid>|<flags>|x1|x10|x100 (variante poussée quand un

    objet est affiché/rafraîchi). VALIDÉ EN PROD : hdv-forgemage-ici passe de

    0 lu / PARASITES 52 à 51 lus / PARASITES 1, **51 runes + moyennes envoyées au

    dashboard**. Note : le [BILAN] 0 carte est l'ancien compteur fichier ; le chemin

    réseau-HDV pousse en direct (voir la ligne [LIVE] ... relevés). Bats/actions de

    debug ajoutés : hdv-forgemage-netdebug, hdv-forgemage-netdebug-ici (route/ici

    + SM_NET_DEBUG=1), réutilisables pour reverse-engineer un futur format HDV.

  • - 2026-09-12 (2) : HDV FORGEMAGE / RUNES atteint en autonomie via un NOUVEL
  • onglet zaapi. Le forgemage n'est PAS dans l'onglet « Hôtels de vente » du zaapi

    mais dans l'onglet « Ateliers » (zaapi > Ateliers > forgemage). Nouveau

    support SM_ZAAPI_TAB dans use_zaapi (pnj_talk.py) : force l'onglet du zaapi

    (mots-clés OCR, défaut hotels,vente) + repli de clic d'onglet positionné selon

    la cible (Ateliers 872,151 / Divers 955,151) si l'OCR de l'onglet rate. Route

    rappel -33,-57 → zaapi forgemage (onglet Ateliers) → map atelier forgemagie

    -38,-55 ; vendeur PNJ au comptoir sous l'enseigne bleue à rune rouge,

    SM_HDV_NPC_XY=700,668 (clic bon du 1er coup, HDV ouvert, catégorie unique

    « Rune de forgemagie » = 54 objets). Nouveaux bats/actions :

    hdv-forgemage (one-click), hdv-forgemage-recon, hdv-forgemage-ici.

    RÉSULTAT : navigation + vendeur OK, mais 0/54 runes capturées : mur de

    capture réseau des prix (cf. LIMITE CONNUE ci-dessous), ici en échec total

    (les 52 clics partent, aucune ligne de prix EHl ne remonte). « PARASITES » dans

    le log = simplement clics − résultats (pas un filtre). À débloquer avec un dump

    paquets dédié (net-debug non pullable en l'état).

  • - 2026-09-12 : HDV ALCHIMISTE scrapé en autonomie (zaapi alch depuis Milice
  • -33,-57 → HDV alchimiste -33,-55 ; vendeur PNJ au comptoir sous l'enseigne à la

    fiole verte, SM_HDV_NPC_XY=1650,457). Nouveaux bats/actions côté bot

    (Dofus-Bot-Nouveau/scraping-manuel/) : hdv-alch (one-click potion→Milice→

    zaapi→scrape), hdv-alch-recon, hdv-alch-ici. 49 objets poussés au dashboard.

    Trois correctifs bot : (1) reconnexion.py étape close_hdv + Close-HDV.bat +

    action close-hdv : ferme une fenêtre HDV restée ouverte par un CLIC souris sur

    « Fermer » (1741,798) au lieu d'Échap (Échap = keybd_event, part dans la fenêtre

    qui a le focus clavier ; au démarrage auto le jeu n'a pas le focus, donc l'Échap

    ne fermait pas le HDV et bloquait la lecture du HUD). (2) use_zaapi (pnj_talk.py)

    double-clique désormais la 1re ligne filtrée en la contraignant à la BANDE de la

    1re ligne (~Y233) : l'OCR hallucinait un faux mot à Y~650 (décor de la Milice

    transparaissant sous la fenêtre semi-opaque) et cliquait dans le vide. (3) Cadence

    de clic réglable (SM_HDV_CLICK_MIN/MAX) dans scrape_hdv_category_net.

    LIMITE CONNUE : sur ce HDV les grosses catégories de PRODUITS FINIS

    (Potion 47→6, Objet de dons 18→3, potions d'oubli 30→0) sous-capturent, alors que

    les catégories ressources passent en entier (Metaria 13/13, Teinture 5/5). Clic +

    scroll + OCR des lignes sont pourtant PARFAITS (46 clics, 47 noms distincts) et le

    client AFFICHE bien les prix : le manque est au niveau de la CAPTURE RÉSEAU des

    lignes de prix EHl pour ces items (cause non tranchée : ni la cadence — 6/47 même

    à 17s/obj — ni le scroll ni le format regex ; nécessite un dump paquets propre de

    Data/net-debug.log côté PC). Les plans de clic appris (Data/hdv_click_plans.json)

    mémorisent la capture partielle : relancer avec SM_HDV_RELEARN=1 après un futur

    correctif réseau.

  • - 2026-09-12 : NAVIGATION À 2 NIVEAUX + Agent isolé (anti-lag). Le panneau Agent du
  • bot (qui poll /api/agent/status toutes les 3 s) était PERMANENT en haut de page

    et tournait en fond sur TOUTES les vues -> lag (signalé Matt). Désormais : (1) il

    a son propre onglet « Agent du bot » (groupe Bot) ; startAgentPolling/

    stopAgentPolling : le poll 3 s ne tourne QUE quand cet onglet est ouvert, stoppé

    partout ailleurs. (2) Les onglets sont regroupés en sections (rangée de groupes +

    sous-onglets) : Cartes (cartes/paquets), Équipement (items/classiques/exos/

    montures/archi/dofus), Ressources & Craft (ressources/craft), Analyse (variations/

    vendus), Bot (agent). GROUPS + renderTabs/setGroup dans index.html ; #groups

    au-dessus de #tabs ; #agentpanel retiré du haut de page (monté dans la vue par

    renderAgent). Front pur (FileResponse par requête, aucun redémarrage).

  • - 2026-09-12 : RECETTES COMPLÈTES via le dump émulateur Ancestra 1.29 (remplace la
  • source scrapstuff équipement-seul). backend/import_recipes_ancestra.py parse

    le dump LOCAL `/root/workspace/perso/dofus-retro-129/data/AncestraRemake/BDD/

    AncestraR_Game.sql : table crafts` (id_résultat, "ingId*qty;...") = SET COMPLET

    des recettes du jeu (tous types, secrètes incluses, le serveur en a besoin). Les

    ids -> noms FR PROPRES + type via moon-bot-api/items.json LOCAL (même espace

    d'ids ; le dump Ancestra a des noms accentués corrompus/tronqués, ex. "Bois de

    Ch" pour Chêne -> moon-bot donne le nom propre). Résultat : 2197 recettes, 10453

    ingrédients, source ancestra-1.29. Moon-bot n'est PAS interrogé en ligne (bloqué

    IP) : on lit le JSON déjà aspiré en local par le projet 1.29. La source

    scrapstuff-retro (équipement seul) est retirée de la base (doublon) ;

    import_recipes.py reste dispo pour recoupement. COUVERTURE PRIX actuelle : 204

    recettes avec coût 100% calculé sur nos prix scrapés (35% des ingrédients

    tarifés). Le manque = surtout Minerai + Bois (le bot ne scrape pas encore ces

    HDV : _HDV_RESOURCE_CATEGORIES est incomplet) : les scraper ferait passer à ~465

    recettes chiffrées. Autres manquants = intermédiaires craftables (Planche, Etoffe,

    familiers, équipement) -> à chiffrer par coût de craft récursif plus tard. Rejouer :

    python3 backend/import_recipes_ancestra.py.

  • - 2026-09-12 : BASE DE RECETTES DE CRAFT (fondation) + onglet Craft (rentabilité).
  • But long terme : croiser 100% des recettes du jeu (dont secrètes) avec les prix

    scrapés pour repérer les crafts rentables (et plus tard le brisage en runes des

    bas niveaux). (1) SCHÉMA recipes_schema.sql : tables recipes

    (result_name, result_level, result_type, job, is_secret, source) +

    recipe_ingredients (ingredient_name, qty), dans la même base que les prix pour

    joindre recette<->marché. backend/recipes.py : init_recipes() (appelé au

    démarrage), price_index() (nom normalisé -> prix marché x1 = 25e percentile de

    listings_v), craft_report() (coût de craft = Σ prix ingrédient × qty, prix de

    vente = prix marché du résultat, marge, ROI, couverture des ingrédients). (2)

    IMPORT backend/import_recipes.py : source scrapstuff-retro = dump GitHub

    raw retro-craft/scrapstuff/fetched_data/items.json (noms FR, format

    {item,count}), ÉQUIPEMENT/armes uniquement (~1285 recettes, 6765 ingrédients).

    Idempotent par source. (3) API GET /api/craft?only_priced= . (4) FRONT :

    onglet « Craft (rentabilité) » (objet, niveau, type, coût de craft, couverture

    ingrédients, prix vente, marge, ROI ; tri + recherche + filtre 100% tarifé).

    ÉTAT : côté COÛT solide (161 recettes avec coût 100% calculé sur prix réels) ;

    côté VENTE encore pauvre (peu de prix d'équipement scrapés + aberrations OCR) ->

    fiabilisé quand on scrapera l'HDV équipement. Couverture ingrédients ~42% (montera

    avec les consommables, qui utilisent surtout des ressources déjà tarifées). SOURCE

    COMPLÈTE (consommables + ressources craftables + recettes SECRÈTES) en cours de

    sourcing : moon-bot ÉCARTÉ (VPS bloqué IP). Rejouer l'import :

    python3 backend/import_recipes.py.

  • - 2026-09-12 : PURGE des ressources de mars 2026 (5740 lignes) : anciens scraps aux
  • libellés OCR pollués (« | Ressource », « cr », « Etoffe »...). Base ressources

    repartie propre sur les runs du 11-12 septembre (accord Matt : repartir sain).

  • - 2026-09-12 : RESSOURCES HDV BRANCHÉES AU DASHBOARD (bout en bout) + onglets de
  • catégories. (1) PIPELINE : scraping-manuel/dashboard_live.py (LiveSender)

    envoie désormais AUSSI Data/dofus_hdv_<jour>.json (records plats du scraper

    HDV {category=res_category, name, x1, x10, x100, avg, ts}) vers /api/ingest

    en catégorie resources, en les emballant au format attendu

    {date, items:[{name, price_x1, price_x10, price_x100, category}]} (date au

    jour = 1 point d'historique/jour/lot, idempotent). Avant, seuls dofus_cards et

    dofus_items partaient : les ressources HDV n'arrivaient jamais. (2) SCHÉMA :

    listings_v expose res_category (MAX). (3) API : GET /api/res-categories

    (sous-catégories HDV : Aile, Cuir, Poudre... + nb d'objets distincts) ;

    GET /api/items accepte res_cat=<Aile|Cuir|...> (filtre). (4) FRONT : onglet

    Ressources affiche une barre de chips de catégories (« Toutes » + une par

    sous-catégorie avec compteur), qui filtre le tableau ; l'historique par lot

    x1/x10/x100 existait déjà dans la fiche objet. (5) BACKFILL : les libellés de

    res_category des données de mars 2026 (OCR pollué : « | Ressource », « cr »,

    « poil »...) sont relabellisés vers les catégories propres du run du jour par

    jointure sur le nom d'objet (.tmp/hdv_full_run.py), pour unifier tout

    l'historique sous des catégories propres. Aucun prix touché. Run complet HDV

    validé (628+ objets sur 21 catégories propres).

  • - 2026-09-12 : OUVERTURE HDV VALIDÉE SUR LE JEU. Nouvelle fonction
  • pnj_talk.open_hdv_buy(nav) : clic sur le PNJ "Vendeur de Ressources de Bonta"

    (position fixe HDV_NPC_XY=(783,512) sur la map d'arrivée -32,-57), puis clic

    "Acheter" dans le menu contextuel → la fenêtre "Hôtel de vente" s'ouvre.

    Contrairement au menu orange du zaapi, ce menu PNJ (gris sur beige) est LISIBLE

    à l'OCR : "Acheter" lu score 0.97 au run réel (repli offset fixe

    _HDV_ACHETER_OFFSET=(77,168) depuis le clic PNJ conservé en filet). Nouveau

    type d'étape de route hdv (route-hdv-open-test.txt, .bat

    Scraping-HDV-Open-Test.bat, action agent hdv-open, agent-1.7). DEUX bugs

    latents corrigés au passage : (1) route_coords[0] plantait (IndexError) sur

    une route SANS coordonnée (pure action hdv) → garde if route_coords and ...

    dans worker() ; (2) un .bat piloté par l'agent NE DOIT PAS finir par

    pause (l'agent bloque sur _proc.wait() jusqu'à la sortie du .bat → flag

    running bloqué, runs suivants refusés "occupé") : le bat de test finit par

    exit /b sans pause. Le raccourci pull exige le nom AVEC extension .png.

  • - 2026-09-12 : Frappe recherche zaapi en PRÉFIXE (anti-détection) :
  • pnj_talk._search_prefix tape ~65% des lettres du 1er mot (min 4) au lieu du

    mot entier ("ressources" → "ressour"), suffisant pour filtrer la liste ; le

    matching de la ligne reste sur le nom complet.

  • - 2026-09-12 : ZAAPI BONTA VALIDÉ SUR LE JEU (téléportation Milice -33,-57 → HDV
  • des ressources -32,-57, run réel). Correctif clé : le menu contextuel du zaapi

    ("Zaapi" + "Se faire transporter") est ILLISIBLE à l'OCR (texte sombre sur fond

    orange, police stylisée → tesseract ne rend que du bruit, vérifié sur capture

    réelle). Solution : use_zaapi (pnj_talk.py) clique désormais "Se faire

    transporter" en POSITION FIXE _ZAAPI_TRANSPORT_XY=(1320,331) (machine à

    position déterministe à Bonta) quand l'OCR échoue, au lieu d'abandonner l'essai.

    Le reste marche par OCR : onglet "Hôtels de vente" (lu via "vente"), champ

    "Rechercher", frappe "ressources", double-clic sur la ligne filtrée. NOTE : le

    HDV des ressources est à -32,-57 (adjacent à la Milice) ; le log "map hors

    route" après téléport est normal (v1 = valider le téléport, pas de scraping).

    Prochaine étape : mapper les maps marchandes autour du HDV pour les scraper, et

    ajouter zaapi equipement.

  • - 2026-09-12 : Nouvelle action agent pull (agent v1.6) = boucle de debug AUTONOME.
  • python3 lib/dofus_bot.py pull [noms...] : l'agent PC lit des fichiers de

    debug_pnj/ (défaut : z*.png récentes) et les POSTe sur /agent/debug du

    serveur ; ils atterrissent dans backend/data/agent-debug/ que l'AIOS peut lire

    directement. Plus besoin que Matt envoie les captures à la main. Serveur :

    endpoint POST /agent/debug?name= (token synchro, ≤12 Mo), pull ajouté à

    _AGENT_ACTIONS. Nginx : client_max_body_size 20M sur le vhost dofus-market

    (les captures 1920x1080 font ~2,7 Mo, sinon 413). Action hdv-bonta ajoutée aux

    DEFAULT_ACTIONS de l'agent (mappe Scraping-HDV-Bonta.bat).

  • - 2026-09-12 : Tests autonomes zaapi (côté VPS, sans le jeu) + 2 correctifs.
  • (1) load_route() (scraping_manuel.py) : le mode maps ne se déclenchait que si

    la route contenait une COORDONNÉE tuple. Une route de téléports enchaînés sans

    coordonnée (ex. zaapi ressources puis zaapi equipement) voyait ses étapes

    zaapi: silencieusement supprimées (retombait en mode directions). Corrigé :

    la présence d'un zaapi: déclenche aussi le mode maps. La route v1 réelle

    (-33,-57 + zaapi ressources) marchait déjà (elle a un tuple) ; le bug était

    latent pour l'étape 3 prévue. (2) _norm() (pnj_talk.py) retirait PAS les

    accents alors que _type_text() les retire : un onglet lu "Hôtels"/"Équipement"

    ne matchait le mot-clé sans accent que par un ratio limite (0.833 < seuil 0.85).

    Corrigé : _norm normalise NFKD comme la frappe → inclusion forte. Tests :

    6 cas de parsing + 7 cas de matching OCR, tous verts. Non testable en autonome :

    le flux end-to-end réel (clic machine + OCR sur le jeu live) exige le jeu ouvert

    sur le PC à Bonta -33,-57. Les 2 fichiers corrigés synchronisés au PC.

  • - 2026-09-12 : Agent v1.4 = options CLI sûres passées aux actions. run accepte
  • désormais args.cli (ex. --retry, --limit N, --only cards|items|resources),

    transmis au .bat via %*. Sanitizer _safe_cli() côté agent : allowlist STRICTE

    (flag inconnu ou valeur invalide => refus TOTAL), aucune injection possible malgré

    shell=True. Helper AIOS : python3 lib/dofus_bot.py run images --retry --limit 40.

    DÉCOUVERTE liée : la source d'images moon-bot (https://wiki.moon-bot.io/items/<slug>/)

    renvoie 404 sur TOUS les items (équipements compris), depuis le PC qui la joint

    bien (DNS réparé côté Matt). Le schéma d'URL / moon_urls.json est périmé : recup-images

    ne peut rien récupérer tant qu'on n'a pas la vraie structure d'URL de moon-bot (VPS

    bloqué en IP sur moon-bot, impossible à sonder côté serveur). Correctif source posé

    dans recup_images.py : une panne réseau (URLError non-HTTP) ne pollue plus le cache

    .images-tried.json (seuls les vrais 404/parse le font). NB : recup-images/ n'est

    pas encore dans le manifest de sync => correctif pas encore livrable au PC sans zip.

    RÉSOLUTION (même jour) : moon-bot abandonné comme source (aucun sprite, juste des

    avatars-lettres). Bascule sur backend/populate_icons.py qui peuple les icônes CÔTÉ

    SERVEUR depuis l'API DofusDude (art Ankama Dofus 3, api.dofusdu.de joignable du VPS).

    567 icônes posées en une passe, 0 échec (items 69% / resources 46% / cards 4%). Plus

    aucune dépendance au PC ni à moon-bot pour les images. Front inchangé (déjà branché sur

    /static/item-icons/<slug>.png avec repli onerror). Aucun redémarrage nécessaire.

  • - 2026-09-11 : Agent AUTO-UPDATABLE (v1.1). Endpoint GET /agent/code (token) sert le
  • code de l'agent ; l'agent self_update() (compile-check + backup + relance via .bat

    boucle exit 10) se met a jour seul au demarrage et sur sync/update. Actions standard

    integrees au code (foire, place-marchande, images) + surcharge agent.json. Action

    serveur update ajoutee. But : plus jamais de re-telechargement manuel pour une

    nouvelle action/correction (agent.json local token/bot_dir jamais ecrase).

  • - 2026-09-11 : Boutons agent avec retour visuel immédiat. Au clic, le bouton passe
  • tout de suite en "⏳ Lancement…" (pulsé, jaune) et TOUS les boutons run se bloquent

    jusqu'à ce que le PC démarre vraiment l'action (le script met qq s à se lancer) ->

    fini les double-clics. agentPending (state client) + renderAgentPanel() séparé du

    fetch ; résolu quand running==action / résultat plus récent / timeout 30 s. Bouton en

    cours = "● En cours…" (vert). Front pur.

  • - 2026-09-11 : Retrait du filtre "Masquer les objets sous X k" du panneau Produits
  • intéressants (inutile pour l'instant, demande Matt). renderDeals/fillDealRows

    simplifiés (plus de dealMin/input), toutes les affaires affichées. Front pur.

  • - 2026-09-11 : Panneau AGENT sur le dashboard (pilotage en 1 clic, sans passer par
  • l'AIOS). En haut du dashboard : pastille en ligne/hors ligne, boutons lancer une

    action (foire, place-marchande, foire-rapide, lecteur), Synchroniser, Stop, et un

    flux de logs live (refresh 3 s). Endpoints session-authed GET /api/agent/status

    + POST /api/agent/command (le navigateur n'a jamais le token de synchro ; allowlist

    d'actions identique). refreshAgent() + #agentpanel dans index.html. Complémentaire

    du pilotage via l'AIOS (lib/dofus_bot.py) : dashboard = usage quotidien, AIOS =

    orchestration/planifié. Restart appliqué.

  • - 2026-09-11 : AGENT PC PILOTÉ PAR L'AIOS (remplace le poll bot-sync, prépare le
  • pilotage à distance du bot). backend/agent_hub.py (état mémoire thread-safe :

    file d'ordres + événements + statut) + routes /agent/next (poll ~2 s), /agent/report,

    /agent/command (actions sync|run|stop|ping), /agent/status. L'agent PC

    (Dofus-Bot-Nouveau/agent/, zip agent.zip) garde le contact en SORTIE (rien à ouvrir

    sur la box), fait la synchro de fichiers ET lance à la demande une action de sa LISTE

    BLANCHE (nom -> .bat dans agent.json ; le serveur n'envoie jamais de shell). Logs

    remontés à l'AIOS. Helper lib/dofus_bot.py. Testé bout-en-bout sur le VPS.

  • - 2026-09-11 : Vrais graphismes des items sur le dashboard. Le front affiche l'icone
  • du jeu a cote de chaque item (tableaux, affaires, fiche) via

    itemIcon(name) -> /static/item-icons/<slug>.png (se retire seul si absente,

    onerror). Slug partage serveur/front/scraper (sync.slug, retire accents + suffixe

    " - No.X" des cartes). Nouveaux endpoints token : /sync/item-names, /sync/icons,

    POST /sync/icon (normalise en PNG <=80px via PIL, dossier frontend/item-icons/).

    Source = moon-bot (art Retro authentique + cartes), INJOIGNABLE depuis le VPS ->

    scraper cote PC Dofus-Bot-Nouveau/recup-images/ (recup_images.py + Recup-Images.bat

    + moon_urls.json 11273 slugs->url + images.json ; zip recup-images.zip). Reprend ou il

    en est (only-missing), extrait og:image, dump les pages en echec pour calibrage.

    Plomberie testee de bout en bout depuis le VPS (item-names 1613, upload+normalisation

    PNG, service /static 200) ; le fetch moon-bot ne marche que sur le PC de Matt. Restart applique.

  • - 2026-09-11 : AUTO-UPDATER du bot (fini le zip à la main). Nouveau service de
  • synchro : backend/sync.py + routes GET /sync/manifest et GET /sync/file

    (token DOFUS_SYNC_TOKEN, header X-Sync-Token). Sert les fichiers de

    Dofus-Bot-Nouveau/scraping-manuel/ (lecture seule) avec un mode par fichier :

    overwrite (code/routes/templates) vs seed (état appris : maps-retro.json etc,

    écrit seulement si absent) ; fichiers écrits en run (route-observee.txt,

    .live_sent.json) exclus. Client Windows dans Dofus-Bot-Nouveau/bot-sync/

    (aios_sync.py stdlib + Sync-Bot.bat + sync.json, zip bot-sync.zip) : poll 10 s,

    télécharge les fichiers changés, backup dans .sync-backup\, protège l'état appris.

    Testé bout-en-bout (public HTTPS 200, seed non écrasé, code restauré, backup, 401

    sans token, path-traversal 404). Restart dofus-market appliqué.

  • - 2026-09-11 : Jet unique affiche EN DIRECT (pas de pastille "jets"). Quand un objet
  • n'a qu'un seul jet (typiquement un Dofus), jetsCell appelle jetDirect(part) qui

    ecrit la valeur a cote du nom : "Dofus Pourpre [icone]+26" (icone du stat si connue,

    sinon libelle complet). Plus de pastille + bulle inutiles sur une seule ligne. Le

    cas multi-jets garde la pastille exo PA/PM + bulle. Front pur, pas de restart.

  • - 2026-09-11 : Icônes de stats du jeu dans les jets (lecture courte). Matt a fourni
  • 13 icônes (screenshots), découpées et stockées dans frontend/stat-icons/*.png

    (servies via /static/stat-icons/). formatJets affiche l'icône + la valeur

    ("❤ +273" au lieu de "+273 en vitalité") quand une icône existe, sinon repli texte

    (PA bleu / PM vert / gris). Mapping ORDONNÉ STAT_RULES + STAT_ICON_SET (clés

    ayant un fichier). Icônes en place : pa, portee, initiative, vitalite, force,

    dommages_pct, sagesse, prospection, dommages, coups_critiques, res_terre,

    res_neutre, res_eau. MANQUENT (stats présentes en base, pas d'icône) : agilite,

    chance, pm, invocations, res_air, intelligence, res_feu, soins. NB : "initiative"

    a son icône mais s'affiche encore comme "effet NNN" en base (à décoder côté bot,

    EFFECT_NAMES). Front pur (frontend/index.html + PNG), pas de restart.

  • - 2026-09-11 : Fiche objet, jets compactés + bulle survolable. La colonne "Jets"
  • de la table Vendeurs n'étale plus tout le roll (débordait horizontalement) :

    jetsCell(s.stats) = "exo PA"/"exo PM" (ou pastille "jets") + bulle info avec le

    roll complet au survol/clic. Badge EXO PA/PM du haut du modal (exoBadge) agrandi

    (14px, gras) pour l'avoir en 1 clin d'œil dès le début. Bulles rendues SURVOLABLES :

    pont transparent .jtag .jtip::before (8px) qui comble l'écart badge→bulle, donc la

    souris peut aller sur la fenêtre de stats sans la fermer (elle reste tant qu'on laisse

    la souris dessus). #msellers table{overflow:visible} pour ne pas rogner la bulle.

    Front pur, pas de restart.

  • - 2026-09-11 : "Produits intéressants", jets compactés. Dans la table des affaires,
  • la colonne Objet n'étale plus tout le roll (ça prenait trop de place) : on affiche

    juste le nom + une étiquette compacte "exo PA" (bleu) / "exo PM" (vert) déduite des

    jets, ou une pastille "jets" pour les autres équipements. Le roll complet est dans

    une BULLE INFO révélée au survol OU au clic (formatJets, PA bleu / PM vert). Front

    pur (frontend/index.html) : helper jetsCell(stats), CSS .jtag/.jbadge/.jtip

    (hover + focus-within + classe .open au clic, event.stopPropagation pour ne pas

    ouvrir la fiche). .deals table{overflow:visible} ajouté car table{overflow:hidden}

    (arrondi des coins) rognait la bulle sur les dernières lignes. Pas de restart (front à chaud).

  • - 2026-09-11 : GARDE-FOU ANTI-DOUBLONS + onglet "Vendus (est.)". (1) Une même
  • annonce (vendeur + objet + jets + prix) rescannée plusieurs fois (3x/jour, ou N

    jours d'affilée) ne compte plus qu'UNE fois dans le prix marché : nouvelle vue

    SQL listings_v (GROUP BY category,item_name,qty,seller,stats,price + days_seen

    = nb de jours distincts vus, first_seen/last_seen). list_items, dofus,

    collection et le baseline de last_run lisent désormais listings_v (sur la

    base actuelle : 41936 observations -> 33182 annonces distinctes). dedup_key

    inclut maintenant les JETS (sinon 2 exos identiques au même prix/jour se

    fusionnaient à tort). (2) list_items expose stale_max (jours max qu'une même

    annonce est restée) ; le détail item affiche une colonne "Vu (j)" par annonce

    (ambre + ⚠ si >=4 j = ne part pas = prix au-dessus du marché). (3) Nouvel

    analytics.disappeared() + /api/sold + onglet "Vendus (est.)" : compare les

    deux dernières visites d'un vendeur ; un objet présent avant, absent la dernière

    fois = a quitté l'étal = probablement vendu (non garanti : capture réseau/OCR

    peut rater un objet). Colonnes : Objet / Vendeur / Vendu~ (dernier prix) /

    Marché / vs marché / Présent (jours) / Vu en vente le / Disparu le / Map.

  • - 2026-09-10 : Détail item = 1 ligne par ANNONCE avec ses JETS complets (avant :
  • groupé par vendeur, jets perdus). item_detail sellers : GROUP BY (seller,

    price, stats, map) + colonne stats, LIMIT 120. Front sellersTable : colonne

    "Jets" (roll complet) affichée dès qu'un item porte des stats. Essentiel pour

    analyser exos/équipements (chaque exemplaire a un roll différent).

  • - 2026-09-10 : "Produits intéressants" redéfini = objets À LEUR PLANCHER. Critère :
  • min_clean <= prix actuel <= 110% du min all-time NETTOYÉ (avant : ≥35% sous le

    marché). raw_clean_min(prices) -> (min brut, min nettoyé = plus bas prix >=

    0.35x médiane, exclut aberrations OCR/fat-finger). Exposé partout : list_items

    ajoute min_raw+min_clean ; deals ajoutent min_raw/min_clean/vs_min.

    Front : table deals (Prix actuel / Min brut / Min nettoyé / vs min / Marché) +

    colonnes "Min brut" & "Min nettoyé" dans la liste d'items. Constantes

    FLOOR_CLEAN_K=0.35, DEAL_NEAR_FLOOR=0.10, DEAL_MIN_HISTORY=5.

  • - 2026-09-10 : 4 sous-catégories d'items + colonne item_type. Le lecteur réseau
  • envoie le type de chaque item ; colonne item_type TEXT (schema + migration

    init_db) stockée par l'ingest. analytics.item_group(type, stats) -> 'archi'

    (Pierre d'âme d'Archi-monstre), 'monture' (Familier/Monture/élevage), 'equip'

    (armes+armures sans PA/PM), 'exo' (équipement avec +PA ou +PM, regex \bP[AM]\b).

    list_items(..., group=) + api_items(group=). Front : 4 onglets = sous-vues

    de la catégorie items. Backfill item_type de l'historique via items-retro.json

    (41325 lignes). NB exo = proxy "donne PA/PM" (affinable avec une base de jets).

  • - 2026-09-10 : Indice de FIABILITÉ du prix marché selon le nb d'offres distinctes.
  • confidence_tier(n) : solide (>=20), moyen (8-19), faible (<8). list_items

    renvoie n_offers (offres distinctes = set (vendeur,prix)) + confidence ;

    item_detail idem (via nb de vendeurs). Front : cellule "Marché" ambre + ~ +

    ⚠ quand faible (prix indicatif, peu de relevés, souvent biaisé haut car les

    moins chères se vendent vite), tooltip avec le nb d'offres. Ne maquille pas le

    prix (reste P25), signale juste sa fiabilité. Réglable CONF_SOLID_N/CONF_MED_N.

  • - 2026-09-10 : Prix marché = PERCENTILE BAS (P25) au lieu de la moyenne élaguée.
  • analytics.market_price(prices) = 25e percentile après nettoyage des

    aberrations OCR basses (< 0.2x médiane). Motif : les annonces boutique sont des

    prix DEMANDÉS ; beaucoup restent surpricées sans partir et gonflaient la

    moyenne (386 : 7,46M -> 5,0M réel ; Meulou : 527k -> 400k). Percentile =

    relatif, marche pour grosses et petites cartes. Réglable :

    MARKET_PCTILE (0.25) et MARKET_OCR_LOW_K (0.20). robust_mean conservée

    mais plus appelée pour l'affichage marché (list_items, item_detail, collection,

    last_run baseline utilisent tous market_price). Copie front mise à jour.

  • - 2026-09-10 : Colonne map_id (id de map serveur). But : afficher la map de la
  • dernière run même quand ses coordonnées ne sont pas résolues. schema.sql +

    migration idempotente dans db.init_db() (ALTER TABLE ADD COLUMN si absente).

    _rows_from_payload/ingest_payload lisent et stockent rec["map_id"] (le

    lecteur réseau l'envoie déjà) ; _dedup inclut map_id. analytics._fmt_map

    renvoie "id X" quand les coords sont nulles ; last_run() sélectionne

    map_id, la liste maps et la colonne Map des deals utilisent ce repli.

    Front : panneau dernière run affiche la liste des maps (chips ; les id X

    non résolus sont surlignés ambre). NB : les runs déjà ingérées avant ce

    changement n'ont pas de map_id (perdu à l'ingestion) : effet à partir de la

    run suivante avec le bot à jour.

  • - 2026-09-10 : Rareté OFFICIELLE TTG (remplace le proxy prix). Mapping numéro de
  • carte -> rareté récupéré depuis l'encyclopédie TTG (endpoint

    https://dofusretrotools.com/api/ttg-data, 464 cartes), écrit dans

    frontend/ttg_rarity.json ({"386":"ultime",...}, servi via /static/).

    4 paliers officiels : Commune (166) < Rare (155) < Épique (94) < Ultime (49).

    Front : rarity(name) extrait le "No.XXX" du nom et lit la rareté réelle

    (avant : seuils de prix). loadRarityMap() chargé au boot. Classes CSS

    tr.t0..t3 (Commune=rien, Rare=bleu, Épique=violet+pulse, Ultime=holo doré

    animé + ★). Légende/filtre ramenés à 4 paliers. Join vérifié : 465/465 cartes

    réelles matchées ; paquets + bruit OCR restent neutres. Pas de restart (front).

  • - 2026-09-10 : Refonte complète du système de rareté (cartes + paquets).
  • 6 paliers nommés par prix marché : Commun <75k, Peu commun <200k, Rare <600k,

    Épique <1,5M, Légendaire <4M, Mythique >=4M (seuils calés sur ~553 cartes).

    Effets CROISSANTS façon Yugioh (CSS tr.r0..r5) : commun = nom blanc sans

    rien -> peu commun/rare/épique = nom coloré + liseré + halo -> légendaire =

    dégradé animé or + ✦ -> mythique = holographique arc-en-ciel animé + ★ +

    liseré qui cycle. Nouvelle fonction JS rarity() = paliers (avant : rampe log

    continue). Légende + filtre par rareté : barre de puces cliquables #rarbar

    (chaque puce = swatch + nom + compte, filtre la liste ; state.rar,

    window.setRar). prefers-reduced-motion géré. Front pur, pas de restart.

  • - 2026-09-10 : Rareté cartes, palier commun neutralisé. rarity() renvoie
  • commun:t<0.25 ; dans loadTable/rowHTML, une carte commune est traitée

    comme sans rareté (nom en blanc simple, ni bord ni fond gradué, comme avant).

    Les effets (bord, tint, nom coloré, animations légendaire) restent sur les

    paliers peu commun -> légendaire. Front pur, pas de restart.

  • - 2026-09-10 : Panneau "Dernière run", 2 colonnes ajoutées au tableau des
  • affaires : "Min all-time" (prix minimum jamais constaté pour l'objet, hors

    aberrations OCR < 10% du marché) et "vs min" (écart de l'offre par rapport à ce

    minimum ; = min en doré si l'offre EST le meilleur prix jamais vu, sinon

    +X%). Back : last_run() calcule floor (min sur historique + run) et

    vs_min par deal. Front : colonnes dans renderLastRun, style .dl-atmin.

    Restart service pour le back.

  • - 2026-09-10 : Coloration des lignes par RARETÉ (onglets Cartes + Paquets).
  • Front-only, pas de champ rareté en base : proxy = prix marché (robust_mean)

    sur une échelle LOG continue. Helper JS rarity(price) (rampe commun gris →

    peu commun vert → rare bleu → épique violet → légendaire or ; RAR_PMIN=5k,

    RAR_PMAX=2M). Chaque ligne : bord gauche + fond dégradés (--rc/--rct) +

    nom coloré. Palier LÉGENDAIRE (≥ RAR_LEGENDARY=2M, ~top 6%) : dégradé animé

    (rarflow), halo doré pulsé (rarpulse), préfixe ✦, respecte

    prefers-reduced-motion. Gaté sur state.cat in (cards,packs) (pas items/

    ressources : prix ≠ rareté). Rendu dans loadTable (rowHTML), CSS tr.rar/

    tr.legendary. Ajuster les seuils = constantes en tête de <script>.

  • - 2026-09-10 : Panneau "Dernière run" TOUJOURS en haut du dashboard.
  • analytics.last_run() + GET /api/last-run + div#lastrun (avant les KPIs),

    rendu par renderLastRun() (appelé au boot ET dans refreshLive, refresh 30 s).

    Deux parties : (1) résumé statistique de la dernière run (période + durée,

    relevés, objets distincts, vendeurs, maps, Dofus, répartition par catégorie) ;

    (2) "Produits intéressants" = bonnes affaires détectées automatiquement : offres

    de la run nettement sous leur prix marché historique (robust_mean calculée hors

    run, remise 35–90%, min 5 relevés d'historique, gain ≥ 2000 k), triées par gain,

    cliquables (ouvre la fiche objet). Run reconstituée par rupture temporelle sur

    ingested_at (le source_file du direct change chaque minute donc inutilisable).

    Helper front DTFR (date+heure locale). Restart service pour le back (front à chaud).

  • - 2026-09-10 : Icônes d'onglets = vrais sprites façon fan-site (cases
  • d'inventaire parchemin), GÉNÉRÉS via image-gen (Nano Banana, gemini-3-pro-image,

    hors quota Claude), recadrés + réduits 128px, servis depuis

    frontend/icons/*.png via /static/icons/. ICONS = <img class="tabic">.

    CSS .tab .tabic 26px arrondi + drop-shadow. Front-only. Sprites : carte

    (blop+étoile), paquet ficelé, épée gemme, ŒUF de Dofus orangé, émeraude, kamas+

    flèche. Régénérables via le même script si besoin d'ajuster le style.

  • - 2026-09-10 : Accueil, coût collection complète (Tabi). analytics.collection()
  • + /api/collection : ne compte que les vraies cartes numérotées ("Nom" - No.X,

    regex _REAL_CARD, exclut paquets + charabia OCR sans No). Deux totaux :

    total_floor (le moins cher de chaque carte en ignorant les prix < 10% du

    marché = anti-OCR bas) et total_market (somme des prix marché élagués) ; plus

    complétude (num_min/max, missing) et top 15 cartes coûteuses. Front : bannière

    cliquable sous les KPIs (helper KBIG compact M/Mrd) + modale détail (clic sur

    une carte -> fiche objet). Données : 464 cartes No.1–464 (2 manquantes : 323,

    337), plancher ~259 M, marché ~366 M kamas.

  • - 2026-09-10 : Dates affichées au format européen JJ/MM/AAAA (helper DATEFR
  • côté front). Couvre : barre "Dernière donnée", colonne "Vu le" (objets), liste

    vendeurs, axe X du graphe, offres Dofus, repli date du direct. Stockage DB

    inchangé (ISO AAAA-MM-JJ), conversion à l'affichage seulement. Front-only, pas

    de restart.

  • - 2026-09-10 : Le filtre période de la fiche objet pilote TOUTE la modale
  • (graphe + stats min/max + liste vendeurs), plus seulement les vendeurs.

    item_detail(..., days) applique le cutoff date('now','-N days') aussi à la

    série du graphe ; /api/item?period=N. Front : sélecteur période (7j/30j/90j/

    Tout) remonté au-dessus du graphe, renderItemModal(d) reconstruit tout au

    changement. Tooltip Chart.js : interaction mode:'index', intersect:false +

    callbacks formatés en kamas, pointHoverRadius sur les deux courbes -> le prix

    marché (courbe médiane) se lit au survol n'importe où sur l'axe.

  • - 2026-09-10 : Onglet Vendeurs retiré (identité vendeur trop éphémère + noms OCR
  • bruités : 68% vus 1 seul jour, 42% de noms douteux). Route /api/sellers et

    analytics.sellers() supprimées. L'info vendeur actionnable reste dans la

    fiche objet (liste "Vendeurs (meilleurs prix)" filtrable par période).

  • - 2026-09-10 : Filtre période sur la liste "Vendeurs (meilleurs prix)" de la
  • fiche objet. item_detail(..., seller_days) + /api/item?period=N : ne garde

    que les relevés des N derniers jours (obs_date >= date('now','-N days')) et

    RECALCULE le meilleur prix sur la période (pas un simple masquage). Front :

    chips 7j / 30j / 90j / Tout au-dessus du tableau, défaut 30j (le vieux stock

    de mars est masqué par défaut), re-fetch qui remplace juste le tableau.

  • - 2026-09-10 : Onglet Paquets séparé. list_items() prend kind
  • (monsters|packs|all) filtrant sur le préfixe "Paquet de cartes" ; route

    /api/items expose kind. Front : onglet "Paquets" (category=cards&kind=packs)

    et l'onglet "Cartes" ne montre plus que les monstres (kind=monsters), zéro

    doublon. 49 paquets / 503 cartes monstres.

  • - 2026-09-10 : Prix marché (moyenne élaguée) remplace la moyenne brute PARTOUT.
  • analytics.robust_mean() : ancre sur la médiane, ne garde que la bande

    [0,35x ; 2,5x] la médiane, moyenne le reste (constantes ROBUST_LOW_K/

    ROBUST_HIGH_K). Vire les cartes complètement surpricées (un vendeur à 20-50x

    fausse une moyenne classique) et les aberrations OCR basses, sans se laisser

    tirer. Appliqué à : table objets (market_price, colonne "Marché"), séries du

    détail (market, ligne "Prix marché" du graphe), paliers de jet Dofus

    (market). Effet mesuré : Bouftou No.81 34 416 -> 8 382 (médiane 8 999) ; un

    objet déjà propre (Le Chouque) reste inchangé. Restart service applique le back

    (le front est re-servi à chaud).

  • - 2026-09-10 : Onglet Dofus (prix par jet). analytics.dofus() + route
  • GET /api/dofus : regroupe les Dofus (item_name LIKE 'Dofus %') par type puis

    par valeur de jet (lue dans stats), agrege meilleur prix / moyen / max /

    nb d'offres par palier. Front index.html : onglet "Dofus" avec selecteur de

    type (chips) et echelle VERTICALE par jet (une barre = meilleur prix du palier,

    largeur proportionnelle au prix max du type), tri par jet ou par prix, clic sur

    un palier = detail des offres (vendeur/prix/position/date). But : comparer d'un

    coup d'oeil quel jet vaut combien. Aucun em-dash ajoute. Restart service applique.

  • - 2026-08-07 — Agent local (partie 2). Ajout de integrations/local-agent/:
  • pont Python entre le bot Windows et /api/ingest. Lit les JSON produits par

    le scraper (aucune modif du bot), envoi incrémental idempotent, mode watch.

    Testé de bout en bout contre l'URL publique (22 fichiers, 401 sur mauvais

    token, 0 doublon sur renvoi). Creds Google legacy (credentials.json,

    token.json) sortis de /root/workspace/Data/ vers un stockage interne

    verrouillé (plus de secret Google dans le workspace).

  • - 2026-08-07 — Création. App FastAPI + SQLite + dashboard. Import de l'historique
  • (31 113 observations: 514 cartes, 803 ressources, 11 items, 1808 vendeurs, du

    2026-03-01 au 2026-03-18). Auth par mot de passe haché + token d'ingestion.

    Publiée sur https://dofus-market.panelbay.com.

  • - 2026-09-06 : Envoi en direct. analytics.summary() expose last_ingest /
  • today_ingest (table ingestions, source agent) ; index.html affiche "Direct :

    dernier envoi il y a X min (+N)" (vert < 10 min) avec refresh 30 s. Cote bot,

    dashboard_live.py envoie chaque vendeur pendant le run (bloc 1 du projet

    "app Dofus" : live data, puis app tray, puis deblocage IA). Pourquoi : voir les

    prix arriver pendant le scraping sans copier de fichiers.

  • - 2026-09-12 (13) : Faux tour complet par contenu identique (place marchande (0,1), 22:26).
  • Deux marchands (Exodur, Satelaidy) vendaient exactement 1 x Cape Dragarmure a 319999 ; le

    lecteur reconnaissait le tour complet par l'empreinte de contenu et coupait la passe a la

    28e boutique sur 30 (Pantougle, Xx-x-x-Xx-[B12] jamais lus, 3 passes identiques, handover,

    5 maps de la route non scrapees). lecteur_reseau.py (lecteur-2026-09-13b) : tour complet =

    meme id marchand rouvert ; empreinte de contenu seulement si l'id est inconnu ; journal

    "meme contenu mais marchand different, je continue". Verifie au rejeu de la copie brute

    run_20260912-222331.pcap (avant : wrap sur Satelaidy ; apres : wrap au vrai retour sur Exodur).

    Regle N8 dans REGLES-BOT-PROPOSITION.md.

  • - 2026-09-12 (14) : Fin de passe par comptage (regle N9, couche 1 validee par Matt).
  • lecteur_reseau.py (lecteur-2026-09-13c) : pass_seen/pass_opens/wrap_reason ; une passe

    s'arrete quand 'Suivant' rouvre une boutique deja ouverte dans la passe (meme id), quand tous

    les marchands annonces sont lus, ou quand le cycle est epuise (ouvertures > annonces + 1).

    L'empreinte de contenu ne sert que si l'id est inconnu. scraping_manuel.py

    (moteur-2026-09-13b) : boutiques lues remises a zero par MAP (plus par passe), reset

    pass_seen a chaque passe, passe sans progres (memes manquants) = passe-la-main direct,

    message [MANUEL] Fin de passe (reseau) : <raison>. Rejeu run_20260912-222331.pcap : maps

    6/6 closes au 6e marchand, (0,1) sans faux tour complet. Couche 2 (clic cible sur la case des

    manquants) non faite.

  • - 2026-09-12 (15) : Run place marchande depuis (0,1) : 4 correctifs (regle N10). (a) DB :
  • 227 lignes / 26 vendeurs "marchand -N" du 12/09 supprimees (doublons verifies par contenu exact

    des releves nommes de 22:25 ; sauvegarde dofus-market.db.bak-20260912-marchandN). (b)

    lecteur_reseau.py (lecteur-2026-09-13d) : memoire id->nom par map Data/marchands-ids.json

    (_remember_merchants sur liste serveur, preload_merchants(coords) quand la liste n'est jamais

    arrivee, nom retrouve a l'ECK4, merchants_from_cache, map_id_for) ; idle_reopen 8 -> 15 s.

    (c) scraping_manuel.py (moteur-2026-09-13c) : preload avant scraping, liste memorisee + tour

    complet = COMPLETE meme avec absents, plafond scraper 60 sans liste / annonces+3 avec. (d)

    route-place-marchande.txt : (1,-1) n'a pas de sortie gauche -> 1,-2 et 0,-2 ajoutees (6

    marchands chacune), 10 maps. Agent 2.8 : action place-marchande-ici

    (Scraping-Place-Marchande-Ici.bat, scrape la map de depart). Tests hors jeu OK (cache +

    rejeu pcap).

  • - 2026-09-13 (16) : Onglet Ventes 62 s -> 1,4 s (10 ms en cache). analytics.disappeared
  • faisait ~7000 requetes par vendeur sans index et regroupait toute la table (vue listings_v)

    pour chacun des 4652 objets disparus. Ajout des index idx_obs_seller_date

    (seller, obs_date) et idx_obs_cat_item_qty (category, item_name, qty) dans schema.sql

    (crees sur la base live), prix marche memorise par objet dans l'appel, resultat cache tant

    que (COUNT(*), MAX(ingested_at)) ne change pas (_SOLD_CACHE, champ cached). Service

    redemarre. Question de Matt (lag PC) : la page ne fait qu'un rafraichissement de 0,4 Ko / 30 s ;

    la charge PC vient du bot en run, pas du dashboard.

  • - 2026-09-13 (17) : Potion de rappel -> Astrub (4,-19) + etape de route zaap. Matt a change
  • le zaap de rappel (Astrub au lieu de la Foire). scraping_manuel.py (moteur-2026-09-13e) :

    ZAAP_RAPPEL=(4,-19) par defaut pour rappel, nouvelle etape zaap <recherche> @ x,y (parsing

    zaap:<texte>|x,y, branche de boucle, arrivee verifiee au reseau puis marche vers l'etape

    suivante). pnj_talk.use_zaap (clic zaap, Utiliser par OCR/offset, recherche, ligne par OCR ou 1re

    ligne, DOUBLE-clic, confirmation reseau), constantes mesurees sur 2 enregistrements (action

    record, sessions zaap-astrub-foire_090943 et zaap-astrub-2_091234, PC Desktop\\Data\\records).

    Foire = "Plaines Rocheuses" [-14,-47]. route-foire-auto.txt : rappel 4,-19 puis `zaap plaines ro

    @ -14,-47. Agent : action borderless` (Borderless-Auto.bat) pour forcer le 1920x1080 a distance.

    Pas encore testee en run reelle.

  • - 2026-09-13 (18) : Fix onglet Craft : historique vide au clic sur un objet crafte.
  • Symptome (Matt) : dans Craft (rentabilite), cliquer un objet ouvrait une fiche vide

    (pas d'historique/vendeurs) malgre un scrap complet des HDV. Cause : le clic appelait

    openItem(result_name) SANS categorie, donc le front envoyait /api/item?category=craft ;

    or item_detail filtre STRICTEMENT sur category et il n'existe AUCUNE ligne

    category='craft' en base (les objets sont en items / resources) -> 0 resultat pour

    toutes les recettes. Correctif : recipes.craft_report expose sell_category (categorie

    obs reelle de l'objet resultat, tiree de price_index().cats, priorite items>resources>cards) ;

    frontend/index.html (drawCraft) passe r.sell_category a openItem (repli 'items').

    Verifie : 1186 recettes, 954 items + 232 resources, historique + vendeurs remontent

    (avant : 0 pt / 0 vendeur). Service redemarre. Cote navigateur : rafraichir la page (Ctrl+Maj+R).