add analytics
This commit is contained in:
282
cron/analytics.js
Normal file
282
cron/analytics.js
Normal file
@@ -0,0 +1,282 @@
|
||||
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 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 approvalDurationClient = {};
|
||||
const approvalDurationCounty = {};
|
||||
|
||||
for (const permit of approvedPermits) {
|
||||
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])
|
||||
approvalDurationClient[permit.county?.name] = [];
|
||||
approvalDurationCounty[permit.county?.name]?.push(diff);
|
||||
}
|
||||
|
||||
for (const client in approvalDurationClient) {
|
||||
approvalDurationClient[client] = calculateAverages(
|
||||
approvalDurationClient[client]
|
||||
);
|
||||
}
|
||||
|
||||
for (const county in approvalDurationCounty) {
|
||||
approvalDurationCounty[county] = calculateAverages(
|
||||
approvalDurationCounty[county]
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
totalApproved: approvedPermits.length,
|
||||
approvalDurationClient,
|
||||
approvalDurationCounty,
|
||||
};
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await mongoose.connect(process.env.DB_URI);
|
||||
|
||||
const startDate = new Date(Date.now() - 3600 * 24 * 30 * 1000);
|
||||
const endDate = new Date();
|
||||
|
||||
const recentPermits = await permitModel
|
||||
.find({
|
||||
openDate: { $gte: startDate, $lte: endDate },
|
||||
})
|
||||
.select(
|
||||
"clientData county openDate issuedDate accelaStatus status manualStatus cleanStatus"
|
||||
);
|
||||
|
||||
const recentProcessed = await processedModel
|
||||
.find({
|
||||
openDate: { $gte: startDate, $lte: endDate },
|
||||
})
|
||||
.select(
|
||||
"clientData county openDate issuedDate accelaStatus status manualStatus cleanStatus"
|
||||
);
|
||||
|
||||
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));
|
||||
Reference in New Issue
Block a user