Repository Public
1// UQO Éval — Métré : test de passage à l'échelle — catalogue synthétique de 10 000 articles (base isolée), recherche et contexte de prix mesurés.2import { afterAll, beforeAll, describe, expect, it } from "vitest";3import fs from "fs";4import os from "os";5import path from "path";67const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "metre-scale-"));8process.env.METRE_DB = path.join(tmp, "metre.db");9process.env.METRE_FILES_DIR = path.join(tmp, "files");10process.env.COST_DB = path.join(tmp, "cost.db");1112const [{ getCostDb }, { searchItems, loadPrices, loadItems }, { getMetreDb }, { buildQuoteContext }, { defaultParams }] = await Promise.all([import("../cost/db"), import("../cost/catalog"), import("./db"), import("./estimates"), import("./estimate-engine")]);1314const N = 10000;15beforeAll(() => {16 const d = getCostDb();17 const ins = d.prepare("INSERT OR IGNORE INTO cost_items(canonical_code,masterformat_code,division,category,name_fr,name_en,unit,material_class,trade,default_waste_pct,reference_price,reference_note) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)");18 const words = ["gypse", "contreplaqué", "isolant", "bardeau", "brique", "béton", "fenêtre", "porte", "carrelage", "peinture", "câble", "tuyau", "poutre", "solive", "membrane"];19 const tx = d.transaction(() => { for (let i = 0; i < N; i++) ins.run(`SYN-${String(i).padStart(5, "0")}`, "06", "Bois", "structure", `${words[i % words.length]} synthétique ${i}`, `synthetic ${words[i % words.length]} ${i}`, "pi2", "material", "charpentier", 0.05, 1 + (i % 97) / 10, "synthétique (test)"); });20 tx();21 getMetreDb();22});23afterAll(() => { try { fs.rmSync(tmp, { recursive: true, force: true }); } catch { /* ignore */ } });2425describe("passage à l'échelle (10 000 articles)", () => {26 it("recherche d'articles en moins de 250 ms et contexte de prix en moins de 2 s", () => {27 const d = getCostDb();28 expect((d.prepare("SELECT COUNT(*) n FROM cost_items").get() as { n: number }).n).toBeGreaterThanOrEqual(N);29 const t0 = performance.now();30 const hits = searchItems("isolant", 30, d);31 const dt = performance.now() - t0;32 expect(hits.length).toBe(30);33 expect(dt).toBeLessThan(250);34 const t1 = performance.now();35 const items = loadItems(d);36 const prices = loadPrices("2026-10-28", null, d, items);37 const dt2 = performance.now() - t1;38 expect(prices.size).toBeGreaterThanOrEqual(N);39 expect(dt2).toBeLessThan(2000);40 const t2 = performance.now();41 const ctx = buildQuoteContext(getMetreDb(), null, defaultParams("2026-10-28", "QC-MTL"));42 const dt3 = performance.now() - t2;43 expect(ctx.pricing.items.size).toBeGreaterThanOrEqual(N);44 expect(dt3).toBeLessThan(3000);45 console.info(`[scale] searchItems ${dt.toFixed(1)} ms · loadPrices(${N}) ${dt2.toFixed(0)} ms · buildQuoteContext ${dt3.toFixed(0)} ms`);46 });47});48