Skip to content

Commit

Permalink
Different methods of center for average auctions/bins (#190)
Browse files Browse the repository at this point in the history
* Different methods of center for average auctions/bins

* Add documentation
  • Loading branch information
kr45732 authored Jul 13, 2023
1 parent c111597 commit a9e8285
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 71 deletions.
6 changes: 6 additions & 0 deletions docs/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,22 @@
- `key` - key to access the API
- `time` - unix timestamp, in milliseconds, for how far back the average auction prices should be calculated. The most is 5 days back
- `step` - how the auction sales should be averaged. For example, 1 would average it by minute, 60 would average it by hour, 1440 would average it by day, and so on
- `center` - measure of center used to determine item prices. Supported methods are 'mean', 'mean_old', 'median', 'modified_median'
- `percent` - percent of median (above and below) to average when using 'modified_median' center

## Average Bins
- `key` - key to access the API
- `time` - unix timestamp, in milliseconds, for how far back the average bin prices should be calculated. The most is 5 days back
- `step` - how the bin sales should be averaged. For example, 1 would average it by minute, 60 would average it by hour, 1440 would average it by day, and so on
- `center` - measure of center used to determine item prices. Supported methods are 'mean', 'mean_old', 'median', 'modified_median'
- `percent` - percent of median (above and below) to average when using 'modified_median' center

## Average Auctions & Bins
- `key` - key to access the API
- `time` - unix timestamp, in milliseconds, for how far back the average auction & bin prices should be calculated. The most is 5 days back
- `step` - how the auction & bin sales should be averaged. For example, 1 would average it by minute, 60 would average it by hour, 1440 would average it by day, and so on
- `center` - measure of center used to determine item prices. Supported methods are 'mean', 'mean_old', 'median', 'modified_median'
- `percent` - percent of median (above and below) to average when using 'modified_median' center

## Query Items
- `key` - key to access the API
Expand Down
6 changes: 0 additions & 6 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ pub struct Config {
pub disable_updating: bool,
// Shh, don't tell anyone!
pub super_secret_config_option: bool,
pub old_method: bool,
}

fn get_env(name: &str) -> String {
Expand All @@ -85,10 +84,6 @@ impl Config {
.unwrap_or_else(|_| String::from("false"))
.parse()
.unwrap_or(false);
let old_method = env::var("OLD_METHOD")
.unwrap_or_else(|_| String::from("false"))
.parse()
.unwrap_or(false);
let postgres_url = get_env("POSTGRES_URL");
let features = get_env("FEATURES")
.replace(',', "+")
Expand All @@ -113,7 +108,6 @@ impl Config {
debug,
disable_updating,
super_secret_config_option,
old_method,
}
}

Expand Down
157 changes: 93 additions & 64 deletions src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,70 +59,84 @@ pub async fn start_server(config: Arc<Config>) {
async fn handle_response(config: Arc<Config>, req: Request<Body>) -> hyper::Result<Response<Body>> {
info!("{} {}", req.method(), req.uri().path());

if let (&Method::GET, "/") = (req.method(), req.uri().path()) {
base(config).await
} else if let (&Method::GET, "/query") = (req.method(), req.uri().path()) {
if config.is_enabled(Feature::Query) {
query(config, req).await
} else {
bad_request("Query feature is not enabled")
if req.method() != Method::GET {
return not_implemented();
}

match req.uri().path() {
"/" => base(config).await,
"/query" => {
if config.is_enabled(Feature::Query) {
query(config, req).await
} else {
bad_request("Query feature is not enabled")
}
}
} else if let (&Method::GET, "/query_items") = (req.method(), req.uri().path()) {
if config.is_enabled(Feature::Query) {
query_items(config, req).await
} else {
bad_request("Query feature is not enabled")
"/query_items" => {
if config.is_enabled(Feature::Query) {
query_items(config, req).await
} else {
bad_request("Query feature is not enabled")
}
}
} else if let (&Method::GET, "/pets") = (req.method(), req.uri().path()) {
if config.is_enabled(Feature::Pets) {
pets(config, req).await
} else {
bad_request("Pets feature is not enabled")
"/pets" => {
if config.is_enabled(Feature::Pets) {
pets(config, req).await
} else {
bad_request("Pets feature is not enabled")
}
}
} else if let (&Method::GET, "/lowestbin") = (req.method(), req.uri().path()) {
if config.is_enabled(Feature::Lowestbin) {
lowestbin(config, req).await
} else {
bad_request("Lowest bins feature is not enabled")
"/lowestbin" => {
if config.is_enabled(Feature::Lowestbin) {
lowestbin(config, req).await
} else {
bad_request("Lowest bins feature is not enabled")
}
}
} else if let (&Method::GET, "/underbin") = (req.method(), req.uri().path()) {
if config.is_enabled(Feature::Underbin) {
underbin(config, req).await
} else {
bad_request("Under bins feature is not enabled")
"/underbin" => {
if config.is_enabled(Feature::Underbin) {
underbin(config, req).await
} else {
bad_request("Under bins feature is not enabled")
}
}
} else if let (&Method::GET, "/average_auction") = (req.method(), req.uri().path()) {
if config.is_enabled(Feature::AverageAuction) {
averages(config, req, vec!["average"]).await
} else {
bad_request("Average auction feature is not enabled")
"/average_auction" => {
if config.is_enabled(Feature::AverageAuction) {
averages(config, req, vec!["average"]).await
} else {
bad_request("Average auction feature is not enabled")
}
}
} else if let (&Method::GET, "/average_bin") = (req.method(), req.uri().path()) {
if config.is_enabled(Feature::AverageBin) {
averages(config, req, vec!["average_bin"]).await
} else {
bad_request("Average bin feature is not enabled")
"/average_bin" => {
if config.is_enabled(Feature::AverageBin) {
averages(config, req, vec!["average_bin"]).await
} else {
bad_request("Average bin feature is not enabled")
}
}
} else if let (&Method::GET, "/average") = (req.method(), req.uri().path()) {
if config.is_enabled(Feature::AverageAuction) && config.is_enabled(Feature::AverageBin) {
averages(config, req, vec!["average", "average_bin"]).await
} else {
bad_request("Both average auction and average bin feature are not enabled")
"/average" => {
if config.is_enabled(Feature::AverageAuction) && config.is_enabled(Feature::AverageBin)
{
averages(config, req, vec!["average", "average_bin"]).await
} else {
bad_request("Both average auction and average bin feature are not enabled")
}
}
} else if let (&Method::GET, "/debug") = (req.method(), req.uri().path()) {
if config.debug {
debug_log(config, req).await
} else {
bad_request("Debug is not enabled")
"/debug" => {
if config.debug {
debug_log(config, req).await
} else {
bad_request("Debug is not enabled")
}
}
} else if let (&Method::GET, "/info") = (req.method(), req.uri().path()) {
if config.debug {
info_log(config, req).await
} else {
bad_request("Debug is not enabled")
"/info" => {
if config.debug {
info_log(config, req).await
} else {
bad_request("Debug is not enabled")
}
}
} else {
not_found()
_ => not_found(),
}
}

Expand Down Expand Up @@ -275,7 +289,9 @@ async fn averages(
) -> hyper::Result<Response<Body>> {
let mut key = String::new();
let mut time = 0;
let mut step: usize = 1;
let mut step = 1;
let mut center = String::from("mean");
let mut percent = 0.25;

// Reads the query parameters from the request and stores them in the corresponding variable
for query_pair in Url::parse(&format!(
Expand All @@ -296,12 +312,15 @@ async fn averages(
Err(e) => return bad_request(&format!("Error parsing step parameter: {}", e)),
},
"key" => key = query_pair.1.to_string(),
"center" => center = query_pair.1.to_string(),
"percent" => match query_pair.1.to_string().parse::<f32>() {
Ok(percent_float) => percent = percent_float,
Err(e) => return bad_request(&format!("Error parsing percent parameter: {}", e)),
},
_ => {}
}
}

let old_method = config.old_method;

// The API key in request doesn't match
if !valid_api_key(config, key, false) {
return unauthorized();
Expand All @@ -311,6 +330,10 @@ async fn averages(
return bad_request("The time parameter cannot be negative");
}

if percent <= 0.0 || percent >= 1.0 {
return bad_request("The percent parameter must be between 0 and 1");
}

// Map each item id to its prices and sales
let avg_map: DashMap<String, AvgVec> = DashMap::new();

Expand Down Expand Up @@ -372,7 +395,11 @@ async fn averages(
avg_map_final.insert(
ele.0,
PartialAvgAh {
price: ele.1.get_average(old_method),
price: match center.as_str() {
"median" => ele.1.get_median(),
"modified_median" => ele.1.get_modified_median(percent),
_ => ele.1.get_average(center == "mean_old"),
},
sales: sales / (count as f32),
},
);
Expand Down Expand Up @@ -746,7 +773,7 @@ async fn query(config: Arc<Config>, req: Request<Body>) -> hyper::Result<Respons
sort_by_query,
);

let enchants_split;
let enchants_split: Vec<String>;
if !enchants.is_empty() {
enchants_split = enchants.split(',').map(|s| s.trim().to_string()).collect();
param_count = array_contains(
Expand All @@ -758,7 +785,7 @@ async fn query(config: Arc<Config>, req: Request<Body>) -> hyper::Result<Respons
sort_by_query,
);
}
let necron_scrolls_split;
let necron_scrolls_split: Vec<String>;
if !necron_scrolls.is_empty() {
necron_scrolls_split = necron_scrolls
.split(',')
Expand All @@ -773,7 +800,7 @@ async fn query(config: Arc<Config>, req: Request<Body>) -> hyper::Result<Respons
sort_by_query,
);
}
let gemstones_split;
let gemstones_split: Vec<String>;
if !gemstones.is_empty() {
gemstones_split = gemstones.split(',').map(|s| s.trim().to_string()).collect();
param_count = array_contains(
Expand Down Expand Up @@ -1111,13 +1138,11 @@ fn array_contains<'a>(
sql: &mut String,
param_vec: &mut Vec<&'a (dyn ToSql + Sync)>,
param_name: &str,
param_value: &'a Vec<String>,
param_value: &'a [String],
param_count: i32,
sort_by_query: bool,
) -> i32 {
if param_count != 1 {
println!("{:?}", param_value);

sql.push_str(if sort_by_query { " +" } else { " AND" });
}

Expand Down Expand Up @@ -1177,3 +1202,7 @@ fn unauthorized() -> hyper::Result<Response<Body>> {
fn not_found() -> hyper::Result<Response<Body>> {
http_err(StatusCode::NOT_FOUND, "Not found")
}

fn not_implemented() -> hyper::Result<Response<Body>> {
http_err(StatusCode::NOT_IMPLEMENTED, "Unsupported method")
}
48 changes: 47 additions & 1 deletion src/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

use crate::utils::is_false;
use crate::utils::{is_false, median};
use dashmap::DashMap;
use postgres_types::{FromSql, ToSql};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -251,6 +251,52 @@ impl AvgVec {
auctions_average.min(bins_average)
}
}

pub fn get_median(&self) -> f32 {
let mut combined_vec: Vec<f32> = Vec::new();

for ele in &self.auctions {
combined_vec.push(ele.price);
}

for ele in &self.bins {
combined_vec.push(ele.price);
}

median(&combined_vec)
}

pub fn get_modified_median(&self, percent: f32) -> f32 {
let mut combined_vec: Vec<f32> = Vec::new();

for ele in &self.auctions {
combined_vec.push(ele.price);
}

for ele in &self.bins {
combined_vec.push(ele.price);
}

let median = median(&combined_vec);
let lower_bound = median * (1.0 - percent);
let upper_bound = median * (1.0 + percent);

let mut sum = 0.0;
let mut count = 0;

for ele in combined_vec {
if lower_bound <= ele && ele <= upper_bound {
sum += ele;
count += 1
}
}

if count == 0 {
median
} else {
sum / count as f32
}
}
}

/* Pets API */
Expand Down
Loading

0 comments on commit a9e8285

Please sign in to comment.