@jjlmoya/utils-forensic-science 1.20.0 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/package.json +2 -2
  2. package/scripts/validate-icons.mjs +57 -0
  3. package/src/category/index.ts +10 -10
  4. package/src/entries.ts +26 -25
  5. package/src/index.ts +7 -6
  6. package/src/tests/locale_completeness.test.ts +23 -23
  7. package/src/tests/tool_validation.test.ts +16 -16
  8. package/src/tool/forensic-evidence-scale-calculator/bibliography.astro +9 -0
  9. package/src/tool/forensic-evidence-scale-calculator/bibliography.ts +16 -0
  10. package/src/tool/forensic-evidence-scale-calculator/component.astro +154 -0
  11. package/src/tool/forensic-evidence-scale-calculator/entry.ts +29 -0
  12. package/src/tool/forensic-evidence-scale-calculator/forensic-evidence-scale-calculator.css +516 -0
  13. package/src/tool/forensic-evidence-scale-calculator/i18n/de.ts +3 -0
  14. package/src/tool/forensic-evidence-scale-calculator/i18n/en.ts +299 -0
  15. package/src/tool/forensic-evidence-scale-calculator/i18n/es.ts +3 -0
  16. package/src/tool/forensic-evidence-scale-calculator/i18n/fr.ts +3 -0
  17. package/src/tool/forensic-evidence-scale-calculator/i18n/id.ts +3 -0
  18. package/src/tool/forensic-evidence-scale-calculator/i18n/it.ts +3 -0
  19. package/src/tool/forensic-evidence-scale-calculator/i18n/ja.ts +3 -0
  20. package/src/tool/forensic-evidence-scale-calculator/i18n/ko.ts +3 -0
  21. package/src/tool/forensic-evidence-scale-calculator/i18n/localized-content.ts +1537 -0
  22. package/src/tool/forensic-evidence-scale-calculator/i18n/nl.ts +3 -0
  23. package/src/tool/forensic-evidence-scale-calculator/i18n/pl.ts +3 -0
  24. package/src/tool/forensic-evidence-scale-calculator/i18n/pt.ts +3 -0
  25. package/src/tool/forensic-evidence-scale-calculator/i18n/ru.ts +3 -0
  26. package/src/tool/forensic-evidence-scale-calculator/i18n/sv.ts +3 -0
  27. package/src/tool/forensic-evidence-scale-calculator/i18n/tr.ts +3 -0
  28. package/src/tool/forensic-evidence-scale-calculator/i18n/zh.ts +3 -0
  29. package/src/tool/forensic-evidence-scale-calculator/index.ts +11 -0
  30. package/src/tool/forensic-evidence-scale-calculator/logic.test.ts +55 -0
  31. package/src/tool/forensic-evidence-scale-calculator/logic.ts +107 -0
  32. package/src/tool/forensic-evidence-scale-calculator/seo.astro +10 -0
  33. package/src/tool/forensic-evidence-scale-calculator/ui.ts +3 -0
  34. package/src/tool/forensic-evidence-scale-calculator/unit-selector.ts +120 -0
  35. package/src/tool/forensic-evidence-scale-calculator/view.ts +203 -0
  36. package/src/tools.ts +8 -6
@@ -0,0 +1,3 @@
1
+ import { createLocalizedContent } from "./localized-content";
2
+
3
+ export const content = createLocalizedContent("nl");
@@ -0,0 +1,3 @@
1
+ import { createLocalizedContent } from "./localized-content";
2
+
3
+ export const content = createLocalizedContent("pl");
@@ -0,0 +1,3 @@
1
+ import { createLocalizedContent } from "./localized-content";
2
+
3
+ export const content = createLocalizedContent("pt");
@@ -0,0 +1,3 @@
1
+ import { createLocalizedContent } from "./localized-content";
2
+
3
+ export const content = createLocalizedContent("ru");
@@ -0,0 +1,3 @@
1
+ import { createLocalizedContent } from "./localized-content";
2
+
3
+ export const content = createLocalizedContent("sv");
@@ -0,0 +1,3 @@
1
+ import { createLocalizedContent } from "./localized-content";
2
+
3
+ export const content = createLocalizedContent("tr");
@@ -0,0 +1,3 @@
1
+ import { createLocalizedContent } from "./localized-content";
2
+
3
+ export const content = createLocalizedContent("zh");
@@ -0,0 +1,11 @@
1
+ import { forensicEvidenceScaleCalculator } from "./entry";
2
+ import type { ToolDefinition } from "../../types";
3
+
4
+ export * from "./entry";
5
+
6
+ export const FORENSIC_EVIDENCE_SCALE_CALCULATOR_TOOL: ToolDefinition = {
7
+ entry: forensicEvidenceScaleCalculator,
8
+ Component: () => import("./component.astro"),
9
+ SEOComponent: () => import("./seo.astro"),
10
+ BibliographyComponent: () => import("./bibliography.astro"),
11
+ };
@@ -0,0 +1,55 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { calculateEvidenceScale, formatMeasurement } from "./logic";
3
+
4
+ describe("calculateEvidenceScale", () => {
5
+ it("converts image pixels into a real-world size and propagates uncertainty", () => {
6
+ const result = calculateEvidenceScale({
7
+ referenceRealSize: 25,
8
+ referencePixelLength: 200,
9
+ evidencePixelLength: 80,
10
+ uncertaintyPercent: 5,
11
+ unit: "mm",
12
+ samePlane: true,
13
+ });
14
+ expect(result.valid).toBe(true);
15
+ expect(result.scalePerPixel).toBe(0.125);
16
+ expect(result.evidenceSize).toBe(10);
17
+ expect(result.uncertainty).toBe(0.5);
18
+ expect(result.lowerBound).toBe(9.5);
19
+ expect(result.upperBound).toBe(10.5);
20
+ expect(result.warnings).toEqual([]);
21
+ });
22
+
23
+ it("keeps the selected unit in the formula and warns about different planes", () => {
24
+ const result = calculateEvidenceScale({
25
+ referenceRealSize: 10,
26
+ referencePixelLength: 500,
27
+ evidencePixelLength: 125,
28
+ uncertaintyPercent: 2,
29
+ unit: "cm",
30
+ samePlane: false,
31
+ });
32
+ expect(result.formula).toBe("125 px x (10 cm / 500 px)");
33
+ expect(result.evidenceSize).toBe(2.5);
34
+ expect(result.warnings).toEqual(["perspective"]);
35
+ expect(formatMeasurement(result.evidenceSize, result.unit)).toBe("2.5 cm");
36
+ });
37
+
38
+ it("rejects missing or unsafe measurements", () => {
39
+ const result = calculateEvidenceScale({
40
+ referenceRealSize: 0,
41
+ referencePixelLength: 0,
42
+ evidencePixelLength: -1,
43
+ uncertaintyPercent: 101,
44
+ unit: "mm",
45
+ samePlane: true,
46
+ });
47
+ expect(result.valid).toBe(false);
48
+ expect(result.errors).toEqual([
49
+ "referenceRealSize",
50
+ "referencePixelLength",
51
+ "evidencePixelLength",
52
+ "uncertaintyPercent",
53
+ ]);
54
+ });
55
+ });
@@ -0,0 +1,107 @@
1
+ export type MeasurementUnit = "mm" | "cm" | "in";
2
+
3
+ export interface ScaleInput {
4
+ referenceRealSize: number;
5
+ referencePixelLength: number;
6
+ evidencePixelLength: number;
7
+ uncertaintyPercent: number;
8
+ unit: MeasurementUnit;
9
+ samePlane: boolean;
10
+ }
11
+
12
+ export interface ScaleResult {
13
+ valid: boolean;
14
+ errors: string[];
15
+ warnings: string[];
16
+ scalePerPixel: number;
17
+ evidenceSize: number;
18
+ uncertainty: number;
19
+ lowerBound: number;
20
+ upperBound: number;
21
+ formula: string;
22
+ unit: MeasurementUnit;
23
+ }
24
+
25
+ export const UNIT_LABELS: Record<MeasurementUnit, string> = {
26
+ mm: "mm",
27
+ cm: "cm",
28
+ in: "in",
29
+ };
30
+ const UNITS: MeasurementUnit[] = ["mm", "cm", "in"];
31
+ const isFinitePositive = (value: number): boolean =>
32
+ Number.isFinite(value) && value > 0;
33
+ const isUnit = (value: string): value is MeasurementUnit =>
34
+ UNITS.includes(value as MeasurementUnit);
35
+ const round = (value: number, decimals = 6): number =>
36
+ Number(value.toFixed(decimals));
37
+
38
+ export const formatMeasurement = (
39
+ value: number,
40
+ unit: MeasurementUnit,
41
+ ): string => {
42
+ if (!Number.isFinite(value)) return "-";
43
+ const absolute = Math.abs(value);
44
+ let decimals = 3;
45
+ if (absolute >= 100) decimals = 0;
46
+ else if (absolute >= 10) decimals = 2;
47
+ return `${value.toFixed(decimals).replace(/\.0+$|(?<=\.\d)0+$/, "")} ${UNIT_LABELS[unit]}`;
48
+ };
49
+
50
+ const validateInput = (input: ScaleInput): string[] => {
51
+ const errors: string[] = [];
52
+ if (!isFinitePositive(input.referenceRealSize))
53
+ errors.push("referenceRealSize");
54
+ if (!isFinitePositive(input.referencePixelLength))
55
+ errors.push("referencePixelLength");
56
+ if (!isFinitePositive(input.evidencePixelLength))
57
+ errors.push("evidencePixelLength");
58
+ if (
59
+ !Number.isFinite(input.uncertaintyPercent) ||
60
+ input.uncertaintyPercent < 0 ||
61
+ input.uncertaintyPercent > 100
62
+ ) {
63
+ errors.push("uncertaintyPercent");
64
+ }
65
+ return errors;
66
+ };
67
+
68
+ const emptyResult = (
69
+ unit: MeasurementUnit,
70
+ errors: string[],
71
+ warnings: string[],
72
+ ): ScaleResult => ({
73
+ valid: false,
74
+ errors,
75
+ warnings,
76
+ scalePerPixel: 0,
77
+ evidenceSize: 0,
78
+ uncertainty: 0,
79
+ lowerBound: 0,
80
+ upperBound: 0,
81
+ formula: "-",
82
+ unit,
83
+ });
84
+
85
+ export const calculateEvidenceScale = (input: ScaleInput): ScaleResult => {
86
+ const unit = isUnit(input.unit) ? input.unit : "mm";
87
+ const errors = validateInput(input);
88
+ const warnings = input.samePlane ? [] : ["perspective"];
89
+
90
+ if (errors.length > 0) return emptyResult(unit, errors, warnings);
91
+
92
+ const scalePerPixel = input.referenceRealSize / input.referencePixelLength;
93
+ const evidenceSize = input.evidencePixelLength * scalePerPixel;
94
+ const uncertainty = evidenceSize * (input.uncertaintyPercent / 100);
95
+ return {
96
+ valid: true,
97
+ errors,
98
+ warnings,
99
+ scalePerPixel: round(scalePerPixel),
100
+ evidenceSize: round(evidenceSize),
101
+ uncertainty: round(uncertainty),
102
+ lowerBound: round(Math.max(0, evidenceSize - uncertainty)),
103
+ upperBound: round(evidenceSize + uncertainty),
104
+ formula: `${input.evidencePixelLength} px x (${input.referenceRealSize} ${unit} / ${input.referencePixelLength} px)`,
105
+ unit,
106
+ };
107
+ };
@@ -0,0 +1,10 @@
1
+ ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { forensicEvidenceScaleCalculator } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+ interface Props { locale?: KnownLocale; }
6
+ const { locale = 'en' } = Astro.props;
7
+ const content = await forensicEvidenceScaleCalculator.i18n[locale]?.();
8
+ if (!content) return null;
9
+ ---
10
+ {content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
@@ -0,0 +1,3 @@
1
+ export interface ForensicEvidenceScaleUI {
2
+ [key: string]: string;
3
+ }
@@ -0,0 +1,120 @@
1
+ import type { MeasurementUnit } from "./logic";
2
+
3
+ interface UnitSelector {
4
+ choice: HTMLElement;
5
+ trigger: HTMLButtonElement;
6
+ label: HTMLElement;
7
+ menu: HTMLElement;
8
+ options: HTMLButtonElement[];
9
+ }
10
+
11
+ const getElement = <T extends Element>(
12
+ root: ParentNode,
13
+ selector: string,
14
+ ): T | null => root.querySelector<T>(selector);
15
+
16
+ const getSelector = (root: HTMLElement): UnitSelector | null => {
17
+ const choice = getElement<HTMLElement>(root, "[data-unit-choice]");
18
+ const trigger = getElement<HTMLButtonElement>(root, "[data-unit-trigger]");
19
+ const label = getElement<HTMLElement>(root, "[data-unit-label]");
20
+ const menu = getElement<HTMLElement>(root, "[data-unit-menu]");
21
+ if (!choice || !trigger || !label || !menu) return null;
22
+ return {
23
+ choice,
24
+ trigger,
25
+ label,
26
+ menu,
27
+ options: Array.from(
28
+ menu.querySelectorAll<HTMLButtonElement>("[data-unit-option]"),
29
+ ),
30
+ };
31
+ };
32
+
33
+ const setUnit = (selector: UnitSelector, unit: MeasurementUnit): void => {
34
+ const hidden = getElement<HTMLInputElement>(selector.choice, '[name="unit"]');
35
+ if (hidden) hidden.value = unit;
36
+ selector.options.forEach((option) => {
37
+ const selected = option.dataset.unitOption === unit;
38
+ option.setAttribute("aria-selected", String(selected));
39
+ if (selected) selector.label.textContent = option.textContent?.trim() ?? "";
40
+ });
41
+ };
42
+
43
+ const bindTrigger = (
44
+ selector: UnitSelector,
45
+ open: () => void,
46
+ close: () => void,
47
+ ): void => {
48
+ selector.trigger.addEventListener("click", () => {
49
+ if (selector.menu.hidden) open();
50
+ else close();
51
+ });
52
+ selector.trigger.addEventListener("keydown", (event) => {
53
+ if (event.key !== "ArrowDown" && event.key !== "Enter" && event.key !== " ")
54
+ return;
55
+ event.preventDefault();
56
+ open();
57
+ selector.options[0]?.focus();
58
+ });
59
+ };
60
+
61
+ interface OptionContext {
62
+ selector: UnitSelector;
63
+ close: () => void;
64
+ onChange: () => void;
65
+ }
66
+
67
+ const bindOption = (
68
+ { selector, close, onChange }: OptionContext,
69
+ option: HTMLButtonElement,
70
+ index: number,
71
+ ): void => {
72
+ option.addEventListener("click", () => {
73
+ setUnit(selector, option.dataset.unitOption as MeasurementUnit);
74
+ close();
75
+ onChange();
76
+ selector.trigger.focus();
77
+ });
78
+ option.addEventListener("keydown", (event) => {
79
+ if (event.key === "Escape") {
80
+ event.preventDefault();
81
+ close();
82
+ selector.trigger.focus();
83
+ } else if (event.key === "ArrowDown" || event.key === "ArrowUp") {
84
+ event.preventDefault();
85
+ const direction = event.key === "ArrowDown" ? 1 : -1;
86
+ selector.options[
87
+ (index + direction + selector.options.length) % selector.options.length
88
+ ]?.focus();
89
+ }
90
+ });
91
+ };
92
+
93
+ export const setupUnitSelector = (
94
+ root: HTMLElement,
95
+ onChange: () => void,
96
+ ): void => {
97
+ const selector = getSelector(root);
98
+ if (!selector || selector.options.length === 0) return;
99
+ const close = (): void => {
100
+ selector.menu.hidden = true;
101
+ selector.trigger.setAttribute("aria-expanded", "false");
102
+ };
103
+ const open = (): void => {
104
+ selector.menu.hidden = false;
105
+ selector.trigger.setAttribute("aria-expanded", "true");
106
+ };
107
+ bindTrigger(selector, open, close);
108
+ selector.options.forEach((option, index) =>
109
+ bindOption({ selector, close, onChange }, option, index),
110
+ );
111
+ root.addEventListener("click", (event) => {
112
+ if (event.target instanceof Node && !selector.choice.contains(event.target))
113
+ close();
114
+ });
115
+ const initialUnit = getElement<HTMLInputElement>(
116
+ root,
117
+ '[name="unit"]',
118
+ )?.value;
119
+ setUnit(selector, (initialUnit ?? "mm") as MeasurementUnit);
120
+ };
@@ -0,0 +1,203 @@
1
+ import {
2
+ calculateEvidenceScale,
3
+ formatMeasurement,
4
+ type MeasurementUnit,
5
+ type ScaleInput,
6
+ } from "./logic";
7
+ import type { ForensicEvidenceScaleUI } from "./ui";
8
+ import { setupUnitSelector } from "./unit-selector";
9
+
10
+ type ScaleResult = ReturnType<typeof calculateEvidenceScale>;
11
+
12
+ const getElement = <T extends Element>(
13
+ root: ParentNode,
14
+ selector: string,
15
+ ): T | null => root.querySelector<T>(selector);
16
+
17
+ const readNumber = (root: ParentNode, name: string): number =>
18
+ Number(getElement<HTMLInputElement>(root, `[name="${name}"]`)?.value ?? NaN);
19
+
20
+ const readInput = (root: HTMLElement): ScaleInput => ({
21
+ referenceRealSize: readNumber(root, "referenceRealSize"),
22
+ referencePixelLength: readNumber(root, "referencePixelLength"),
23
+ evidencePixelLength: readNumber(root, "evidencePixelLength"),
24
+ uncertaintyPercent: readNumber(root, "uncertaintyPercent"),
25
+ unit: (getElement<HTMLInputElement>(root, '[name="unit"]')?.value ??
26
+ "mm") as MeasurementUnit,
27
+ samePlane:
28
+ getElement<HTMLInputElement>(root, '[name="samePlane"]')?.checked ?? false,
29
+ });
30
+
31
+ const setText = (root: ParentNode, selector: string, text: string): void => {
32
+ const element = getElement<HTMLElement>(root, selector);
33
+ if (element) element.textContent = text;
34
+ };
35
+
36
+ const uiText = (ui: ForensicEvidenceScaleUI, key: string): string =>
37
+ ui[key] ?? "";
38
+
39
+ interface ReadoutContext {
40
+ ui: ForensicEvidenceScaleUI;
41
+ result: ScaleResult;
42
+ unit: MeasurementUnit;
43
+ uncertaintyPercent: number;
44
+ }
45
+
46
+ const readoutValue = (
47
+ result: ScaleResult,
48
+ validText: string,
49
+ fallback: string,
50
+ ): string => (result.valid ? validText : fallback);
51
+
52
+ const updateMainReadouts = (
53
+ root: HTMLElement,
54
+ context: ReadoutContext,
55
+ ): void => {
56
+ const { ui, result } = context;
57
+ setText(
58
+ root,
59
+ '[data-result="size"]',
60
+ readoutValue(
61
+ result,
62
+ formatMeasurement(result.evidenceSize, result.unit),
63
+ uiText(ui, "notCalculated"),
64
+ ),
65
+ );
66
+ setText(
67
+ root,
68
+ '[data-result="interval"]',
69
+ readoutValue(
70
+ result,
71
+ `${formatMeasurement(result.lowerBound, result.unit)} - ${formatMeasurement(result.upperBound, result.unit)}`,
72
+ uiText(ui, "enterValues"),
73
+ ),
74
+ );
75
+ };
76
+
77
+ const updateScaleReadouts = (
78
+ root: HTMLElement,
79
+ context: ReadoutContext,
80
+ ): void => {
81
+ const { result, unit, uncertaintyPercent } = context;
82
+ setText(
83
+ root,
84
+ '[data-result="scale"]',
85
+ readoutValue(result, `${result.scalePerPixel} ${unit}/px`, "-"),
86
+ );
87
+ setText(
88
+ root,
89
+ '[data-result="margin"]',
90
+ readoutValue(result, `${uncertaintyPercent}%`, "-"),
91
+ );
92
+ };
93
+
94
+ const updateTraceReadouts = (
95
+ root: HTMLElement,
96
+ context: ReadoutContext,
97
+ ): void => {
98
+ const { ui, result } = context;
99
+ setText(
100
+ root,
101
+ '[data-result="formula"]',
102
+ readoutValue(result, result.formula, uiText(ui, "formulaPending")),
103
+ );
104
+ setText(
105
+ root,
106
+ '[data-result="message"]',
107
+ readoutValue(
108
+ result,
109
+ uiText(ui, "calculatedMessage"),
110
+ uiText(ui, "enterValues"),
111
+ ),
112
+ );
113
+ };
114
+
115
+ const updateReadouts = (root: HTMLElement, context: ReadoutContext): void => {
116
+ updateMainReadouts(root, context);
117
+ updateScaleReadouts(root, context);
118
+ updateTraceReadouts(root, context);
119
+ };
120
+
121
+ const updateWarning = (
122
+ root: HTMLElement,
123
+ ui: ForensicEvidenceScaleUI,
124
+ result: ScaleResult,
125
+ ): void => {
126
+ const warning = getElement<HTMLElement>(root, '[data-result="warning"]');
127
+ if (!warning) return;
128
+ warning.textContent =
129
+ result.warnings.length > 0
130
+ ? uiText(ui, "perspectiveWarning")
131
+ : uiText(ui, "samePlaneMessage");
132
+ warning.dataset.visible = result.warnings.length > 0 ? "true" : "false";
133
+ };
134
+
135
+ const referenceEnd = (input: ScaleInput): number =>
136
+ Math.min(
137
+ 520,
138
+ 220 +
139
+ Math.max(
140
+ 45,
141
+ (input.referencePixelLength / Math.max(input.evidencePixelLength, 1)) *
142
+ 70,
143
+ ),
144
+ );
145
+
146
+ const evidenceEnd = (input: ScaleInput): number =>
147
+ Math.min(
148
+ 560,
149
+ 250 +
150
+ Math.max(
151
+ 34,
152
+ (input.evidencePixelLength / Math.max(input.referencePixelLength, 1)) *
153
+ 260,
154
+ ),
155
+ );
156
+
157
+ const updateScene = (
158
+ root: HTMLElement,
159
+ input: ScaleInput,
160
+ result: ScaleResult,
161
+ ): void => {
162
+ const referenceLine = getElement<SVGLineElement>(
163
+ root,
164
+ '[data-scene="reference-line"]',
165
+ );
166
+ const evidenceLine = getElement<SVGLineElement>(
167
+ root,
168
+ '[data-scene="evidence-line"]',
169
+ );
170
+ const scene = getElement<HTMLElement>(root, "[data-scene]");
171
+ if (scene)
172
+ scene.dataset.warning = result.warnings.length > 0 ? "true" : "false";
173
+ if (!result.valid) return;
174
+ if (referenceLine)
175
+ referenceLine.setAttribute("x2", String(referenceEnd(input)));
176
+ if (evidenceLine) evidenceLine.setAttribute("x2", String(evidenceEnd(input)));
177
+ };
178
+
179
+ const render = (root: HTMLElement, ui: ForensicEvidenceScaleUI): void => {
180
+ const input = readInput(root);
181
+ const result = calculateEvidenceScale(input);
182
+ root.dataset.ready = result.valid ? "true" : "false";
183
+ updateReadouts(root, {
184
+ ui,
185
+ result,
186
+ unit: input.unit,
187
+ uncertaintyPercent: input.uncertaintyPercent,
188
+ });
189
+ updateWarning(root, ui, result);
190
+ updateScene(root, input, result);
191
+ };
192
+
193
+ export const initForensicEvidenceScale = (
194
+ root: HTMLElement,
195
+ ui: ForensicEvidenceScaleUI,
196
+ ): void => {
197
+ setupUnitSelector(root, () => render(root, ui));
198
+ root.querySelectorAll<HTMLInputElement>("input").forEach((field) => {
199
+ field.addEventListener("input", () => render(root, ui));
200
+ field.addEventListener("change", () => render(root, ui));
201
+ });
202
+ render(root, ui);
203
+ };
package/src/tools.ts CHANGED
@@ -15,9 +15,10 @@ import { BLOODSTAIN_PATTERN_ORIGIN_ANALYZER_TOOL } from './tool/bloodstain-patte
15
15
  import { FORENSIC_FINGERPRINT_MINUTIAE_IDENTIFIER_TOOL } from './tool/forensic-fingerprint-minutiae-identifier/index';
16
16
  import { FIRE_PATTERN_ORIGIN_ANALYZER_TOOL } from './tool/fire-pattern-origin-analyzer/index';
17
17
  import { FORENSIC_TOOLMARK_STRIATION_MATCHER_TOOL } from './tool/forensic-toolmark-striation-matcher/index';
18
- import { TIME_OF_DEATH_ALGOR_MORTIS_CALCULATOR_TOOL } from './tool/time-of-death-algor-mortis-calculator/index';
19
- import { VOICE_SPECTROGRAM_ANALYZER_TOOL } from './tool/voice-spectrogram-analyzer/index';
20
- import { DNA_PROFILE_MATCH_PROBABILITY_LAB_TOOL } from './tool/dna-profile-match-probability-lab/index';
18
+ import { TIME_OF_DEATH_ALGOR_MORTIS_CALCULATOR_TOOL } from './tool/time-of-death-algor-mortis-calculator/index';
19
+ import { VOICE_SPECTROGRAM_ANALYZER_TOOL } from './tool/voice-spectrogram-analyzer/index';
20
+ import { DNA_PROFILE_MATCH_PROBABILITY_LAB_TOOL } from './tool/dna-profile-match-probability-lab/index';
21
+ import { FORENSIC_EVIDENCE_SCALE_CALCULATOR_TOOL } from './tool/forensic-evidence-scale-calculator/index';
21
22
 
22
23
  export const ALL_TOOLS: ToolDefinition[] = [
23
24
  FORENSIC_AGE_ESTIMATOR_TOOL,
@@ -35,7 +36,8 @@ export const ALL_TOOLS: ToolDefinition[] = [
35
36
  FORENSIC_FINGERPRINT_MINUTIAE_IDENTIFIER_TOOL,
36
37
  FIRE_PATTERN_ORIGIN_ANALYZER_TOOL,
37
38
  FORENSIC_TOOLMARK_STRIATION_MATCHER_TOOL,
38
- TIME_OF_DEATH_ALGOR_MORTIS_CALCULATOR_TOOL,
39
- VOICE_SPECTROGRAM_ANALYZER_TOOL,
40
- DNA_PROFILE_MATCH_PROBABILITY_LAB_TOOL
39
+ TIME_OF_DEATH_ALGOR_MORTIS_CALCULATOR_TOOL,
40
+ VOICE_SPECTROGRAM_ANALYZER_TOOL,
41
+ DNA_PROFILE_MATCH_PROBABILITY_LAB_TOOL,
42
+ FORENSIC_EVIDENCE_SCALE_CALCULATOR_TOOL
41
43
  ];