Spaces:
Sleeping
Sleeping
| -- Fix for the overdue percentage calculation | |
| -- Original line 32 had syntax error | |
| -- CORRECTED Query 1: | |
| WITH customer_stats AS ( | |
| SELECT | |
| cust_number, | |
| COUNT(*) as total_invoices, | |
| COUNT(CASE WHEN clear_date IS NOT NULL THEN 1 END) as cleared_count, | |
| AVG(days_to_clear) as avg_days, | |
| PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY days_to_clear) as median_days, | |
| STDDEV(days_to_clear) as std_days, | |
| MIN(days_to_clear) as min_days, | |
| MAX(days_to_clear) as max_days, | |
| AVG(total_open_amount) as avg_amount, | |
| SUM(total_open_amount) as total_amount, | |
| -- FIXED: Overdue percentage calculation | |
| CASE | |
| WHEN COUNT(CASE WHEN clear_date IS NOT NULL THEN 1 END) > 0 | |
| THEN (CAST(COUNT(CASE WHEN is_overdue = TRUE THEN 1 END) AS NUMERIC) / | |
| CAST(COUNT(CASE WHEN clear_date IS NOT NULL THEN 1 END) AS NUMERIC) * 100) | |
| ELSE 0.0 | |
| END as pct_overdue, | |
| (SELECT cust_payment_terms FROM invoices_history WHERE cust_number = $1 GROUP BY cust_payment_terms ORDER BY COUNT(*) DESC LIMIT 1) as most_common_payment_term, | |
| (SELECT business_code FROM invoices_history WHERE cust_number = $1 GROUP BY business_code ORDER BY COUNT(*) DESC LIMIT 1) as most_common_business_code, | |
| (SELECT invoice_currency FROM invoices_history WHERE cust_number = $1 GROUP BY invoice_currency ORDER BY COUNT(*) DESC LIMIT 1) as most_common_currency | |
| FROM invoices_history | |
| WHERE cust_number = $1 | |
| GROUP BY cust_number | |
| ) | |
| SELECT | |
| cust_number, | |
| total_invoices as cust_invoice_count, | |
| cleared_count as cust_cleared_count, | |
| ROUND(avg_days, 2) as cust_avg_days, | |
| ROUND(median_days, 2) as cust_median_days, | |
| ROUND(COALESCE(std_days, 0), 2) as cust_std_days, | |
| min_days as cust_min_days, | |
| max_days as cust_max_days, | |
| ROUND(avg_amount, 2) as cust_avg_amount, | |
| ROUND(total_amount, 2) as cust_total_amount, | |
| ROUND(pct_overdue, 2) as cust_pct_overdue, | |
| most_common_payment_term, | |
| most_common_business_code, | |
| most_common_currency | |
| FROM customer_stats; | |
| -- ============================================ | |
| -- QUERY 2: Batch Compute All Customer Aggregates | |
| -- Usage: Nightly ETL job | |
| -- ============================================ | |
| -- Name: compute_all_customer_aggregates | |
| -- Description: Computes aggregates for ALL customers with cleared invoices | |
| WITH customer_stats AS ( | |
| SELECT | |
| cust_number, | |
| COUNT(*) as total_invoices, | |
| COUNT(CASE WHEN clear_date IS NOT NULL THEN 1 END) as cleared_count, | |
| AVG(days_to_clear) as avg_days, | |
| PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY days_to_clear) as median_days, | |
| STDDEV(days_to_clear) as std_days, | |
| MIN(days_to_clear) as min_days, | |
| MAX(days_to_clear) as max_days, | |
| AVG(total_open_amount) as avg_amount, | |
| SUM(total_open_amount) as total_amount, | |
| COUNT(CASE WHEN is_overdue = TRUE THEN 1 END)::NUMERIC / | |
| NULLIF(COUNT(CASE WHEN clear_date IS NOT NULL THEN 1 END), 0) * 100 as pct_overdue, | |
| MODE() WITHIN GROUP (ORDER BY cust_payment_terms) as most_common_payment_term, | |
| MODE() WITHIN GROUP (ORDER BY business_code) as most_common_business_code, | |
| MODE() WITHIN GROUP (ORDER BY invoice_currency) as most_common_currency | |
| FROM invoices_history | |
| WHERE clear_date IS NOT NULL -- Only customers with history | |
| GROUP BY cust_number | |
| HAVING COUNT(CASE WHEN clear_date IS NOT NULL THEN 1 END) > 0 | |
| ) | |
| SELECT | |
| cust_number, | |
| total_invoices as cust_invoice_count, | |
| cleared_count as cust_cleared_count, | |
| ROUND(avg_days, 2) as cust_avg_days, | |
| ROUND(median_days, 2) as cust_median_days, | |
| ROUND(COALESCE(std_days, 0), 2) as cust_std_days, | |
| min_days as cust_min_days, | |
| max_days as cust_max_days, | |
| ROUND(avg_amount, 2) as cust_avg_amount, | |
| ROUND(total_amount, 2) as cust_total_amount, | |
| ROUND(COALESCE(pct_overdue, 0), 2) as cust_pct_overdue, | |
| most_common_payment_term, | |
| most_common_business_code, | |
| most_common_currency, | |
| NOW() as last_computed_at | |
| FROM customer_stats; | |
| -- ============================================ | |
| -- QUERY 3: Upsert Customer Aggregates | |
| -- Usage: Insert or update customer_aggregates table | |
| -- ============================================ | |
| -- Name: upsert_customer_aggregates | |
| -- Description: Insert/update aggregates with conflict handling | |
| -- Parameters: All customer aggregate fields | |
| INSERT INTO customer_aggregates ( | |
| cust_number, | |
| cust_invoice_count, | |
| cust_cleared_count, | |
| cust_avg_days, | |
| cust_median_days, | |
| cust_std_days, | |
| cust_min_days, | |
| cust_max_days, | |
| cust_avg_amount, | |
| cust_total_amount, | |
| cust_pct_overdue, | |
| most_common_payment_term, | |
| most_common_business_code, | |
| most_common_currency, | |
| last_computed_at | |
| ) VALUES ( | |
| $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, NOW() | |
| ) | |
| ON CONFLICT (cust_number) | |
| DO UPDATE SET | |
| cust_invoice_count = EXCLUDED.cust_invoice_count, | |
| cust_cleared_count = EXCLUDED.cust_cleared_count, | |
| cust_avg_days = EXCLUDED.cust_avg_days, | |
| cust_median_days = EXCLUDED.cust_median_days, | |
| cust_std_days = EXCLUDED.cust_std_days, | |
| cust_min_days = EXCLUDED.cust_min_days, | |
| cust_max_days = EXCLUDED.cust_max_days, | |
| cust_avg_amount = EXCLUDED.cust_avg_amount, | |
| cust_total_amount = EXCLUDED.cust_total_amount, | |
| cust_pct_overdue = EXCLUDED.cust_pct_overdue, | |
| most_common_payment_term = EXCLUDED.most_common_payment_term, | |
| most_common_business_code = EXCLUDED.most_common_business_code, | |
| most_common_currency = EXCLUDED.most_common_currency, | |
| last_computed_at = NOW(); | |
| -- ============================================ | |
| -- QUERY 4: Compute Payment Terms Aggregates | |
| -- Usage: Pre-compute payment term statistics | |
| -- ============================================ | |
| -- Name: compute_payment_terms_aggregates | |
| WITH payment_stats AS ( | |
| SELECT | |
| cust_payment_terms, | |
| AVG(days_to_clear) as avg_days, | |
| PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY days_to_clear) as median_days, | |
| COUNT(*) as invoice_count | |
| FROM invoices_history | |
| WHERE clear_date IS NOT NULL | |
| AND cust_payment_terms IS NOT NULL | |
| GROUP BY cust_payment_terms | |
| ) | |
| SELECT | |
| cust_payment_terms, | |
| ROUND(avg_days, 2) as payment_terms_avg_days, | |
| ROUND(median_days, 2) as payment_terms_median_days, | |
| invoice_count as payment_terms_count, | |
| NOW() as last_computed_at | |
| FROM payment_stats; | |
| -- ============================================ | |
| -- QUERY 5: Compute Business Code Aggregates | |
| -- Usage: Pre-compute business code statistics | |
| -- ============================================ | |
| -- Name: compute_business_code_aggregates | |
| WITH business_stats AS ( | |
| SELECT | |
| business_code, | |
| AVG(days_to_clear) as avg_days, | |
| PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY days_to_clear) as median_days, | |
| COUNT(*) as invoice_count | |
| FROM invoices_history | |
| WHERE clear_date IS NOT NULL | |
| AND business_code IS NOT NULL | |
| GROUP BY business_code | |
| ) | |
| SELECT | |
| business_code, | |
| ROUND(avg_days, 2) as business_avg_days, | |
| ROUND(median_days, 2) as business_median_days, | |
| invoice_count as business_count, | |
| NOW() as last_computed_at | |
| FROM business_stats; | |
| -- ============================================ | |
| -- QUERY 6: Get Customer Features (for inference) | |
| -- Usage: Retrieve all features for a customer | |
| -- ============================================ | |
| -- Name: get_customer_features | |
| -- Description: Get customer aggregates for prediction | |
| -- Parameters: $1 = cust_number | |
| SELECT | |
| cust_number, | |
| cust_invoice_count, | |
| cust_cleared_count, | |
| cust_avg_days, | |
| cust_median_days, | |
| cust_std_days, | |
| cust_min_days, | |
| cust_max_days, | |
| cust_avg_amount, | |
| cust_total_amount, | |
| cust_pct_overdue, | |
| most_common_payment_term, | |
| most_common_business_code, | |
| most_common_currency, | |
| last_computed_at | |
| FROM customer_aggregates | |
| WHERE cust_number = $1; | |
| -- ============================================ | |
| -- QUERY 7: Get Payment Terms Features | |
| -- Usage: Retrieve payment term stats | |
| -- ============================================ | |
| -- Name: get_payment_terms_features | |
| -- Parameters: $1 = cust_payment_terms | |
| SELECT | |
| cust_payment_terms, | |
| payment_terms_avg_days, | |
| payment_terms_median_days, | |
| payment_terms_count | |
| FROM payment_terms_aggregates | |
| WHERE cust_payment_terms = $1; | |
| -- ============================================ | |
| -- QUERY 8: Get Business Code Features | |
| -- Usage: Retrieve business code stats | |
| -- ============================================ | |
| -- Name: get_business_code_features | |
| -- Parameters: $1 = business_code | |
| SELECT | |
| business_code, | |
| business_avg_days, | |
| business_median_days, | |
| business_count | |
| FROM business_code_aggregates | |
| WHERE business_code = $1; | |
| -- ============================================ | |
| -- QUERY 9: Insert Invoice (with upsert) | |
| -- Usage: Ingest new invoice data | |
| -- ============================================ | |
| -- Name: upsert_invoice | |
| -- Parameters: All invoice fields | |
| INSERT INTO invoices_history ( | |
| invoice_id, | |
| business_code, | |
| cust_number, | |
| name_customer, | |
| posting_date, | |
| document_create_date, | |
| document_create_date_alt, | |
| due_in_date, | |
| baseline_create_date, | |
| clear_date, | |
| total_open_amount, | |
| invoice_currency, | |
| document_type, | |
| cust_payment_terms, | |
| posting_id, | |
| business_year | |
| ) VALUES ( | |
| $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16 | |
| ) | |
| ON CONFLICT (invoice_id) | |
| DO UPDATE SET | |
| clear_date = EXCLUDED.clear_date, | |
| is_open = EXCLUDED.is_open, | |
| updated_at = NOW(); | |
| -- ============================================ | |
| -- QUERY 10: Insert Prediction Log | |
| -- Usage: Record prediction for monitoring | |
| -- ============================================ | |
| -- Name: insert_prediction_log | |
| -- Parameters: prediction fields | |
| INSERT INTO predictions_log ( | |
| invoice_id, | |
| cust_number, | |
| posting_date, | |
| total_open_amount, | |
| business_code, | |
| cust_payment_terms, | |
| features_json, | |
| predicted_days_to_clear, | |
| predicted_clear_date, | |
| model_version, | |
| model_path | |
| ) VALUES ( | |
| $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11 | |
| ) RETURNING prediction_id; | |
| -- ============================================ | |
| -- QUERY 11: Update Prediction with Actual Outcome | |
| -- Usage: Record actual outcome for model monitoring | |
| -- ============================================ | |
| -- Name: update_prediction_outcome | |
| -- Parameters: $1 = prediction_id, $2 = actual_clear_date | |
| UPDATE predictions_log | |
| SET | |
| actual_clear_date = $2, | |
| actual_days_to_clear = EXTRACT(DAY FROM ($2 - posting_date))::INTEGER, | |
| prediction_error = EXTRACT(DAY FROM ($2 - posting_date))::INTEGER - predicted_days_to_clear, | |
| absolute_error = ABS(EXTRACT(DAY FROM ($2 - posting_date))::INTEGER - predicted_days_to_clear), | |
| outcome_recorded_at = NOW() | |
| WHERE prediction_id = $1; | |
| -- ============================================ | |
| -- QUERY 12: Get Recent Predictions Performance | |
| -- Usage: Monitor model accuracy | |
| -- ============================================ | |
| -- Name: get_prediction_metrics | |
| -- Description: Calculate model performance over last N days | |
| -- Parameters: $1 = days_back (e.g., 30) | |
| SELECT | |
| COUNT(*) as total_predictions, | |
| COUNT(actual_days_to_clear) as predictions_with_outcome, | |
| ROUND(AVG(ABS(prediction_error)), 2) as mae, | |
| ROUND(SQRT(AVG(prediction_error * prediction_error)), 2) as rmse, | |
| ROUND(AVG(CASE | |
| WHEN ABS(prediction_error) <= 3 THEN 1.0 | |
| ELSE 0.0 | |
| END) * 100, 2) as pct_within_3_days, | |
| ROUND(AVG(CASE | |
| WHEN ABS(prediction_error) <= 7 THEN 1.0 | |
| ELSE 0.0 | |
| END) * 100, 2) as pct_within_7_days | |
| FROM predictions_log | |
| WHERE predicted_at >= NOW() - INTERVAL '$1 days' | |
| AND actual_days_to_clear IS NOT NULL; | |
| -- ============================================ | |
| -- End of Query Templates | |
| -- ============================================ |