Skip to content

Commit

Permalink
Merge pull request #105 from swellander/normalize-scores
Browse files Browse the repository at this point in the history
Normalize scores
  • Loading branch information
escottalexander authored Jul 30, 2024
2 parents 5e26f75 + bc36953 commit c6e6de5
Showing 1 changed file with 39 additions and 14 deletions.
53 changes: 39 additions & 14 deletions packages/nextjs/pages/api/etl/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { RF4ImpactMetricsByProject } from "~~/app/types/OSO";
import dbConnect from "~~/services/mongodb/dbConnect";
import ETLLog from "~~/services/mongodb/models/etlLog";
import GlobalScore, { TempGlobalScore } from "~~/services/mongodb/models/globalScore";
import Metric, { Metrics } from "~~/services/mongodb/models/metric";
import Metric, { IMetric, Metrics } from "~~/services/mongodb/models/metric";
import Project from "~~/services/mongodb/models/project";
import ProjectMovement, { IProjectMovement, TempProjectMovement } from "~~/services/mongodb/models/projectMovement";
import ProjectScore, { IProjectScore, TempProjectScore } from "~~/services/mongodb/models/projectScore";
Expand Down Expand Up @@ -54,6 +54,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)

// Get metrics that are activated
const metrics = await Metric.findAllActivated();
// Get all metrics that projects already have scores for
const allMetricsExceptImpactIndex = metrics.filter(metric => metric.name !== "impact_index");
if (!metrics) {
throw new Error("No metrics found");
}
Expand All @@ -79,14 +81,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const globalScoreData = Object.assign({}, metricNamesObj) as { [key in keyof Metrics]: number };
const projectScoreOps = [];

const maxScoreByMetric = getMaxScoresFromOsoData(osoData, allMetricsExceptImpactIndex);

for (const project of osoData) {
const projectMapping = mapping.find((map: any) => map.oso_name === project.project_name);
if (!projectMapping) {
console.error(`No mapping found for ${project.project_name}`);
continue;
}
const projectId = projectMapping.application_id;
const impact_index = getImpactIndex(project as unknown as Metrics, weightings);
const impact_index = getImpactIndex(project as unknown as Metrics, weightings, maxScoreByMetric);

const projectMetrics = {} as { [key in keyof Metrics]: number };
for (const metric of metrics) {
Expand All @@ -113,7 +117,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
}

globalScoreData.impact_index = getImpactIndex(globalScoreData, weightings);
globalScoreData.impact_index = getImpactIndex(globalScoreData, weightings, maxScoreByMetric);

// Batch insert project scores
if (projectScoreOps.length > 0) {
Expand Down Expand Up @@ -267,21 +271,27 @@ const getMovement = (current: number, comparison: number) => {
return diff;
};

const getImpactIndex = (project: Metrics, weights: Metrics): number => {
const getImpactIndex = (
project: Metrics,
weights: Metrics,
maxScoreByMetric: { [key in keyof Metrics]: number },
): number => {
let impact_index = 0;
const metricKeys = Object.keys(weights) as (keyof Metrics)[];
const metrics = Object.keys(maxScoreByMetric) as (keyof Metrics)[];
let weightSum = 0;
for (const metric of metricKeys) {
for (const metric of metrics) {
weightSum += weights[metric];
}
// If the weights don't add up to 100 then we expect they are each a 0-100 percentage and need to be adjusted
const indPct = Math.round(weightSum) !== 100;
for (const metric of metricKeys) {
const multiplier = indPct ? weights[metric] / weightSum : weights[metric];
// Normalize the weights to their percentage of the total
const projectMetric = project[metric as unknown as keyof Metrics];
if (projectMetric) {
impact_index += projectMetric * multiplier;
for (const metric of metrics) {
const multiplier = weights[metric] / weightSum;

// Normalize score
const max = maxScoreByMetric[metric] || 0;
const normalizer = max !== 0 ? 100 / max : 0;
const rawScoreForMetric = project[metric as unknown as keyof Metrics];
const normalizedScore = rawScoreForMetric * normalizer;
if (normalizedScore) {
impact_index += normalizedScore * multiplier;
}
}
return impact_index;
Expand All @@ -304,3 +314,18 @@ const isTooEarly = (lastRunDate: Date): boolean => {
const window = parseInt(process.env.ETL_WINDOW_MS || "0") || 85500000; // 23h 45m
return lastRunDate > new Date(new Date().getTime() - window);
};

const getMaxScoresFromOsoData = (osoData: RF4ImpactMetricsByProject[], allMetricsExceptImpactIndex: IMetric[]) =>
osoData.reduce((acc, project) => {
for (const metric of allMetricsExceptImpactIndex) {
const scoreForMetric = project[metric.name as keyof RF4ImpactMetricsByProject] as number;
if (metric.name in acc && scoreForMetric) {
if (scoreForMetric > acc[metric.name as keyof Metrics]) {
acc[metric.name as keyof Metrics] = scoreForMetric;
}
} else {
acc[metric.name as keyof Metrics] = scoreForMetric || 0;
}
}
return acc;
}, {} as { [key in keyof Metrics]: number });

0 comments on commit c6e6de5

Please sign in to comment.