Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .env.local
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
DATABASE_URL="postgres://neondb_owner:npg_fthjFP8Ece2Z@ep-icy-leaf-afdbv5ts-pooler.c-2.us-west-2.aws.neon.tech/neondb?sslmode=require"
# .env
DB_HOST=localhost
DB_PORT=5432
DB_NAME=databas # Name of your PostgreSQL database
DB_USER=postgres # Your PostgreSQL username
DB_PASSWORD=blackEARTH3018334
25 changes: 16 additions & 9 deletions lib/db.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import bcrypt from 'bcrypt';
import pkg from 'pg';
const { Pool } = pkg;
const { Pool } = require('pg');

const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: {
rejectUnauthorized: false,
},
host: process.env.DB_HOST,
port: process.env.DB_PORT,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
});

export { bcrypt };
export default pool;
// Test the connection
pool.query('SELECT NOW()', (err, res) => {
if (err) {
console.error('Database connection error:', err);
} else {
console.log('Database connected successfully');
}
});

module.exports = pool;
186 changes: 55 additions & 131 deletions src/app/api/reports/insurance-analysis/route.js
Original file line number Diff line number Diff line change
@@ -1,147 +1,71 @@
import pool from '../../../../../lib/db';
import { NextResponse } from 'next/server';
import pool from '../../../../../lib/db';

export async function GET(request) {
try {
const { searchParams } = new URL(request.url);
const startDate = searchParams.get('startDate');
const endDate = searchParams.get('endDate');
const analysisType = searchParams.get('type') || 'detailed'; // detailed or summary

if (analysisType === 'summary') {
// Monthly summary view
let summaryQuery = `
let query = `
WITH monthly_data AS (
SELECT
DATE_TRUNC('month', a.date) AS month_year,
COUNT(DISTINCT a.appointment_id) AS total_appointments,
SUM(COALESCE(pay.amount, 0)) AS total_payments,
SUM(COALESCE(ic.amount_approved, 0)) AS total_insurance_covered,
SUM(COALESCE(pay.amount, 0) - COALESCE(ic.amount_approved, 0)) AS total_out_of_pocket,
CASE
WHEN SUM(COALESCE(pay.amount, 0)) > 0 THEN
(SUM(COALESCE(ic.amount_approved, 0)) / SUM(pay.amount) * 100)
ELSE 0
END AS overall_insurance_coverage_percentage,
COUNT(DISTINCT CASE WHEN ic.amount_approved > 0 THEN a.appointment_id END) AS appointments_with_insurance,
COUNT(DISTINCT CASE WHEN ic.amount_approved IS NULL OR ic.amount_approved = 0 THEN a.appointment_id END) AS appointments_without_insurance
DATE_TRUNC('month', a.date) as month_year,
COUNT(DISTINCT a.appointment_id) as total_appointments,
SUM(p.amount) as total_payments,
SUM(COALESCE(ic.amount_approved, 0)) as total_insurance_covered,
SUM(p.amount - COALESCE(ic.amount_approved, 0)) as total_out_of_pocket
FROM appointment a
LEFT JOIN payment pay ON a.appointment_id = pay.appointment_id
LEFT JOIN insurance_claim ic ON a.appointment_id = ic.appointment_id AND ic.status = 'Approved'
WHERE a.date IS NOT NULL
`;

const queryParams = [];
let paramIndex = 1;

if (startDate) {
summaryQuery += ` AND a.date >= $${paramIndex}`;
queryParams.push(startDate);
paramIndex++;
}

if (endDate) {
summaryQuery += ` AND a.date <= $${paramIndex}`;
queryParams.push(endDate);
paramIndex++;
}

summaryQuery += `
GROUP BY DATE_TRUNC('month', a.date)
ORDER BY month_year DESC
`;

const summaryResult = await pool.query(summaryQuery, queryParams);

return NextResponse.json({
success: true,
type: 'summary',
data: summaryResult.rows,
summary: {
totalRecords: summaryResult.rows.length,
dateRange: { start: startDate, end: endDate }
}
}, { status: 200 });

} else {
// Detailed view
let detailedQuery = `
SELECT
a.appointment_id,
p.patient_id,
p.name AS patient_name,
a.date AS appointment_date,
COALESCE(pay.amount, 0) AS total_payment,
COALESCE(ic.amount_approved, 0) AS insurance_covered,
COALESCE(pay.amount, 0) - COALESCE(ic.amount_approved, 0) AS out_of_pocket,
CASE
WHEN COALESCE(pay.amount, 0) > 0 THEN
(COALESCE(ic.amount_approved, 0) / pay.amount * 100)
ELSE 0
END AS insurance_coverage_percentage,
ip.policy_name,
ip.coverage_percentage AS policy_coverage_rate,
ic.status AS claim_status,
ic.amount_claimed,
d.name AS doctor_name,
b.name AS branch_name
FROM appointment a
INNER JOIN patient p ON a.patient_id = p.patient_id
LEFT JOIN payment pay ON a.appointment_id = pay.appointment_id
INNER JOIN payment p ON a.appointment_id = p.appointment_id
LEFT JOIN insurance_claim ic ON a.appointment_id = ic.appointment_id
LEFT JOIN insurance_policy ip ON p.insurance_id = ip.insurance_id
LEFT JOIN doctor d ON a.doctor_id = d.doctor_id
LEFT JOIN branch b ON a.branch_id = b.branch_id
WHERE (pay.amount IS NOT NULL OR ic.amount_approved IS NOT NULL)
`;

const queryParams = [];
let paramIndex = 1;

if (startDate) {
detailedQuery += ` AND a.date >= $${paramIndex}`;
queryParams.push(startDate);
paramIndex++;
}

if (endDate) {
detailedQuery += ` AND a.date <= $${paramIndex}`;
queryParams.push(endDate);
paramIndex++;
}

detailedQuery += ` ORDER BY a.date DESC`;

const detailedResult = await pool.query(detailedQuery, queryParams);

// Calculate summary statistics
const totalPayments = detailedResult.rows.reduce((sum, row) => sum + parseFloat(row.total_payment), 0);
const totalInsuranceCovered = detailedResult.rows.reduce((sum, row) => sum + parseFloat(row.insurance_covered), 0);
const totalOutOfPocket = detailedResult.rows.reduce((sum, row) => sum + parseFloat(row.out_of_pocket), 0);
const appointmentsWithInsurance = detailedResult.rows.filter(row => parseFloat(row.insurance_covered) > 0).length;
WHERE a.status = 'Completed'
${startDate ? "AND a.date >= $1" : ""}
${endDate ? `AND a.date <= $${startDate ? "2" : "1"}` : ""}
GROUP BY DATE_TRUNC('month', a.date)
)
SELECT
to_char(month_year, 'YYYY-MM-DD') as month_year,
total_appointments,
total_payments::numeric(10,2),
total_insurance_covered::numeric(10,2),
total_out_of_pocket::numeric(10,2),
CASE
WHEN total_payments > 0 THEN
ROUND((total_insurance_covered / total_payments * 100)::numeric, 2)
ELSE 0
END as coverage_percentage
FROM monthly_data
ORDER BY month_year;
`;

const queryParams = [];
if (startDate) queryParams.push(startDate);
if (endDate) queryParams.push(endDate);

console.log('Executing query with params:', queryParams); // Debug log

const result = await pool.query(query, queryParams);

console.log('Query results:', result.rows); // Debug log

const summary = {
total_appointments: result.rows.reduce((sum, row) => sum + Number(row.total_appointments), 0),
total_payments: result.rows.reduce((sum, row) => sum + Number(row.total_payments), 0),
total_insurance_covered: result.rows.reduce((sum, row) => sum + Number(row.total_insurance_covered), 0),
total_out_of_pocket: result.rows.reduce((sum, row) => sum + Number(row.total_out_of_pocket), 0),
avg_coverage_percentage: result.rows.length > 0
? result.rows.reduce((sum, row) => sum + Number(row.coverage_percentage), 0) / result.rows.length
: 0
};

return NextResponse.json({
success: true,
type: 'detailed',
data: detailedResult.rows,
summary: {
totalAppointments: detailedResult.rows.length,
totalPayments: totalPayments,
totalInsuranceCovered: totalInsuranceCovered,
totalOutOfPocket: totalOutOfPocket,
appointmentsWithInsurance: appointmentsWithInsurance,
appointmentsWithoutInsurance: detailedResult.rows.length - appointmentsWithInsurance,
overallInsuranceCoveragePercentage: totalPayments > 0 ? (totalInsuranceCovered / totalPayments * 100) : 0,
dateRange: { start: startDate, end: endDate }
}
}, { status: 200 });
}
return NextResponse.json({
success: true,
data: result.rows,
summary
});

} catch (error) {
console.error('Insurance coverage analysis error:', error);
return NextResponse.json({
success: false,
error: 'Failed to generate insurance coverage analysis',
details: error.message
}, { status: 500 });
console.error('Insurance analysis error:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
Loading