From b71add4dac147f5d5e097062e55a4f08776eb0e6 Mon Sep 17 00:00:00 2001 From: louispaulb Date: Fri, 24 Jul 2026 15:04:41 -0400 Subject: [PATCH] feat(legacy-sync): adresse depuis le CORPS du ticket + exclut hors-sujet (pneus/stage) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adresse de service absente (delivery/facturation vides) → on extrait « Adresse : … » du corps du ticket (ex. #143107 « 2081 route 202 Hinchinbrooke ») : affichée + géocodée (RQA > OSM, ville détectée via muni). Le dispatch n'est plus aveugle quand l'adresse est tapée dans les notes. - NEG_RE : exclut aussi « pneu / demande de stage / stagiaire » (hors-sujet, option D). Co-Authored-By: Claude Opus 4.8 --- .../targo-hub/lib/legacy-dispatch-sync.js | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/services/targo-hub/lib/legacy-dispatch-sync.js b/services/targo-hub/lib/legacy-dispatch-sync.js index b5faa60..b7277be 100644 --- a/services/targo-hub/lib/legacy-dispatch-sync.js +++ b/services/targo-hub/lib/legacy-dispatch-sync.js @@ -558,7 +558,23 @@ async function buildJob (t, { dryRun = false } = {}) { const dc = coord(t.dv_lat, t.dv_lon) const svcAddr = [t.dv_addr, t.dv_city, t.dv_zip].filter(Boolean).join(', ') const billAddr = [t.address1, t.address2, t.city, t.state, t.zip].filter(Boolean).join(', ') - const addr = svcAddr || billAddr + let addr = svcAddr || billAddr + let bodyGeo = null + // FALLBACK adresse : ni delivery ni facturation → extraire « Adresse : … » du CORPS du ticket (ex. #143107 : + // « Adresse : 2081 route 202 Hinchinbrooke »). Sert l'affichage ET le géocodage (ville détectée via muni sur la chaîne). + if (!addr && t.first_msg) { + const body = stripHtml(t.first_msg) + const m = body.match(/adresse\s*:?\s*([0-9][^\n·|]{5,88})/i) + if (m) { + const raw = m[1].trim().replace(/\s+/g, ' ').replace(/\s+(voir|svp|s'il|merci|t[eé]l|cell|contact|pour)\b.*$/i, '').trim() + if (raw.length >= 6) { + addr = raw + let cityG = '' + try { await muni.load(); for (const w of raw.split(/[\s,]+/).filter(x => x.length >= 4 && !/^\d/.test(x)).reverse()) { const mm = muni.resolveSync(w); if (mm.matched && !mm.ambiguous) { cityG = String(mm.canonical || '').split(/\s+/).map(x => x === 'ST' ? 'Saint' : x === 'STE' ? 'Sainte' : x).join('-'); break } } } catch (e) {} + bodyGeo = { line: raw, city: cityG } + } + } + } // Le client existe (ou vient d'être créé) mais n'a AUCUNE Service Location → on la crée depuis l'adresse de service legacy // (delivery de préférence, sinon facturation) pour que la fiche montre l'adresse et que le job pointe vers un lieu réel. if (cust && !sl && CREATE_MISSING && !dryRun) { @@ -642,6 +658,10 @@ async function buildJob (t, { dryRun = false } = {}) { else if (ctr) { payload.latitude = ctr.lat; payload.longitude = ctr.lon; coordSrc = 'postal_centroid' } // 3) repli : jamais la mauvaise ville } } + if (!coordSrc && bodyGeo) { // coords depuis l'adresse extraite du CORPS : RQA autoritaire, sinon OSM + const g = (await geocodeRQA(bodyGeo.line, '', bodyGeo.city)) || (await geocodeNominatim(bodyGeo.line, bodyGeo.city, '')) + if (g) { payload.latitude = g.lat; payload.longitude = g.lon; coordSrc = 'body_addr_geocode' } + } if (!coordSrc) { // sans compte : retrouver le client par le TÉLÉPHONE du sujet (compte UNIQUE + 1 seule adresse) → adresse PRÉCISE + lien client const pm = await accountByPhone(pool(), t.subject) if (pm) { @@ -786,7 +806,7 @@ async function ingestAssignedImpl ({ dryRun = false, loDays = -7, hiDays = 30, i const FIELD_RE = /install|r[eé]paration|t[eé]l[eé]|monteur|fusion|d[eé]sinstall|coupure|bris|signal|modem|\bonu\b|\bont\b|fibre|ramassage|connect|d[eé]viation|antenne|prise|internet|d[eé]m[eé]nagement|changement|activation|\bstb\b|d[eé]codeur/i // + « nouveau message » / « message vocal » : notifications de messages ENTRANTS clients (pas des interventions terrain) — // elles n'ont ni client ni adresse et encombraient le dispatch. Exclues de l'import terrain (option 1, retour terrain #255465). - const NEG_RE = /cong[eé]|absence|vacances|\bf[eé]ri[eé]|r[eé]union|formation|nouveau message|message vocal/i + const NEG_RE = /cong[eé]|absence|vacances|\bf[eé]ri[eé]|r[eé]union|formation|nouveau message|message vocal|\bpneu|demande de stage|stagiaire/i let created = 0, updated = 0, skipped = 0, errors = 0 const details = [], errSamples = [] for (const t of (rows || [])) {