refactor(api): extract rate card match support
This commit is contained in:
@@ -2,6 +2,10 @@ import type { Prisma } from "@capakraken/db";
|
||||
import { ROLE_BRIEF_SELECT } from "../db/selects.js";
|
||||
import { findUniqueOrThrow } from "../db/helpers.js";
|
||||
import { fmtEur } from "../lib/format-utils.js";
|
||||
import {
|
||||
pickBestRateCardLineMatch,
|
||||
scoreRateCardLineMatch,
|
||||
} from "./rate-card-match-support.js";
|
||||
|
||||
export const rateCardLineSelect = {
|
||||
id: true,
|
||||
@@ -43,6 +47,25 @@ type ResolveRateLineCriteria = {
|
||||
workType?: string | undefined;
|
||||
};
|
||||
|
||||
const lookupBestRateMatchWeights = {
|
||||
roleId: 4,
|
||||
chapter: 2,
|
||||
seniority: 1,
|
||||
} as const;
|
||||
|
||||
const lookupBestRateCaseInsensitiveKeys = [
|
||||
"chapter",
|
||||
"seniority",
|
||||
] as const;
|
||||
|
||||
const resolveBestRateLineWeights = {
|
||||
roleId: 4,
|
||||
chapter: 2,
|
||||
location: 1,
|
||||
seniority: 1,
|
||||
workType: 1,
|
||||
} as const;
|
||||
|
||||
function buildEffectiveRateCardWhere(effectiveAt: Date): Prisma.RateCardWhereInput {
|
||||
return {
|
||||
isActive: true,
|
||||
@@ -126,30 +149,21 @@ export async function lookupBestRateMatch(
|
||||
|
||||
for (const card of rateCards) {
|
||||
for (const line of card.lines) {
|
||||
let score = 0;
|
||||
let mismatch = false;
|
||||
|
||||
if (roleId && line.role) {
|
||||
if (line.role.id === roleId) {
|
||||
score += 4;
|
||||
} else {
|
||||
mismatch = true;
|
||||
}
|
||||
}
|
||||
if (input.chapter && line.chapter) {
|
||||
if (line.chapter.toLowerCase() === input.chapter.toLowerCase()) {
|
||||
score += 2;
|
||||
} else {
|
||||
mismatch = true;
|
||||
}
|
||||
}
|
||||
if (input.seniority && line.seniority) {
|
||||
if (line.seniority.toLowerCase() === input.seniority.toLowerCase()) {
|
||||
score += 1;
|
||||
} else {
|
||||
mismatch = true;
|
||||
}
|
||||
}
|
||||
const scored = scoreRateCardLineMatch({
|
||||
roleId: line.role?.id ?? null,
|
||||
chapter: line.chapter,
|
||||
seniority: line.seniority,
|
||||
}, {
|
||||
criteria: {
|
||||
roleId,
|
||||
chapter: input.chapter,
|
||||
seniority: input.seniority,
|
||||
},
|
||||
weights: lookupBestRateMatchWeights,
|
||||
caseInsensitiveKeys: lookupBestRateCaseInsensitiveKeys,
|
||||
});
|
||||
let score = scored.score;
|
||||
const mismatch = scored.mismatch;
|
||||
if (input.clientId && card.client?.id === input.clientId) {
|
||||
score += 3;
|
||||
}
|
||||
@@ -288,52 +302,8 @@ export function resolveBestRateLineMatch<
|
||||
workType: string | null;
|
||||
},
|
||||
>(lines: TLine[], criteria: ResolveRateLineCriteria): TLine | null {
|
||||
const scored = lines.map((line) => {
|
||||
let score = 0;
|
||||
let mismatch = false;
|
||||
|
||||
if (criteria.roleId && line.roleId) {
|
||||
if (line.roleId === criteria.roleId) {
|
||||
score += 4;
|
||||
} else {
|
||||
mismatch = true;
|
||||
}
|
||||
}
|
||||
if (criteria.chapter && line.chapter) {
|
||||
if (line.chapter === criteria.chapter) {
|
||||
score += 2;
|
||||
} else {
|
||||
mismatch = true;
|
||||
}
|
||||
}
|
||||
if (criteria.location && line.location) {
|
||||
if (line.location === criteria.location) {
|
||||
score += 1;
|
||||
} else {
|
||||
mismatch = true;
|
||||
}
|
||||
}
|
||||
if (criteria.seniority && line.seniority) {
|
||||
if (line.seniority === criteria.seniority) {
|
||||
score += 1;
|
||||
} else {
|
||||
mismatch = true;
|
||||
}
|
||||
}
|
||||
if (criteria.workType && line.workType) {
|
||||
if (line.workType === criteria.workType) {
|
||||
score += 1;
|
||||
} else {
|
||||
mismatch = true;
|
||||
}
|
||||
}
|
||||
|
||||
return { line, score, mismatch };
|
||||
return pickBestRateCardLineMatch(lines, {
|
||||
criteria,
|
||||
weights: resolveBestRateLineWeights,
|
||||
});
|
||||
|
||||
const candidates = scored
|
||||
.filter((candidate) => !candidate.mismatch)
|
||||
.sort((left, right) => right.score - left.score);
|
||||
|
||||
return candidates[0]?.line ?? null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user