Initial commit
This commit is contained in:
484
services/calculsTechniquesService.ts
Normal file
484
services/calculsTechniquesService.ts
Normal file
@@ -0,0 +1,484 @@
|
||||
import ApiService from './ApiService';
|
||||
|
||||
/**
|
||||
* Service pour les calculs techniques ultra-détaillés BTP
|
||||
* Le plus ambitieux système de calculs BTP d'Afrique
|
||||
*/
|
||||
|
||||
// =================== INTERFACES PARAMÈTRES ===================
|
||||
|
||||
export interface ParametresCalculBriques {
|
||||
surface: number;
|
||||
epaisseurMur: number;
|
||||
codeBrique: string;
|
||||
zoneClimatique: string;
|
||||
typeAppareillage: 'DROIT' | 'QUINCONCE' | 'FLAMAND' | 'ANGLAIS';
|
||||
jointHorizontal: number;
|
||||
jointVertical: number;
|
||||
ouvertures: Ouverture[];
|
||||
}
|
||||
|
||||
export interface Ouverture {
|
||||
largeur: number;
|
||||
hauteur: number;
|
||||
}
|
||||
|
||||
export interface ParametresCalculMortier {
|
||||
volumeMaconnerie: number;
|
||||
typeMortier: 'POSE_BRIQUES' | 'JOINTOIEMENT' | 'ENDUIT_BASE' | 'ENDUIT_FINITION' | 'STANDARD';
|
||||
zoneClimatique: string;
|
||||
}
|
||||
|
||||
export interface ParametresCalculBetonArme {
|
||||
volume: number;
|
||||
classeBeton: 'C20/25' | 'C25/30' | 'C30/37' | 'C35/45';
|
||||
classeExposition: 'XC1' | 'XC3' | 'XC4' | 'XS1' | 'XS3';
|
||||
typeOuvrage: 'DALLE' | 'POUTRE' | 'POTEAU' | 'VOILE';
|
||||
epaisseur: number;
|
||||
zoneClimatique: string;
|
||||
}
|
||||
|
||||
// =================== INTERFACES RÉSULTATS ===================
|
||||
|
||||
export interface ResultatCalculBriques {
|
||||
nombreBriques: number;
|
||||
nombrePalettes: number;
|
||||
briquesParM2: number;
|
||||
surfaceNette: number;
|
||||
mortier: ResultatCalculMortier;
|
||||
facteurPerte: number;
|
||||
facteurClimatique: number;
|
||||
nombreCouches: number;
|
||||
recommendationsZone: string[];
|
||||
}
|
||||
|
||||
export interface ResultatCalculMortier {
|
||||
volumeTotal: number;
|
||||
cimentKg: number;
|
||||
sableLitres: number;
|
||||
eauLitres: number;
|
||||
sacs50kg: number;
|
||||
}
|
||||
|
||||
export interface ResultatCalculBetonArme {
|
||||
volume: number;
|
||||
cimentKg: number;
|
||||
cimentSacs50kg: number;
|
||||
sableKg: number;
|
||||
sableM3: number;
|
||||
graviersKg: number;
|
||||
graviersM3: number;
|
||||
eauLitres: number;
|
||||
acierKgTotal: number;
|
||||
repartitionAcier: Record<number, number>; // diamètre -> poids en kg
|
||||
enrobage: number;
|
||||
dosageAdapte: DosageBeton;
|
||||
adaptationsClimatiques: string[];
|
||||
}
|
||||
|
||||
export interface DosageBeton {
|
||||
ciment: number; // kg/m³
|
||||
eau: number; // L/m³
|
||||
graviers: number; // kg/m³
|
||||
sable: number; // kg/m³
|
||||
}
|
||||
|
||||
export interface DosageBetonInfo {
|
||||
usage: string;
|
||||
ciment: string;
|
||||
resistance: string;
|
||||
exposition: string;
|
||||
}
|
||||
|
||||
// =================== CLASSES DE SERVICE ===================
|
||||
|
||||
export class CalculsTechniquesService {
|
||||
private static readonly BASE_PATH = '/api/v1/calculs-techniques';
|
||||
|
||||
// =================== CALCULS MAÇONNERIE ===================
|
||||
|
||||
/**
|
||||
* Calcul ultra-précis quantité briques pour mur
|
||||
* Prend en compte dimensions exactes, joints, appareillage, pertes, zone climatique
|
||||
*/
|
||||
static async calculerBriquesMur(params: ParametresCalculBriques): Promise<ResultatCalculBriques> {
|
||||
const response = await ApiService.post<ResultatCalculBriques>(
|
||||
`${this.BASE_PATH}/briques-mur`,
|
||||
params
|
||||
);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcul mortier pour maçonnerie traditionnelle
|
||||
*/
|
||||
static async calculerMortierMaconnerie(params: ParametresCalculMortier): Promise<ResultatCalculMortier> {
|
||||
const response = await ApiService.post<ResultatCalculMortier>(
|
||||
`${this.BASE_PATH}/mortier-maconnerie`,
|
||||
params
|
||||
);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimation rapide briques pour surface donnée
|
||||
*/
|
||||
static async estimationRapideBriques(surface: number, typeBrique: string = 'brique-rouge-15x10x5'): Promise<{
|
||||
estimationBasse: number;
|
||||
estimationHaute: number;
|
||||
estimationMoyenne: number;
|
||||
baseCalcul: string;
|
||||
}> {
|
||||
// Calcul côté client pour estimation rapide
|
||||
const briquesParM2Moyen = 67; // Moyenne pour brique 15x10x5cm
|
||||
const facteurPerteMoyen = 1.08; // 8% de perte moyenne
|
||||
|
||||
const estimationMoyenne = Math.ceil(surface * briquesParM2Moyen * facteurPerteMoyen);
|
||||
const estimationBasse = Math.ceil(estimationMoyenne * 0.85);
|
||||
const estimationHaute = Math.ceil(estimationMoyenne * 1.25);
|
||||
|
||||
return {
|
||||
estimationBasse,
|
||||
estimationHaute,
|
||||
estimationMoyenne,
|
||||
baseCalcul: `Surface: ${surface}m² × ${briquesParM2Moyen} briques/m² × ${facteurPerteMoyen} (pertes)`
|
||||
};
|
||||
}
|
||||
|
||||
// =================== CALCULS BÉTON ARMÉ ===================
|
||||
|
||||
/**
|
||||
* Calcul béton armé avec adaptation climatique africaine
|
||||
*/
|
||||
static async calculerBetonArme(params: ParametresCalculBetonArme): Promise<ResultatCalculBetonArme> {
|
||||
const response = await ApiService.post<ResultatCalculBetonArme>(
|
||||
`${this.BASE_PATH}/beton-arme`,
|
||||
params
|
||||
);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les dosages béton standard avec adaptations climatiques
|
||||
*/
|
||||
static async getDosagesBeton(): Promise<{
|
||||
dosages: Record<string, DosageBetonInfo>;
|
||||
notes: string[];
|
||||
}> {
|
||||
const response = await ApiService.get<{
|
||||
dosages: Record<string, DosageBetonInfo>;
|
||||
notes: string[];
|
||||
}>(`${this.BASE_PATH}/dosages-beton`);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimation rapide béton pour volume donné
|
||||
*/
|
||||
static async estimationRapideBeton(volume: number, classeBeton: string = 'C25/30'): Promise<{
|
||||
cimentSacs: number;
|
||||
sableM3: number;
|
||||
graviersM3: number;
|
||||
eauLitres: number;
|
||||
coutEstime: number;
|
||||
}> {
|
||||
// Dosages moyens selon classe
|
||||
const dosages = {
|
||||
'C20/25': { ciment: 300, sable: 1100, graviers: 650, eau: 165 },
|
||||
'C25/30': { ciment: 350, sable: 1050, graviers: 600, eau: 175 },
|
||||
'C30/37': { ciment: 385, sable: 1000, graviers: 580, eau: 180 },
|
||||
'C35/45': { ciment: 420, sable: 950, graviers: 550, eau: 185 }
|
||||
};
|
||||
|
||||
const dosage = dosages[classeBeton] || dosages['C25/30'];
|
||||
|
||||
const cimentKg = volume * dosage.ciment;
|
||||
const cimentSacs = Math.ceil(cimentKg / 50);
|
||||
const sableKg = volume * dosage.sable;
|
||||
const sableM3 = sableKg / 1600; // densité sable
|
||||
const graviersKg = volume * dosage.graviers;
|
||||
const graviersM3 = graviersKg / 1500; // densité graviers
|
||||
const eauLitres = volume * dosage.eau;
|
||||
|
||||
// Estimation coût (prix moyens Afrique de l'Ouest)
|
||||
const coutEstime = (cimentSacs * 8000) + // 8000 FCFA/sac
|
||||
(sableM3 * 25000) + // 25000 FCFA/m³
|
||||
(graviersM3 * 30000) + // 30000 FCFA/m³
|
||||
(eauLitres * 2); // 2 FCFA/L
|
||||
|
||||
return {
|
||||
cimentSacs,
|
||||
sableM3: Math.ceil(sableM3 * 100) / 100, // 2 décimales
|
||||
graviersM3: Math.ceil(graviersM3 * 100) / 100,
|
||||
eauLitres,
|
||||
coutEstime
|
||||
};
|
||||
}
|
||||
|
||||
// =================== CALCULS COMPLEXES ===================
|
||||
|
||||
/**
|
||||
* Calcul complet d'un mur (briques + mortier + enduit)
|
||||
*/
|
||||
static async calculerMurComplet(params: {
|
||||
surface: number;
|
||||
epaisseurMur: number;
|
||||
codeBrique: string;
|
||||
zoneClimatique: string;
|
||||
typeAppareillage: string;
|
||||
avecEnduit: boolean;
|
||||
typeEnduit?: 'CIMENT' | 'CHAUX' | 'PLATRE';
|
||||
}): Promise<{
|
||||
briques: ResultatCalculBriques;
|
||||
enduit?: {
|
||||
mortierM3: number;
|
||||
cimentKg: number;
|
||||
sableKg: number;
|
||||
eauLitres: number;
|
||||
};
|
||||
coutTotal: number;
|
||||
tempsTotal: number; // en heures
|
||||
}> {
|
||||
// Calcul briques
|
||||
const paramsB: ParametresCalculBriques = {
|
||||
surface: params.surface,
|
||||
epaisseurMur: params.epaisseurMur,
|
||||
codeBrique: params.codeBrique,
|
||||
zoneClimatique: params.zoneClimatique,
|
||||
typeAppareillage: params.typeAppareillage as any,
|
||||
jointHorizontal: 10, // défaut 10mm
|
||||
jointVertical: 10, // défaut 10mm
|
||||
ouvertures: []
|
||||
};
|
||||
|
||||
const briques = await this.calculerBriquesMur(paramsB);
|
||||
|
||||
let enduit;
|
||||
if (params.avecEnduit) {
|
||||
// Calcul enduit (15mm d'épaisseur moyenne)
|
||||
const volumeEnduit = params.surface * 0.015; // 1.5cm
|
||||
const dosageEnduit = params.typeEnduit === 'CHAUX' ? 250 : 350; // kg/m³
|
||||
|
||||
enduit = {
|
||||
mortierM3: volumeEnduit,
|
||||
cimentKg: volumeEnduit * dosageEnduit,
|
||||
sableKg: volumeEnduit * 800, // 800kg sable/m³ mortier
|
||||
eauLitres: volumeEnduit * 200 // 200L eau/m³ mortier
|
||||
};
|
||||
}
|
||||
|
||||
// Estimation coûts et temps
|
||||
const coutBriques = briques.nombreBriques * 250; // 250 FCFA/brique
|
||||
const coutMortier = briques.mortier.cimentKg * 160; // 160 FCFA/kg ciment
|
||||
const coutEnduit = enduit ? enduit.cimentKg * 160 : 0;
|
||||
const coutTotal = coutBriques + coutMortier + coutEnduit;
|
||||
|
||||
const tempsBriques = briques.nombreBriques * 3 / 60; // 3min/brique
|
||||
const tempsEnduit = params.avecEnduit ? params.surface * 45 / 60 : 0; // 45min/m²
|
||||
const tempsTotal = tempsBriques + tempsEnduit;
|
||||
|
||||
return {
|
||||
briques,
|
||||
enduit,
|
||||
coutTotal,
|
||||
tempsTotal
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcul dalle béton complète (béton + armatures + coffrages)
|
||||
*/
|
||||
static async calculerDalleComplete(params: {
|
||||
surface: number;
|
||||
epaisseur: number;
|
||||
classeBeton: string;
|
||||
zoneClimatique: string;
|
||||
typeArmature: 'LEGERE' | 'NORMALE' | 'RENFORCEE';
|
||||
avecCoffrage: boolean;
|
||||
}): Promise<{
|
||||
beton: ResultatCalculBetonArme;
|
||||
coffrage?: {
|
||||
planchesM2: number;
|
||||
etaisNombre: number;
|
||||
};
|
||||
coutTotal: number;
|
||||
tempsTotal: number;
|
||||
}> {
|
||||
const volume = params.surface * (params.epaisseur / 100); // épaisseur en cm -> m
|
||||
|
||||
const paramsBA: ParametresCalculBetonArme = {
|
||||
volume,
|
||||
classeBeton: params.classeBeton as any,
|
||||
classeExposition: 'XC3', // défaut intérieur humide
|
||||
typeOuvrage: 'DALLE',
|
||||
epaisseur: params.epaisseur,
|
||||
zoneClimatique: params.zoneClimatique
|
||||
};
|
||||
|
||||
const beton = await this.calculerBetonArme(paramsBA);
|
||||
|
||||
let coffrage;
|
||||
if (params.avecCoffrage) {
|
||||
// Surface coffrante = surface dalle + rives
|
||||
const perimetre = 2 * Math.sqrt(params.surface * 4); // approximation carré
|
||||
const surfaceCoffrante = params.surface + (perimetre * params.epaisseur / 100);
|
||||
|
||||
coffrage = {
|
||||
planchesM2: surfaceCoffrante * 1.15, // 15% majoration
|
||||
etaisNombre: Math.ceil(params.surface / 2) // 1 étai par 2m²
|
||||
};
|
||||
}
|
||||
|
||||
// Estimation coûts
|
||||
const coutBeton = (beton.cimentSacs50kg * 8000) +
|
||||
(beton.sableM3.valueOf() * 25000) +
|
||||
(beton.graviersM3.valueOf() * 30000);
|
||||
const coutAcier = beton.acierKgTotal * 1200; // 1200 FCFA/kg acier
|
||||
const coutCoffrage = coffrage ? (coffrage.planchesM2 * 5000) + (coffrage.etaisNombre * 15000) : 0;
|
||||
const coutTotal = coutBeton + coutAcier + coutCoffrage;
|
||||
|
||||
// Estimation temps
|
||||
const tempsBeton = volume * 2; // 2h/m³
|
||||
const tempsArmature = beton.acierKgTotal * 0.5; // 30min/kg
|
||||
const tempsCoffrage = coffrage ? coffrage.planchesM2 * 0.25 : 0; // 15min/m²
|
||||
const tempsTotal = tempsBeton + tempsArmature + tempsCoffrage;
|
||||
|
||||
return {
|
||||
beton,
|
||||
coffrage,
|
||||
coutTotal,
|
||||
tempsTotal
|
||||
};
|
||||
}
|
||||
|
||||
// =================== OUTILS UTILITAIRES ===================
|
||||
|
||||
/**
|
||||
* Conversion d'unités de mesure BTP
|
||||
*/
|
||||
static convertirUnites(valeur: number, uniteSource: string, uniteDestination: string): number {
|
||||
const conversions: Record<string, Record<string, number>> = {
|
||||
'm': { 'cm': 100, 'mm': 1000, 'km': 0.001 },
|
||||
'm²': { 'cm²': 10000, 'mm²': 1000000, 'ha': 0.0001 },
|
||||
'm³': { 'l': 1000, 'cm³': 1000000, 'mm³': 1000000000 },
|
||||
'kg': { 'g': 1000, 't': 0.001, 'quintal': 0.01 },
|
||||
'MPa': { 'kPa': 1000, 'Pa': 1000000, 'bar': 10 }
|
||||
};
|
||||
|
||||
if (conversions[uniteSource]?.[uniteDestination]) {
|
||||
return valeur * conversions[uniteSource][uniteDestination];
|
||||
}
|
||||
|
||||
throw new Error(`Conversion non supportée: ${uniteSource} vers ${uniteDestination}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation des paramètres de calcul
|
||||
*/
|
||||
static validerParametres(type: 'BRIQUES' | 'MORTIER' | 'BETON', params: any): {
|
||||
valide: boolean;
|
||||
erreurs: string[];
|
||||
avertissements: string[];
|
||||
} {
|
||||
const erreurs: string[] = [];
|
||||
const avertissements: string[] = [];
|
||||
|
||||
switch (type) {
|
||||
case 'BRIQUES':
|
||||
if (!params.surface || params.surface <= 0) erreurs.push('Surface requise et > 0');
|
||||
if (!params.epaisseurMur || params.epaisseurMur <= 0) erreurs.push('Épaisseur mur requise et > 0');
|
||||
if (!params.codeBrique) erreurs.push('Code brique requis');
|
||||
if (!params.zoneClimatique) erreurs.push('Zone climatique requise');
|
||||
|
||||
if (params.surface > 1000) avertissements.push('Surface très importante (>1000m²)');
|
||||
if (params.epaisseurMur > 30) avertissements.push('Mur très épais (>30cm)');
|
||||
break;
|
||||
|
||||
case 'BETON':
|
||||
if (!params.volume || params.volume <= 0) erreurs.push('Volume requis et > 0');
|
||||
if (!params.classeBeton) erreurs.push('Classe béton requise');
|
||||
if (!params.typeOuvrage) erreurs.push('Type ouvrage requis');
|
||||
|
||||
if (params.volume > 500) avertissements.push('Volume très important (>500m³)');
|
||||
if (params.epaisseur && params.epaisseur < 10) avertissements.push('Épaisseur faible (<10cm)');
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
valide: erreurs.length === 0,
|
||||
erreurs,
|
||||
avertissements
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Génération de devis détaillé
|
||||
*/
|
||||
static async genererDevis(calculs: {
|
||||
briques?: ResultatCalculBriques;
|
||||
mortier?: ResultatCalculMortier;
|
||||
beton?: ResultatCalculBetonArme;
|
||||
}, options?: {
|
||||
margeEntreprise?: number; // %
|
||||
tva?: number; // %
|
||||
delaiExecution?: number; // jours
|
||||
}): Promise<{
|
||||
lignesDevis: Array<{
|
||||
designation: string;
|
||||
quantite: number;
|
||||
unite: string;
|
||||
prixUnitaire: number;
|
||||
montantHT: number;
|
||||
}>;
|
||||
totalHT: number;
|
||||
totalTTC: number;
|
||||
delaiExecution: number;
|
||||
}> {
|
||||
const lignesDevis: Array<{
|
||||
designation: string;
|
||||
quantite: number;
|
||||
unite: string;
|
||||
prixUnitaire: number;
|
||||
montantHT: number;
|
||||
}> = [];
|
||||
|
||||
// Ajout lignes selon calculs
|
||||
if (calculs.briques) {
|
||||
lignesDevis.push({
|
||||
designation: 'Briques terre cuite',
|
||||
quantite: calculs.briques.nombreBriques,
|
||||
unite: 'unité',
|
||||
prixUnitaire: 250,
|
||||
montantHT: calculs.briques.nombreBriques * 250
|
||||
});
|
||||
}
|
||||
|
||||
if (calculs.beton) {
|
||||
lignesDevis.push({
|
||||
designation: 'Ciment Portland CEM I',
|
||||
quantite: calculs.beton.cimentSacs50kg,
|
||||
unite: 'sac 50kg',
|
||||
prixUnitaire: 8000,
|
||||
montantHT: calculs.beton.cimentSacs50kg * 8000
|
||||
});
|
||||
}
|
||||
|
||||
const totalHT = lignesDevis.reduce((sum, ligne) => sum + ligne.montantHT, 0);
|
||||
const marge = (options?.margeEntreprise || 20) / 100;
|
||||
const tva = (options?.tva || 18) / 100;
|
||||
|
||||
const totalAvecMarge = totalHT * (1 + marge);
|
||||
const totalTTC = totalAvecMarge * (1 + tva);
|
||||
|
||||
return {
|
||||
lignesDevis,
|
||||
totalHT: totalAvecMarge,
|
||||
totalTTC,
|
||||
delaiExecution: options?.delaiExecution || 15
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user