79 lines
2.1 KiB
TypeScript
79 lines
2.1 KiB
TypeScript
type MatchKey = "roleId" | "chapter" | "location" | "seniority" | "workType";
|
|
|
|
type MatchableRateCardLine = Partial<Record<MatchKey, string | null>>;
|
|
type RateCardLineMatchCriteria = Partial<Record<MatchKey, string | undefined>>;
|
|
type RateCardLineMatchWeights = Partial<Record<MatchKey, number>>;
|
|
|
|
type RateCardLineMatchResult<TLine> = {
|
|
line: TLine;
|
|
score: number;
|
|
mismatch: boolean;
|
|
};
|
|
|
|
function normalizeRateCardMatchValue(
|
|
value: string,
|
|
caseInsensitive: boolean,
|
|
): string {
|
|
return caseInsensitive ? value.toLowerCase() : value;
|
|
}
|
|
|
|
function rateCardMatchValuesEqual(input: {
|
|
lineValue: string;
|
|
criteriaValue: string;
|
|
caseInsensitive: boolean;
|
|
}): boolean {
|
|
return normalizeRateCardMatchValue(input.lineValue, input.caseInsensitive) ===
|
|
normalizeRateCardMatchValue(input.criteriaValue, input.caseInsensitive);
|
|
}
|
|
|
|
export function scoreRateCardLineMatch<TLine extends MatchableRateCardLine>(
|
|
line: TLine,
|
|
input: {
|
|
criteria: RateCardLineMatchCriteria;
|
|
weights: RateCardLineMatchWeights;
|
|
caseInsensitiveKeys?: readonly MatchKey[];
|
|
},
|
|
): RateCardLineMatchResult<TLine> {
|
|
const caseInsensitiveKeys = new Set(input.caseInsensitiveKeys ?? []);
|
|
let score = 0;
|
|
let mismatch = false;
|
|
|
|
for (const [key, criteriaValue] of Object.entries(input.criteria) as Array<[MatchKey, string | undefined]>) {
|
|
const lineValue = line[key];
|
|
if (!criteriaValue || !lineValue) {
|
|
continue;
|
|
}
|
|
|
|
if (rateCardMatchValuesEqual({
|
|
lineValue,
|
|
criteriaValue,
|
|
caseInsensitive: caseInsensitiveKeys.has(key),
|
|
})) {
|
|
score += input.weights[key] ?? 0;
|
|
continue;
|
|
}
|
|
|
|
mismatch = true;
|
|
}
|
|
|
|
return { line, score, mismatch };
|
|
}
|
|
|
|
export function pickBestRateCardLineMatch<
|
|
TLine extends MatchableRateCardLine,
|
|
>(
|
|
lines: TLine[],
|
|
input: {
|
|
criteria: RateCardLineMatchCriteria;
|
|
weights: RateCardLineMatchWeights;
|
|
caseInsensitiveKeys?: readonly MatchKey[];
|
|
},
|
|
): TLine | null {
|
|
const candidates = lines
|
|
.map((line) => scoreRateCardLineMatch(line, input))
|
|
.filter((candidate) => !candidate.mismatch)
|
|
.sort((left, right) => right.score - left.score);
|
|
|
|
return candidates[0]?.line ?? null;
|
|
}
|