351 lines
8.7 KiB
JavaScript
351 lines
8.7 KiB
JavaScript
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));
|