Repository Public
1// UQO Éval — Métré estimatif / intelligence de la construction (module « metre »)2/**3 * Types partagés du module Métré. Module PUR (importable côté client).4 *5 * Vocabulaire :6 * - projet (project) : un bâtiment / une propriété à chiffrer7 * - plan (plan_file/page) : PDF ou image importé, calibré par page8 * - objet (plan_object) : polygone, polyligne, ouverture, annotation dessinés sur une page9 * - mesure (measurement) : ligne du registre de métré (formule + entrées + valeur + unité)10 * - lien (quantity_link) : mesure → assemblage × facteur d'ajustement explicite11 * - estimation (estimate) : état de travail mutable ; révision (version) = instantané immuable12 * - preuve de prix : classe d'évidence (barème publié, contrat octroyé, soumission…)13 */1415export type Role = "student" | "instructor" | "admin" | "professional";16export type Lang = "fr" | "en";17export type UnitSystem = "metric" | "imperial";1819/* ---------------------------------------------------------------- unités */2021export type LengthUnit = "m" | "ft" | "mm" | "cm" | "in";22export type AreaUnit = "m2" | "ft2";23export type VolumeUnit = "m3" | "ft3" | "yd3";24export type MeasureUnit = LengthUnit | AreaUnit | VolumeUnit | "unit" | "h" | "lump";2526/* ------------------------------------------------------------- utilisateur */2728export interface MetreUser {29 id: string;30 displayName: string;31 role: Role;32 createdAt: string;33}3435/* ----------------------------------------------------------------- projet */3637export type BuildingKind = "detached" | "semi_detached" | "row" | "plex" | "condo" | "chalet" | "commercial" | "other";3839export interface Project {40 id: string;41 ownerId: string;42 name: string;43 address: string | null;44 municipality: string | null;45 regionCode: string | null; // cost_locations.code (QC-MTL, QC-QUE, QC-GAT…)46 propertyId: string | null; // id_provinc MAMH (lien facultatif avec UQO Éval)47 valuationDate: string; // YYYY-MM-DD48 buildingKind: BuildingKind;49 stories: number;50 unitSystem: UnitSystem;51 notes: string | null;52 isTemplate: boolean;53 /** verrouillé par son créateur : lecture seule pour les autres (plateforme ouverte) */54 locked: boolean;55 /** fiche technique pour le relevé des quantités (null = défauts) */56 spec: Record<string, unknown> | null;57 createdAt: string;58 updatedAt: string;59}6061/* ------------------------------------------------------------------ plans */6263export interface PlanFile {64 id: string;65 projectId: string;66 filename: string;67 mime: string;68 bytes: number;69 sha256: string;70 pageCount: number;71 createdAt: string;72}7374export interface PlanPage {75 id: string;76 fileId: string;77 pageNo: number; // 1-based78 widthPx: number;79 heightPx: number;80 label: string | null;81 calibration: Calibration | null; // révision courante82 /** origine : import (pdf/png/jpg/dxf), vierge (plan dessiné), carte (satellite géoréférencée), gabarit */83 source?: "import" | "blank" | "map" | "template";84 /** niveau (sous-sol, rez-de-chaussée, étage, toit, site) */85 level?: string | null;86}8788/** Calibration d'échelle par deux points connus (révisionnée, jamais écrasée). */89export interface Calibration {90 id: string;91 pageId: string;92 revision: number;93 p1: Point;94 p2: Point;95 realLength: number;96 unit: LengthUnit;97 /** pixels par mètre (dérivé) */98 pxPerMeter: number;99 /** incertitude relative estimée (clic ± 1,5 px sur chaque point) */100 uncertaintyPct: number;101 createdBy: string;102 createdAt: string;103}104105export interface Point { x: number; y: number }106107export type ObjectKind = "room" | "slab" | "wall" | "opening" | "roof" | "foundation" | "line" | "area" | "annotation" | "dimension" | "text" | "stairs" | "column" | "footprint" | "lot";108109export interface ObjectProps {110 /** hauteur (m) — murs */111 height?: number;112 /** épaisseur (m) — dalles, fondations */113 thickness?: number;114 /** pente en pouces de hausse par 12 po (toits) */115 pitch?: number;116 /** largeur × hauteur (m) — ouvertures dessinées comme un point/segment */117 openingWidth?: number;118 openingHeight?: number;119 /** id du mur dont l'ouverture se déduit (une seule déduction possible) */120 deductFrom?: string | null;121 /** nombre d'unités (ouvertures identiques, etc.) */122 count?: number;123 /** calque d'affichage */124 color?: string;125 note?: string;126 /** ouvertures : porte | fenêtre | baie libre ; battement ±1 (côté), ±2 (charnière à droite) */127 openingKind?: "door" | "window" | "opening";128 swing?: number;129 /** mur extérieur (compte dans l'enveloppe) */130 exterior?: boolean;131 /** texte d'annotation / taille (px page) */132 text?: string;133 fontSize?: number;134 /** cotation : décalage perpendiculaire (px page) */135 offset?: number;136 /** pièce : usage (chambre, cuisine…) */137 usage?: string;138}139140export interface PlanObject {141 id: string;142 pageId: string;143 kind: ObjectKind;144 layer: string;145 name: string;146 points: Point[]; // pixels page147 closed: boolean;148 props: ObjectProps;149 /** révision de calibration au moment de la dernière mesure */150 createdAt: string;151 updatedAt: string;152}153154/* ------------------------------------------------------------ registre */155156export type FormulaKey =157 | "polygon_area"158 | "polyline_length"159 | "wall_gross_area"160 | "wall_net_area"161 | "slab_volume"162 | "roof_surface_area"163 | "opening_area"164 | "count"165 | "manual";166167export interface Measurement {168 id: string;169 projectId: string;170 objectId: string | null; // null = saisie manuelle171 pageId: string | null;172 name: string;173 formula: FormulaKey;174 formulaText: string; // forme lisible, ex. « mur_brut = longueur × hauteur »175 inputs: Record<string, number | string | null>;176 value: number;177 unit: MeasureUnit;178 calibrationRevision: number | null;179 /** la calibration a changé depuis le calcul → recalcul requis */180 stale: boolean;181 confidence: "measured" | "estimated" | "assumed";182 verifiedBy: string | null;183 verifiedAt: string | null;184 computedAt: string;185 /** méthodes de dérivation (ex. identifiants des ouvertures déduites) */186 lineage: string[];187}188189/* -------------------------------------------------------------- preuves */190191export type EvidenceClass =192 | "published_schedule" // barème publié (DTNI, MTMD, grille APCHQ…)193 | "awarded_contract_total" // montant d'un contrat octroyé (SEAO) — JAMAIS un prix unitaire194 | "awarded_unit_bid" // prix unitaire d'une soumission retenue (bordereau)195 | "supplier_quote" // prix de fournisseur / détaillant observé196 | "contractor_quote" // soumission d'entrepreneur197 | "permit_declared_value" // valeur déclarée au permis (≠ coût réel)198 | "modeled" // estimation de modèle (étiquetée)199 | "user_assumption"; // hypothèse explicite de l'utilisateur200201/** Nature du prix unitaire utilisé par une ligne d'estimation. */202export type PriceBasis =203 | "catalogue" // coût d'assemblage du catalogue UQO Éval (prix observés / officiels / référence)204 | "custom_assembly" // assemblage défini dans le module (recette)205 | "assumption" // prix unitaire saisi par l'utilisateur206 | "observation" // observation de prix normalisée du module (classe d'évidence)207 | "rsmeans"; // ligne de coût unitaire RSMeans (Gordian) localisée pour le Québec — coûts « bare » (sans O&P de l'installateur)208209export interface Provenance {210 basis: PriceBasis;211 /** nature des prix sous-jacents (observé/officiel/référence/hypothèse) */212 kind: "observed" | "official" | "indexed" | "derived" | "reference" | "assumption" | "modeled";213 evidenceClass: EvidenceClass | null;214 source: string;215 sourceUrl: string | null;216 observedAt: string | null;217 note: string | null;218 confidence: number; // 0-100219 /** part du coût matériau reposant sur des prix observés (0-1) */220 observedShare: number;221}222223/* ------------------------------------------------------------ assemblages */224225export interface CustomComponent {226 id: string;227 /** article du catalogue (cost_items.canonical_code) OU assemblage imbriqué (code d'assemblage du module/catalogue) */228 refType: "item" | "assembly";229 refCode: string;230 quantity: number; // par unité de sortie de l'assemblage231 wasteRate: number; // 0.05 = 5 % (appliqué une seule fois, sur les matériaux)232 labourHours: number;233 trade: string | null;234 equipmentHours: number;235 equipmentRate: number; // $/h (hypothèse documentée)236 subcontract: number; // $ par unité de sortie237 transport: number; // $ par unité de sortie238 /** prix tout compris (installé) : ne pas ajouter de main-d'œuvre ni de pertes */239 installed: boolean;240 notes: string | null;241}242243export interface CustomAssembly {244 id: string;245 projectId: string | null; // null = partagé (enseignant/admin)246 code: string;247 nameFr: string;248 nameEn: string;249 unit: MeasureUnit;250 outputQuantity: number; // quantité de sortie produite par la recette (≥ 0)251 category: string;252 version: number;253 components: CustomComponent[];254 exclusions: string | null;255 createdBy: string;256 createdAt: string;257 updatedAt: string;258}259260/* ------------------------------------------------------------- estimation */261262export interface EstimateLineInput {263 id: string;264 wbs: string; // division / structure de répartition (ex. « 03 — Béton »)265 description: string;266 assemblyCode: string | null; // catalogue ou assemblage du module267 measurementId: string | null;268 quantity: number;269 unit: MeasureUnit | string;270 adjustmentFactor: number; // assembly_quantity = takeoff_quantity × facteur explicite271 basis: PriceBasis;272 /** prix unitaire saisi (basis = assumption) — $/unité, direct (matériau + MO + équipement)273 * ou coût installé si `installed` */274 unitPrice: number | null;275 installed: boolean;276 assumptionNote: string | null;277 excluded: boolean;278 sortOrder: number;279 /** origine : relevé des quantités automatique (remplacé à chaque génération) ou saisie */280 origin?: "qs" | "manual";281 /** numéro de ligne RSMeans (12 chiffres, MasterFormat 2018) lorsque basis = "rsmeans" */282 rsmeansLine?: string | null;283}284285export interface EstimateParams {286 regionCode: string | null;287 priceDate: string; // date des prix (instantané)288 generalConditionsPct: number;289 overheadPct: number;290 profitPct: number;291 contingencyPct: number;292 /** actualisation (indice StatCan) : date cible ; null = aucune */293 escalateTo: string | null;294 escalationFactor: number | null; // calculé (indice) ou saisi295 escalationSource: string | null;296 applyGst: boolean; // TPS 5 %297 applyQst: boolean; // TVQ 9,975 %298 roundingCents: number; // 1 = cent, 100 = dollar299 /** localisation RSMeans (id de ville ; null = déduite du projet) et type de main-d'œuvre pour les lignes « rsmeans » */300 rsmeansLocationId?: number | null;301 rsmeansLabor?: "STD" | "OPN" | "RES" | "FMR";302}303304export interface EstimateLineResult {305 id: string;306 wbs: string;307 description: string;308 assemblyCode: string | null;309 measurementId: string | null;310 takeoffQuantity: number;311 adjustmentFactor: number;312 quantity: number;313 unit: string;314 basis: PriceBasis;315 installed: boolean;316 unitMaterial: number; // $ par unité (cents → $ arrondis à 0,01)317 unitLabour: number;318 unitEquipment: number;319 unitOther: number; // sous-traitance + transport320 unitDirect: number;321 material: number;322 labour: number;323 equipment: number;324 other: number;325 direct: number; // avant facteur régional326 located: number; // après facteur régional (matériau/MO/équipement distincts)327 provenance: Provenance;328 warnings: string[];329 excluded: boolean;330}331332export interface EstimateTotals {333 direct: number;334 located: number;335 locationAdjustment: number;336 generalConditions: number;337 overhead: number;338 profit: number;339 contingency: number;340 subtotalBeforeEscalation: number;341 escalation: number;342 subtotalBeforeTax: number;343 gst: number;344 qst: number;345 total: number;346 material: number;347 labour: number;348 equipment: number;349 other: number;350}351352export interface EstimateResult {353 computedAt: string;354 params: EstimateParams;355 location: { code: string; nameFr: string; nameEn: string; materialFactor: number; labourFactor: number; equipmentFactor: number; confidence: number; method: string };356 lines: EstimateLineResult[];357 byWbs: { wbs: string; direct: number; located: number; sharePct: number }[];358 totals: EstimateTotals;359 /** ordre d'application documenté (pour le rapport) */360 applicationOrder: string[];361 warnings: string[];362 coverage: { linesPriced: number; linesTotal: number; observedShare: number; assumptionShare: number };363 catalogueVersion: string;364 formulaVersion: string;365}366367export interface EstimateVersion {368 id: string;369 estimateId: string;370 projectId: string;371 number: number;372 label: string;373 scenario: string | null;374 createdBy: string;375 createdAt: string;376 contentHash: string;377 total: number;378 result: EstimateResult;379 lines: EstimateLineInput[];380}381382/* ------------------------------------------------------------- évaluation */383384export interface FunctionalItem {385 id: string;386 label: string;387 curable: boolean;388 amount: number; // coût de correction (curable) ou perte de valeur (incurable)389 /** la conception de remplacement élimine déjà cette déficience → ne pas déduire deux fois */390 eliminatedByReplacement: boolean;391}392393export interface ValuationInput {394 basis: "replacement" | "reproduction";395 /** coût à neuf retenu (de la révision d'estimation liée) */396 costNew: number;397 /** ajustement explicite reproduction ↔ remplacement (ex. −8 % matériaux modernes) */398 basisAdjustmentPct: number;399 siteImprovements: number;400 physical: { method: "age_life" | "percent"; effectiveAge: number | null; economicLife: number; percent: number | null };401 functional: FunctionalItem[];402 external: { amount: number; note: string | null };403 land: { value: number; source: "role" | "user" | "market" | "residual"; note: string | null };404}405406export interface ValuationResult {407 costNewUsed: number;408 basisAdjustment: number;409 siteImprovements: number;410 totalCostNew: number;411 physicalPct: number;412 physical: number;413 functionalCurable: number;414 functionalIncurable: number;415 functionalSkipped: { id: string; label: string; amount: number }[];416 external: number;417 totalDepreciation: number;418 depreciatedImprovements: number;419 landValue: number;420 indicatedValue: number;421 steps: { label: string; amount: number }[];422 warnings: string[];423}424