Skip to content

Commit 60cbb00

Browse files
committed
fix(web): improve number formatting and history validation
- Use significant figures for very small decimals (< 0.0001) to prevent values like 0.0000366 from displaying as "0" - Add entity ID/URL path validation to filter corrupted history entries where entityId doesn't match the URL destination
1 parent af472f8 commit 60cbb00

File tree

2 files changed

+37
-0
lines changed

2 files changed

+37
-0
lines changed

apps/web/src/utils/format-number.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ const DECIMAL_PRECISION_FIELD_PATTERNS = ["score", "share", "percentile", "fwci"
2626
*/
2727
const DECIMAL_PRECISION_DIGITS = 4;
2828

29+
/**
30+
* Threshold below which we use significant figures instead of fixed decimals
31+
* Values smaller than this would round to 0 with DECIMAL_PRECISION_DIGITS
32+
*/
33+
const SMALL_VALUE_THRESHOLD = 0.0001;
34+
2935
/**
3036
* Check if a field name indicates a year value
3137
* @param fieldName - The field name to check
@@ -83,6 +89,10 @@ export const formatNumber = (value: number, fieldName?: string): string => {
8389

8490
// For score/share/percentile fields, preserve decimal precision
8591
if (fieldName && isDecimalPrecisionField(fieldName) && !Number.isInteger(value)) {
92+
// For very small values, use significant figures to avoid showing "0"
93+
if (Math.abs(value) > 0 && Math.abs(value) < SMALL_VALUE_THRESHOLD) {
94+
return value.toPrecision(2);
95+
}
8696
return value.toLocaleString(undefined, {
8797
minimumFractionDigits: 0,
8898
maximumFractionDigits: DECIMAL_PRECISION_DIGITS,

packages/utils/src/storage/catalogue-db.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1084,13 +1084,40 @@ export class CatalogueService {
10841084
// Runtime cleanup: filter out corrupted entries that may have been created after migration
10851085
// Check both entityId and notes (which contains URL) for corruption patterns
10861086
const urlEncodedPattern = "[object%20Object]";
1087+
1088+
// Entity ID prefix to URL path mapping for validation
1089+
const entityPrefixToPath: Record<string, string> = {
1090+
W: "/works/",
1091+
A: "/authors/",
1092+
I: "/institutions/",
1093+
S: "/sources/",
1094+
P: "/publishers/",
1095+
F: "/funders/",
1096+
T: "/topics/",
1097+
C: "/concepts/",
1098+
};
1099+
10871100
return entities.filter((entity) => {
10881101
if (entity.entityId.length === 0) return false;
10891102
if (entity.entityId.includes(CORRUPTED_ENTITY_ID_PATTERN)) return false;
10901103
if (entity.entityId.includes(urlEncodedPattern)) return false;
10911104
// Also check notes for corrupted URLs
10921105
if (entity.notes?.includes(CORRUPTED_ENTITY_ID_PATTERN)) return false;
10931106
if (entity.notes?.includes(urlEncodedPattern)) return false;
1107+
1108+
// Validate entityId matches URL path (detect mismatch like Work ID with Author URL)
1109+
const urlMatch = entity.notes?.match(/URL: ([^\n]+)/);
1110+
if (urlMatch) {
1111+
const url = urlMatch[1];
1112+
const entityPrefix = entity.entityId.charAt(0).toUpperCase();
1113+
const expectedPath = entityPrefixToPath[entityPrefix];
1114+
// If we have a known prefix and URL doesn't match expected path, filter it out
1115+
// Skip validation for non-entity pages like /about, /settings
1116+
if (expectedPath && !url.includes(expectedPath) && !url.startsWith("/about") && !url.startsWith("/settings")) {
1117+
return false;
1118+
}
1119+
}
1120+
10941121
return true;
10951122
});
10961123
}

0 commit comments

Comments
 (0)