SPB Git forge

spb/vrai-prix

Public

Vrai-Prix — l'évaluation du vrai prix des propriétés résidentielles au Québec.

60commits 1branches 0releases
12.3 MBsize
maindefault branch
17 days agolast push
TypeScript 90.2% JavaScript 3.5% Python 3.4% CSS 1.9% HTML 0.6%
4.0 KB · 62 lines typescript
Raw Blame History
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