repair air manual collection module calculation

This commit is contained in:
ALim Aidrus 2025-08-13 16:08:25 +08:00
parent 1b1b869a1d
commit f7fefd3f45

View File

@ -49,6 +49,8 @@ class _AirManualCollectionWidgetState extends State<AirManualCollectionWidget> {
final _pm10TimeResultController = TextEditingController(); final _pm10TimeResultController = TextEditingController();
final _pm10PressureController = TextEditingController(); final _pm10PressureController = TextEditingController();
final _pm10PressureResultController = TextEditingController(); final _pm10PressureResultController = TextEditingController();
final _pm10TavgController = TextEditingController();
final _pm10QstdController = TextEditingController();
final _pm10VstdController = TextEditingController(); final _pm10VstdController = TextEditingController();
// PM2.5 Controllers // PM2.5 Controllers
@ -58,6 +60,8 @@ class _AirManualCollectionWidgetState extends State<AirManualCollectionWidget> {
final _pm25TimeResultController = TextEditingController(); final _pm25TimeResultController = TextEditingController();
final _pm25PressureController = TextEditingController(); final _pm25PressureController = TextEditingController();
final _pm25PressureResultController = TextEditingController(); final _pm25PressureResultController = TextEditingController();
final _pm25TavgController = TextEditingController();
final _pm25QstdController = TextEditingController();
final _pm25VstdController = TextEditingController(); final _pm25VstdController = TextEditingController();
@override @override
@ -88,6 +92,8 @@ class _AirManualCollectionWidgetState extends State<AirManualCollectionWidget> {
_pm10TimeResultController.dispose(); _pm10TimeResultController.dispose();
_pm10PressureController.dispose(); _pm10PressureController.dispose();
_pm10PressureResultController.dispose(); _pm10PressureResultController.dispose();
_pm10TavgController.dispose();
_pm10QstdController.dispose();
_pm10VstdController.dispose(); _pm10VstdController.dispose();
_pm25FlowRateController.dispose(); _pm25FlowRateController.dispose();
_pm25FlowRateResultController.dispose(); _pm25FlowRateResultController.dispose();
@ -95,6 +101,8 @@ class _AirManualCollectionWidgetState extends State<AirManualCollectionWidget> {
_pm25TimeResultController.dispose(); _pm25TimeResultController.dispose();
_pm25PressureController.dispose(); _pm25PressureController.dispose();
_pm25PressureResultController.dispose(); _pm25PressureResultController.dispose();
_pm25TavgController.dispose();
_pm25QstdController.dispose();
_pm25VstdController.dispose(); _pm25VstdController.dispose();
super.dispose(); super.dispose();
} }
@ -153,35 +161,58 @@ class _AirManualCollectionWidgetState extends State<AirManualCollectionWidget> {
void _calculateVstd(int type) { void _calculateVstd(int type) {
setState(() { setState(() {
try { try {
// Get the correct controllers for either PM10 or PM2.5
final flowRateCtrl = type == 1 ? _pm10FlowRateController : _pm25FlowRateController;
final pressureCtrl = type == 1 ? _pm10PressureController : _pm25PressureController; final pressureCtrl = type == 1 ? _pm10PressureController : _pm25PressureController;
final flowResultCtrl = type == 1 ? _pm10FlowRateResultController : _pm25FlowRateResultController;
final timeResultCtrl = type == 1 ? _pm10TimeResultController : _pm25TimeResultController; final timeResultCtrl = type == 1 ? _pm10TimeResultController : _pm25TimeResultController;
final vstdCtrl = type == 1 ? _pm10VstdController : _pm25VstdController;
final pressureResultCtrl = type == 1 ? _pm10PressureResultController : _pm25PressureResultController; final pressureResultCtrl = type == 1 ? _pm10PressureResultController : _pm25PressureResultController;
final tAvgCtrl = type == 1 ? _pm10TavgController : _pm25TavgController;
final qStdCtrl = type == 1 ? _pm10QstdController : _pm25QstdController;
final vstdCtrl = type == 1 ? _pm10VstdController : _pm25VstdController;
if (pressureCtrl.text.isEmpty || _finalTempController.text.isEmpty || flowResultCtrl.text.isEmpty || timeResultCtrl.text.isEmpty) { if (flowRateCtrl.text.isEmpty ||
pressureCtrl.text.isEmpty ||
_finalTempController.text.isEmpty ||
timeResultCtrl.text.isEmpty) {
tAvgCtrl.text = "";
qStdCtrl.text = "";
vstdCtrl.text = "0.000"; vstdCtrl.text = "0.000";
return; return;
} }
const double hpaToinHg = 0.0295;
const double inHgToMmHg = 25.4;
// 1. Get time and user-input flowrate (cfm)
double totalTime_min = double.parse(timeResultCtrl.text);
// **CRITICAL FIX**: Use the actual flowrate keyed in by the user (in cfm)
double qa_cfm_actual = double.parse(flowRateCtrl.text);
// 2. Calculate actual pressure
double pressureHpa = double.parse(pressureCtrl.text); double pressureHpa = double.parse(pressureCtrl.text);
double pressureInHg = pressureHpa * 0.02953; double pressureInHg = pressureHpa * hpaToinHg;
double p_actual_mmHg = pressureInHg * inHgToMmHg; // result pressure
pressureResultCtrl.text = pressureInHg.toStringAsFixed(2); pressureResultCtrl.text = pressureInHg.toStringAsFixed(2);
double qa_m3min = double.parse(flowResultCtrl.text); // 3. Calculate average temperature in Celsius
double totalTime_min = double.parse(timeResultCtrl.text); double t_avg_celsius = (widget.initialTemp + double.parse(_finalTempController.text)) / 2.0; // ambient temperature
double pa_mmHg = pressureInHg * 25.4; tAvgCtrl.text = t_avg_celsius.toStringAsFixed(2);
double t_avg_celsius = (widget.initialTemp + double.parse(_finalTempController.text)) / 2.0;
double numerator = qa_m3min * pa_mmHg * 298.15; // 4. Calculate Standard Flowrate (QSTD) using your exact formula
double denominator = 760 * (273.15 + t_avg_celsius); // Formula: (flowrate * result pressure * 298) / (760 * (273 * ambient temperature))
double q_std = numerator / denominator; double q_std_numerator = qa_cfm_actual * pressureInHg * 298;
double q_std_denominator = (760 * (273 + t_avg_celsius));
double q_std = (q_std_denominator == 0) ? 0 : q_std_numerator / q_std_denominator;
qStdCtrl.text = q_std.toStringAsFixed(5);
// 5. Final VSTD
// Formula: QSTD * Total time
double v_std = q_std * totalTime_min; double v_std = q_std * totalTime_min;
if (type == 1) { if (type == 1) { // PM10
_pm10VstdController.text = v_std.toStringAsFixed(3); _pm10VstdController.text = v_std.toStringAsFixed(3);
widget.data.pm10Vstd = v_std; widget.data.pm10Vstd = v_std;
} else { } else { // PM2.5
_pm25VstdController.text = v_std.toStringAsFixed(3); _pm25VstdController.text = v_std.toStringAsFixed(3);
widget.data.pm25Vstd = v_std; widget.data.pm25Vstd = v_std;
} }
@ -192,13 +223,10 @@ class _AirManualCollectionWidgetState extends State<AirManualCollectionWidget> {
}); });
} }
// **CRITICAL FIX IS HERE**
void _onSubmitPressed() { void _onSubmitPressed() {
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
_formKey.currentState!.save(); // Saves data from fields with an onSaved function _formKey.currentState!.save();
// **THE FIX**: Manually save the values from the read-only result controllers
// into the data model before submitting, as they don't have onSaved functions.
widget.data.pm10FlowrateResult = _pm10FlowRateResultController.text; widget.data.pm10FlowrateResult = _pm10FlowRateResultController.text;
widget.data.pm10TotalTimeResult = _pm10TimeResultController.text; widget.data.pm10TotalTimeResult = _pm10TimeResultController.text;
widget.data.pm10PressureResult = _pm10PressureResultController.text; widget.data.pm10PressureResult = _pm10PressureResultController.text;
@ -206,13 +234,11 @@ class _AirManualCollectionWidgetState extends State<AirManualCollectionWidget> {
widget.data.pm25TotalTimeResult = _pm25TimeResultController.text; widget.data.pm25TotalTimeResult = _pm25TimeResultController.text;
widget.data.pm25PressureResult = _pm25PressureResultController.text; widget.data.pm25PressureResult = _pm25PressureResultController.text;
// Save optional remarks
widget.data.optionalRemark1 = _optionalRemark1Controller.text; widget.data.optionalRemark1 = _optionalRemark1Controller.text;
widget.data.optionalRemark2 = _optionalRemark2Controller.text; widget.data.optionalRemark2 = _optionalRemark2Controller.text;
widget.data.optionalRemark3 = _optionalRemark3Controller.text; widget.data.optionalRemark3 = _optionalRemark3Controller.text;
widget.data.optionalRemark4 = _optionalRemark4Controller.text; widget.data.optionalRemark4 = _optionalRemark4Controller.text;
// Now, call the submission function with the fully populated data model
widget.onSubmit(); widget.onSubmit();
} }
} }
@ -261,17 +287,62 @@ class _AirManualCollectionWidgetState extends State<AirManualCollectionWidget> {
validator: (v) => v == null ? 'Required' : null, validator: (v) => v == null ? 'Required' : null,
onSaved: (v) => widget.data.powerFailure = v, onSaved: (v) => widget.data.powerFailure = v,
), ),
const Padding(padding: EdgeInsets.symmetric(vertical: 20), child: Text("PM10 Data", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16))), const Padding(padding: EdgeInsets.symmetric(vertical: 20), child: Text("PM10 Data", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16))),
_buildResultRow(_pm10FlowRateController, "Flow Rate (cfm)", _pm10FlowRateResultController, "Result (m³/min)", (v) => setState(() => _pm10FlowRateResultController.text = ((double.tryParse(v) ?? 0) * 0.0283).toStringAsFixed(4)), (v) => widget.data.pm10Flowrate = double.tryParse(v!)), _buildResultRow(_pm10FlowRateController, "Flow Rate (cfm)", _pm10FlowRateResultController, "Result (m³/min)", (v) => setState(() => _pm10FlowRateResultController.text = ((double.tryParse(v) ?? 0) * 0.0283).toStringAsFixed(4)), (v) => widget.data.pm10Flowrate = double.tryParse(v!)),
_buildResultRow(_pm10TimeController, "Total Time (hours)", _pm10TimeResultController, "Result (mins)", (v) => setState(() => _pm10TimeResultController.text = ((double.tryParse(v) ?? 0) * 60).toStringAsFixed(2)), (v) => widget.data.pm10TotalTime = v), _buildResultRow(_pm10TimeController, "Total Time (hours)", _pm10TimeResultController, "Result (mins)", (v) => setState(() => _pm10TimeResultController.text = ((double.tryParse(v) ?? 0) * 60).toStringAsFixed(2)), (v) => widget.data.pm10TotalTime = v),
_buildResultRow(_pm10PressureController, "Pressure (hPa)", _pm10PressureResultController, "Result (inHg)", (v) => _calculateVstd(1), (v) => widget.data.pm10Pressure = double.tryParse(v!)), _buildResultRow(_pm10PressureController, "Pressure (hPa)", _pm10PressureResultController, "Result (inHg)", (v) => _calculateVstd(1), (v) => widget.data.pm10Pressure = double.tryParse(v!)),
const SizedBox(height: 8),
TextFormField(
controller: _pm10TavgController,
readOnly: true,
decoration: const InputDecoration(
labelText: 'Ambient Temperature [Tavg=(T1+T2)/2] (°C)',
border: OutlineInputBorder(),
filled: true,
fillColor: Colors.black12),
),
const SizedBox(height: 8),
TextFormField(
controller: _pm10QstdController,
readOnly: true,
decoration: const InputDecoration(
labelText: 'Flowrate (Standard Air), QSTD (m³/min)',
border: OutlineInputBorder(),
filled: true,
fillColor: Colors.black12),
),
const SizedBox(height: 8),
TextFormField(controller: _pm10VstdController, readOnly: true, decoration: const InputDecoration(labelText: 'VSTD PM10 (m³)', border: OutlineInputBorder(), filled: true, fillColor: Colors.black12)), TextFormField(controller: _pm10VstdController, readOnly: true, decoration: const InputDecoration(labelText: 'VSTD PM10 (m³)', border: OutlineInputBorder(), filled: true, fillColor: Colors.black12)),
const Padding(padding: EdgeInsets.symmetric(vertical: 20), child: Text("PM2.5 Data", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16))), const Padding(padding: EdgeInsets.symmetric(vertical: 20), child: Text("PM2.5 Data", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16))),
_buildResultRow(_pm25FlowRateController, "Flow Rate (cfm)", _pm25FlowRateResultController, "Result (m³/min)", (v) => setState(() => _pm25FlowRateResultController.text = ((double.tryParse(v) ?? 0) * 0.0283).toStringAsFixed(4)), (v) => widget.data.pm25Flowrate = double.tryParse(v!)), _buildResultRow(_pm25FlowRateController, "Flow Rate (cfm)", _pm25FlowRateResultController, "Result (m³/min)", (v) => setState(() => _pm25FlowRateResultController.text = ((double.tryParse(v) ?? 0) * 0.0283).toStringAsFixed(4)), (v) => widget.data.pm25Flowrate = double.tryParse(v!)),
_buildResultRow(_pm25TimeController, "Total Time (hours)", _pm25TimeResultController, "Result (mins)", (v) => setState(() => _pm25TimeResultController.text = ((double.tryParse(v) ?? 0) * 60).toStringAsFixed(2)), (v) => widget.data.pm25TotalTime = v), _buildResultRow(_pm25TimeController, "Total Time (hours)", _pm25TimeResultController, "Result (mins)", (v) => setState(() => _pm25TimeResultController.text = ((double.tryParse(v) ?? 0) * 60).toStringAsFixed(2)), (v) => widget.data.pm25TotalTime = v),
_buildResultRow(_pm25PressureController, "Pressure (hPa)", _pm25PressureResultController, "Result (inHg)", (v) => _calculateVstd(2), (v) => widget.data.pm25Pressure = double.tryParse(v!)), _buildResultRow(_pm25PressureController, "Pressure (hPa)", _pm25PressureResultController, "Result (inHg)", (v) => _calculateVstd(2), (v) => widget.data.pm25Pressure = double.tryParse(v!)),
const SizedBox(height: 8),
TextFormField(
controller: _pm25TavgController,
readOnly: true,
decoration: const InputDecoration(
labelText: 'Ambient Temperature [Tavg=(T1+T2)/2] (°C)',
border: OutlineInputBorder(),
filled: true,
fillColor: Colors.black12),
),
const SizedBox(height: 8),
TextFormField(
controller: _pm25QstdController,
readOnly: true,
decoration: const InputDecoration(
labelText: 'Flowrate (Standard Air), QSTD (m³/min)',
border: OutlineInputBorder(),
filled: true,
fillColor: Colors.black12),
),
const SizedBox(height: 8),
TextFormField(controller: _pm25VstdController, readOnly: true, decoration: const InputDecoration(labelText: 'VSTD PM2.5 (m³)', border: OutlineInputBorder(), filled: true, fillColor: Colors.black12)), TextFormField(controller: _pm25VstdController, readOnly: true, decoration: const InputDecoration(labelText: 'VSTD PM2.5 (m³)', border: OutlineInputBorder(), filled: true, fillColor: Colors.black12)),
const SizedBox(height: 16), const SizedBox(height: 16),