Skip to content

Commit 6cdf774

Browse files
authored
Support specific aliases for PnP step labels (#3461)
2 parents 90b551e + b83b214 commit 6cdf774

File tree

3 files changed

+63
-29
lines changed

3 files changed

+63
-29
lines changed

src/components/pnp/findStepColumns.ts

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,29 @@
1-
import { sortBy } from '@seedcompany/common';
1+
import { mapOf, sortBy } from '@seedcompany/common';
22
import levenshtein from 'fastest-levenshtein';
3-
import { startCase, without } from 'lodash';
3+
import { startCase } from 'lodash';
44
import { type Column } from '~/common/xlsx.util';
55
import { ProductStep as Step } from '../product/dto';
66
import { type PnpExtractionResult, PnpProblemType } from './extraction-result';
77
import { type PlanningSheet } from './planning-sheet';
88
import { type ProgressSheet } from './progress-sheet';
99

10+
const ApprovedAliases = mapOf<string, Step>([
11+
['draft & keyboard', Step.ExegesisAndFirstDraft],
12+
['first draft', Step.ExegesisAndFirstDraft],
13+
['exegesis, 1st draft, keyboard', Step.ExegesisAndFirstDraft],
14+
['internalization & first draft', Step.ExegesisAndFirstDraft],
15+
['exegesis 1st draft & keybrd', Step.ExegesisAndFirstDraft],
16+
['first draft & keyboard', Step.ExegesisAndFirstDraft],
17+
['exegesis, 1st draft. keyboard', Step.ExegesisAndFirstDraft],
18+
['team check & 1st testing', Step.TeamCheck],
19+
['team check & revision', Step.TeamCheck],
20+
['team check & 1st test', Step.TeamCheck],
21+
['field test', Step.CommunityTesting],
22+
['community check', Step.CommunityTesting],
23+
['community review', Step.CommunityTesting],
24+
['community testing & revision', Step.CommunityTesting],
25+
]);
26+
1027
/**
1128
* Fuzzy match available steps to their column address.
1229
*/
@@ -15,43 +32,60 @@ export function findStepColumns(
1532
result?: PnpExtractionResult,
1633
availableSteps: readonly Step[] = [...Step],
1734
) {
18-
const matchedColumns: Partial<Record<Step, Column>> = {};
19-
let remainingSteps = availableSteps;
35+
const matchedColumns = new Map<Step, Column>();
36+
const remainingSteps = new Set(availableSteps);
2037
const possibleSteps = sheet.stepLabels
2138
.walkRight()
2239
.filter((cell) => !!cell.asString)
23-
.map((cell) => ({ label: cell.asString!, column: cell.column, cell }))
40+
.map((cell) => ({
41+
label: cell.asString!.trim(),
42+
column: cell.column,
43+
cell,
44+
}))
2445
.toArray();
2546
possibleSteps.forEach(({ label, column, cell }, index) => {
2647
if (index === possibleSteps.length - 1) {
2748
// The last step should always be called Completed in CORD per Seth.
28-
// Written PnP already match, but OBS calls it Record. This is mislabeled
29-
// depending on the methodology.
30-
matchedColumns[Step.Completed] = column;
49+
// Written PnP already matches, but OBS calls it Record.
50+
// This is mislabeled depending on the methodology.
51+
matchedColumns.set(Step.Completed, column);
3152
return;
3253
}
33-
const distances = remainingSteps.map((step) => {
34-
const humanLabel = startCase(step).replace(' And ', ' & ');
35-
const distance = levenshtein.distance(label, humanLabel);
36-
return [step, distance] as const;
37-
});
38-
// Pick the step that is the closest fuzzy match
39-
const chosen = sortBy(
40-
// 5 is too far ignore those
41-
distances.filter(([_, distance]) => distance < 5),
42-
([_, distance]) => distance,
43-
)[0]?.[0];
54+
55+
const chosen = chooseStep(label, remainingSteps);
4456
if (!chosen) {
4557
result?.addProblem(NonStandardStep, cell, { label });
4658
return;
4759
}
48-
matchedColumns[chosen] = column;
49-
50-
remainingSteps = without(remainingSteps, chosen);
60+
matchedColumns.set(chosen, column);
61+
remainingSteps.delete(chosen);
5162
});
52-
return matchedColumns as Record<Step, Column>;
63+
return matchedColumns as ReadonlyMap<Step, Column>;
5364
}
5465

66+
const chooseStep = (
67+
label: string,
68+
available: ReadonlySet<Step>,
69+
): Step | undefined => {
70+
const alias = ApprovedAliases.get(label.toLowerCase());
71+
if (alias) {
72+
return available.has(alias) ? alias : undefined;
73+
}
74+
75+
const distances = available.values().map((step) => {
76+
const humanLabel = startCase(step).replace(' And ', ' & ');
77+
const distance = levenshtein.distance(label, humanLabel);
78+
return { step, distance };
79+
});
80+
// Pick the step that is the closest fuzzy match
81+
const chosen = sortBy(
82+
// 5 is too far ignoring those
83+
distances.filter(({ distance }) => distance < 5),
84+
({ distance }) => distance,
85+
).at(0);
86+
return chosen?.step;
87+
};
88+
5589
const NonStandardStep = PnpProblemType.register({
5690
name: 'NonStandardStep',
5791
severity: 'Error',

src/components/product-progress/step-progress-extractor.service.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ export class StepProgressExtractor {
6666
const parseProgressRow =
6767
(
6868
pnp: Pnp,
69-
stepColumns: Record<Step, Column>,
70-
planningStepColumns: Record<Step, Column>,
69+
stepColumns: ReadonlyMap<Step, Column>,
70+
planningStepColumns: ReadonlyMap<Step, Column>,
7171
result: PnpProgressExtractionResult,
7272
) =>
7373
(cell: Cell<ProgressSheet>, index: number): ExtractedRow => {
@@ -81,7 +81,7 @@ const parseProgressRow =
8181
const steps = entries(stepColumns).flatMap<StepProgressInput>(
8282
([step, column]) => {
8383
const fiscalYear = pnp.planning.cell(
84-
planningStepColumns[step],
84+
planningStepColumns.get(step)!,
8585
planningRow,
8686
);
8787

src/components/product/product.extractor.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ export class ProductExtractor {
8181
const parseProductRow =
8282
(
8383
pnp: Pnp,
84-
stepColumns: Record<Step, Column>,
85-
progressStepColumns: Record<Step, Column>,
84+
stepColumns: ReadonlyMap<Step, Column>,
85+
progressStepColumns: ReadonlyMap<Step, Column>,
8686
result: PnpPlanningExtractionResult,
8787
) =>
8888
(cell: Cell<PlanningSheet>, index: number): ExtractedRow => {
@@ -94,7 +94,7 @@ const parseProductRow =
9494
const steps = entries(stepColumns).flatMap(([step, column]) => {
9595
const plannedCell = sheet.cell(column, row);
9696
const progressCell = pnp.progress.cell(
97-
progressStepColumns[step],
97+
progressStepColumns.get(step)!,
9898
progressRow,
9999
);
100100

0 commit comments

Comments
 (0)