From b97aa7ddf91de974f5bce9671df467e7812b74b4 Mon Sep 17 00:00:00 2001 From: ALim Aidrus Date: Tue, 25 Nov 2025 09:42:32 +0800 Subject: [PATCH] Update model data for river and marine ftp to centralize with mms 1.0 setup --- lib/home_page.dart | 2 +- lib/models/in_situ_sampling_data.dart | 154 ++++++++++-------- lib/models/river_in_situ_sampling_data.dart | 142 ++++++++-------- .../river_inves_manual_sampling_data.dart | 2 +- .../river_manual_triennial_sampling_data.dart | 74 +++++---- lib/screens/settings.dart | 2 +- .../river_in_situ_sampling_service.dart | 2 +- ...ver_manual_triennial_sampling_service.dart | 6 +- 8 files changed, 210 insertions(+), 174 deletions(-) diff --git a/lib/home_page.dart b/lib/home_page.dart index b9674af..420a5d4 100644 --- a/lib/home_page.dart +++ b/lib/home_page.dart @@ -101,7 +101,7 @@ class _HomePageState extends State { ); }, ), - title: const Text("MMS Version 3.12.01"), + title: const Text("MMS Version 3.12.03"), actions: [ IconButton( icon: const Icon(Icons.person), diff --git a/lib/models/in_situ_sampling_data.dart b/lib/models/in_situ_sampling_data.dart index 7589526..cb2bcff 100644 --- a/lib/models/in_situ_sampling_data.dart +++ b/lib/models/in_situ_sampling_data.dart @@ -308,99 +308,117 @@ class InSituSamplingData { }; } + /// Creates a single JSON object with all submission data, mimicking 'db.json' /// Creates a single JSON object with all submission data, mimicking 'db.json' String toDbJson() { - // This is a direct conversion of the model's properties to a map, - // with keys matching the expected 'db.json' file format. final data = { - 'battery_cap': batteryVoltage == -999.0 ? null : batteryVoltage, - 'device_name': sondeId, - 'sampling_type': samplingType, - 'report_id': reportId, - 'sampler_2ndname': secondSampler?['first_name'], - 'sample_state': selectedStateName, - 'station_id': selectedStation?['man_station_code'], - 'tech_id': firstSamplerUserId, - 'tech_phonenum': null, // This field was not in the model - 'tech_name': firstSamplerName, - 'latitude': stationLatitude, - 'longitude': stationLongitude, - 'record_dt': '$samplingDate $samplingTime', - 'do_mgl': oxygenConcentration == -999.0 ? null : oxygenConcentration, - 'do_sat': oxygenSaturation == -999.0 ? null : oxygenSaturation, - 'ph': ph == -999.0 ? null : ph, - 'salinity': salinity == -999.0 ? null : salinity, - 'tss': tss == -999.0 ? null : tss, - 'temperature': temperature == -999.0 ? null : temperature, - 'turbidity': turbidity == -999.0 ? null : turbidity, - 'tds': tds == -999.0 ? null : tds, - 'electric_conductivity': electricalConductivity == -999.0 ? null : electricalConductivity, - 'sample_id': sampleIdCode, - 'tarball': "", // <-- MODIFIED: Hardcoded to "N/A" - 'weather': weather, - 'tide_lvl': tideLevel, - 'sea_cond': seaCondition, - 'remarks_event': eventRemarks, - 'remarks_lab': labRemarks, + // --- Sorted exactly according to your Marine db.json sample --- + 'battery_cap': batteryVoltage ?? -999.0, + 'device_name': sondeId ?? "", + 'sampling_type': samplingType ?? "", + 'report_id': reportId ?? "", + 'sampler_2ndname': secondSampler?['first_name'] ?? "", + 'sample_state': selectedStateName ?? "", + 'station_id': selectedStation?['man_station_code'] ?? "", + 'tech_id': firstSamplerUserId ?? -999, // Default to -999 for int + 'tech_phonenum': "", // Not in model, default to empty string + 'tech_name': firstSamplerName ?? "", + 'latitude': stationLatitude ?? "", + 'longitude': stationLongitude ?? "", + 'record_dt': (samplingDate != null && samplingTime != null) + ? '$samplingDate $samplingTime' + : "", + + // --- Sensor Readings --- + 'do_mgl': oxygenConcentration ?? -999.0, + 'do_sat': oxygenSaturation ?? -999.0, + 'ph': ph ?? -999.0, + 'salinity': salinity ?? -999.0, + 'tss': tss ?? -999.0, + 'temperature': temperature ?? -999.0, + 'turbidity': turbidity ?? -999.0, + 'tds': tds ?? -999.0, + 'electric_conductivity': electricalConductivity ?? -999.0, + + // --- Manual/Observations --- + 'sample_id': sampleIdCode ?? "", + 'tarball': "No", // Field removed from model logic, default to empty + 'weather': weather ?? "", + 'tide_lvl': tideLevel ?? "", + 'sea_cond': seaCondition ?? "", + 'remarks_event': eventRemarks ?? "", + 'remarks_lab': labRemarks ?? "", }; - // Remove null values before encoding - //data.removeWhere((key, value) => value == null); + + // ❌ DO NOT UNCOMMENT. Keeps all keys even if values are null/default. + // data.removeWhere((key, value) => value == null); + return jsonEncode(data); } /// Creates a JSON object for basic form info, mimicking 'marine_insitu_basic_form.json'. String toBasicFormJson() { final data = { - 'tech_name': firstSamplerName, - 'sampler_2ndname': secondSampler?['first_name'], // Assuming 'first_name' key - 'sample_date': samplingDate, - 'sample_time': samplingTime, - 'sampling_type': samplingType, - 'sample_state': selectedStateName, - 'station_id': selectedStation?['man_station_code'], // Marine-specific key - 'station_latitude': stationLatitude, - 'station_longitude': stationLongitude, - 'latitude': currentLatitude, - 'longitude': currentLongitude, - 'sample_id': sampleIdCode, + // --- Sorted exactly according to your Marine sample --- + 'tech_name': firstSamplerName ?? "", + 'sampler_2ndname': secondSampler?['first_name'] ?? "", + 'sample_date': samplingDate ?? "", + 'sample_time': samplingTime ?? "", + 'sampling_type': samplingType ?? "", + 'sample_state': selectedStateName ?? "", + 'sample_category': selectedCategoryName ?? "", // Added to match sample + 'station_id': selectedStation?['man_station_code'] ?? "", + 'station_latitude': stationLatitude ?? "", + 'station_longitude': stationLongitude ?? "", + 'latitude': currentLatitude ?? "", + 'longitude': currentLongitude ?? "", + 'sample_id': sampleIdCode ?? "", }; - // Remove null values before encoding - data.removeWhere((key, value) => value == null); + + // ❌ DO NOT UNCOMMENT + // data.removeWhere((key, value) => value == null); + return jsonEncode(data); } /// Creates a JSON object for sensor readings, mimicking 'marine_sampling_reading.json'. String toReadingJson() { final data = { - 'do_mgl': oxygenConcentration == -999.0 ? null : oxygenConcentration, - 'do_sat': oxygenSaturation == -999.0 ? null : oxygenSaturation, - 'ph': ph == -999.0 ? null : ph, - 'salinity': salinity == -999.0 ? null : salinity, - 'tds': tds == -999.0 ? null : tds, - 'tss': tss == -999.0 ? null : tss, - 'temperature': temperature == -999.0 ? null : temperature, - 'turbidity': turbidity == -999.0 ? null : turbidity, - 'electric_conductivity': electricalConductivity == -999.0 ? null : electricalConductivity, - 'date_sampling_reading': dataCaptureDate, - 'time_sampling_reading': dataCaptureTime, + // --- Sorted exactly according to your Marine sample --- + 'do_mgl': oxygenConcentration ?? -999.0, + 'do_sat': oxygenSaturation ?? -999.0, + 'ph': ph ?? -999.0, + 'salinity': salinity ?? -999.0, + 'tds': tds ?? -999.0, + 'tss': tss ?? -999.0, + 'temperature': temperature ?? -999.0, + 'turbidity': turbidity ?? -999.0, + 'electric_conductivity': electricalConductivity ?? -999.0, + 'date_sampling_reading': dataCaptureDate ?? "", + 'time_sampling_reading': dataCaptureTime ?? "", }; - // Remove null values before encoding - data.removeWhere((key, value) => value == null); + + // ❌ DO NOT UNCOMMENT + // data.removeWhere((key, value) => value == null); + return jsonEncode(data); } /// Creates a JSON object for manual info, mimicking 'marine_manual_info.json'. String toManualInfoJson() { final data = { - 'tarball': "", // <-- MODIFIED: Hardcoded to "N/A" - 'weather': weather, - 'tide_lvl': tideLevel, - 'sea_cond': seaCondition, - 'remarks_event': eventRemarks, - 'remarks_lab': labRemarks, + // --- Sorted exactly according to your Marine sample --- + 'tarball': "", // Field removed from model logic, default to empty + 'weather': weather ?? "", + 'tide_lvl': tideLevel ?? "", + 'sea_cond': seaCondition ?? "", + 'remarks_event': eventRemarks ?? "", + 'remarks_lab': labRemarks ?? "", }; - // Remove null values before encoding - data.removeWhere((key, value) => value == null); + + // ❌ DO NOT UNCOMMENT + // data.removeWhere((key, value) => value == null); + return jsonEncode(data); } diff --git a/lib/models/river_in_situ_sampling_data.dart b/lib/models/river_in_situ_sampling_data.dart index 10504de..e9941d0 100644 --- a/lib/models/river_in_situ_sampling_data.dart +++ b/lib/models/river_in_situ_sampling_data.dart @@ -316,91 +316,99 @@ class RiverInSituSamplingData { }; } - /// Creates a single JSON object with all submission data, mimicking 'db.json' String toDbJson() { - // This is a direct conversion of the model's properties to a map, - // with keys matching the expected JSON file format. final data = { - // --- START FIX: Map model properties to correct db.json keys --- - 'battery_cap': batteryVoltage == -999.0 ? null : batteryVoltage, // Handle -999 - 'device_name': sondeId, - 'sampling_type': samplingType, - 'report_id': reportId, - 'sampler_2ndname': secondSampler?['first_name'], // Use first_name as likely user name - 'sample_state': selectedStateName, - 'station_id': selectedStation?['sampling_station_code'], - 'tech_id': firstSamplerUserId, - 'tech_name': firstSamplerName, - 'latitude': stationLatitude, // Assuming station lat/lon is intended here - 'longitude': stationLongitude, // Assuming station lat/lon is intended here - 'record_dt': '$samplingDate $samplingTime', - 'do_mgl': oxygenConcentration == -999.0 ? null : oxygenConcentration, - 'do_sat': oxygenSaturation == -999.0 ? null : oxygenSaturation, - 'ph': ph == -999.0 ? null : ph, - 'salinity': salinity == -999.0 ? null : salinity, - 'temperature': temperature == -999.0 ? null : temperature, - 'turbidity': turbidity == -999.0 ? null : turbidity, - 'tds': tds == -999.0 ? null : tds, - 'electric_conductivity': electricalConductivity == -999.0 ? null : electricalConductivity, - 'ammonia': ammonia == -999.0 ? null : ammonia, - 'flowrate': flowrateValue, - 'odour': '', // Not collected - 'floatable': '', // Not collected - 'sample_id': sampleIdCode, - 'weather': weather, - 'remarks_event': eventRemarks, - 'remarks_lab': labRemarks, - // --- END FIX --- + // --- Sorted exactly according to your sample --- + 'battery_cap': batteryVoltage ?? -999.0, + 'device_name': sondeId ?? "", + 'sampling_type': samplingType ?? "", + 'report_id': reportId ?? "", + 'sampler_2ndname': secondSampler?['first_name'] ?? "", + 'sample_state': selectedStateName ?? "", + 'station_id': selectedStation?['sampling_station_code'] ?? "", + 'tech_id': firstSamplerUserId ?? -999, // Default to -999 if no ID + 'tech_phonenum': "", // Field present in sample but not in model, defaults to empty + 'tech_name': firstSamplerName ?? "", + 'latitude': stationLatitude ?? "", + 'longitude': stationLongitude ?? "", + 'record_dt': (samplingDate != null && samplingTime != null) + ? '$samplingDate $samplingTime' + : "", + + // --- Sensor Readings --- + 'do_mgl': oxygenConcentration ?? -999.0, + 'do_sat': oxygenSaturation ?? -999.0, + 'ph': ph ?? -999.0, + 'salinity': salinity ?? -999.0, + 'temperature': temperature ?? -999.0, + 'turbidity': turbidity ?? -999.0, + 'tds': tds ?? -999.0, + 'electric_conductivity': electricalConductivity ?? -999.0, + 'flowrate': flowrateValue ?? -999.0, + + // --- Manual/Observations --- + 'odour': "", // Default empty + 'floatable': "", // Default empty + 'sample_id': sampleIdCode ?? "", + 'weather': weather ?? "", + 'remarks_event': eventRemarks ?? "", + 'remarks_lab': labRemarks ?? "", }; - // Remove null values before encoding - //data.removeWhere((key, value) => value == null); + + // ❌ DO NOT UNCOMMENT. We want to keep all keys even if values are default. + // data.removeWhere((key, value) => value == null); + return jsonEncode(data); } /// Creates a JSON object for basic form info, mimicking 'river_insitu_basic_form.json'. String toBasicFormJson() { final data = { - // --- START FIX: Map model properties to correct form keys --- - 'tech_name': firstSamplerName, - 'sampler_2ndname': secondSampler?['first_name'], - 'sample_date': samplingDate, - 'sample_time': samplingTime, - 'sampling_type': samplingType, - 'sample_state': selectedStateName, - 'station_id': selectedStation?['sampling_station_code'], - 'station_latitude': stationLatitude, - 'station_longitude': stationLongitude, - 'latitude': currentLatitude, // Current location lat - 'longitude': currentLongitude, // Current location lon - 'sample_id': sampleIdCode, - // --- END FIX --- + // --- Sorted sequence: tech_name -> sampler_2ndname -> date/time -> type -> state -> station info -> location -> sample_id + 'tech_name': firstSamplerName ?? "", + 'sampler_2ndname': secondSampler?['first_name'] ?? "", + 'sample_date': samplingDate ?? "", + 'sample_time': samplingTime ?? "", + 'sampling_type': samplingType ?? "", + 'sample_state': selectedStateName ?? "", + 'station_id': selectedStation?['sampling_station_code'] ?? "", + 'station_latitude': stationLatitude ?? "", + 'station_longitude': stationLongitude ?? "", + 'latitude': currentLatitude ?? "", // Current user location lat + 'longitude': currentLongitude ?? "", // Current user location lon + 'sample_id': sampleIdCode ?? "", }; - // Remove null values before encoding - data.removeWhere((key, value) => value == null); + + // ❌ REMOVE or COMMENT OUT this line so no keys are deleted + // data.removeWhere((key, value) => value == null); + return jsonEncode(data); } + /// Creates a JSON object for sensor readings, mimicking 'river_sampling_reading.json'. /// Creates a JSON object for sensor readings, mimicking 'river_sampling_reading.json'. String toReadingJson() { final data = { - // --- START FIX: Map model properties to correct reading keys --- - 'do_mgl': oxygenConcentration == -999.0 ? null : oxygenConcentration, - 'do_sat': oxygenSaturation == -999.0 ? null : oxygenSaturation, - 'ph': ph == -999.0 ? null : ph, - 'salinity': salinity == -999.0 ? null : salinity, - 'temperature': temperature == -999.0 ? null : temperature, - 'turbidity': turbidity == -999.0 ? null : turbidity, - 'tds': tds == -999.0 ? null : tds, - 'electric_conductivity': electricalConductivity == -999.0 ? null : electricalConductivity, - 'ammonia': ammonia == -999.0 ? null : ammonia, - 'flowrate': flowrateValue, - 'date_sampling_reading': dataCaptureDate, // Use data capture date/time - 'time_sampling_reading': dataCaptureTime, // Use data capture date/time - // --- END FIX --- + // --- Sorted exactly according to your sample --- + 'do_mgl': oxygenConcentration ?? -999.0, + 'do_sat': oxygenSaturation ?? -999.0, + 'ph': ph ?? -999.0, + 'salinity': salinity ?? -999.0, + 'temperature': temperature ?? -999.0, + 'turbidity': turbidity ?? -999.0, + 'tds': tds ?? -999.0, + 'electric_conductivity': electricalConductivity ?? -999.0, + 'flowrate': flowrateValue ?? -999.0, + + // --- Date and Time --- + 'date_sampling_reading': dataCaptureDate ?? "", + 'time_sampling_reading': dataCaptureTime ?? "", }; - // Remove null values before encoding - data.removeWhere((key, value) => value == null); + + // ❌ REMOVE or COMMENT OUT this line to ensure no keys are skipped/removed + // data.removeWhere((key, value) => value == null); + return jsonEncode(data); } diff --git a/lib/models/river_inves_manual_sampling_data.dart b/lib/models/river_inves_manual_sampling_data.dart index 52d9164..ac24502 100644 --- a/lib/models/river_inves_manual_sampling_data.dart +++ b/lib/models/river_inves_manual_sampling_data.dart @@ -446,7 +446,7 @@ class RiverInvesManualSamplingData { 'tds': tds == -999.0 ? null : tds, 'electric_conductivity': electricalConductivity == -999.0 ? null : electricalConductivity, 'ammonia': ammonia == -999.0 ? null : ammonia, - 'flowrate': flowrateValue, + 'flowrate': flowrateValue ?? -999.0, 'odour': '', // Not collected 'floatable': '', // Not collected 'sample_id': sampleIdCode, diff --git a/lib/models/river_manual_triennial_sampling_data.dart b/lib/models/river_manual_triennial_sampling_data.dart index 3443466..7e3b602 100644 --- a/lib/models/river_manual_triennial_sampling_data.dart +++ b/lib/models/river_manual_triennial_sampling_data.dart @@ -322,7 +322,7 @@ class RiverManualTriennialSamplingData { 'turbidity': turbidity == -999.0 ? null : turbidity, 'tds': tds == -999.0 ? null : tds, 'electric_conductivity': electricalConductivity == -999.0 ? null : electricalConductivity, - 'ammonia': ammonia == -999.0 ? null : ammonia, + //'ammonia': ammonia == -999.0 ? null : ammonia, 'flowrate': flowrateValue, 'odour': '', // Not collected 'floatable': '', // Not collected @@ -335,47 +335,57 @@ class RiverManualTriennialSamplingData { return jsonEncode(data); } - /// Creates a JSON object for basic form info, mimicking 'river_triennial_basic_form.json'. + /// Creates a JSON object for basic form info, mimicking 'river_insitu_basic_form.json'. String toBasicFormJson() { final data = { - // --- START FIX: Map model properties to correct form keys --- - 'tech_name': firstSamplerName, - 'sampler_2ndname': secondSampler?['first_name'], - 'sample_date': samplingDate, - 'sample_time': samplingTime, - 'sampling_type': samplingType, - 'sample_state': selectedStateName, - 'station_id': selectedStation?['sampling_station_code'], - 'station_latitude': stationLatitude, - 'station_longitude': stationLongitude, - 'latitude': currentLatitude, // Current location lat - 'longitude': currentLongitude, // Current location lon - 'sample_id': sampleIdCode, - // --- END FIX --- + // Keys sorted exactly according to the provided sample + 'tech_name': firstSamplerName ?? "", + 'sampler_2ndname': secondSampler?['first_name'] ?? "", + 'sample_date': samplingDate ?? "", + 'sample_time': samplingTime ?? "", + 'sampling_type': samplingType ?? "", + 'sample_state': selectedStateName ?? "", + 'station_id': selectedStation?['sampling_station_code'] ?? "", + 'station_latitude': stationLatitude ?? "", + 'station_longitude': stationLongitude ?? "", + 'latitude': currentLatitude ?? "", // Current user location lat + 'longitude': currentLongitude ?? "", // Current user location lon + 'sample_id': sampleIdCode ?? "", }; - data.removeWhere((key, value) => value == null); + + // ❌ REMOVE or COMMENT OUT this line so no keys are deleted + // data.removeWhere((key, value) => value == null); + return jsonEncode(data); } /// Creates a JSON object for sensor readings, mimicking 'river_sampling_reading.json'. String toReadingJson() { final data = { - // --- START FIX: Map model properties to correct reading keys --- - 'do_mgl': oxygenConcentration == -999.0 ? null : oxygenConcentration, - 'do_sat': oxygenSaturation == -999.0 ? null : oxygenSaturation, - 'ph': ph == -999.0 ? null : ph, - 'salinity': salinity == -999.0 ? null : salinity, - 'temperature': temperature == -999.0 ? null : temperature, - 'turbidity': turbidity == -999.0 ? null : turbidity, - 'tds': tds == -999.0 ? null : tds, - 'electric_conductivity': electricalConductivity == -999.0 ? null : electricalConductivity, - 'ammonia': ammonia == -999.0 ? null : ammonia, - 'flowrate': flowrateValue, - 'date_sampling_reading': dataCaptureDate, // Use data capture date/time - 'time_sampling_reading': dataCaptureTime, // Use data capture date/time - // --- END FIX --- + // Use ?? operator to default to -999.0 if null + 'do_mgl': oxygenConcentration ?? -999.0, + 'do_sat': oxygenSaturation ?? -999.0, + 'ph': ph ?? -999.0, + 'salinity': salinity ?? -999.0, + 'temperature': temperature ?? -999.0, + 'turbidity': turbidity ?? -999.0, + 'tds': tds ?? -999.0, + 'electric_conductivity': electricalConductivity ?? -999.0, + + // Keep 'ammonia' commented out if it's not used in Triennial, + // otherwise uncomment and use: 'ammonia': ammonia ?? -999.0, + + // Flowrate defaults to -999.0 (as requested in previous turn) + 'flowrate': flowrateValue ?? -999.0, + + // Date and Time default to empty string "" if null + 'date_sampling_reading': dataCaptureDate ?? "", + 'time_sampling_reading': dataCaptureTime ?? "", }; - data.removeWhere((key, value) => value == null); + + // ❌ REMOVE or COMMENT OUT this line so keys are NEVER deleted + // data.removeWhere((key, value) => value == null); + return jsonEncode(data); } diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart index fc8ed83..b9cf900 100644 --- a/lib/screens/settings.dart +++ b/lib/screens/settings.dart @@ -221,7 +221,7 @@ class _SettingsScreenState extends State { ListTile( leading: const Icon(Icons.info_outline), title: const Text('App Version'), - subtitle: const Text('MMS Version 3.12.01'), + subtitle: const Text('MMS Version 3.12.03'), dense: true, ), ListTile( diff --git a/lib/services/river_in_situ_sampling_service.dart b/lib/services/river_in_situ_sampling_service.dart index 4e7c334..e2628e0 100644 --- a/lib/services/river_in_situ_sampling_service.dart +++ b/lib/services/river_in_situ_sampling_service.dart @@ -603,7 +603,7 @@ class RiverInSituSamplingService { mapImage(data.backgroundStationImage, 'background'); mapImage(data.upstreamRiverImage, 'upstream'); mapImage(data.downstreamRiverImage, 'downstream'); - mapImage(data.sampleTurbidityImage, 'sample_turbidity'); + mapImage(data.sampleTurbidityImage, 'turbidity'); mapImage(data.optionalImage1, 'optional_1'); mapImage(data.optionalImage2, 'optional_2'); mapImage(data.optionalImage3, 'optional_3'); diff --git a/lib/services/river_manual_triennial_sampling_service.dart b/lib/services/river_manual_triennial_sampling_service.dart index 18c7b87..909ebde 100644 --- a/lib/services/river_manual_triennial_sampling_service.dart +++ b/lib/services/river_manual_triennial_sampling_service.dart @@ -607,9 +607,9 @@ class RiverManualTriennialSamplingService { // --- START FIX: Include all four JSON files --- jsonDataMap: { 'db.json': data.toDbJson(), - 'river_triennial_basic_form.json': data.toBasicFormJson(), // ADDED - 'river_triennial_reading.json': data.toReadingJson(), // ADDED - 'river_triennial_manual_info.json': data.toManualInfoJson(), // ADDED + 'river_insitu_basic_form.json': data.toBasicFormJson(), // ADDED + 'river_sampling_reading.json': data.toReadingJson(), // ADDED + 'river_manual_info.json': data.toManualInfoJson(), // ADDED }, // --- END FIX --- baseFileName: baseFileName,