diff --git a/Cargo.lock b/Cargo.lock index f9a06dbb..17ea417a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1571,7 +1571,7 @@ dependencies = [ [[package]] name = "datamanager" -version = "0.1.0" +version = "0.0.1" dependencies = [ "aws-config", "aws-credential-types", diff --git a/applications/datamanager/Dockerfile b/applications/datamanager/Dockerfile index fbb35a2d..cc756320 100644 --- a/applications/datamanager/Dockerfile +++ b/applications/datamanager/Dockerfile @@ -21,9 +21,9 @@ RUN rustup toolchain install stable RUN rustup default stable -ENV LIBRARY_PATH=/usr/local/lib:$LIBRARY_PATH -ENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH -ENV PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH +ENV LIBRARY_PATH=/usr/local/lib +ENV LD_LIBRARY_PATH=/usr/local/lib +ENV PKG_CONFIG_PATH=/usr/local/lib/pkgconfig WORKDIR /app @@ -51,8 +51,8 @@ COPY applications/datamanager/src/ applications/datamanager/src/ ENV DUCKDB_LIB_DIR=/usr/local/lib ENV DUCKDB_INCLUDE_DIR=/usr/local/include -ENV LIBRARY_PATH=/usr/local/lib${LIBRARY_PATH:+:$LIBRARY_PATH} -ENV LD_LIBRARY_PATH=/usr/local/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH} +ENV LIBRARY_PATH=/usr/local/lib +ENV LD_LIBRARY_PATH=/usr/local/lib RUN --mount=type=cache,target=/usr/local/cargo/registry \ --mount=type=cache,target=/app/target \ diff --git a/applications/datamanager/src/equity_bars.rs b/applications/datamanager/src/equity_bars.rs index fec01d86..215a9e78 100644 --- a/applications/datamanager/src/equity_bars.rs +++ b/applications/datamanager/src/equity_bars.rs @@ -6,7 +6,7 @@ use axum::{ http::{header, StatusCode}, response::{IntoResponse, Response}, }; -use chrono::{DateTime, Utc}; +use chrono::{DateTime, Datelike, Utc, Weekday}; use polars::prelude::*; use serde::Deserialize; use tracing::{debug, info, warn}; @@ -123,6 +123,16 @@ pub async fn sync( ) -> impl IntoResponse { info!("Sync date: {}", payload.date); + let weekday = payload.date.weekday(); + if weekday == Weekday::Sat || weekday == Weekday::Sun { + info!("Skipping weekend date: {}", payload.date.format("%Y-%m-%d")); + return ( + StatusCode::OK, + "Skipping weekend, no trading data available", + ) + .into_response(); + } + let massive_api_key = state.massive.key.clone(); let date = payload.date.format("%Y-%m-%d").to_string(); @@ -149,7 +159,10 @@ pub async fn sync( resp } Err(err) => { - warn!("Failed to send request to Massive API: {}", err); + warn!( + "Failed to send request to Massive API: {}", + err.without_url() + ); return ( StatusCode::INTERNAL_SERVER_ERROR, "Failed to send API request", diff --git a/applications/datamanager/src/equity_details.rs b/applications/datamanager/src/equity_details.rs index a8df2942..2cf0e7c4 100644 --- a/applications/datamanager/src/equity_details.rs +++ b/applications/datamanager/src/equity_details.rs @@ -1,61 +1,17 @@ use crate::state::State; -use crate::storage::{read_equity_details_dataframe_from_s3, write_equity_details_dataframe_to_s3}; +use crate::storage::read_equity_details_csv_from_s3; use axum::{ extract::State as AxumState, http::{header, StatusCode}, response::IntoResponse, }; -use polars::prelude::*; -use serde::Deserialize; -use tracing::{debug, info, warn}; - -const EQUITY_TYPES: &[&str] = &["CS", "ADRC", "ADRP", "ADRS"]; - -#[derive(Deserialize, Debug)] -struct TickerResult { - ticker: Option, - #[serde(rename = "type")] - ticker_type: Option, - sector: Option, - industry: Option, -} - -#[derive(Deserialize, Debug)] -struct TickerResponse { - results: Option>, - next_url: Option, -} +use tracing::{info, warn}; pub async fn get(AxumState(state): AxumState) -> impl IntoResponse { info!("Fetching equity details CSV from S3"); - match read_equity_details_dataframe_from_s3(&state).await { - Ok(dataframe) => { - let mut buffer = Vec::new(); - let mut writer = CsvWriter::new(&mut buffer); - match writer.finish(&mut dataframe.clone()) { - Ok(_) => {} - Err(err) => { - info!("Failed to write CSV: {}", err); - return ( - StatusCode::INTERNAL_SERVER_ERROR, - format!("Failed to write CSV: {}", err), - ) - .into_response(); - } - } - - let csv_content = match String::from_utf8(buffer) { - Ok(content) => content, - Err(err) => { - info!("Failed to convert CSV to UTF-8: {}", err); - return ( - StatusCode::INTERNAL_SERVER_ERROR, - format!("Failed to convert CSV to UTF-8: {}", err), - ) - .into_response(); - } - }; + match read_equity_details_csv_from_s3(&state).await { + Ok(csv_content) => { let mut response = csv_content.into_response(); response.headers_mut().insert( header::CONTENT_TYPE, @@ -65,7 +21,7 @@ pub async fn get(AxumState(state): AxumState) -> impl IntoResponse { response } Err(err) => { - info!("Failed to fetch equity details from S3: {}", err); + warn!("Failed to fetch equity details from S3: {}", err); ( StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to fetch equity details: {}", err), @@ -75,222 +31,7 @@ pub async fn get(AxumState(state): AxumState) -> impl IntoResponse { } } -pub async fn sync(AxumState(state): AxumState) -> impl IntoResponse { - info!("Syncing equity details from Massive API"); - - let massive_api_key = state.massive.key.clone(); - let base_url = format!("{}/v3/reference/tickers", state.massive.base); - - let mut all_tickers: Vec = Vec::new(); - let mut current_url = base_url; - let expected_origin = match reqwest::Url::parse(¤t_url) { - Ok(parsed) => parsed, - Err(err) => { - warn!("Failed to parse base URL: {}", err); - return ( - StatusCode::INTERNAL_SERVER_ERROR, - "Invalid base URL configuration", - ) - .into_response(); - } - }; - let mut is_first_page = true; - let mut page_count: usize = 0; - const MAX_PAGES: usize = 1000; - - loop { - if page_count >= MAX_PAGES { - warn!( - "Reached maximum page limit of {}, stopping pagination", - MAX_PAGES - ); - break; - } - page_count += 1; - debug!("Fetching ticker page: {}", page_count); - - let mut request = state - .http_client - .get(¤t_url) - .header("accept", "application/json"); - - if is_first_page { - request = request.query(&[ - ("market", "stocks"), - ("active", "true"), - ("limit", "1000"), - ("apiKey", massive_api_key.as_str()), - ]); - } else { - request = request.query(&[("apiKey", massive_api_key.as_str())]); - } - - let response = match request.send().await { - Ok(response) => { - info!( - "Received response from Massive API, status: {}", - response.status() - ); - response - } - Err(err) => { - warn!("Failed to send request to Massive API: {}", err); - return ( - StatusCode::INTERNAL_SERVER_ERROR, - "Failed to send API request", - ) - .into_response(); - } - }; - - let text_content = match response.error_for_status() { - Ok(response) => match response.text().await { - Ok(text) => { - info!("Received response body, length: {} bytes", text.len()); - text - } - Err(err) => { - warn!("Failed to read response text: {}", err); - return ( - StatusCode::INTERNAL_SERVER_ERROR, - "Failed to read API response", - ) - .into_response(); - } - }, - Err(err) => { - warn!("API request failed with error status: {}", err); - return (StatusCode::INTERNAL_SERVER_ERROR, "API request failed").into_response(); - } - }; - - let page: TickerResponse = match serde_json::from_str(&text_content) { - Ok(value) => { - debug!("JSON parsed successfully"); - value - } - Err(err) => { - warn!("Failed to parse JSON response: {}", err); - return ( - StatusCode::INTERNAL_SERVER_ERROR, - "Invalid JSON response from API", - ) - .into_response(); - } - }; - - let results = page.results.unwrap_or_default(); - info!("Fetched {} tickers on this page", results.len()); - all_tickers.extend(results); - - match page.next_url { - Some(next_url) if !next_url.is_empty() => match reqwest::Url::parse(&next_url) { - Ok(parsed) - if parsed.scheme() == expected_origin.scheme() - && parsed.host() == expected_origin.host() - && parsed.port() == expected_origin.port() => - { - current_url = next_url; - is_first_page = false; - tokio::time::sleep(tokio::time::Duration::from_millis(250)).await; - } - Ok(_) => { - warn!("Next URL origin does not match expected origin, stopping pagination"); - break; - } - Err(err) => { - warn!("Failed to parse next URL: {}", err); - break; - } - }, - _ => break, - } - } - - info!( - "Fetched {} total tickers from Massive API", - all_tickers.len() - ); - - let mut tickers: Vec = Vec::new(); - let mut sectors: Vec = Vec::new(); - let mut industries: Vec = Vec::new(); - - for result in all_tickers { - let ticker = match result.ticker { - Some(value) if !value.is_empty() => value, - _ => continue, - }; - - let ticker_type = result.ticker_type.unwrap_or_default(); - if !EQUITY_TYPES.contains(&ticker_type.as_str()) { - continue; - } - - let sector = match result.sector { - Some(value) if !value.is_empty() => value.to_uppercase(), - _ => "NOT AVAILABLE".to_string(), - }; - - let industry = match result.industry { - Some(value) if !value.is_empty() => value.to_uppercase(), - _ => "NOT AVAILABLE".to_string(), - }; - - tickers.push(ticker.to_uppercase()); - sectors.push(sector); - industries.push(industry); - } - - info!("Filtered to {} equity tickers", tickers.len()); - - if tickers.is_empty() { - return (StatusCode::OK, "No equity ticker data available").into_response(); - } - - let details_data = df! { - "ticker" => tickers, - "sector" => sectors, - "industry" => industries, - }; - - info!("Creating DataFrame from ticker data"); - match details_data { - Ok(data) => { - info!( - "Created DataFrame with {} rows and {} columns", - data.height(), - data.width() - ); - - info!("Uploading DataFrame to S3"); - match write_equity_details_dataframe_to_s3(&state, &data).await { - Ok(s3_key) => { - info!("Successfully uploaded DataFrame to S3 at key: {}", s3_key); - let response_message = format!( - "DataFrame created with {} rows and uploaded to S3: {}", - data.height(), - s3_key - ); - (StatusCode::OK, response_message).into_response() - } - Err(err) => { - warn!("Failed to upload to S3: {}", err); - ( - StatusCode::BAD_GATEWAY, - format!("DataFrame created but S3 upload failed: {}", err), - ) - .into_response() - } - } - } - Err(err) => { - warn!("Failed to create DataFrame: {}", err); - ( - StatusCode::INTERNAL_SERVER_ERROR, - "Failed to create DataFrame", - ) - .into_response() - } - } +pub async fn sync(_state: AxumState) -> impl IntoResponse { + warn!("Equity details sync is not implemented"); + (StatusCode::NOT_IMPLEMENTED, "Sync is not implemented").into_response() } diff --git a/applications/datamanager/src/storage.rs b/applications/datamanager/src/storage.rs index 5efd2133..40f0ebd5 100644 --- a/applications/datamanager/src/storage.rs +++ b/applications/datamanager/src/storage.rs @@ -13,7 +13,7 @@ use serde::Deserialize; use std::io::Cursor; use tracing::{debug, error, info, warn}; -const EQUITY_DETAILS_CATEGORIES_KEY: &str = "equity/details/categories.csv"; +const EQUITY_DETAILS_KEY: &str = "equity/details/details.csv"; pub async fn write_equity_bars_dataframe_to_s3( state: &State, @@ -45,7 +45,7 @@ pub async fn write_equity_details_dataframe_to_s3( ) -> Result { info!("Uploading equity details DataFrame to S3 as CSV"); - let key = EQUITY_DETAILS_CATEGORIES_KEY.to_string(); + let key = EQUITY_DETAILS_KEY.to_string(); let mut buffer = Vec::new(); let mut writer = CsvWriter::new(&mut buffer); @@ -754,10 +754,10 @@ fn execute_portfolio_query_without_action( Ok(portfolios) } -pub async fn read_equity_details_dataframe_from_s3(state: &State) -> Result { +pub async fn read_equity_details_csv_from_s3(state: &State) -> Result { info!("Reading equity details CSV from S3"); - let key = EQUITY_DETAILS_CATEGORIES_KEY; + let key = EQUITY_DETAILS_KEY; let response = state .s3_client @@ -783,6 +783,12 @@ pub async fn read_equity_details_dataframe_from_s3(state: &State) -> Result Result { + let csv_content = read_equity_details_csv_from_s3(state).await?; + let dataframe = create_equity_details_dataframe(csv_content)?; info!( diff --git a/applications/datamanager/tests/test_handlers.rs b/applications/datamanager/tests/test_handlers.rs index be69ff34..8a374754 100644 --- a/applications/datamanager/tests/test_handlers.rs +++ b/applications/datamanager/tests/test_handlers.rs @@ -322,7 +322,7 @@ async fn test_equity_details_get_returns_csv_content() { put_test_object( &s3, - "equity/details/categories.csv", + "equity/details/details.csv", b"ticker,sector,industry\nAAPL,Technology,Consumer Electronics\n".to_vec(), ) .await; @@ -721,136 +721,24 @@ async fn test_portfolios_get_returns_internal_server_error_when_storage_query_fa #[tokio::test(flavor = "multi_thread", worker_threads = 4)] #[serial] -async fn test_equity_details_sync_and_get_round_trip() { - let (endpoint, _s3, _env_guard) = setup_test_bucket().await; - - let mut massive_server = Server::new_async().await; - let _mock = massive_server - .mock("GET", "/v3/reference/tickers") - .match_query(Matcher::AllOf(vec![ - Matcher::UrlEncoded("market".into(), "stocks".into()), - Matcher::UrlEncoded("active".into(), "true".into()), - Matcher::UrlEncoded("limit".into(), "1000".into()), - Matcher::UrlEncoded("apiKey".into(), "test-api-key".into()), - ])) - .with_status(200) - .with_body( - r#"{ - "results": [{ - "ticker": "AAPL", - "type": "CS", - "sector": "Technology", - "industry": "Consumer Electronics" - }], - "status": "OK" - }"#, - ) - .create_async() - .await; - - let (app, _env_guard) = spawn_app(&endpoint, massive_server.url()).await; +async fn test_equity_bars_sync_returns_ok_for_weekend_date() { + // 2025-01-04 is a Saturday, 2025-01-05 is a Sunday — no API or S3 calls expected + let (app, _env_guard) = spawn_app_with_unreachable_s3("http://127.0.0.1:1".to_string()).await; let client = reqwest::Client::new(); let response = client - .post(app.url("/equity-details")) + .post(app.url("/equity-bars")) + .header(reqwest::header::CONTENT_TYPE, "application/json") + .body(r#"{"date":"2025-01-04T00:00:00Z"}"#) .send() .await .unwrap(); assert_eq!(response.status(), StatusCode::OK); - let response = client.get(app.url("/equity-details")).send().await.unwrap(); - assert_eq!(response.status(), StatusCode::OK); - - let body = response.text().await.unwrap(); - assert!(body.contains("AAPL")); - assert!(body.contains("TECHNOLOGY")); - assert!(body.contains("CONSUMER ELECTRONICS")); -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 4)] -#[serial] -async fn test_equity_details_sync_with_pagination() { - let (endpoint, _s3, _env_guard) = setup_test_bucket().await; - - let mut massive_server = Server::new_async().await; - let next_url = format!("{}/v3/reference/tickers/next", massive_server.url()); - - let first_body = format!( - r#"{{ - "results": [{{"ticker": "AAPL", "type": "CS", "sector": "Technology", "industry": "Hardware"}}], - "next_url": "{}", - "status": "OK" - }}"#, - next_url - ); - - let _mock_page1 = massive_server - .mock("GET", "/v3/reference/tickers") - .match_query(Matcher::AllOf(vec![ - Matcher::UrlEncoded("market".into(), "stocks".into()), - Matcher::UrlEncoded("active".into(), "true".into()), - Matcher::UrlEncoded("limit".into(), "1000".into()), - Matcher::UrlEncoded("apiKey".into(), "test-api-key".into()), - ])) - .with_status(200) - .with_body(first_body) - .create_async() - .await; - - let _mock_page2 = massive_server - .mock("GET", "/v3/reference/tickers/next") - .match_query(Matcher::UrlEncoded("apiKey".into(), "test-api-key".into())) - .with_status(200) - .with_body( - r#"{ - "results": [{"ticker": "MSFT", "type": "CS", "sector": "Technology", "industry": "Software"}], - "status": "OK" - }"#, - ) - .create_async() - .await; - - let (app, _env_guard) = spawn_app(&endpoint, massive_server.url()).await; - let client = reqwest::Client::new(); - let response = client - .post(app.url("/equity-details")) - .send() - .await - .unwrap(); - assert_eq!(response.status(), StatusCode::OK); - - let response = client.get(app.url("/equity-details")).send().await.unwrap(); - assert_eq!(response.status(), StatusCode::OK); - - let body = response.text().await.unwrap(); - assert!(body.contains("AAPL")); - assert!(body.contains("MSFT")); -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 4)] -#[serial] -async fn test_equity_details_sync_returns_ok_when_api_has_no_results() { - let (endpoint, _s3, _env_guard) = setup_test_bucket().await; - - let mut massive_server = Server::new_async().await; - let _mock = massive_server - .mock("GET", "/v3/reference/tickers") - .match_query(Matcher::AllOf(vec![ - Matcher::UrlEncoded("market".into(), "stocks".into()), - Matcher::UrlEncoded("active".into(), "true".into()), - Matcher::UrlEncoded("limit".into(), "1000".into()), - Matcher::UrlEncoded("apiKey".into(), "test-api-key".into()), - ])) - .with_status(200) - .with_body(r#"{"results": [], "status": "OK"}"#) - .create_async() - .await; - - let (app, _env_guard) = spawn_app(&endpoint, massive_server.url()).await; - - let response = reqwest::Client::new() - .post(app.url("/equity-details")) + .post(app.url("/equity-bars")) + .header(reqwest::header::CONTENT_TYPE, "application/json") + .body(r#"{"date":"2025-01-05T00:00:00Z"}"#) .send() .await .unwrap(); @@ -859,7 +747,7 @@ async fn test_equity_details_sync_returns_ok_when_api_has_no_results() { #[tokio::test(flavor = "multi_thread", worker_threads = 4)] #[serial] -async fn test_equity_details_sync_returns_internal_server_error_when_api_request_fails() { +async fn test_equity_details_sync_returns_not_implemented() { let (endpoint, _s3, _env_guard) = setup_test_bucket().await; let (app, _env_guard) = spawn_app(&endpoint, "http://127.0.0.1:1".to_string()).await; @@ -868,123 +756,5 @@ async fn test_equity_details_sync_returns_internal_server_error_when_api_request .send() .await .unwrap(); - assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 4)] -#[serial] -async fn test_equity_details_sync_returns_bad_gateway_when_s3_upload_fails() { - let mut massive_server = Server::new_async().await; - let _mock = massive_server - .mock("GET", "/v3/reference/tickers") - .match_query(Matcher::Any) - .with_status(200) - .with_body( - r#"{ - "results": [{"ticker": "AAPL", "type": "CS", "sector": "Technology", "industry": "Hardware"}], - "status": "OK" - }"#, - ) - .create_async() - .await; - - let (app, _env_guard) = spawn_app_with_unreachable_s3(massive_server.url()).await; - - let response = reqwest::Client::new() - .post(app.url("/equity-details")) - .send() - .await - .unwrap(); - assert_eq!(response.status(), StatusCode::BAD_GATEWAY); -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 4)] -#[serial] -async fn test_equity_details_sync_returns_internal_server_error_for_api_error_status() { - let (endpoint, _s3, _env_guard) = setup_test_bucket().await; - - let mut massive_server = Server::new_async().await; - let _mock = massive_server - .mock("GET", "/v3/reference/tickers") - .match_query(Matcher::Any) - .with_status(500) - .with_body("Internal Server Error") - .create_async() - .await; - - let (app, _env_guard) = spawn_app(&endpoint, massive_server.url()).await; - - let response = reqwest::Client::new() - .post(app.url("/equity-details")) - .send() - .await - .unwrap(); - assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 4)] -#[serial] -async fn test_equity_details_sync_returns_internal_server_error_for_invalid_json() { - let (endpoint, _s3, _env_guard) = setup_test_bucket().await; - - let mut massive_server = Server::new_async().await; - let _mock = massive_server - .mock("GET", "/v3/reference/tickers") - .match_query(Matcher::Any) - .with_status(200) - .with_body("not-json") - .create_async() - .await; - - let (app, _env_guard) = spawn_app(&endpoint, massive_server.url()).await; - - let response = reqwest::Client::new() - .post(app.url("/equity-details")) - .send() - .await - .unwrap(); - assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 4)] -#[serial] -async fn test_equity_details_sync_filters_non_equity_types() { - let (endpoint, _s3, _env_guard) = setup_test_bucket().await; - - let mut massive_server = Server::new_async().await; - let _mock = massive_server - .mock("GET", "/v3/reference/tickers") - .match_query(Matcher::Any) - .with_status(200) - .with_body( - r#"{ - "results": [ - {"ticker": "AAPL", "type": "CS", "sector": "Technology", "industry": "Hardware"}, - {"ticker": "XYZ", "type": "WARRANT", "sector": "Finance", "industry": "Banking"}, - {"ticker": "DEF", "type": "ETF", "sector": "Finance", "industry": "Funds"}, - {"ticker": "GHI", "type": "ADRC", "sector": null, "industry": null} - ], - "status": "OK" - }"#, - ) - .create_async() - .await; - - let (app, _env_guard) = spawn_app(&endpoint, massive_server.url()).await; - let client = reqwest::Client::new(); - - let response = client - .post(app.url("/equity-details")) - .send() - .await - .unwrap(); - assert_eq!(response.status(), StatusCode::OK); - - let response = client.get(app.url("/equity-details")).send().await.unwrap(); - let body = response.text().await.unwrap(); - assert!(body.contains("AAPL")); - assert!(!body.contains("XYZ")); - assert!(!body.contains("DEF")); - assert!(body.contains("GHI")); - assert!(body.contains("NOT AVAILABLE")); + assert_eq!(response.status(), StatusCode::NOT_IMPLEMENTED); } diff --git a/applications/datamanager/tests/test_storage.rs b/applications/datamanager/tests/test_storage.rs index a0f0ac03..de50a732 100644 --- a/applications/datamanager/tests/test_storage.rs +++ b/applications/datamanager/tests/test_storage.rs @@ -374,7 +374,7 @@ async fn test_read_equity_details_dataframe_from_s3_success() { put_test_object( &s3, - "equity/details/categories.csv", + "equity/details/details.csv", b"ticker,sector,industry\nAAPL,Technology,Consumer Electronics\n".to_vec(), ) .await; @@ -394,7 +394,7 @@ async fn test_read_equity_details_dataframe_from_s3_returns_error_for_invalid_ut let (endpoint, s3, _env_guard) = setup_test_bucket().await; let state = create_state(&endpoint).await; - put_test_object(&s3, "equity/details/categories.csv", vec![0xff, 0xfe, 0xfd]).await; + put_test_object(&s3, "equity/details/details.csv", vec![0xff, 0xfe, 0xfd]).await; let result = read_equity_details_dataframe_from_s3(&state).await; @@ -502,7 +502,7 @@ async fn test_write_equity_details_dataframe_to_s3_success() { .await .unwrap(); - assert_eq!(s3_key, "equity/details/categories.csv"); + assert_eq!(s3_key, "equity/details/details.csv"); let read_back = read_equity_details_dataframe_from_s3(&state).await.unwrap(); assert_eq!(read_back.height(), 1); diff --git a/applications/equitypricemodel/pyproject.toml b/applications/equitypricemodel/pyproject.toml index 5cf05da2..97c2f9f8 100644 --- a/applications/equitypricemodel/pyproject.toml +++ b/applications/equitypricemodel/pyproject.toml @@ -7,6 +7,7 @@ dependencies = [ "internal>=0.0.1", "boto3>=1.35.0", "fastapi>=0.115.0", + "uvicorn>=0.34.0", "pandera[polars]>=0.26.0", "polars>=1.29.0", "requests>=2.32.5", diff --git a/applications/portfoliomanager/pyproject.toml b/applications/portfoliomanager/pyproject.toml index 4f5c5f4e..316a5cec 100644 --- a/applications/portfoliomanager/pyproject.toml +++ b/applications/portfoliomanager/pyproject.toml @@ -6,6 +6,7 @@ requires-python = "==3.12.10" dependencies = [ "internal>=0.0.1", "fastapi>=0.115.0", + "uvicorn>=0.34.0", "httpx>=0.27.0", "pandera[polars]>=0.26.0", "polars>=1.29.0", diff --git a/infrastructure/__main__.py b/infrastructure/__main__.py index 6116c3c7..43496959 100644 --- a/infrastructure/__main__.py +++ b/infrastructure/__main__.py @@ -1471,7 +1471,7 @@ def serialize_secret_config_object( "environment": [ { "name": "MASSIVE_BASE_URL", - "value": "https://api.massive.io", + "value": "https://api.massive.com", }, { "name": "AWS_S3_DATA_BUCKET_NAME", diff --git a/tools/src/tools/prepare_training_data.py b/tools/src/tools/prepare_training_data.py index 4d06a240..4ea0b4c8 100644 --- a/tools/src/tools/prepare_training_data.py +++ b/tools/src/tools/prepare_training_data.py @@ -83,7 +83,7 @@ def read_categories_from_s3( bucket_name: str, ) -> pl.DataFrame: """Read categories CSV from S3.""" - key = "equity/details/categories.csv" + key = "equity/details/details.csv" logger.info("Reading categories from S3", bucket=bucket_name, key=key) diff --git a/tools/tests/test_prepare_training_data.py b/tools/tests/test_prepare_training_data.py index c2b372df..dbbd67fe 100644 --- a/tools/tests/test_prepare_training_data.py +++ b/tools/tests/test_prepare_training_data.py @@ -132,7 +132,7 @@ def test_read_categories_from_s3_returns_dataframe() -> None: assert result["ticker"][0] == "AAPL" mock_s3_client.get_object.assert_called_once_with( Bucket="test-bucket", - Key="equity/details/categories.csv", + Key="equity/details/details.csv", ) diff --git a/uv.lock b/uv.lock index 6bfae6be..0a369cf0 100644 --- a/uv.lock +++ b/uv.lock @@ -328,6 +328,7 @@ dependencies = [ { name = "sentry-sdk", extra = ["fastapi"] }, { name = "structlog" }, { name = "tinygrad" }, + { name = "uvicorn" }, ] [package.dev-dependencies] @@ -347,6 +348,7 @@ requires-dist = [ { name = "sentry-sdk", extras = ["fastapi"], specifier = ">=2.0.0" }, { name = "structlog", specifier = ">=25.5.0" }, { name = "tinygrad", specifier = ">=0.10.3" }, + { name = "uvicorn", specifier = ">=0.34.0" }, ] [package.metadata.requires-dev] @@ -883,6 +885,7 @@ dependencies = [ { name = "requests" }, { name = "sentry-sdk", extra = ["fastapi"] }, { name = "structlog" }, + { name = "uvicorn" }, ] [package.metadata] @@ -896,6 +899,7 @@ requires-dist = [ { name = "requests", specifier = ">=2.32.5" }, { name = "sentry-sdk", extras = ["fastapi"], specifier = ">=2.0.0" }, { name = "structlog", specifier = ">=25.5.0" }, + { name = "uvicorn", specifier = ">=0.34.0" }, ] [[package]]