1use super::demand;
2use crate::models::{BidderId, Bound, DemandCurve, Group, ProductId, map_wrapper, uuid_wrapper};
3use serde::{Deserialize, Serialize};
4use std::hash::Hash;
5use thiserror::Error;
6use time::OffsetDateTime;
7use utoipa::ToSchema;
8
9uuid_wrapper!(AuthId);
10
11#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
15#[serde(try_from = "RawAuthorization", into = "RawAuthorization")]
16pub struct AuthData {
17 pub demand: DemandCurve,
19
20 #[schema(value_type = Option<f64>)]
22 pub min_trade: f64,
23
24 #[schema(value_type = Option<f64>)]
26 pub max_trade: f64,
27}
28
29impl AuthData {
30 pub fn new(
32 demand: DemandCurve,
33 min_trade: f64,
34 max_trade: f64,
35 ) -> Result<Self, ValidationError> {
36 if min_trade.is_nan() || max_trade.is_nan() {
37 return Err(ValidationError::NAN);
38 }
39 if min_trade > max_trade {
40 return Err(ValidationError::INFEASIBLETRADE);
41 }
42
43 Ok(Self {
44 demand,
45 min_trade,
46 max_trade,
47 })
48 }
49}
50
51#[derive(Debug, Error)]
53pub enum ValidationError {
54 #[error("NaN value encountered")]
56 NAN,
57 #[error("Invalid demand curve: {0:?}")]
59 DEMAND(demand::ValidationError),
60 #[error("Trade restriction is infeasible")]
62 INFEASIBLETRADE,
63}
64
65#[derive(Serialize, Deserialize)]
70pub struct RawAuthorization {
71 pub demand: demand::RawDemandCurve,
72 pub min_trade: Bound,
73 pub max_trade: Bound,
74}
75
76impl TryFrom<RawAuthorization> for AuthData {
77 type Error = ValidationError;
78
79 fn try_from(value: RawAuthorization) -> Result<Self, Self::Error> {
80 AuthData::new(
81 value
82 .demand
83 .try_into()
84 .map_err(|err| ValidationError::DEMAND(err))?,
85 value.min_trade.or_neg_inf(),
86 value.max_trade.or_pos_inf(),
87 )
88 }
89}
90
91impl From<AuthData> for RawAuthorization {
92 fn from(value: AuthData) -> Self {
93 Self {
94 demand: value.demand.into(),
95 min_trade: value.min_trade.into(),
96 max_trade: value.max_trade.into(),
97 }
98 }
99}
100
101#[derive(Serialize, Deserialize, PartialEq, ToSchema, Debug)]
106pub struct AuthHistoryRecord {
107 pub data: Option<AuthData>,
109 #[serde(with = "time::serde::rfc3339")]
111 pub version: OffsetDateTime,
112}
113
114#[derive(Serialize, Deserialize, PartialEq, ToSchema, Debug)]
122pub struct AuthRecord {
123 pub bidder_id: BidderId,
125
126 pub auth_id: AuthId,
128
129 #[serde(default, skip_serializing_if = "Option::is_none")]
131 pub portfolio: Option<Portfolio>,
132
133 pub data: Option<AuthData>,
135
136 #[serde(with = "time::serde::rfc3339")]
138 pub version: OffsetDateTime,
139
140 #[serde(default, skip_serializing_if = "Option::is_none")]
142 pub trade: Option<f64>,
143}
144
145map_wrapper!(Portfolio, ProductId, f64);
146
147impl AuthRecord {
148 pub fn into_solver(
154 self,
155 scale: f64,
156 ) -> Option<(
157 Portfolio,
158 fts_solver::DemandCurve<AuthId, Group, Vec<fts_solver::Point>>,
159 )> {
160 let trade = self.trade.unwrap_or_default();
161 if let Some(data) = self.data {
162 let (min_rate, max_rate) = data.demand.domain();
163 let min_trade = (data.min_trade - trade).max(min_rate * scale).min(0.0);
164 let max_trade = (data.max_trade - trade).min(max_rate * scale).max(0.0);
165
166 Some((
167 self.portfolio.unwrap_or_default(),
168 fts_solver::DemandCurve {
169 domain: (min_trade, max_trade),
170 group: std::iter::once((self.auth_id, 1.0)).collect(),
171 points: data.demand.as_solver(scale),
172 },
173 ))
174 } else {
175 None
176 }
177 }
178}