Vrai-Prix — l'évaluation du vrai prix des propriétés résidentielles au Québec.
TypeScript 90.2%
JavaScript 3.5%
Python 3.4%
CSS 1.9%
HTML 0.6%
1// Auteur : Simon-Pierre Boucher — contact@spboucher.ai2/**3 * Planificateur de la base de coûts (méthode du coût) :4 * npx tsx scripts/cost-sync.ts # tous les connecteurs dus (fréquence nominale)5 * npx tsx scripts/cost-sync.ts apchq statcan # connecteurs choisis (même si non dus)6 * npx tsx scripts/cost-sync.ts canac --force # ignore le cache de hash / la fréquence7 * npx tsx scripts/cost-sync.ts --items LUM-2X6-8,GYP-1/2-4X8 canac8 * npx tsx scripts/cost-sync.ts --max 20 bmr9 * Fréquences nominales : détaillants 1 j · APCHQ 7 j (+ hash) · CCQ 7 j · StatCan 30 j.10 * Charge .env.local sans dépendance. Conçu pour PM2 (cron_restart) sur le nœud.11 */12import fs from "fs";13import path from "path";1415function loadEnv(): void {16 for (const f of [".env.local", ".env"]) {17 const p = path.join(process.cwd(), f);18 if (!fs.existsSync(p)) continue;19 for (const line of fs.readFileSync(p, "utf8").split("\n")) {20 const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)\s*$/);21 if (!m || line.trim().startsWith("#")) continue;22 if (process.env[m[1]] == null) process.env[m[1]] = m[2].replace(/^["']|["']$/g, "");23 }24 }25}26loadEnv();2728async function main(): Promise<void> {29 const { RUNNABLE_KEYS, isRunnable } = await import("../src/lib/cost/connectors/registry");30 const { isDue, runConnector } = await import("../src/lib/cost/connectors/run");31 const { getCostDb } = await import("../src/lib/cost/db");3233 const args = process.argv.slice(2);34 const force = args.includes("--force");35 const itemsArg = args.find((a) => a.startsWith("--items"));36 const items = itemsArg ? (itemsArg.includes("=") ? itemsArg.split("=")[1] : args[args.indexOf(itemsArg) + 1]).split(",").map((s) => s.trim()).filter(Boolean) : undefined;37 const maxArg = args.find((a) => a.startsWith("--max"));38 const maxPages = maxArg ? Number(maxArg.includes("=") ? maxArg.split("=")[1] : args[args.indexOf(maxArg) + 1]) : undefined;39 const verbose = args.includes("--verbose") || args.includes("-v");40 const requested = args.filter((a) => !a.startsWith("-") && isRunnable(a) && a !== (items ? items.join(",") : "") && a !== String(maxPages));41 const d = getCostDb();42 const keys = requested.length ? requested : RUNNABLE_KEYS.filter((k) => force || isDue(k, d));43 if (!keys.length) { console.log("[cost-sync] rien à faire (aucun connecteur dû)."); return; }44 console.log(`[cost-sync] ${new Date().toISOString()} — connecteurs : ${keys.join(", ")}${force ? " (--force)" : ""}${items ? ` — articles : ${items.join(",")}` : ""}`);45 let failed = 0;46 for (const key of keys) {47 if (!isRunnable(key)) continue;48 const t0 = Date.now();49 const stats = await runConnector(key, {50 force, onlyItems: items, maxPages, db: d,51 log: (e) => { if (verbose || e.status === "error" || e.status === "unavailable") console.log(` [${key}] ${e.status.padEnd(11)} ${e.url ?? ""} ${e.message ?? ""}`.trimEnd()); },52 });53 if (stats.status === "error") failed++;54 console.log(`[cost-sync] ${key.padEnd(13)} ${stats.status.padEnd(11)} pages ${stats.pages} · obs ${stats.observations} · acceptées ${stats.accepted} · rejetées ${stats.rejected} · inchangées ${stats.unchanged} · erreurs ${stats.errors} · ${Math.round((Date.now() - t0) / 1000)} s${stats.message ? ` — ${stats.message}` : ""}`);55 }56 const s = d.prepare("SELECT (SELECT COUNT(*) FROM cost_item_prices WHERE price_kind IN ('observed','official')) prices, (SELECT COUNT(DISTINCT cost_item_id) FROM cost_item_prices WHERE observation_date >= date('now','-120 days')) items, (SELECT COUNT(*) FROM labour_rates) labour, (SELECT COUNT(*) FROM construction_cost_indices) idx").get() as { prices: number; items: number; labour: number; idx: number };57 console.log(`[cost-sync] base : ${s.prices} observations de prix · ${s.items} articles avec prix récent · ${s.labour} grilles de main-d'œuvre · ${s.idx} points d'indice`);58 if (failed) process.exitCode = 1;59}6061main().catch((e) => { console.error("[cost-sync] échec :", e); process.exit(1); });62