diff --git a/backend/src/api.rs b/backend/src/api.rs index e0e877929..e7c864454 100644 --- a/backend/src/api.rs +++ b/backend/src/api.rs @@ -2069,7 +2069,10 @@ pub async fn get_plan_report( } }; - // 4. Build PDF data structs (owned, so they can cross the thread boundary) + // 4. Compute projected yield (real-time accrued yield since last ping) + let projected_yield = compute_projected_accrued_yield(&plan); + + // 5. Build PDF data structs (owned, so they can cross the thread boundary) let report_data = crate::pdf::PlanReportData { plan_id: plan.id.to_string(), owner_address: plan.owner_address.clone(), @@ -2079,6 +2082,7 @@ pub async fn get_plan_report( earn_yield: plan.earn_yield, yield_rate_bps: plan.yield_rate_bps, accrued_yield: plan.accrued_yield.to_string(), + projected_accrued_yield: format!("{projected_yield:.7}"), created_at: plan.created_at.to_rfc3339(), grace_period_seconds: plan.grace_period_seconds, beneficiaries: beneficiary_rows @@ -2098,7 +2102,7 @@ pub async fn get_plan_report( .collect(), }; - // 5. Generate PDF in a blocking thread (printpdf is CPU-bound / not async) + // 6. Generate PDF in a blocking thread (printpdf is CPU-bound / not async) let pdf_bytes = match tokio::task::spawn_blocking(move || crate::pdf::generate(report_data)).await { Ok(Ok(bytes)) => bytes, @@ -2118,7 +2122,7 @@ pub async fn get_plan_report( } }; - // 6. Return the PDF as a downloadable attachment + // 7. Return the PDF as a downloadable attachment let filename = format!("plan-{plan_id}-report.pdf"); ( StatusCode::OK, diff --git a/backend/src/pdf.rs b/backend/src/pdf.rs index c46d04e99..1f450d8f8 100644 --- a/backend/src/pdf.rs +++ b/backend/src/pdf.rs @@ -9,6 +9,7 @@ pub struct PlanReportData { pub earn_yield: bool, pub yield_rate_bps: i32, pub accrued_yield: String, + pub projected_accrued_yield: String, pub created_at: String, pub grace_period_seconds: i64, pub beneficiaries: Vec, @@ -67,37 +68,57 @@ pub fn generate(data: PlanReportData) -> Result, String> { 9.0, format!("Owner: {}", data.owner_address) ); + line!(regular, 9.0, format!("Status: {}", data.status)); line!( regular, 9.0, - format!("Token: {}", data.token_address) + format!("Grace Period (s): {}", data.grace_period_seconds) ); - line!(regular, 9.0, format!("Amount: {}", data.amount)); - line!(regular, 9.0, format!("Status: {}", data.status)); line!( regular, 9.0, - format!("Earn Yield: {}", data.earn_yield) + format!("Created At: {}", data.created_at) + ); + y -= section_gap; + + // Assets + line!(bold, 11.0, "Assets"); + line!( + regular, + 9.0, + format!("Token Address: {}", data.token_address) ); + line!(regular, 9.0, format!("Locked Amount: {}", data.amount)); + y -= section_gap; + + // Yield Details + let apy_pct = data.yield_rate_bps as f32 / 100.0; + line!(bold, 11.0, "Yield Details"); line!( regular, 9.0, - format!("Yield Rate (bps): {}", data.yield_rate_bps) + format!("Earn Yield: {}", data.earn_yield) ); line!( regular, 9.0, - format!("Accrued Yield: {}", data.accrued_yield) + format!( + "Yield Rate: {} bps ({:.2}% APY)", + data.yield_rate_bps, apy_pct + ) ); line!( regular, 9.0, - format!("Grace Period (s): {}", data.grace_period_seconds) + format!("Accrued Yield: {} (snapshotted)", data.accrued_yield) ); line!( regular, 9.0, - format!("Created At: {}", data.created_at) + format!( + "Projected Yield: {} (real-time)", + data.projected_accrued_yield + ) ); y -= section_gap;