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 _pm10PressureController = TextEditingController();
final _pm10PressureResultController = TextEditingController();
final _pm10TavgController = TextEditingController();
final _pm10QstdController = TextEditingController();
final _pm10VstdController = TextEditingController();
// PM2.5 Controllers
@ -58,6 +60,8 @@ class _AirManualCollectionWidgetState extends State<AirManualCollectionWidget> {
final _pm25TimeResultController = TextEditingController();
final _pm25PressureController = TextEditingController();
final _pm25PressureResultController = TextEditingController();
final _pm25TavgController = TextEditingController();
final _pm25QstdController = TextEditingController();
final _pm25VstdController = TextEditingController();
@override
@ -88,6 +92,8 @@ class _AirManualCollectionWidgetState extends State<AirManualCollectionWidget> {
_pm10TimeResultController.dispose();
_pm10PressureController.dispose();
_pm10PressureResultController.dispose();
_pm10TavgController.dispose();
_pm10QstdController.dispose();
_pm10VstdController.dispose();
_pm25FlowRateController.dispose();
_pm25FlowRateResultController.dispose();
@ -95,6 +101,8 @@ class _AirManualCollectionWidgetState extends State<AirManualCollectionWidget> {
_pm25TimeResultController.dispose();
_pm25PressureController.dispose();
_pm25PressureResultController.dispose();
_pm25TavgController.dispose();
_pm25QstdController.dispose();
_pm25VstdController.dispose();
super.dispose();
}
@ -153,35 +161,58 @@ class _AirManualCollectionWidgetState extends State<AirManualCollectionWidget> {
void _calculateVstd(int type) {
setState(() {
try {
// Get the correct controllers for either PM10 or PM2.5
final flowRateCtrl = type == 1 ? _pm10FlowRateController : _pm25FlowRateController;
final pressureCtrl = type == 1 ? _pm10PressureController : _pm25PressureController;
final flowResultCtrl = type == 1 ? _pm10FlowRateResultController : _pm25FlowRateResultController;
final timeResultCtrl = type == 1 ? _pm10TimeResultController : _pm25TimeResultController;
final vstdCtrl = type == 1 ? _pm10VstdController : _pm25VstdController;
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";
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 pressureInHg = pressureHpa * 0.02953;
double pressureInHg = pressureHpa * hpaToinHg;
double p_actual_mmHg = pressureInHg * inHgToMmHg; // result pressure
pressureResultCtrl.text = pressureInHg.toStringAsFixed(2);
double qa_m3min = double.parse(flowResultCtrl.text);
double totalTime_min = double.parse(timeResultCtrl.text);
double pa_mmHg = pressureInHg * 25.4;
double t_avg_celsius = (widget.initialTemp + double.parse(_finalTempController.text)) / 2.0;
// 3. Calculate average temperature in Celsius
double t_avg_celsius = (widget.initialTemp + double.parse(_finalTempController.text)) / 2.0; // ambient temperature
tAvgCtrl.text = t_avg_celsius.toStringAsFixed(2);
double numerator = qa_m3min * pa_mmHg * 298.15;
double denominator = 760 * (273.15 + t_avg_celsius);
double q_std = numerator / denominator;
// 4. Calculate Standard Flowrate (QSTD) using your exact formula
// Formula: (flowrate * result pressure * 298) / (760 * (273 * ambient temperature))
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;
if (type == 1) {
if (type == 1) { // PM10
_pm10VstdController.text = v_std.toStringAsFixed(3);
widget.data.pm10Vstd = v_std;
} else {
} else { // PM2.5
_pm25VstdController.text = v_std.toStringAsFixed(3);
widget.data.pm25Vstd = v_std;
}
@ -192,13 +223,10 @@ class _AirManualCollectionWidgetState extends State<AirManualCollectionWidget> {
});
}
// **CRITICAL FIX IS HERE**
void _onSubmitPressed() {
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.pm10TotalTimeResult = _pm10TimeResultController.text;
widget.data.pm10PressureResult = _pm10PressureResultController.text;
@ -206,13 +234,11 @@ class _AirManualCollectionWidgetState extends State<AirManualCollectionWidget> {
widget.data.pm25TotalTimeResult = _pm25TimeResultController.text;
widget.data.pm25PressureResult = _pm25PressureResultController.text;
// Save optional remarks
widget.data.optionalRemark1 = _optionalRemark1Controller.text;
widget.data.optionalRemark2 = _optionalRemark2Controller.text;
widget.data.optionalRemark3 = _optionalRemark3Controller.text;
widget.data.optionalRemark4 = _optionalRemark4Controller.text;
// Now, call the submission function with the fully populated data model
widget.onSubmit();
}
}
@ -261,17 +287,62 @@ class _AirManualCollectionWidgetState extends State<AirManualCollectionWidget> {
validator: (v) => v == null ? 'Required' : null,
onSaved: (v) => widget.data.powerFailure = v,
),
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(_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!)),
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)),
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(_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!)),
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)),
const SizedBox(height: 16),