|
| 1 | +use chrono::{Duration, NaiveDate, Utc}; |
| 2 | +use log::warn; |
| 3 | +use serde_json::{Map, Value}; |
| 4 | + |
| 5 | +use crate::indicators::{set_meta, set_result, sum, Calculate, Indicators, Settings}; |
| 6 | + |
| 7 | +#[derive(Default)] |
| 8 | +pub struct R055 { |
| 9 | + threshold: Option<f64>, // resolved in finalize() |
| 10 | + start_date: NaiveDate, |
| 11 | + end_date: NaiveDate, |
| 12 | + currency: Option<String>, |
| 13 | +} |
| 14 | + |
| 15 | +impl Calculate for R055 { |
| 16 | + fn new(settings: &mut Settings) -> Self { |
| 17 | + let setting = std::mem::take(&mut settings.R055).unwrap_or_default(); |
| 18 | + let today = Utc::now().date_naive(); |
| 19 | + |
| 20 | + Self { |
| 21 | + threshold: setting.threshold, |
| 22 | + start_date: setting.start_date.unwrap_or(today - Duration::days(365)), |
| 23 | + end_date: setting.end_date.unwrap_or(today), |
| 24 | + currency: settings.currency.clone(), |
| 25 | + } |
| 26 | + } |
| 27 | + |
| 28 | + fn fold(&self, item: &mut Indicators, release: &Map<String, Value>, ocid: &str) { |
| 29 | + if let Some(Value::Object(tender)) = release.get("tender") |
| 30 | + && let Some(Value::Object(tender_period)) = tender.get("tenderPeriod") |
| 31 | + && let Some(Value::String(date)) = tender_period.get("startDate") |
| 32 | + && let Some(Value::String(method)) = tender.get("procurementMethod") |
| 33 | + && method == "open" |
| 34 | + && let Ok(date) = NaiveDate::parse_from_str(date, "%Y-%m-%dT%H:%M:%S%z") |
| 35 | + && date >= self.start_date |
| 36 | + && date <= self.end_date |
| 37 | + && let Some(Value::Object(value)) = tender.get("value") |
| 38 | + && let Some(Value::Number(amount)) = value.get("amount") |
| 39 | + && let Some(Value::String(currency)) = value.get("currency") |
| 40 | + && let Some(amount) = amount.as_f64() |
| 41 | + { |
| 42 | + if currency |
| 43 | + == item |
| 44 | + .currency |
| 45 | + .get_or_insert_with(|| self.currency.as_ref().map_or_else(|| currency.clone(), Clone::clone)) |
| 46 | + { |
| 47 | + item.r055_open_tender_amount.insert(ocid.to_owned(), amount); |
| 48 | + } else { |
| 49 | + warn!("{} is not {:?}, skipping.", currency, item.currency); |
| 50 | + } |
| 51 | + } |
| 52 | + |
| 53 | + if let Some(Value::Array(awards)) = release.get("awards") { |
| 54 | + for award in awards { |
| 55 | + if let Some(Value::String(status)) = award.get("status") |
| 56 | + && let Some(Value::Array(suppliers)) = award.get("suppliers") |
| 57 | + && suppliers.len() == 1 |
| 58 | + && let Some(Value::String(supplier_id)) = suppliers[0].get("id") |
| 59 | + && status == "active" |
| 60 | + && let Some(Value::String(date)) = award.get("date") |
| 61 | + && let Ok(date) = NaiveDate::parse_from_str(date, "%Y-%m-%dT%H:%M:%S%z") |
| 62 | + && date >= self.start_date |
| 63 | + && date <= self.end_date |
| 64 | + && let Some(Value::Object(value)) = award.get("value") |
| 65 | + && let Some(Value::Number(amount)) = value.get("amount") |
| 66 | + && let Some(Value::String(currency)) = value.get("currency") |
| 67 | + && let Some(amount) = amount.as_f64() |
| 68 | + { |
| 69 | + if currency |
| 70 | + == item.currency.get_or_insert_with(|| { |
| 71 | + self.currency.as_ref().map_or_else(|| currency.clone(), Clone::clone) |
| 72 | + }) |
| 73 | + { |
| 74 | + if let Some(Value::Object(buyer)) = release.get("buyer") |
| 75 | + && let Some(Value::String(id)) = buyer.get("id") |
| 76 | + { |
| 77 | + item.r055_direct_awarded_amount_supplier_buyer |
| 78 | + .insert((id.clone(), supplier_id.clone()), amount); |
| 79 | + } |
| 80 | + if let Some(Value::Object(tender)) = release.get("tender") |
| 81 | + && let Some(Value::Object(procuring_entity)) = tender.get("procuringEntity") |
| 82 | + && let Some(Value::String(id)) = procuring_entity.get("id") |
| 83 | + { |
| 84 | + item.r055_direct_awarded_amount_supplier_procuring_entity |
| 85 | + .insert((id.clone(), supplier_id.clone()), amount); |
| 86 | + } |
| 87 | + } else { |
| 88 | + warn!("{} is not {:?}, skipping.", currency, item.currency); |
| 89 | + } |
| 90 | + } |
| 91 | + } |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + fn reduce(&self, item: &mut Indicators, other: &mut Indicators) { |
| 96 | + let mut min = 0.0; |
| 97 | + for (key, value) in std::mem::take(&mut other.r055_open_tender_amount) { |
| 98 | + if min <= value { |
| 99 | + item.r055_open_tender_amount.insert(key, value); |
| 100 | + min = value; |
| 101 | + } |
| 102 | + } |
| 103 | + sum!(item, other, r055_direct_awarded_amount_supplier_buyer); |
| 104 | + sum!(item, other, r055_direct_awarded_amount_supplier_procuring_entity); |
| 105 | + } |
| 106 | + |
| 107 | + fn finalize(&self, item: &mut Indicators) { |
| 108 | + let min_amount = self.threshold.map_or_else( |
| 109 | + || { |
| 110 | + std::mem::take(&mut item.r055_open_tender_amount) |
| 111 | + .values() |
| 112 | + .copied() |
| 113 | + .last() |
| 114 | + .unwrap_or(0.0) |
| 115 | + }, |
| 116 | + |v| v, |
| 117 | + ); |
| 118 | + set_meta!(item, R055, "lower_open_amount", min_amount); |
| 119 | + |
| 120 | + for (id, amount) in &item.r055_direct_awarded_amount_supplier_buyer { |
| 121 | + if *amount >= min_amount { |
| 122 | + set_result!(item, Buyer, id.0, R055, *amount); |
| 123 | + set_result!(item, Tenderer, id.1, R055, *amount); |
| 124 | + } |
| 125 | + } |
| 126 | + for (id, amount) in &item.r055_direct_awarded_amount_supplier_procuring_entity { |
| 127 | + if *amount >= min_amount { |
| 128 | + set_result!(item, ProcuringEntity, id.0, R055, *amount); |
| 129 | + set_result!(item, Tenderer, id.1, R055, *amount); |
| 130 | + } |
| 131 | + } |
| 132 | + } |
| 133 | +} |
0 commit comments