Skip to content

Commit 77dc7c0

Browse files
authored
Merge pull request #24 from ConnerWithAnE/levenshtein
feat: use levenshtein distance for long strings
2 parents 60540f3 + 9414e9f commit 77dc7c0

File tree

5 files changed

+141
-38
lines changed

5 files changed

+141
-38
lines changed

web/package-lock.json

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,17 @@
1616
"@nextui-org/system": "^2.2.6",
1717
"@nextui-org/theme": "^2.2.11",
1818
"@types/react-icons": "^3.0.0",
19+
"fastest-levenshtein": "^1.0.16",
1920
"framer-motion": "^11.13.1",
2021
"react": "^18.3.1",
2122
"react-dom": "^18.3.1",
23+
"react-icons": "^5.4.0",
2224
"react-router-dom": "^7.0.1",
23-
"reacticons": "^0.0.1",
24-
"react-icons": "^5.4.0"
25+
"reacticons": "^0.0.1"
2526
},
2627
"devDependencies": {
2728
"@eslint/js": "^9.15.0",
29+
"@types/node": "^22.13.13",
2830
"@types/react": "^18.3.12",
2931
"@types/react-dom": "^18.3.1",
3032
"@vitejs/plugin-react": "^4.3.4",

web/src/components/database-entries/entry-sliver.tsx

Lines changed: 78 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { useForm } from "../../DataContext";
2424
import { HiCheckCircle } from "react-icons/hi2";
2525
import { HiExclamationTriangle } from "react-icons/hi2";
2626
import { HiExclamationCircle } from "react-icons/hi2";
27+
import { compareStringPasses } from "../../types/levenshtein-distance";
2728

2829
type EntrySliverProp = {
2930
gptPass: GPTResponse;
@@ -158,13 +159,23 @@ export default function EntrySliver({
158159
const tests_2 = pass_2[i][typesafeKey];
159160
const tests_3 = pass_3[i][typesafeKey];
160161

161-
// if the key is special_notes, just fill in one value and return
162-
// this is a compromise, as special_notes is often one long string and hard to compare
162+
// the 'special_notes' field is a long string and thus should probably be compared using a different method
163163
if (typesafeKey === "special_notes") {
164-
updatedTest = {
165-
...updatedTest,
166-
[typesafeKey]: tests_1,
167-
};
164+
const result = compareStringPasses(tests_1, tests_2, tests_3);
165+
if (result.pass) {
166+
updatedTest = {
167+
...updatedTest,
168+
[typesafeKey]: result.pass,
169+
};
170+
}
171+
if (result.severity) {
172+
addConflict2(
173+
updatedConflicts,
174+
`parts-${testIndex}-sees-${i}-${key}`,
175+
result.severity
176+
);
177+
}
178+
168179
return;
169180
}
170181

@@ -238,13 +249,22 @@ export default function EntrySliver({
238249
const tests_2 = pass_2[i][typesafeKey];
239250
const tests_3 = pass_3[i][typesafeKey];
240251

241-
// if the key is special_notes, just fill in one value and return
242-
// this is a compromise, as special_notes is often one long string and hard to compare
252+
// the 'special_notes' field is a long string and thus should probably be compared using a different method
243253
if (typesafeKey === "special_notes") {
244-
updatedTest = {
245-
...updatedTest,
246-
[typesafeKey]: tests_1,
247-
};
254+
const result = compareStringPasses(tests_1, tests_2, tests_3);
255+
if (result.pass) {
256+
updatedTest = {
257+
...updatedTest,
258+
[typesafeKey]: result.pass,
259+
};
260+
}
261+
if (result.severity) {
262+
addConflict2(
263+
updatedConflicts,
264+
`parts-${testIndex}-sees-${i}-${key}`,
265+
result.severity
266+
);
267+
}
248268
return;
249269
}
250270

@@ -318,16 +338,25 @@ export default function EntrySliver({
318338
const tests_2 = pass_2[i][typesafeKey];
319339
const tests_3 = pass_3[i][typesafeKey];
320340

321-
// if the key is special_notes, just fill in one value and return
322-
// this is a compromise, as special_notes is often one long string and hard to compare
341+
// the 'special_notes' field is a long string and thus should probably be compared using a different method
323342
if (typesafeKey === "special_notes") {
324-
updatedTest = {
325-
...updatedTest,
326-
[typesafeKey]: tests_1,
327-
};
343+
const result = compareStringPasses(tests_1, tests_2, tests_3);
344+
if (result.pass) {
345+
updatedTest = {
346+
...updatedTest,
347+
[typesafeKey]: result.pass,
348+
};
349+
}
350+
if (result.severity) {
351+
addConflict2(
352+
updatedConflicts,
353+
`parts-${testIndex}-sees-${i}-${key}`,
354+
result.severity
355+
);
356+
}
357+
328358
return;
329359
}
330-
331360
if (
332361
tests_1 === tests_2 &&
333362
tests_1 === tests_3 &&
@@ -447,13 +476,24 @@ export default function EntrySliver({
447476
};
448477
return;
449478
}
450-
// if the key is other_details, just fill in one value and return
451-
// this is a compromise, as other_details is often one long string and hard to compare
479+
480+
// the 'other_details' field is a long string and thus should probably be compared using a different method
452481
if (typesafeKey === "other_details") {
453-
updatedPart = {
454-
...updatedPart,
455-
[typesafeKey]: parts_1,
456-
};
482+
const result = compareStringPasses(parts_1, parts_2, parts_3);
483+
if (result.pass) {
484+
updatedPart = {
485+
...updatedPart,
486+
[typesafeKey]: result.pass,
487+
};
488+
}
489+
if (result.severity) {
490+
addConflict2(
491+
updatedConflicts,
492+
`parts-${i}-${key}`,
493+
result.severity
494+
);
495+
}
496+
457497
return;
458498
}
459499

@@ -544,12 +584,20 @@ export default function EntrySliver({
544584
if (editedEntry[typesafeKey] !== undefined) {
545585
return;
546586
}
547-
// the 'objective' field is a long string and thus cannot be compared via string comparison
587+
// the 'objective' field is a long string and thus should probably be compared using a different method
548588
if (typesafeKey === "objective") {
549-
updatedEntry = {
550-
...updatedEntry,
551-
objective: pass_1,
552-
};
589+
const result = compareStringPasses(pass_1, pass_2, pass_3);
590+
591+
if (result.pass) {
592+
updatedEntry = {
593+
...updatedEntry,
594+
objective: result.pass,
595+
};
596+
}
597+
if (result.severity) {
598+
addConflict2(updatedConflicts, key, result.severity);
599+
}
600+
553601
return;
554602
}
555603
// Compare the part information in the passes

web/src/mockfulldatatype.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -818,7 +818,7 @@ export const mockFullDataTypePasses5: FullDataType[] = [
818818
increased_power_usage: false,
819819
power_usage_description: "string",
820820
special_notes:
821-
"This is a test to make sure that the rendering works",
821+
"Testing included latchup protection circuitry to prevent destructive latchup.",
822822
},
823823
{
824824
source: "Co60",
@@ -829,7 +829,7 @@ export const mockFullDataTypePasses5: FullDataType[] = [
829829
increased_power_usage: false,
830830
power_usage_description: "string",
831831
special_notes:
832-
"This is a test to make sure that the rendering works",
832+
"Rated as moderate risk due to low predicted event rates and/or non-destructive nature of observed latchups",
833833
},
834834
],
835835
sees: [
@@ -959,7 +959,7 @@ export const mockFullDataTypePasses5: FullDataType[] = [
959959
increased_power_usage: false,
960960
power_usage_description: "string",
961961
special_notes:
962-
"This is a test to make sure that the rendering works",
962+
"Testing included latchup protection circuitry to prevent destructive latchup.",
963963
},
964964
{
965965
source: "Co60",
@@ -970,7 +970,7 @@ export const mockFullDataTypePasses5: FullDataType[] = [
970970
increased_power_usage: false,
971971
power_usage_description: "string",
972972
special_notes:
973-
"This is a test to make sure that the rendering works",
973+
"Rated as moderate risk due to non-destructive nature of observed latchups and/or low predicted event rates",
974974
},
975975
],
976976
sees: [
@@ -1100,7 +1100,7 @@ export const mockFullDataTypePasses5: FullDataType[] = [
11001100
increased_power_usage: false,
11011101
power_usage_description: "string",
11021102
special_notes:
1103-
"This is a test to make sure that the rendering works",
1103+
"Testing included latchup protection circuitry to prevent destructive latchup.",
11041104
},
11051105
{
11061106
source: "Co60",
@@ -1111,7 +1111,7 @@ export const mockFullDataTypePasses5: FullDataType[] = [
11111111
increased_power_usage: false,
11121112
power_usage_description: "string",
11131113
special_notes:
1114-
"This is a test to make sure that the rendering works",
1114+
"Determined to have moderate risk because of non-destructive nature of observed latchups and/or low predicted event rates",
11151115
},
11161116
],
11171117
sees: [
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { distance } from "fastest-levenshtein";
2+
import { Severity } from "./types";
3+
4+
export function compareStringPasses(
5+
a: string,
6+
b: string,
7+
c: string
8+
): { pass?: string; severity?: Severity } {
9+
const distance1 = distance(a, b);
10+
const distance2 = distance(b, c);
11+
const distance3 = distance(a, c);
12+
13+
// all passes are similar enough
14+
if (distance1 < 5 && distance2 < 5 && distance3 < 5) {
15+
return { pass: a };
16+
}
17+
// if two passes are similar enough
18+
if ((distance1 < 5 && distance3 < 5) || (distance1 < 5 && distance2 < 5)) {
19+
return { pass: a, severity: 1 };
20+
}
21+
if (distance2 < 5 && distance3 < 5) {
22+
return { pass: b, severity: 1 };
23+
}
24+
return { severity: 2 };
25+
}

0 commit comments

Comments
 (0)