feat(phase4): 接入真实预测计算模型(中国建材院)
替换占位公式,按模型精确实现 prediction.ts: Ln=A/V → 筛Yp≥0.01 → Ln修正=Σ(Li·Ypi)/Ypn → Yn修正=Y0+Yp·e^(−Ln修正/B) → C密闭=Σ(Yi修正·Li) → E=0.51+8.74·C密闭/Σ(Li·Ypi) → U=1/(1+E·ACH) → C通风=U·C密闭 → WS=e^(0.102(T−23))·e^(1.2312(H−0.5)) → C=WS·C通风 贡献率 Gn=(Yn修正·Ln)/C密闭;污染源=超标污染物按Gn降序累计>50%。 结果新增 cClosed/limits/sources 字段。已用官方算例校验最终浓度全部±0.01通过。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
956954cf09
commit
022bd721ee
|
|
@ -14,110 +14,167 @@ export interface EmissionParams {
|
||||||
/** 一种材料在某空间的用量 + 各污染物散发参数 */
|
/** 一种材料在某空间的用量 + 各污染物散发参数 */
|
||||||
export interface SpaceMaterialInput {
|
export interface SpaceMaterialInput {
|
||||||
materialId: string;
|
materialId: string;
|
||||||
/** 使用量(配合用量单位,通常为面积 m² 或其它) */
|
/** 使用面积 A (m²)。长度单位时调用方需先 ×0.1 折算 */
|
||||||
usageAmount: number;
|
usageAmount: number;
|
||||||
/** 每项污染物的散发参数 */
|
|
||||||
params: Record<Pollutant, EmissionParams>;
|
params: Record<Pollutant, EmissionParams>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 空间环境条件 */
|
/** 空间环境条件 */
|
||||||
export interface SpaceConditions {
|
export interface SpaceConditions {
|
||||||
/** 体积 m³ */
|
/** 体积 V (m³) */
|
||||||
volume: number;
|
volume: number;
|
||||||
/** 温度 ℃ */
|
/** 温度 T (°C) */
|
||||||
temperature: number;
|
temperature: number;
|
||||||
/** 湿度 %rh */
|
/** 湿度 H (%rh,如 50;内部折算为小数) */
|
||||||
humidity: number;
|
humidity: number;
|
||||||
/** 通风换气率 次/小时 (ACH) */
|
/** 通风换气次数 ACH (次/小时) */
|
||||||
ventilationRate: number;
|
ventilationRate: number;
|
||||||
standard: StandardCode;
|
standard: StandardCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MaterialContribution {
|
export interface MaterialContribution {
|
||||||
materialId: string;
|
materialId: string;
|
||||||
/** 每项污染物对最终浓度的贡献 (mg/m³) */
|
/** 各污染物:该材料占最终浓度的份额 (mg/m³) = Gn × C */
|
||||||
contribution: Record<Pollutant, number>;
|
contribution: Record<Pollutant, number>;
|
||||||
/** 每项污染物的贡献率 0~1 */
|
/** 各污染物贡献率 Gn (0~1) = (Yn修正·Ln)/C密闭 */
|
||||||
contributionRate: Record<Pollutant, number>;
|
contributionRate: Record<Pollutant, number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SpacePredictionResult {
|
export interface SpacePredictionResult {
|
||||||
/** 各污染物预测浓度 mg/m³ */
|
/** 各污染物最终预测浓度 C (mg/m³) */
|
||||||
concentration: Record<Pollutant, number>;
|
concentration: Record<Pollutant, number>;
|
||||||
|
/** 各污染物密闭浓度 C密闭 (mg/m³),贡献率分母 */
|
||||||
|
cClosed: Record<Pollutant, number>;
|
||||||
|
/** 所用标准的各污染物限值 */
|
||||||
|
limits: Record<Pollutant, number>;
|
||||||
/** 各污染物是否超标 */
|
/** 各污染物是否超标 */
|
||||||
exceeded: Record<Pollutant, boolean>;
|
exceeded: Record<Pollutant, boolean>;
|
||||||
/** 各材料贡献 */
|
/** 各材料贡献 */
|
||||||
contributions: MaterialContribution[];
|
contributions: MaterialContribution[];
|
||||||
|
/** 各污染物的污染源材料ID(仅超标污染物;累计贡献率>50%的前几个材料) */
|
||||||
|
sources: Record<Pollutant, string[]>;
|
||||||
rating: PredictionRating;
|
rating: PredictionRating;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 平衡释放量范围阈值:Yp < 0.01 视为该污染物不释放 */
|
||||||
|
const YP_THRESHOLD = 0.01;
|
||||||
|
|
||||||
const zero = (): Record<Pollutant, number> =>
|
const zero = (): Record<Pollutant, number> =>
|
||||||
Object.fromEntries(POLLUTANTS.map((p) => [p, 0])) as Record<Pollutant, number>;
|
Object.fromEntries(POLLUTANTS.map((p) => [p, 0])) as Record<Pollutant, number>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ⚠️ 核心散发速率公式 —— 把你已标定的公式放这里。
|
* 单空间多污染物预测(中国建材院模型 20240307)。
|
||||||
* 当前为占位实现:稳态面积散发速率随温度/湿度做简单修正。
|
* 每种污染物独立计算:
|
||||||
*
|
* Ln=A/V → 筛Yp≥0.01 → Ln修正=Σ(Li·Ypi)/Ypn → Yn修正=Y0+Yp·e^(−Ln修正/B)
|
||||||
* 返回某材料某污染物的稳态面积散发速率 (mg/m²·h)。
|
* → C密闭=Σ(Yi修正·Li) → E=0.51+8.74·C密闭/Σ(Li·Ypi) → U=1/(1+E·ACH)
|
||||||
*
|
* → C通风=U·C密闭 → WS=e^(0.102(T−23))·e^(1.2312(H−0.5)) → C=WS·C通风
|
||||||
* @param p 散发参数 Y0/Yp/B
|
* 贡献率 Gn=(Yn修正·Ln)/C密闭;污染源=超标污染物中按Gn降序累计>50%的材料。
|
||||||
* @param cond 空间环境(温湿度等)
|
|
||||||
*/
|
|
||||||
export function emissionRate(p: EmissionParams, cond: SpaceConditions): number {
|
|
||||||
// —— PLACEHOLDER:替换为你的标定公式 ——
|
|
||||||
// 占位:以 Y0 为基线,Yp 受温度偏离 25℃ 影响,B 作为湿度/装载修正系数。
|
|
||||||
const tempFactor = 1 + 0.02 * (cond.temperature - 25); // 每偏离1℃修正2%
|
|
||||||
const humFactor = 1 + 0.005 * (cond.humidity - 50); // 每偏离50%rh修正0.5%
|
|
||||||
const rate = (p.y0 + p.yp * Math.max(0, tempFactor - 1) + p.b * humFactor * 0.0) * tempFactor * humFactor;
|
|
||||||
return Math.max(0, rate);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 预测单个空间的污染物浓度(质量平衡稳态模型)。
|
|
||||||
*
|
|
||||||
* 稳态:生成速率 G = 去除速率 = ACH × V × C => C = G / (ACH × V)
|
|
||||||
* G_pollutant = Σ_material ( emissionRate × usageAmount )
|
|
||||||
*/
|
*/
|
||||||
export function predictSpace(
|
export function predictSpace(
|
||||||
materials: SpaceMaterialInput[],
|
materials: SpaceMaterialInput[],
|
||||||
cond: SpaceConditions,
|
cond: SpaceConditions,
|
||||||
): SpacePredictionResult {
|
): SpacePredictionResult {
|
||||||
|
const V = Math.max(1e-6, cond.volume);
|
||||||
|
const ACH = Math.max(0, cond.ventilationRate);
|
||||||
|
const T = cond.temperature;
|
||||||
|
const H = cond.humidity > 1 ? cond.humidity / 100 : cond.humidity; // %rh → 小数
|
||||||
|
const WS = Math.exp(0.102 * (T - 23)) * Math.exp(1.2312 * (H - 0.5));
|
||||||
const limits = STANDARD_LIMITS[cond.standard];
|
const limits = STANDARD_LIMITS[cond.standard];
|
||||||
|
|
||||||
|
// 真实承载率 Ln = A/V
|
||||||
|
const Ln = new Map<string, number>();
|
||||||
|
for (const m of materials) Ln.set(m.materialId, m.usageAmount / V);
|
||||||
|
|
||||||
const concentration = zero();
|
const concentration = zero();
|
||||||
const denom = Math.max(1e-6, cond.ventilationRate * cond.volume);
|
const cClosed = zero();
|
||||||
|
const exceeded = {} as Record<Pollutant, boolean>;
|
||||||
// 累计各材料各污染物的生成量
|
const sources = {} as Record<Pollutant, string[]>;
|
||||||
const rawContrib: Record<string, Record<Pollutant, number>> = {};
|
const contribByMat: Record<string, Record<Pollutant, number>> = {};
|
||||||
|
const rateByMat: Record<string, Record<Pollutant, number>> = {};
|
||||||
for (const m of materials) {
|
for (const m of materials) {
|
||||||
rawContrib[m.materialId] = zero();
|
contribByMat[m.materialId] = zero();
|
||||||
for (const pol of POLLUTANTS) {
|
rateByMat[m.materialId] = zero();
|
||||||
const G = emissionRate(m.params[pol], cond) * m.usageAmount;
|
}
|
||||||
const c = G / denom;
|
|
||||||
rawContrib[m.materialId][pol] = c;
|
for (const p of POLLUTANTS) {
|
||||||
concentration[pol] += c;
|
// 1. 筛选释放该污染物的材料(Yp≥0.01)
|
||||||
|
const emitters = materials.filter((m) => (m.params[p]?.yp ?? 0) >= YP_THRESHOLD);
|
||||||
|
// Σ(Li·Ypi)
|
||||||
|
const sumLYp = emitters.reduce((s, m) => s + Ln.get(m.materialId)! * m.params[p].yp, 0);
|
||||||
|
|
||||||
|
// 2. Yn修正 = Y0 + Yp·e^(−Ln修正/B),其中 Ln修正 = Σ(Li·Ypi)/Ypn
|
||||||
|
const yCorr = new Map<string, number>();
|
||||||
|
for (const m of emitters) {
|
||||||
|
const { y0, yp, b } = m.params[p];
|
||||||
|
const lnCorr = yp > 0 ? sumLYp / yp : 0;
|
||||||
|
const term = b > 0 ? yp * Math.exp(-lnCorr / b) : 0;
|
||||||
|
yCorr.set(m.materialId, y0 + term);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 密闭浓度 C密闭 = Σ(Yn修正·Ln)
|
||||||
|
const cClose = emitters.reduce((s, m) => s + yCorr.get(m.materialId)! * Ln.get(m.materialId)!, 0);
|
||||||
|
cClosed[p] = round3(cClose);
|
||||||
|
|
||||||
|
// 4. 通风修正
|
||||||
|
const E = sumLYp > 0 ? 0.51 + 8.74 * (cClose / sumLYp) : 0;
|
||||||
|
const U = 1 / (1 + E * ACH);
|
||||||
|
const cVent = U * cClose;
|
||||||
|
|
||||||
|
// 5. 温湿度修正 → 最终浓度
|
||||||
|
const C = WS * cVent;
|
||||||
|
concentration[p] = round3(C);
|
||||||
|
|
||||||
|
// 6. 贡献率 Gn = (Yn修正·Ln)/C密闭
|
||||||
|
for (const m of emitters) {
|
||||||
|
const gn = cClose > 0 ? (yCorr.get(m.materialId)! * Ln.get(m.materialId)!) / cClose : 0;
|
||||||
|
rateByMat[m.materialId][p] = gn;
|
||||||
|
contribByMat[m.materialId][p] = round3(gn * C);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. 超标判定 + 污染源(累计Gn>50%)
|
||||||
|
const over = C > limits[p];
|
||||||
|
exceeded[p] = over;
|
||||||
|
if (over) {
|
||||||
|
const sorted = emitters
|
||||||
|
.map((m) => ({ id: m.materialId, gn: rateByMat[m.materialId][p] }))
|
||||||
|
.sort((a, b) => b.gn - a.gn);
|
||||||
|
const src: string[] = [];
|
||||||
|
let cum = 0;
|
||||||
|
for (const s of sorted) {
|
||||||
|
src.push(s.id);
|
||||||
|
cum += s.gn;
|
||||||
|
if (cum > 0.5) break;
|
||||||
|
}
|
||||||
|
sources[p] = src;
|
||||||
|
} else {
|
||||||
|
sources[p] = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 贡献率
|
const contributions: MaterialContribution[] = materials.map((m) => ({
|
||||||
const contributions: MaterialContribution[] = materials.map((m) => {
|
materialId: m.materialId,
|
||||||
const contribution = rawContrib[m.materialId];
|
contribution: contribByMat[m.materialId],
|
||||||
const contributionRate = zero();
|
contributionRate: rateByMat[m.materialId],
|
||||||
for (const pol of POLLUTANTS) {
|
}));
|
||||||
contributionRate[pol] = concentration[pol] > 0 ? contribution[pol] / concentration[pol] : 0;
|
|
||||||
}
|
|
||||||
return { materialId: m.materialId, contribution, contributionRate };
|
|
||||||
});
|
|
||||||
|
|
||||||
const exceeded = Object.fromEntries(
|
return {
|
||||||
POLLUTANTS.map((p) => [p, concentration[p] > limits[p]]),
|
concentration,
|
||||||
) as Record<Pollutant, boolean>;
|
cClosed,
|
||||||
|
limits,
|
||||||
|
exceeded,
|
||||||
|
contributions,
|
||||||
|
sources,
|
||||||
|
rating: ratingFor(concentration, cond.standard),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return { concentration, exceeded, contributions, rating: ratingFor(concentration, cond.standard) };
|
function round3(n: number): number {
|
||||||
|
return Math.round(n * 1000) / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 由各污染物浓度相对限值的比值给出评级。
|
* 评级(占位:官方评分/评级规则文档"待补充")。
|
||||||
* A: 全部 ≤ 60% 限值; B: ≤ 100%; C: ≤ 150%; D: 超过。
|
* 现按各污染物相对限值的最差比值给级:A≤60% B≤100% C≤150% D>150%。
|
||||||
*/
|
*/
|
||||||
export function ratingFor(
|
export function ratingFor(
|
||||||
concentration: Record<Pollutant, number>,
|
concentration: Record<Pollutant, number>,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue