Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3d77c0e1b |
3623
Cargo.lock
generated
Normal file
3623
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
edition = "2024"
|
||||
name = "web"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
fathom-function = { git = "ssh://git@github.com/fathom-io/pipeline-calculations.git", branch = "main" }
|
||||
pipeline-application = { git = "ssh://git@github.com/fathom-io/pipeline-calculations.git", branch = "main" }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
tokio = { version = "1.43.0", features = ["macros", "rt-multi-thread"] }
|
||||
uuid = { version = "1" }
|
||||
128
README.md
128
README.md
@ -1,2 +1,128 @@
|
||||
# 2aa7988e1f00494bb9dc8cf2ef803ac5
|
||||
# Dynamic segmentation function
|
||||
|
||||
A function that exposes the dynamic segmentation algorithm for the pipeline use
|
||||
case.
|
||||
|
||||
This function will recompute the segmentation for all pipelines based on the
|
||||
provided segmentation configuration
|
||||
|
||||
Segmentation is the process of taking local data, crossings, and facilities on
|
||||
the pipeline and creating pipeline segments where this local data changes (a
|
||||
cut line).
|
||||
|
||||
In fathom we support dynamic segmentation where the user is able to configure
|
||||
the properties they would like to consider when segmentizing and to support
|
||||
re-segmentizing the pipeline at any point such as when new data is available
|
||||
from an ILI report.
|
||||
|
||||
Segmentation can be dynamically configured to consider the following properties
|
||||
|
||||
- pipeline diameter (currently required)
|
||||
- wall thickness (currently required)
|
||||
- material grade
|
||||
- coating type
|
||||
- design factor
|
||||
- high consequence area
|
||||
- unusual sensitive area
|
||||
- join type
|
||||
- soil type
|
||||
- soil ph
|
||||
|
||||
Additionally the following crossing types can be considered
|
||||
|
||||
- Road
|
||||
- River
|
||||
- Railroad
|
||||
- Overhead
|
||||
- Highway
|
||||
- Pipeline
|
||||
|
||||
Finally, the following facilities can also be used to create cut lines
|
||||
|
||||
- Valves
|
||||
- Insulation joints
|
||||
- Repairs
|
||||
|
||||
The user is able to configure which the the local properties and which of the
|
||||
crossing and facility types they would like to consider for creating the
|
||||
segmentation. For the purpose of the analytics required later in the process we
|
||||
require the user segments on diameter and wall thickness. The other properties
|
||||
are optional.
|
||||
|
||||
## Example
|
||||
|
||||
```none
|
||||
diameter ├──────────────────────────┼────────────────────────────────────┤
|
||||
40 inch 42 inch
|
||||
wall thickness ├───────┼─────────────────────────────────────────────────┼─────┤
|
||||
12.0 9.0 12.0
|
||||
material grade ├──────────────────────────┼──────────────────────────────┼─────┤
|
||||
L290 L320
|
||||
design factor ├─────────────────────────────────────────────────────────┼─────┤
|
||||
L72
|
||||
road crossing ├──┤ ├──┤
|
||||
river crossing ├──┤ ├──┤
|
||||
valve │ │
|
||||
repair ├─┤
|
||||
|
||||
|
||||
segments ├─┼──┼──┼──────────┼──┼────┼────┼─┼──────────┼──┼──┼──┼───┼──┼──┤
|
||||
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
||||
```
|
||||
|
||||
## Input
|
||||
|
||||
### Arguments
|
||||
|
||||
- `org_id`: as string which should be a valid `uuid` for the organization
|
||||
- `project_id`: the id of the data project where the pipeline data is found
|
||||
- `facilities`: an array of `string` each value should be one of
|
||||
- `insulation_joint`
|
||||
- `repair`
|
||||
- `valve`.
|
||||
- `crossings`: an `object` with key for each supported crossing type and values
|
||||
as objects representing the selected values
|
||||
- `manual_cutlines`: an optional `object` with key for each cutline as objects
|
||||
representing the log distance
|
||||
- `local_data`: an array of `string` each value should be one of
|
||||
- `material_grade`
|
||||
- `coating_type`
|
||||
- `design_factor`
|
||||
- `high_consequence_area`
|
||||
- `unusual_sensitive_area`
|
||||
- `joint_type`
|
||||
- `soil_type`
|
||||
- `soil_ph`
|
||||
|
||||
## Creating the function on the platform
|
||||
|
||||
To create this function on the platform using the `cli` set up the port forwarding as shown in README.
|
||||
|
||||
Then run the following command to create the function.
|
||||
|
||||
```bash
|
||||
cargo run functions create \
|
||||
-f functions/segmentation \
|
||||
-d "Runs the segmentation algorithm for the provided pipelines" \
|
||||
-i org_id=string \
|
||||
-i project_id=string \
|
||||
-i pipeline_id=array \
|
||||
-i facilities=array \
|
||||
-i crossings=object \
|
||||
-i manual_cutlines=object \
|
||||
-i local_data=array
|
||||
```
|
||||
|
||||
## Testing the function locally
|
||||
|
||||
You can run and test the function locally by running
|
||||
|
||||
```bash
|
||||
cargo run
|
||||
```
|
||||
|
||||
Then you can check it work with `curl` as follows
|
||||
|
||||
```bash
|
||||
curl localhost:8080 -d $(jq '. | tojson' functions/segmentation/example_input.json)
|
||||
```
|
||||
|
||||
88
example_input.json
Normal file
88
example_input.json
Normal file
@ -0,0 +1,88 @@
|
||||
{
|
||||
"facilities": [],
|
||||
"local_data": [
|
||||
"material_grade",
|
||||
"coating_type",
|
||||
"design_factor",
|
||||
"high_consequence_area",
|
||||
"unusual_sensitive_area",
|
||||
"joint_type",
|
||||
"soil_type",
|
||||
"soil_ph"
|
||||
],
|
||||
"org_id": "2cbfe270-d195-48ad-aed1-24145924635c",
|
||||
"pipeline_id": [
|
||||
"01966d47-1d4c-7751-a1f1-0617caa3a00d"
|
||||
],
|
||||
"project_id": "680b61b0aedd6f9e639d8699",
|
||||
"manual_cutlines": {
|
||||
"cutline_1": {
|
||||
"log_distance": {
|
||||
"inputType": "number",
|
||||
"value": 50
|
||||
}
|
||||
}
|
||||
},
|
||||
"crossings": {
|
||||
"highway": {
|
||||
"longer_than": {
|
||||
"inputType": "number",
|
||||
"value": null
|
||||
},
|
||||
"selected": {
|
||||
"inputType": "checkbox",
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"overhead": {
|
||||
"longer_than": {
|
||||
"inputType": "number",
|
||||
"value": null
|
||||
},
|
||||
"selected": {
|
||||
"inputType": "checkbox",
|
||||
"value": true
|
||||
}
|
||||
},
|
||||
"pipeline": {
|
||||
"longer_than": {
|
||||
"inputType": "number",
|
||||
"value": null
|
||||
},
|
||||
"selected": {
|
||||
"inputType": "checkbox",
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"railroad": {
|
||||
"longer_than": {
|
||||
"inputType": "number",
|
||||
"value": null
|
||||
},
|
||||
"selected": {
|
||||
"inputType": "checkbox",
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"river": {
|
||||
"longer_than": {
|
||||
"inputType": "number",
|
||||
"value": null
|
||||
},
|
||||
"selected": {
|
||||
"inputType": "checkbox",
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"road": {
|
||||
"longer_than": {
|
||||
"inputType": "number",
|
||||
"value": 10
|
||||
},
|
||||
"selected": {
|
||||
"inputType": "checkbox",
|
||||
"value": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
206
src/main.rs
Normal file
206
src/main.rs
Normal file
@ -0,0 +1,206 @@
|
||||
use fathom_function::forms::TableCellValue;
|
||||
use fathom_function::tracing;
|
||||
use pipeline_application::application::{
|
||||
Application, CrossingConfiguration, CrossingKind, FacilityType, SegmentationConfiguration,
|
||||
uom::si::{
|
||||
f64::Length,
|
||||
length::{kilometer, meter},
|
||||
},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[fathom_function::function]
|
||||
async fn segment(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.segment(pipeline_id, &input.configuration)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
tracing::error!(%pipeline_id, ?err, "Error running segmentation algorithm");
|
||||
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)]
|
||||
configuration: Configuration,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Configuration {
|
||||
facilities: Vec<FacilityType>,
|
||||
local_data: Vec<String>,
|
||||
#[serde(default)]
|
||||
manual_cutlines: ManualCutlines,
|
||||
crossings: Crossings,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Default)]
|
||||
struct ManualCutlines {
|
||||
#[serde(default)]
|
||||
cutline_1: CutlineRow,
|
||||
#[serde(default)]
|
||||
cutline_2: CutlineRow,
|
||||
#[serde(default)]
|
||||
cutline_3: CutlineRow,
|
||||
#[serde(default)]
|
||||
cutline_4: CutlineRow,
|
||||
#[serde(default)]
|
||||
cutline_5: CutlineRow,
|
||||
#[serde(default)]
|
||||
cutline_6: CutlineRow,
|
||||
#[serde(default)]
|
||||
cutline_7: CutlineRow,
|
||||
#[serde(default)]
|
||||
cutline_8: CutlineRow,
|
||||
#[serde(default)]
|
||||
cutline_9: CutlineRow,
|
||||
#[serde(default)]
|
||||
cutline_10: CutlineRow,
|
||||
}
|
||||
|
||||
impl ManualCutlines {
|
||||
fn to_cutlines(&self) -> impl Iterator<Item = Length> {
|
||||
[
|
||||
self.cutline_1.as_length(),
|
||||
self.cutline_2.as_length(),
|
||||
self.cutline_3.as_length(),
|
||||
self.cutline_4.as_length(),
|
||||
self.cutline_5.as_length(),
|
||||
self.cutline_6.as_length(),
|
||||
self.cutline_7.as_length(),
|
||||
self.cutline_8.as_length(),
|
||||
self.cutline_9.as_length(),
|
||||
self.cutline_10.as_length(),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CutlineRow {
|
||||
log_distance: TableCellValue,
|
||||
}
|
||||
|
||||
impl Default for CutlineRow {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
log_distance: TableCellValue::Number { value: None },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CutlineRow {
|
||||
fn as_length(&self) -> Option<Length> {
|
||||
Option::<f64>::try_from(&self.log_distance)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(Length::new::<kilometer>)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Crossings {
|
||||
highway: CrossingRow,
|
||||
road: CrossingRow,
|
||||
river: CrossingRow,
|
||||
railroad: CrossingRow,
|
||||
overhead: CrossingRow,
|
||||
pipeline: CrossingRow,
|
||||
}
|
||||
|
||||
impl Crossings {
|
||||
fn to_crossing_configurations(&self) -> impl Iterator<Item = CrossingConfiguration> {
|
||||
[
|
||||
self.highway
|
||||
.to_crossing_configuration(CrossingKind::Highway),
|
||||
self.road.to_crossing_configuration(CrossingKind::Road),
|
||||
self.river.to_crossing_configuration(CrossingKind::River),
|
||||
self.railroad
|
||||
.to_crossing_configuration(CrossingKind::Railroad),
|
||||
self.overhead
|
||||
.to_crossing_configuration(CrossingKind::Overhead),
|
||||
self.pipeline
|
||||
.to_crossing_configuration(CrossingKind::Pipeline),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CrossingRow {
|
||||
longer_than: TableCellValue,
|
||||
selected: TableCellValue,
|
||||
}
|
||||
|
||||
impl CrossingRow {
|
||||
fn to_crossing_configuration(
|
||||
&self,
|
||||
crossing_kind: CrossingKind,
|
||||
) -> Option<CrossingConfiguration> {
|
||||
if (&self.selected).try_into().unwrap_or(false) {
|
||||
Some(CrossingConfiguration {
|
||||
crossing_kind,
|
||||
longer_than: Length::new::<meter>(
|
||||
(&self.longer_than).try_into().ok().unwrap_or(0.0),
|
||||
),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Configuration> for SegmentationConfiguration {
|
||||
fn from(value: &Configuration) -> Self {
|
||||
let mut config =
|
||||
value
|
||||
.facilities
|
||||
.iter()
|
||||
.fold(Self::default_false(), |config, fac| match fac {
|
||||
FacilityType::InsulationJoint => config.by_insulation_joints(true),
|
||||
FacilityType::Repair => config.by_repairs(true),
|
||||
FacilityType::Valve => config.by_valves(true),
|
||||
_ => config,
|
||||
});
|
||||
|
||||
config = value.crossings.to_crossing_configurations().fold(
|
||||
config,
|
||||
SegmentationConfiguration::with_crossing_configuration,
|
||||
);
|
||||
|
||||
value
|
||||
.local_data
|
||||
.iter()
|
||||
.fold(config, |config, loc| match loc.as_str() {
|
||||
"material_grade" => config.by_material_grade(true),
|
||||
"coating_type" => config.by_coating_type(true),
|
||||
"design_factor" => config.by_design_factor(true),
|
||||
"high_consequence_area" => config.by_high_consequence_area(true),
|
||||
"unusual_sensitive_area" => config.by_unusual_sensitive_area(true),
|
||||
"joint_type" => config.by_joint_type(true),
|
||||
"soil_type" => config.by_soil_type(true),
|
||||
"soil_ph" => config.by_soil_ph(true),
|
||||
_ => config,
|
||||
})
|
||||
.with_manual_cutlines(value.manual_cutlines.to_cutlines())
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user