279735a4ac1f4a61b3cdd4e9e88.../src/main.rs
2025-06-10 16:27:10 +00:00

164 lines
6.3 KiB
Rust

use fathom_function::{chrono::NaiveDate, forms::deserialize_date, tracing};
use pipeline_application::{
application::{
Application, FieldAssessment, PurposeOfMeasurement, SurfaceLocation,
features::{FeatureIdentification, FeatureType},
orientation::Orientation,
},
serialization::{serialize_meter, serialize_millimeter},
};
use serde::{Deserialize, Serialize};
use serde_this_or_that::as_opt_u64;
use uom::si::f64::Length;
use uuid::Uuid;
#[fathom_function::function]
async fn upload_field_assessment(input: Input) -> Result<Output, String> {
let app = Application::new_from_compile_env(input.org_id, input.project_id).unwrap();
for pipeline_id in input.pipeline_id {
app.upload_field_assessment(pipeline_id, &input.report)
.await
.map_err(|err| {
tracing::error!(%pipeline_id, ?err, "Error uploading field assessment");
format!("{err:?}")
})?;
}
Ok(Output {
status: "Success".to_owned(),
})
}
#[derive(Debug, Serialize)]
struct Output {
status: String,
}
#[derive(Debug, Deserialize)]
struct Input {
org_id: Uuid,
project_id: String,
pipeline_id: Vec<Uuid>,
#[serde(flatten)]
report: Report,
}
#[derive(Debug, Deserialize)]
struct Report {
/// The date of this field inspection.
#[serde(deserialize_with = "deserialize_date")]
date: NaiveDate,
/// The log distance: the absolute distance measured in meters from the launcher.
#[serde(with = "serialize_meter")]
log_distance: Length,
/// The Girthweld number. These follow a sequential order of 10 (e.g., 10, 20, 30, ...) to
/// enumerate each joint from the initial to the final one. This ordering system facilitates
/// future repairs using the "Pipeline Sectional Replacement" method.
#[serde(deserialize_with = "as_opt_u64")]
girth_weld: Option<u64>,
/// The circumferential position of the starting point of an anomaly, specifically the top-left
/// position on the pipe.
///
/// This orientation is measured in O'clock notation, ranging from 00:00 to 12:00, indicating
/// the position around the circumference of the pipe. This is the number of hours
#[serde(deserialize_with = "as_opt_u64")]
anomaly_orientation_hours: Option<u64>,
/// The circumferential position of the starting point of an anomaly, specifically the top-left
/// position on the pipe.
///
/// This orientation is measured in O'clock notation, ranging from 00:00 to 12:00, indicating
/// the position around the circumference of the pipe. This is the number of minutes
#[serde(deserialize_with = "as_opt_u64")]
anomaly_orientation_minutes: Option<u64>,
/// The measured wall thickness of the pipeline taken from an un-corroded location. Corresponds
/// to the un-corroded wall thickness.
#[serde(with = "serialize_millimeter::opt")]
measured_wall_thickness: Option<Length>,
/// The measured wall thickness of the pipeline taken from the river bottom of the corrosion.
#[serde(with = "serialize_millimeter::opt")]
measured_remaining_wall_thickness: Option<Length>,
/// The surface location of the anomaly.
///
/// Each identified anomaly, denoted as ANOM, falls into one of four categories: Internal
/// (INT), External (EXT), Midwall (MID), or Unknown (UNK) for any reason. These
/// classifications are assigned based on the specific characteristics and location of the
/// anomaly on the pipeline. The categorization helps provide a comprehensive understanding of
/// the nature of the anomalies present.
surface_location: Option<SurfaceLocation>,
/// The measured length (in mm) of an anomaly, (the length of the corrosion box) in relation to
/// the reporting threshold.
#[serde(with = "serialize_millimeter")]
measured_length: Length,
/// The circumferential width of an anomaly, this denotes the measured measurement of the
/// anomaly's width in circumferential direction, comparable to the width of the corrosion box,
/// concerning the reporting threshold and the unit is mm.
#[serde(with = "serialize_millimeter::opt")]
measured_width: Option<Length>,
/// The measured depth of the anomaly.
///
/// This measurement is essential for evaluating the structural integrity of the pipeline. A
/// higher percentage of metal loss indicates a more significant impact on the pipeline's
/// strength and durability.
#[serde(with = "serialize_millimeter")]
measured_max_depth: Length,
/// The reason for this field assessment
purpose_of_measurement: PurposeOfMeasurement,
/// The accuracy of the tool used
#[serde(with = "serialize_millimeter::opt")]
tool_accuracy: Option<Length>,
/// The accuracy of the pit gauge used
#[serde(with = "serialize_millimeter::opt")]
pit_gauge_accuracy: Option<Length>,
/// The inspector name or identifier
inspector: String,
/// And additional remarks about the inspection
remarks: Option<String>,
}
impl Report {
fn orientation(&self) -> Option<Orientation> {
Some(Orientation::new(
self.anomaly_orientation_hours? as _,
self.anomaly_orientation_minutes? as _,
))
}
}
impl From<&Report> for FieldAssessment {
fn from(value: &Report) -> Self {
Self {
date: value.date.and_time(Default::default()).and_utc(),
log_distance: value.log_distance,
feature_type: Some(FeatureType::ANOM),
feature_identification: Some(FeatureIdentification::CORR),
girth_weld: value.girth_weld.map(|v| v as _),
anomaly_orientation: value.orientation(),
measured_wall_thickness: value.measured_wall_thickness,
measured_remaining_wall_thickness: value.measured_remaining_wall_thickness,
surface_location: value.surface_location,
measured_length: value.measured_length,
measured_width: value.measured_width,
measured_max_depth: value.measured_max_depth,
purpose_of_measurement: value.purpose_of_measurement.to_owned(),
tool_accuracy: value.tool_accuracy,
pit_gauge_accuracy: value.pit_gauge_accuracy,
inspector: value.inspector.to_owned(),
remarks: value.remarks.to_owned(),
}
}
}