add deep diff pd

This commit is contained in:
Pakin 2025-09-05 08:56:21 +07:00
parent 64a7dae017
commit 6ccc6cb779
5 changed files with 67 additions and 53 deletions

2
Cargo.lock generated
View file

@ -143,7 +143,7 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "libtbr"
version = "0.1.0"
version = "0.1.1"
dependencies = [
"chrono",
"log",

View file

@ -1,6 +1,6 @@
[package]
name = "libtbr"
version = "0.1.0"
version = "0.1.1"
edition = "2024"
[dependencies]
@ -8,4 +8,4 @@ chrono = "0.4.41"
log = "0.4.27"
rayon = "1.10.0"
serde = { version = "1.0.219", features = ["derive", "serde_derive"] }
serde_json = "1.0.140"
serde_json = "1.0.140"

View file

@ -25,55 +25,6 @@ let recipe_dir = cfg.get("RECIPE_DIR").unwrap();
---
** Helper functions **
This will be included in the next version. So the following functions may just have to implement by yourself for now.
```rust
use std::collections::HashMap;
use std::fs::File;
use std::io::{self, Error, Read};
/// Get valid country names
fn valid_country_name() -> Vec<&'static str> {
vec!["mys", "sgp", "aus", "tha", "hkg", "dubai", "uae"]
}
/// Get latest versions of recipes
fn grep_latest_versions(dir_path: &str) -> Result<HashMap<String, usize>, io::Error> {
let mut vs = HashMap::new();
// open dir
let entries = std::fs::read_dir(dir_path)?
.map(|res| res.map(|e| e.path()))
.collect::<Result<Vec<_>, io::Error>>()?;
for e in entries {
let path = e.clone().to_str().unwrap().to_string();
let mut cl_path = path.clone();
let path_split = path.split("/").collect::<Vec<&str>>();
let dir_name = path_split[path_split.len() - 1];
if valid_country_name().contains(&dir_name) {
cl_path.push_str("/version");
// read filename
let mut file = File::open(cl_path)?;
let mut data = String::new();
file.read_to_string(&mut data).unwrap();
vs.insert(dir_name.to_string(), data.parse::<usize>().unwrap());
}
// expect dir with country
}
Ok(vs)
}
```
### Get recipe from specific country (latest)
```rust
...

View file

@ -48,6 +48,7 @@ macro_rules! update_each_field {
/// about its structure.
/// * `MaterialCode`: MaterialCode is a vector (array) that contains instances of the MaterialCode
/// struct.
#[allow(non_snake_case)]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Recipe {
pub Timestamp: String,
@ -91,6 +92,8 @@ impl Recipe {
}
}
/// Get undefined fields from the recipe. This may included newer fields.
///
pub fn get_additional_fields(&self) -> Option<Value> {
if self.extra.is_empty() {
return None;
@ -115,12 +118,14 @@ impl Recipe {
.find(|&r| r.productCode == product_code)
}
/// Similar to `search_pd` but ignoring exacted matching.
pub fn search_pd_by_no_country_code(&self, product_code: String) -> Option<&Recipe01> {
self.Recipe01
.iter()
.find(|&r| r.productCode.contains(&product_code))
}
/// Mulitple product code searching with parallel and no exact matching.
pub fn search_multi_in_parallel(&self, pds: Vec<String>) -> Vec<Option<Recipe01>> {
let mut found = Vec::new();
@ -136,6 +141,7 @@ impl Recipe {
found
}
/// Get position of product code in the recipe's `Recipe01`
pub fn get_pd_index(&self, product_code: String) -> usize {
self.Recipe01
.iter()
@ -143,26 +149,32 @@ impl Recipe {
.unwrap()
}
/// Search expected material setting if existed
pub fn search_material_settings(&self, material_code: String) -> Option<&MaterialSetting> {
self.MaterialSetting
.iter()
.find(|r| r.id.to_string() == material_code)
}
/// Search expected topping list if existed
pub fn search_topping_list(&self, id: String) -> Option<&ToppingList> {
self.Topping.ToppingList.iter().find(|&r| r.id == id)
}
/// Search expected topping group if existed
pub fn search_topping_group(&self, id: String) -> Option<&ToppingGroup> {
self.Topping.ToppingGroup.iter().find(|&r| r.groupID == id)
}
/// Search expected material code if existed
pub fn search_material_code(&self, material_code: String) -> Option<&MaterialCode> {
self.MaterialCode
.iter()
.find(|&r| r.materialID == material_code)
}
/// Check if expected product code is different in both recipes.
/// This may return `false` if either one did not have this product code.
pub fn diff_pd_between_recipes(&self, another_recipe: &Recipe, product_code: String) -> bool {
let menu1 = Self::search_pd(self, product_code.clone());
let menu2 = Self::search_pd(another_recipe, product_code.clone());
@ -177,6 +189,7 @@ impl Recipe {
menu1 == menu2
}
/// Similar to `diff_pd_between_recipes` but will not check exacted matching.
pub fn diff_pd_between_recipes_ignore_country(
&self,
another_recipe: &Recipe,
@ -195,6 +208,35 @@ impl Recipe {
menu1 == menu2
}
pub fn deep_diff_pd_between_recipes(
&self,
another_recipe: &Recipe,
product_code: String,
) -> Vec<(String, bool)> {
let mut result = Vec::new();
let self_recipe = Self::search_pd_by_no_country_code(self, product_code.clone());
let another_recipe =
Self::search_pd_by_no_country_code(another_recipe, product_code.clone());
if self_recipe.is_some() && another_recipe.is_some() {
let sr = self_recipe.unwrap();
let ar = another_recipe.unwrap();
let diff_field_list = sr.compare(ar);
for key in sr.to_map().keys() {
if diff_field_list.contains(key) {
result.push((key.to_string(), true));
} else {
result.push((key.to_string(), false));
}
}
}
result
}
pub fn diff_material_setting_between_recipes(
&self,
another_recipe: &Recipe,
@ -522,6 +564,7 @@ impl Recipe {
pub fn add_prefix_country_code(&mut self) {}
}
#[allow(non_snake_case)]
fn StrShowTextErrorDefault() -> Option<Vec<Value>> {
Some(vec![
Value::String("เต่าบินเกิดเหตุขัดข้อง".into()),
@ -569,6 +612,7 @@ impl PartialRecipe {
/// temperature setting for a machine.
/// * `temperatureMin`: The `temperatureMin` property is of type `Value`. It represents the minimum
/// temperature setting for the machine.
#[allow(non_snake_case)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct MachineSetting {
pub Comment: Option<Vec<String>>,
@ -659,26 +703,32 @@ impl CommonRecipeTrait for MachineSetting {
}
}
#[allow(non_snake_case)]
fn BlankString() -> Option<String> {
Some("".to_string())
}
#[allow(non_snake_case)]
fn BlankBool() -> Option<bool> {
Some(false)
}
#[allow(non_snake_case)]
fn BlankVecString() -> Option<Vec<String>> {
Some(vec![].into())
}
#[allow(non_snake_case)]
fn BlankVecRecipe() -> Option<Vec<Recipe01>> {
Some(vec![].into())
}
#[allow(non_snake_case)]
fn BlankVecMenuTopping() -> Option<Vec<MenuToppingList>> {
Some(vec![].into())
}
#[allow(non_snake_case)]
fn BlankOtherStrShowTextError() -> Option<Vec<String>> {
Some(vec![
"".to_string(),
@ -692,14 +742,17 @@ fn BlankOtherStrShowTextError() -> Option<Vec<String>> {
])
}
#[allow(non_snake_case)]
fn BlankValueString() -> Option<Value> {
Some(Value::String("".into()))
}
#[allow(non_snake_case, dead_code)]
fn BlankValueArray() -> Option<Value> {
Some(Value::Array(vec![].into()))
}
#[allow(non_snake_case, dead_code)]
fn BlankValueBool() -> Option<Value> {
Some(Value::Bool(false.into()))
}
@ -754,6 +807,7 @@ fn BlankValueBool() -> Option<Value> {
/// * `useGram`: A boolean value indicating whether the recipe uses grams for measurement or not.
/// * `weight_float`: The `weight_float` property is of type `Value` and represents the weight of the
/// recipe as a floating-point number.
#[allow(non_snake_case)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct Recipe01 {
#[serde(default = "BlankString")]
@ -1371,6 +1425,7 @@ impl Recipe01 {
/// water in the recipe.
/// * `waterYield`: The `waterYield` property represents the amount of water produced or obtained from
/// the recipe.
#[allow(non_snake_case)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct RecipeList {
pub MixOrder: Value,
@ -1465,6 +1520,7 @@ impl RecipeList {
/// maximum number of retries for a payment.
/// * `RawMaterialUnit`: The `RawMaterialUnit` property is an optional string that represents the unit
/// of measurement for the raw material.
#[allow(non_snake_case)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct MaterialSetting {
pub AlarmIDWhenOffline: Value,
@ -1576,6 +1632,7 @@ impl MachineSetting {
}
}
#[allow(non_snake_case)]
fn BlankListGroupID() -> Option<Vec<Value>> {
Some(vec![
Value::String("0".into()),
@ -1585,6 +1642,7 @@ fn BlankListGroupID() -> Option<Vec<Value>> {
])
}
#[allow(non_snake_case)]
fn BlankGroupID() -> Option<Value> {
Some(Value::String("0".into()))
}
@ -1600,6 +1658,7 @@ fn BlankGroupID() -> Option<Value> {
/// which the menu topping list belongs.
/// * `isUse`: The `isUse` property is a boolean value that indicates whether the menu topping list is
/// being used or not.
#[allow(non_snake_case)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct MenuToppingList {
#[serde(default = "BlankListGroupID")]
@ -1620,6 +1679,7 @@ pub struct MenuToppingList {
/// struct. Each ToppingGroup struct represents a group of toppings.
/// * `ToppingList`: The `ToppingList` property is a vector (dynamic array) that contains elements of
/// type `ToppingList`.
#[allow(non_snake_case)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct Topping {
pub ToppingGroup: Vec<ToppingGroup>,
@ -1643,6 +1703,7 @@ pub struct Topping {
/// * `name`: The `name` property is a string that represents the name of the topping group.
/// * `otherName`: The `otherName` property is a string that represents an alternative name for the
/// `ToppingGroup`. It can be used to provide additional information or a different name for the group.
#[allow(non_snake_case)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct ToppingGroup {
pub groupID: Value,
@ -1703,6 +1764,7 @@ impl ToppingGroup {
/// * `useGram`: A boolean value indicating whether the topping uses grams for measurement or not.
/// * `weight_float`: The `weight_float` property is of type `Value` and represents the weight of the
/// topping in float format.
#[allow(non_snake_case)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct ToppingList {
pub ExtendID: Value,
@ -1754,6 +1816,7 @@ impl ToppingList {
/// * `materialCode`: The `materialCode` property is an optional field that represents the code
/// associated with a material. It is of type `Option<String>`, which means it can either be
/// `Some(String)` if a value is present, or `None` if no value is provided.
#[allow(non_snake_case)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct MaterialCode {
#[serde(default = "BlankString")]

View file

@ -5,7 +5,7 @@ use std::collections::HashMap;
use crate::models::recipe::*;
use crate::recipe_functions::common::*;
/// Version 2 of `models::Recipe`
/// Version 2 of `models::Recipe`, WIP
///
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct VersionRecipe {