import { useMemo, useRef, useState } from "react"; import { invoke } from "@tauri-apps/api/core"; import { Alert, Button, Card, ConfigProvider, InputNumber, Table, Tag } from "antd"; import zhCN from "antd/locale/zh_CN"; type Conclusion = "Ok" | "IncreaseMeasurementsToSix" | "RecalibrateInstrument"; type CalibrationParams = { factor: number; expanded_uncertainty_percent: number; coverage_factor: number; }; type NuclideMeasurements = { measured_values: number[]; calibration: CalibrationParams; }; type AcceptanceLimits = { ira_limit: number; ir_limit: number; }; type SampleInput = { ra: NuclideMeasurements; th: NuclideMeasurements; k: NuclideMeasurements; limits: AcceptanceLimits; }; type McmIndexStats = { mean: number; std_dev: number; p2_5: number; p97_5: number; standard_value: number; pass_probability: number; fail_probability: number; }; type McmResult = { iterations: number; ira: McmIndexStats; ir: McmIndexStats; overall_pass_probability: number; overall_fail_probability: number; }; type NuclideResult = { mean_measured: number; mean_calibrated: number; type_a_uncertainty: number; type_b_relative: number; type_b_uncertainty: number; sensitivity_coefficient: number; combined_uncertainty: number; }; type IndexResult = { value: number; standard_uncertainty: number; relative_uncertainty_percent: number; }; type CalculationResult = { measurement_count: number; ra: NuclideResult; th: NuclideResult; k: NuclideResult; ira: IndexResult; ir: IndexResult; conclusion: Conclusion; mcm: McmResult; }; type MeasurementRow = { key: number; ra: number | null; th: number | null; k: number | null; }; type ResultRow = { name: string } & NuclideResult; type McmRow = { name: string } & McmIndexStats; type CalibrationRow = { key: string; name: string } & CalibrationParams; type FocusableInput = { focus: () => void; }; const defaultCalibration = { ra: { factor: 0.916, expanded_uncertainty_percent: 6.3, coverage_factor: 2 }, th: { factor: 0.884, expanded_uncertainty_percent: 6.9, coverage_factor: 2 }, k: { factor: 0.961, expanded_uncertainty_percent: 6.7, coverage_factor: 2 } }; const defaultLimits: AcceptanceLimits = { ira_limit: 1.0, ir_limit: 1.0 }; const initialRows: MeasurementRow[] = [ { key: 1, ra: 100, th: 110, k: 560 }, { key: 2, ra: 102, th: 111, k: 565 }, { key: 3, ra: 98, th: 109, k: 555 }, { key: 4, ra: 101, th: 110, k: 562 }, { key: 5, ra: 99, th: 112, k: 558 }, { key: 6, ra: 100, th: 108, k: 561 } ]; const conclusionText: Record = { Ok: "OK", IncreaseMeasurementsToSix: "请增加试验次数至 6 次", RecalibrateInstrument: "校准仪器后重新测量" }; function formatNumber(value: number, digits = 4) { if (!Number.isFinite(value)) return "-"; return value.toFixed(digits); } function formatPercent(value: number, digits = 2) { if (!Number.isFinite(value)) return "-"; return (value * 100).toFixed(digits) + "%"; } function App() { const [rows, setRows] = useState(initialRows); const [limits, setLimits] = useState(defaultLimits); const [result, setResult] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); const firstCellRefs = useRef>({}); const dataSource = useMemo(() => rows, [rows]); const updateRow = (key: number, field: keyof Omit, value: number | null) => { setRows((current) => current.map((row) => (row.key === key ? { ...row, [field]: value } : row)) ); }; const addRow = () => { const key = Date.now(); setRows((current) => [...current, { key, ra: null, th: null, k: null }]); window.setTimeout(() => firstCellRefs.current[key]?.focus(), 0); }; const removeRow = (key: number) => { setRows((current) => current.filter((row) => row.key !== key)); }; const buildInput = (): SampleInput => { const toValues = (field: keyof Omit) => rows.map((row) => row[field]).filter((value): value is number => typeof value === "number"); return { ra: { measured_values: toValues("ra"), calibration: defaultCalibration.ra }, th: { measured_values: toValues("th"), calibration: defaultCalibration.th }, k: { measured_values: toValues("k"), calibration: defaultCalibration.k }, limits }; }; const calculate = async () => { setLoading(true); setError(null); try { const response = await invoke("calculate", { input: buildInput() }); setResult(response); } catch (err) { setResult(null); setError(err instanceof Error ? err.message : String(err)); } finally { setLoading(false); } }; return (
pagination={false} dataSource={dataSource} rowKey="key" size="small" columns={[ { title: "序号", key: "index", width: 56, align: "center", render: (_, _row, index) => index + 1 }, { title: "Ra-226", dataIndex: "ra", render: (_, row) => ( { firstCellRefs.current[row.key] = instance; }} value={row.ra} min={0} onChange={(value) => updateRow(row.key, "ra", value)} /> ) }, { title: "Th-232", dataIndex: "th", render: (_, row) => ( updateRow(row.key, "th", value)} /> ) }, { title: "K-40", dataIndex: "k", render: (_, row) => ( updateRow(row.key, "k", value)} /> ) }, { title: "", key: "action", width: 88, render: (_, row) => ( ) } ]} />
className="calibration-table" pagination={false} size="small" rowKey="key" dataSource={[ { key: "ra", name: "Ra", ...defaultCalibration.ra }, { key: "th", name: "Th", ...defaultCalibration.th }, { key: "k", name: "K", ...defaultCalibration.k } ]} columns={[ { title: "核素", dataIndex: "name", align: "center" }, { title: "a", dataIndex: "factor", align: "center" }, { title: "U", dataIndex: "expanded_uncertainty_percent", align: "center", render: (value: number) => `${value}%` }, { title: "k", dataIndex: "coverage_factor", align: "center" } ]} />
IRa 标准值 setLimits((current) => ({ ...current, ira_limit: value ?? 0 }))} />
Ir 标准值 setLimits((current) => ({ ...current, ir_limit: value ?? 0 }))} />
{error ? : null}
{result ? (
判定 {conclusionText[result.conclusion]}
pagination={false} rowKey="name" size="small" dataSource={[ { name: "Ra-226", ...result.ra }, { name: "Th-232", ...result.th }, { name: "K-40", ...result.k } ]} columns={[ { title: "核素", dataIndex: "name" }, { title: "均值", dataIndex: "mean_measured", render: (value: number) => formatNumber(value) }, { title: "校准活度", dataIndex: "mean_calibrated", render: (value: number) => formatNumber(value) }, { title: "A 类", dataIndex: "type_a_uncertainty", render: (value: number) => formatNumber(value) }, { title: "B 类相对", dataIndex: "type_b_relative", render: (value) => formatNumber(value * 100, 3) + "%" }, { title: "合成", dataIndex: "combined_uncertainty", render: (value: number) => formatNumber(value) } ]} />
综合合格概率 {formatPercent(result.mcm.overall_pass_probability)} IRa 与 Ir 同时合格
综合不合格概率 {formatPercent(result.mcm.overall_fail_probability)} 任一指数超标
仿真判定 = 0.95 ? "success" : "warning"}> {result.mcm.overall_pass_probability >= 0.95 ? "合格" : "不合格风险"}
pagination={false} rowKey="name" size="small" dataSource={[ { name: "IRa", ...result.mcm.ira }, { name: "Ir", ...result.mcm.ir } ]} columns={[ { title: "指数", dataIndex: "name" }, { title: "平均值", dataIndex: "mean", render: (value: number) => formatNumber(value) }, { title: "标准偏差", dataIndex: "std_dev", render: (value: number) => formatNumber(value) }, { title: "P2.5", dataIndex: "p2_5", render: (value: number) => formatNumber(value) }, { title: "P97.5", dataIndex: "p97_5", render: (value: number) => formatNumber(value) }, { title: "标准值", dataIndex: "standard_value", render: (value: number) => formatNumber(value, 2) }, { title: "合格概率", dataIndex: "pass_probability", render: (value: number) => ( = 0.95 ? "success" : "warning"}>{formatPercent(value)} ) } ]} />
) : (
请计算后查看结果
)}
); } function ResultTile(props: { title: string; value: number; uncertainty: number }) { return (
{props.title} {formatNumber(props.value)} 相对不确定度 {formatNumber(props.uncertainty, 2)}%
); } export { App };