import mongoose from "mongoose"; const permitSchema = new mongoose.Schema({ tenantId: { type: String, required: true, }, pid: { type: String, unique: true, }, permitNumber: String, county: Object, client: { type: mongoose.Types.ObjectId, ref: "organization", }, clientData: Object, permitDate: Date, stage: new mongoose.Schema( { pipeline: Array, currentStage: Number, }, { _id: false } ), status: String, manualStatus: String, cleanStatus: String, permitType: String, utility: String, assignedTo: { type: mongoose.Types.ObjectId, ref: "user", }, link: String, address: Object, recordType: String, description: String, applicationDetails: Object, applicationInfo: Object, applicationInfoTable: Object, conditions: Array, ownerDetails: String, parcelInfo: Object, paymentData: Object, professionalsList: Array, inspections: Object, createdAt: Date, createdBy: { type: mongoose.Types.ObjectId, ref: "user", }, newProcessingStatus: Array, newPayment: Array, newConditions: Array, professionals: Object, recordId: String, relatedRecords: Object, accelaStatus: String, openDate: Date, lastUpdateDate: Date, statusUpdated: Date, issuedDate: Date, communityName: String, lot: String, block: String, jobNumber: String, startDate: Date, history: Array, }).index({ tenantId: 1, permitNumber: 1 }, { unique: true }); const permitModel = mongoose.model("permit", permitSchema); const processedModel = mongoose.model("processed", permitSchema, "processed"); const analyticsModel = mongoose.model( "analytics", new mongoose.Schema( { tenantId: String, }, { strict: false } ), "analytics" ); let combinedPermits = []; function dateDiff(date1, date2, unit = "days") { const d1 = new Date(date1); const d2 = new Date(date2); const diffMs = Math.abs(d2 - d1); const conversions = { milliseconds: diffMs, seconds: diffMs / 1000, minutes: diffMs / (1000 * 60), hours: diffMs / (1000 * 60 * 60), days: diffMs / (1000 * 60 * 60 * 24), weeks: diffMs / (1000 * 60 * 60 * 24 * 7), months: diffMs / (1000 * 60 * 60 * 24 * 30.44), years: diffMs / (1000 * 60 * 60 * 24 * 365.25), }; if (conversions.hasOwnProperty(unit)) { return conversions[unit]; } else { throw new Error( `Invalid unit: ${unit}. Use one of: ${Object.keys(conversions).join( ", " )}` ); } } async function getSubmissionMetrics() { const groupByCountySubmissions = {}; const groupByClientSubmissions = {}; for (const permit of combinedPermits) { try { const dateStr = permit.openDate.toISOString().split("T")[0]; if (!groupByClientSubmissions[dateStr]) groupByClientSubmissions[dateStr] = {}; if (!groupByCountySubmissions[dateStr]) groupByCountySubmissions[dateStr] = {}; if (!groupByClientSubmissions[dateStr][permit.clientData?.name]) groupByClientSubmissions[dateStr][permit.clientData?.name] = 0; groupByClientSubmissions[dateStr][permit.clientData?.name]++; if (!groupByCountySubmissions[dateStr][permit.county?.name]) groupByCountySubmissions[dateStr][permit.county?.name] = 0; groupByCountySubmissions[dateStr][permit.county?.name]++; } catch (err) { console.log(err); } } return { groupByClientSubmissions, groupByCountySubmissions, }; } function calculateAverages(arr) { if (!Array.isArray(arr)) { throw new TypeError("Input must be an array"); } if (arr.length === 0) { return { min: null, max: null, median: null, average: null, sum: null, count: 0, }; } // Filter out non-numeric values and convert valid numbers const cleanArr = arr.map(Number).filter((n) => !isNaN(n)); if (cleanArr.length === 0) { return { min: null, max: null, median: null, average: null, sum: null, count: 0, }; } // Sort array for median calculation (slice to avoid mutating original) const sorted = [...cleanArr].sort((a, b) => a - b); const count = sorted.length; const sum = sorted.reduce((acc, val) => acc + val, 0); const average = sum / count; // Calculate median const middle = Math.floor(count / 2); let median; if (count % 2 === 0) { median = (sorted[middle - 1] + sorted[middle]) / 2; } else { median = sorted[middle]; } return { min: sorted[0], max: sorted[count - 1], median, average, }; } async function getApprovalMetrics() { const approvedPermits = combinedPermits.filter( (item) => item.accelaStatus === "APPROVED" ); const issuedCountByClient = {}; const issuedCountByCounty = {}; const approvalDurationClient = {}; const approvalDurationCounty = {}; const approvalDurationByPermitTypeClient = {}; const approvalDurationByPermitTypeCounty = {}; for (const permit of approvedPermits) { if (!issuedCountByClient[permit.clientData?.name]) issuedCountByClient[permit.clientData?.name] = 0; issuedCountByClient[permit.clientData?.name]++; if (!issuedCountByCounty[permit.county?.name]) issuedCountByCounty[permit.county?.name] = 0; issuedCountByCounty[permit.county?.name]++; const diff = dateDiff(permit.issuedDate, permit.openDate); if (!approvalDurationClient[permit.clientData?.name]) approvalDurationClient[permit.clientData?.name] = []; approvalDurationClient[permit.clientData?.name]?.push(diff); if (!approvalDurationCounty[permit.county?.name]) approvalDurationCounty[permit.county?.name] = []; approvalDurationCounty[permit.county?.name]?.push(diff); if (!approvalDurationByPermitTypeClient[permit.clientData?.name]) approvalDurationByPermitTypeClient[permit.clientData?.name] = {}; if ( !approvalDurationByPermitTypeClient[permit.clientData?.name][ permit.recordType ] ) approvalDurationByPermitTypeClient[permit.clientData?.name][ permit.recordType ] = []; approvalDurationByPermitTypeClient[permit.clientData?.name][ permit.recordType ]?.push(diff); if (!approvalDurationByPermitTypeCounty[permit.county?.name]) approvalDurationByPermitTypeCounty[permit.county?.name] = {}; if ( !approvalDurationByPermitTypeCounty[permit.county?.name][ permit.recordType ] ) approvalDurationByPermitTypeCounty[permit.county?.name][ permit.recordType ] = []; approvalDurationByPermitTypeCounty[permit.county?.name][ permit.recordType ]?.push(diff); } for (const client in approvalDurationClient) { approvalDurationClient[client] = calculateAverages( approvalDurationClient[client] ); } for (const county in approvalDurationCounty) { approvalDurationCounty[county] = calculateAverages( approvalDurationCounty[county] ); } for (const client in approvalDurationByPermitTypeClient) { for (const type in approvalDurationByPermitTypeClient[client]) { approvalDurationByPermitTypeClient[client][type] = calculateAverages( approvalDurationByPermitTypeClient[client][type] ); } } for (const county in approvalDurationByPermitTypeCounty) { for (const type in approvalDurationByPermitTypeCounty[county]) { approvalDurationByPermitTypeCounty[county][type] = calculateAverages( approvalDurationByPermitTypeCounty[county][type] ); } } return { totalApproved: approvedPermits.length, issuedCountByClient, issuedCountByCounty, approvalDurationClient, approvalDurationCounty, approvalDurationByPermitTypeClient, approvalDurationByPermitTypeCounty, }; } (async () => { await mongoose.connect(process.env.DB_URI); const startDate = new Date(Date.now() - 3600 * 24 * 500 * 1000); const endDate = new Date(); const recentPermits = await permitModel .find({ openDate: { $gte: startDate, $lte: endDate }, }) .select( "clientData county openDate issuedDate accelaStatus status manualStatus cleanStatus recordType" ); const recentProcessed = await processedModel .find({ openDate: { $gte: startDate, $lte: endDate }, }) .select( "clientData county openDate issuedDate accelaStatus status manualStatus cleanStatus recordType" ); combinedPermits = [...recentPermits, ...recentProcessed]; const submissionsByOrg = await getSubmissionMetrics(); const approvedCount = await getApprovalMetrics(); const analytics = { ...submissionsByOrg, ...approvedCount, }; await analyticsModel.findOneAndUpdate( { tenantId: "arf4w59nzduytv7" }, analytics, { upsert: true, } ); await mongoose.connection.close(); })().catch((err) => console.log(err));