@jjlmoya/utils-forensic-science 1.1.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 (316) hide show
  1. package/package.json +69 -0
  2. package/scripts/crystal-lattice-catalog-translations.mjs +459 -0
  3. package/scripts/postinstall.mjs +27 -0
  4. package/scripts/sync-crystal-lattice-i18n.mjs +50 -0
  5. package/src/category/i18n/de.ts +55 -0
  6. package/src/category/i18n/en.ts +55 -0
  7. package/src/category/i18n/es.ts +55 -0
  8. package/src/category/i18n/fr.ts +55 -0
  9. package/src/category/i18n/id.ts +55 -0
  10. package/src/category/i18n/it.ts +55 -0
  11. package/src/category/i18n/ja.ts +56 -0
  12. package/src/category/i18n/ko.ts +56 -0
  13. package/src/category/i18n/nl.ts +55 -0
  14. package/src/category/i18n/pl.ts +55 -0
  15. package/src/category/i18n/pt.ts +55 -0
  16. package/src/category/i18n/ru.ts +55 -0
  17. package/src/category/i18n/sv.ts +55 -0
  18. package/src/category/i18n/tr.ts +55 -0
  19. package/src/category/i18n/zh.ts +56 -0
  20. package/src/category/index.ts +48 -0
  21. package/src/category/seo.astro +15 -0
  22. package/src/components/PreviewNavSidebar.astro +116 -0
  23. package/src/components/PreviewToolbar.astro +143 -0
  24. package/src/data.ts +10 -0
  25. package/src/entries.ts +48 -0
  26. package/src/env.d.ts +5 -0
  27. package/src/index.ts +29 -0
  28. package/src/layouts/PreviewLayout.astro +117 -0
  29. package/src/pages/[locale]/[slug].astro +162 -0
  30. package/src/pages/[locale].astro +261 -0
  31. package/src/pages/index.astro +3 -0
  32. package/src/tests/diacritics_density.test.ts +140 -0
  33. package/src/tests/faq_count.test.ts +19 -0
  34. package/src/tests/i18n_coverage.test.ts +36 -0
  35. package/src/tests/inverted_punctuation.test.ts +84 -0
  36. package/src/tests/locale_completeness.test.ts +24 -0
  37. package/src/tests/mocks/astro_mock.js +2 -0
  38. package/src/tests/no_en_dash.test.ts +70 -0
  39. package/src/tests/no_h1_in_components.test.ts +48 -0
  40. package/src/tests/schemas_fulfillment.test.ts +23 -0
  41. package/src/tests/script_density.test.ts +94 -0
  42. package/src/tests/seo_length.test.ts +46 -0
  43. package/src/tests/shared-test-helpers.ts +56 -0
  44. package/src/tests/slug_language_code_format.test.ts +23 -0
  45. package/src/tests/slug_uniqueness.test.ts +81 -0
  46. package/src/tests/title_quality.test.ts +55 -0
  47. package/src/tests/tool_exports.test.ts +34 -0
  48. package/src/tests/tool_validation.test.ts +16 -0
  49. package/src/tests/widmark.test.ts +28 -0
  50. package/src/tool/forensic-age-estimator/bibliography.astro +14 -0
  51. package/src/tool/forensic-age-estimator/bibliography.ts +16 -0
  52. package/src/tool/forensic-age-estimator/component.astro +321 -0
  53. package/src/tool/forensic-age-estimator/dental-skeletal-third-molar-age-estimator.css +634 -0
  54. package/src/tool/forensic-age-estimator/entry.ts +32 -0
  55. package/src/tool/forensic-age-estimator/i18n/de.ts +245 -0
  56. package/src/tool/forensic-age-estimator/i18n/en.ts +245 -0
  57. package/src/tool/forensic-age-estimator/i18n/es.ts +245 -0
  58. package/src/tool/forensic-age-estimator/i18n/fr.ts +245 -0
  59. package/src/tool/forensic-age-estimator/i18n/id.ts +245 -0
  60. package/src/tool/forensic-age-estimator/i18n/it.ts +245 -0
  61. package/src/tool/forensic-age-estimator/i18n/ja.ts +245 -0
  62. package/src/tool/forensic-age-estimator/i18n/ko.ts +245 -0
  63. package/src/tool/forensic-age-estimator/i18n/nl.ts +245 -0
  64. package/src/tool/forensic-age-estimator/i18n/pl.ts +245 -0
  65. package/src/tool/forensic-age-estimator/i18n/pt.ts +245 -0
  66. package/src/tool/forensic-age-estimator/i18n/ru.ts +245 -0
  67. package/src/tool/forensic-age-estimator/i18n/sv.ts +245 -0
  68. package/src/tool/forensic-age-estimator/i18n/tr.ts +245 -0
  69. package/src/tool/forensic-age-estimator/i18n/zh.ts +246 -0
  70. package/src/tool/forensic-age-estimator/index.ts +11 -0
  71. package/src/tool/forensic-age-estimator/logic.ts +81 -0
  72. package/src/tool/forensic-age-estimator/seo.astro +15 -0
  73. package/src/tool/forensic-blood-test-simulator/bibliography.astro +6 -0
  74. package/src/tool/forensic-blood-test-simulator/bibliography.ts +12 -0
  75. package/src/tool/forensic-blood-test-simulator/component.astro +168 -0
  76. package/src/tool/forensic-blood-test-simulator/dom-utils.ts +40 -0
  77. package/src/tool/forensic-blood-test-simulator/entry.ts +32 -0
  78. package/src/tool/forensic-blood-test-simulator/forensic-presumptive-blood-testing-luminol-kastle-meyer-simulator.css +532 -0
  79. package/src/tool/forensic-blood-test-simulator/helpers.ts +16 -0
  80. package/src/tool/forensic-blood-test-simulator/i18n/de.ts +185 -0
  81. package/src/tool/forensic-blood-test-simulator/i18n/en.ts +185 -0
  82. package/src/tool/forensic-blood-test-simulator/i18n/es.ts +185 -0
  83. package/src/tool/forensic-blood-test-simulator/i18n/fr.ts +220 -0
  84. package/src/tool/forensic-blood-test-simulator/i18n/id.ts +220 -0
  85. package/src/tool/forensic-blood-test-simulator/i18n/it.ts +220 -0
  86. package/src/tool/forensic-blood-test-simulator/i18n/ja.ts +220 -0
  87. package/src/tool/forensic-blood-test-simulator/i18n/ko.ts +220 -0
  88. package/src/tool/forensic-blood-test-simulator/i18n/nl.ts +220 -0
  89. package/src/tool/forensic-blood-test-simulator/i18n/pl.ts +220 -0
  90. package/src/tool/forensic-blood-test-simulator/i18n/pt.ts +220 -0
  91. package/src/tool/forensic-blood-test-simulator/i18n/ru.ts +220 -0
  92. package/src/tool/forensic-blood-test-simulator/i18n/sv.ts +220 -0
  93. package/src/tool/forensic-blood-test-simulator/i18n/tr.ts +220 -0
  94. package/src/tool/forensic-blood-test-simulator/i18n/zh.ts +220 -0
  95. package/src/tool/forensic-blood-test-simulator/index.ts +11 -0
  96. package/src/tool/forensic-blood-test-simulator/logic.ts +174 -0
  97. package/src/tool/forensic-blood-test-simulator/script.ts +192 -0
  98. package/src/tool/forensic-blood-test-simulator/seo.astro +15 -0
  99. package/src/tool/forensic-fiber-comparison-microscope/bibliography.astro +6 -0
  100. package/src/tool/forensic-fiber-comparison-microscope/bibliography.ts +16 -0
  101. package/src/tool/forensic-fiber-comparison-microscope/component.astro +146 -0
  102. package/src/tool/forensic-fiber-comparison-microscope/entry.ts +32 -0
  103. package/src/tool/forensic-fiber-comparison-microscope/forensic-fiber-comparison-microscope.css +629 -0
  104. package/src/tool/forensic-fiber-comparison-microscope/i18n/de.ts +250 -0
  105. package/src/tool/forensic-fiber-comparison-microscope/i18n/en.ts +246 -0
  106. package/src/tool/forensic-fiber-comparison-microscope/i18n/es.ts +246 -0
  107. package/src/tool/forensic-fiber-comparison-microscope/i18n/fr.ts +246 -0
  108. package/src/tool/forensic-fiber-comparison-microscope/i18n/id.ts +250 -0
  109. package/src/tool/forensic-fiber-comparison-microscope/i18n/it.ts +246 -0
  110. package/src/tool/forensic-fiber-comparison-microscope/i18n/ja.ts +250 -0
  111. package/src/tool/forensic-fiber-comparison-microscope/i18n/ko.ts +246 -0
  112. package/src/tool/forensic-fiber-comparison-microscope/i18n/nl.ts +246 -0
  113. package/src/tool/forensic-fiber-comparison-microscope/i18n/pl.ts +250 -0
  114. package/src/tool/forensic-fiber-comparison-microscope/i18n/pt.ts +250 -0
  115. package/src/tool/forensic-fiber-comparison-microscope/i18n/ru.ts +250 -0
  116. package/src/tool/forensic-fiber-comparison-microscope/i18n/sv.ts +250 -0
  117. package/src/tool/forensic-fiber-comparison-microscope/i18n/tr.ts +246 -0
  118. package/src/tool/forensic-fiber-comparison-microscope/i18n/zh.ts +250 -0
  119. package/src/tool/forensic-fiber-comparison-microscope/index.ts +11 -0
  120. package/src/tool/forensic-fiber-comparison-microscope/logic.ts +244 -0
  121. package/src/tool/forensic-fiber-comparison-microscope/render.ts +265 -0
  122. package/src/tool/forensic-fiber-comparison-microscope/seo.astro +15 -0
  123. package/src/tool/forensic-fiber-comparison-microscope/view.ts +267 -0
  124. package/src/tool/forensic-glass-becke-line-simulator/bibliography.astro +6 -0
  125. package/src/tool/forensic-glass-becke-line-simulator/bibliography.ts +16 -0
  126. package/src/tool/forensic-glass-becke-line-simulator/component.astro +81 -0
  127. package/src/tool/forensic-glass-becke-line-simulator/entry.ts +32 -0
  128. package/src/tool/forensic-glass-becke-line-simulator/forensic-glass-becke-line-simulator.css +392 -0
  129. package/src/tool/forensic-glass-becke-line-simulator/i18n/de.ts +231 -0
  130. package/src/tool/forensic-glass-becke-line-simulator/i18n/en.ts +231 -0
  131. package/src/tool/forensic-glass-becke-line-simulator/i18n/es.ts +231 -0
  132. package/src/tool/forensic-glass-becke-line-simulator/i18n/fr.ts +231 -0
  133. package/src/tool/forensic-glass-becke-line-simulator/i18n/id.ts +231 -0
  134. package/src/tool/forensic-glass-becke-line-simulator/i18n/it.ts +231 -0
  135. package/src/tool/forensic-glass-becke-line-simulator/i18n/ja.ts +231 -0
  136. package/src/tool/forensic-glass-becke-line-simulator/i18n/ko.ts +231 -0
  137. package/src/tool/forensic-glass-becke-line-simulator/i18n/nl.ts +231 -0
  138. package/src/tool/forensic-glass-becke-line-simulator/i18n/pl.ts +231 -0
  139. package/src/tool/forensic-glass-becke-line-simulator/i18n/pt.ts +231 -0
  140. package/src/tool/forensic-glass-becke-line-simulator/i18n/ru.ts +231 -0
  141. package/src/tool/forensic-glass-becke-line-simulator/i18n/sv.ts +231 -0
  142. package/src/tool/forensic-glass-becke-line-simulator/i18n/tr.ts +231 -0
  143. package/src/tool/forensic-glass-becke-line-simulator/i18n/zh.ts +231 -0
  144. package/src/tool/forensic-glass-becke-line-simulator/index.ts +11 -0
  145. package/src/tool/forensic-glass-becke-line-simulator/logic.ts +100 -0
  146. package/src/tool/forensic-glass-becke-line-simulator/seo.astro +15 -0
  147. package/src/tool/forensic-glass-becke-line-simulator/view.ts +281 -0
  148. package/src/tool/forensic-image-authenticity-analyzer/bibliography.astro +9 -0
  149. package/src/tool/forensic-image-authenticity-analyzer/bibliography.ts +7 -0
  150. package/src/tool/forensic-image-authenticity-analyzer/component.astro +250 -0
  151. package/src/tool/forensic-image-authenticity-analyzer/entry.ts +29 -0
  152. package/src/tool/forensic-image-authenticity-analyzer/forensic-image-metadata-authenticity-analyzer.css +679 -0
  153. package/src/tool/forensic-image-authenticity-analyzer/i18n/de.ts +105 -0
  154. package/src/tool/forensic-image-authenticity-analyzer/i18n/en.ts +105 -0
  155. package/src/tool/forensic-image-authenticity-analyzer/i18n/es.ts +105 -0
  156. package/src/tool/forensic-image-authenticity-analyzer/i18n/fr.ts +105 -0
  157. package/src/tool/forensic-image-authenticity-analyzer/i18n/id.ts +76 -0
  158. package/src/tool/forensic-image-authenticity-analyzer/i18n/it.ts +105 -0
  159. package/src/tool/forensic-image-authenticity-analyzer/i18n/ja.ts +72 -0
  160. package/src/tool/forensic-image-authenticity-analyzer/i18n/ko.ts +72 -0
  161. package/src/tool/forensic-image-authenticity-analyzer/i18n/nl.ts +75 -0
  162. package/src/tool/forensic-image-authenticity-analyzer/i18n/pl.ts +75 -0
  163. package/src/tool/forensic-image-authenticity-analyzer/i18n/pt.ts +105 -0
  164. package/src/tool/forensic-image-authenticity-analyzer/i18n/ru.ts +75 -0
  165. package/src/tool/forensic-image-authenticity-analyzer/i18n/sv.ts +76 -0
  166. package/src/tool/forensic-image-authenticity-analyzer/i18n/tr.ts +76 -0
  167. package/src/tool/forensic-image-authenticity-analyzer/i18n/zh.ts +71 -0
  168. package/src/tool/forensic-image-authenticity-analyzer/index.ts +11 -0
  169. package/src/tool/forensic-image-authenticity-analyzer/logic.ts +283 -0
  170. package/src/tool/forensic-image-authenticity-analyzer/seo.astro +10 -0
  171. package/src/tool/forensic-microcrystal-drug-simulator/bibliography.astro +6 -0
  172. package/src/tool/forensic-microcrystal-drug-simulator/bibliography.ts +12 -0
  173. package/src/tool/forensic-microcrystal-drug-simulator/component.astro +240 -0
  174. package/src/tool/forensic-microcrystal-drug-simulator/entry.ts +32 -0
  175. package/src/tool/forensic-microcrystal-drug-simulator/forensic-microcrystal-drug-simulator.css +430 -0
  176. package/src/tool/forensic-microcrystal-drug-simulator/i18n/de.ts +244 -0
  177. package/src/tool/forensic-microcrystal-drug-simulator/i18n/en.ts +244 -0
  178. package/src/tool/forensic-microcrystal-drug-simulator/i18n/es.ts +244 -0
  179. package/src/tool/forensic-microcrystal-drug-simulator/i18n/fr.ts +244 -0
  180. package/src/tool/forensic-microcrystal-drug-simulator/i18n/id.ts +244 -0
  181. package/src/tool/forensic-microcrystal-drug-simulator/i18n/it.ts +244 -0
  182. package/src/tool/forensic-microcrystal-drug-simulator/i18n/ja.ts +244 -0
  183. package/src/tool/forensic-microcrystal-drug-simulator/i18n/ko.ts +244 -0
  184. package/src/tool/forensic-microcrystal-drug-simulator/i18n/nl.ts +244 -0
  185. package/src/tool/forensic-microcrystal-drug-simulator/i18n/pl.ts +244 -0
  186. package/src/tool/forensic-microcrystal-drug-simulator/i18n/pt.ts +244 -0
  187. package/src/tool/forensic-microcrystal-drug-simulator/i18n/ru.ts +244 -0
  188. package/src/tool/forensic-microcrystal-drug-simulator/i18n/sv.ts +244 -0
  189. package/src/tool/forensic-microcrystal-drug-simulator/i18n/tr.ts +244 -0
  190. package/src/tool/forensic-microcrystal-drug-simulator/i18n/zh.ts +244 -0
  191. package/src/tool/forensic-microcrystal-drug-simulator/index.ts +11 -0
  192. package/src/tool/forensic-microcrystal-drug-simulator/logic.ts +189 -0
  193. package/src/tool/forensic-microcrystal-drug-simulator/seo.astro +15 -0
  194. package/src/tool/forensic-sex-determinator/bibliography.astro +14 -0
  195. package/src/tool/forensic-sex-determinator/bibliography.ts +12 -0
  196. package/src/tool/forensic-sex-determinator/component.astro +463 -0
  197. package/src/tool/forensic-sex-determinator/entry.ts +32 -0
  198. package/src/tool/forensic-sex-determinator/forensic-sex-determinator.css +413 -0
  199. package/src/tool/forensic-sex-determinator/i18n/de.ts +211 -0
  200. package/src/tool/forensic-sex-determinator/i18n/en.ts +211 -0
  201. package/src/tool/forensic-sex-determinator/i18n/es.ts +211 -0
  202. package/src/tool/forensic-sex-determinator/i18n/fr.ts +211 -0
  203. package/src/tool/forensic-sex-determinator/i18n/id.ts +211 -0
  204. package/src/tool/forensic-sex-determinator/i18n/it.ts +211 -0
  205. package/src/tool/forensic-sex-determinator/i18n/ja.ts +211 -0
  206. package/src/tool/forensic-sex-determinator/i18n/ko.ts +211 -0
  207. package/src/tool/forensic-sex-determinator/i18n/nl.ts +211 -0
  208. package/src/tool/forensic-sex-determinator/i18n/pl.ts +211 -0
  209. package/src/tool/forensic-sex-determinator/i18n/pt.ts +211 -0
  210. package/src/tool/forensic-sex-determinator/i18n/ru.ts +211 -0
  211. package/src/tool/forensic-sex-determinator/i18n/sv.ts +211 -0
  212. package/src/tool/forensic-sex-determinator/i18n/tr.ts +211 -0
  213. package/src/tool/forensic-sex-determinator/i18n/zh.ts +211 -0
  214. package/src/tool/forensic-sex-determinator/index.ts +11 -0
  215. package/src/tool/forensic-sex-determinator/logic.ts +89 -0
  216. package/src/tool/forensic-sex-determinator/seo.astro +15 -0
  217. package/src/tool/forensic-stature-estimator/bibliography.astro +14 -0
  218. package/src/tool/forensic-stature-estimator/bibliography.ts +12 -0
  219. package/src/tool/forensic-stature-estimator/component.astro +49 -0
  220. package/src/tool/forensic-stature-estimator/components/EstimationPanel.astro +65 -0
  221. package/src/tool/forensic-stature-estimator/components/OsteometricBoard.astro +39 -0
  222. package/src/tool/forensic-stature-estimator/components/OsteometricSelector.astro +109 -0
  223. package/src/tool/forensic-stature-estimator/dom-utils.ts +71 -0
  224. package/src/tool/forensic-stature-estimator/entry.ts +32 -0
  225. package/src/tool/forensic-stature-estimator/forensic-stature-estimator.css +689 -0
  226. package/src/tool/forensic-stature-estimator/helpers.ts +51 -0
  227. package/src/tool/forensic-stature-estimator/i18n/de.ts +196 -0
  228. package/src/tool/forensic-stature-estimator/i18n/en.ts +196 -0
  229. package/src/tool/forensic-stature-estimator/i18n/es.ts +196 -0
  230. package/src/tool/forensic-stature-estimator/i18n/fr.ts +196 -0
  231. package/src/tool/forensic-stature-estimator/i18n/id.ts +196 -0
  232. package/src/tool/forensic-stature-estimator/i18n/it.ts +196 -0
  233. package/src/tool/forensic-stature-estimator/i18n/ja.ts +196 -0
  234. package/src/tool/forensic-stature-estimator/i18n/ko.ts +196 -0
  235. package/src/tool/forensic-stature-estimator/i18n/nl.ts +196 -0
  236. package/src/tool/forensic-stature-estimator/i18n/pl.ts +196 -0
  237. package/src/tool/forensic-stature-estimator/i18n/pt.ts +196 -0
  238. package/src/tool/forensic-stature-estimator/i18n/ru.ts +196 -0
  239. package/src/tool/forensic-stature-estimator/i18n/sv.ts +196 -0
  240. package/src/tool/forensic-stature-estimator/i18n/tr.ts +196 -0
  241. package/src/tool/forensic-stature-estimator/i18n/zh.ts +196 -0
  242. package/src/tool/forensic-stature-estimator/index.ts +11 -0
  243. package/src/tool/forensic-stature-estimator/logic.ts +119 -0
  244. package/src/tool/forensic-stature-estimator/script.ts +288 -0
  245. package/src/tool/forensic-stature-estimator/seo.astro +15 -0
  246. package/src/tool/forensic-tlc-ink-simulator/bibliography.astro +6 -0
  247. package/src/tool/forensic-tlc-ink-simulator/bibliography.ts +16 -0
  248. package/src/tool/forensic-tlc-ink-simulator/component.astro +245 -0
  249. package/src/tool/forensic-tlc-ink-simulator/entry.ts +32 -0
  250. package/src/tool/forensic-tlc-ink-simulator/forensic-tlc-ink-simulator.css +462 -0
  251. package/src/tool/forensic-tlc-ink-simulator/i18n/de.ts +243 -0
  252. package/src/tool/forensic-tlc-ink-simulator/i18n/en.ts +243 -0
  253. package/src/tool/forensic-tlc-ink-simulator/i18n/es.ts +243 -0
  254. package/src/tool/forensic-tlc-ink-simulator/i18n/fr.ts +243 -0
  255. package/src/tool/forensic-tlc-ink-simulator/i18n/id.ts +243 -0
  256. package/src/tool/forensic-tlc-ink-simulator/i18n/it.ts +243 -0
  257. package/src/tool/forensic-tlc-ink-simulator/i18n/ja.ts +235 -0
  258. package/src/tool/forensic-tlc-ink-simulator/i18n/ko.ts +235 -0
  259. package/src/tool/forensic-tlc-ink-simulator/i18n/nl.ts +243 -0
  260. package/src/tool/forensic-tlc-ink-simulator/i18n/pl.ts +243 -0
  261. package/src/tool/forensic-tlc-ink-simulator/i18n/pt.ts +243 -0
  262. package/src/tool/forensic-tlc-ink-simulator/i18n/ru.ts +243 -0
  263. package/src/tool/forensic-tlc-ink-simulator/i18n/sv.ts +243 -0
  264. package/src/tool/forensic-tlc-ink-simulator/i18n/tr.ts +243 -0
  265. package/src/tool/forensic-tlc-ink-simulator/i18n/zh.ts +235 -0
  266. package/src/tool/forensic-tlc-ink-simulator/index.ts +11 -0
  267. package/src/tool/forensic-tlc-ink-simulator/logic.ts +152 -0
  268. package/src/tool/forensic-tlc-ink-simulator/seo.astro +15 -0
  269. package/src/tool/gsr-dispersion-calculator/bibliography.astro +6 -0
  270. package/src/tool/gsr-dispersion-calculator/bibliography.ts +16 -0
  271. package/src/tool/gsr-dispersion-calculator/component.astro +294 -0
  272. package/src/tool/gsr-dispersion-calculator/entry.ts +32 -0
  273. package/src/tool/gsr-dispersion-calculator/gsr-dispersion-calculator.css +305 -0
  274. package/src/tool/gsr-dispersion-calculator/i18n/de.ts +272 -0
  275. package/src/tool/gsr-dispersion-calculator/i18n/en.ts +272 -0
  276. package/src/tool/gsr-dispersion-calculator/i18n/es.ts +272 -0
  277. package/src/tool/gsr-dispersion-calculator/i18n/fr.ts +272 -0
  278. package/src/tool/gsr-dispersion-calculator/i18n/id.ts +272 -0
  279. package/src/tool/gsr-dispersion-calculator/i18n/it.ts +272 -0
  280. package/src/tool/gsr-dispersion-calculator/i18n/ja.ts +272 -0
  281. package/src/tool/gsr-dispersion-calculator/i18n/ko.ts +272 -0
  282. package/src/tool/gsr-dispersion-calculator/i18n/nl.ts +272 -0
  283. package/src/tool/gsr-dispersion-calculator/i18n/pl.ts +272 -0
  284. package/src/tool/gsr-dispersion-calculator/i18n/pt.ts +272 -0
  285. package/src/tool/gsr-dispersion-calculator/i18n/ru.ts +272 -0
  286. package/src/tool/gsr-dispersion-calculator/i18n/sv.ts +272 -0
  287. package/src/tool/gsr-dispersion-calculator/i18n/tr.ts +272 -0
  288. package/src/tool/gsr-dispersion-calculator/i18n/zh.ts +272 -0
  289. package/src/tool/gsr-dispersion-calculator/index.ts +11 -0
  290. package/src/tool/gsr-dispersion-calculator/logic.ts +148 -0
  291. package/src/tool/gsr-dispersion-calculator/seo.astro +15 -0
  292. package/src/tool/widmark-alcohol-simulator/bibliography.astro +14 -0
  293. package/src/tool/widmark-alcohol-simulator/bibliography.ts +12 -0
  294. package/src/tool/widmark-alcohol-simulator/component.astro +453 -0
  295. package/src/tool/widmark-alcohol-simulator/entry.ts +32 -0
  296. package/src/tool/widmark-alcohol-simulator/i18n/de.ts +193 -0
  297. package/src/tool/widmark-alcohol-simulator/i18n/en.ts +206 -0
  298. package/src/tool/widmark-alcohol-simulator/i18n/es.ts +193 -0
  299. package/src/tool/widmark-alcohol-simulator/i18n/fr.ts +193 -0
  300. package/src/tool/widmark-alcohol-simulator/i18n/id.ts +193 -0
  301. package/src/tool/widmark-alcohol-simulator/i18n/it.ts +193 -0
  302. package/src/tool/widmark-alcohol-simulator/i18n/ja.ts +193 -0
  303. package/src/tool/widmark-alcohol-simulator/i18n/ko.ts +193 -0
  304. package/src/tool/widmark-alcohol-simulator/i18n/nl.ts +193 -0
  305. package/src/tool/widmark-alcohol-simulator/i18n/pl.ts +193 -0
  306. package/src/tool/widmark-alcohol-simulator/i18n/pt.ts +193 -0
  307. package/src/tool/widmark-alcohol-simulator/i18n/ru.ts +193 -0
  308. package/src/tool/widmark-alcohol-simulator/i18n/sv.ts +193 -0
  309. package/src/tool/widmark-alcohol-simulator/i18n/tr.ts +193 -0
  310. package/src/tool/widmark-alcohol-simulator/i18n/zh.ts +193 -0
  311. package/src/tool/widmark-alcohol-simulator/index.ts +11 -0
  312. package/src/tool/widmark-alcohol-simulator/logic.ts +97 -0
  313. package/src/tool/widmark-alcohol-simulator/seo.astro +15 -0
  314. package/src/tool/widmark-alcohol-simulator/widmark-alcohol-simulator.css +386 -0
  315. package/src/tools.ts +27 -0
  316. package/src/types.ts +70 -0
@@ -0,0 +1,250 @@
1
+ import type { ToolLocaleContent } from '../../../types';
2
+ import { bibliography } from '../bibliography';
3
+
4
+ const slug = 'forensic-fiber-comparison-microscope';
5
+ const title = '法医纤维比对显微镜模拟器';
6
+ const description = '构建检材(未知来源)纺织纤维特征,与已知参考样本比对,并解读形态学、偏光及紫外-可见(UV-Vis)染料光谱证据。';
7
+
8
+ const howTo = [
9
+ {
10
+ name: '构建检材纤维特征',
11
+ text: '设定回收纤维的材质、直径、捻向、偏光响应及染料特征。',
12
+ },
13
+ {
14
+ name: '选择已知比对纤维',
15
+ text: '在右侧选择来自嫌疑人衣物的已知纤维,如棉、羊毛、涤纶(聚酯纤维)或尼龙。',
16
+ },
17
+ {
18
+ name: '调节焦距与偏光',
19
+ text: '移动微调焦距滑块并旋转起偏镜,观察图像清晰度、表面纹理及双折射响应。',
20
+ },
21
+ {
22
+ name: '比对形态及紫外-可见光谱',
23
+ text: '结合形态评分、光谱评分、综合比对结果及吸光度曲线,判定样本是符合还是存在明显差异。',
24
+ },
25
+ ];
26
+
27
+ const faq = [
28
+ {
29
+ question: '纤维显微镜检查能确定纤维来自哪一件具体衣服吗?',
30
+ answer: '通常不能。纤维比对可以显示检材纤维与已知纺织品之间的一致性或差异,但若要进行单一源归属判定,则需要仔细的背景关联、经过验证的方法,且通常需要额外的化学或仪器分析。',
31
+ },
32
+ {
33
+ question: '为什么在纤维比对中使用偏振光?',
34
+ answer: '偏振光有助于显示双折射、消光行为以及天然纤维与合成纤维之间的结构差异。由于分子定向的缘故,合成纤维通常表现出更强的偏光响应。',
35
+ },
36
+ {
37
+ question: '紫外-可见(UV-Vis)光谱分析有何作用?',
38
+ answer: '显微镜检查描述的是形态和光学行为,而紫外-可见吸光度可以比对不同波长下的染料行为。外观相似但染料不同的纤维在光谱图上会清晰地分离开来。',
39
+ },
40
+ {
41
+ question: '为什么结果显示为评分而不是最终的身份鉴定结论?',
42
+ answer: '法医微量物证的报告应当留有合理的余地。这些评分是关于形态和染料相似性的教学用总结,并非实验室鉴定结论或法律意见。',
43
+ },
44
+ ];
45
+
46
+ export const content: ToolLocaleContent = {
47
+ slug,
48
+ title,
49
+ description,
50
+ ui: {
51
+ viewerAria: '比对两根纺织纤维的虚拟分视场法医显微镜',
52
+ chartAria: '比对检材与已知纤维染料光谱的紫外-可见吸光度图表',
53
+ questionedSample: '检材样本',
54
+ fixedEvidence: '固定的回收物证',
55
+ builderSummaryDefault: '棉 / 18 µm / 不规则 / 靛蓝',
56
+ customQuestionedLabel: '案中的检材纤维',
57
+ material: '材质',
58
+ cotton: '棉',
59
+ woolMaterial: '羊毛',
60
+ polyesterMaterial: '涤纶',
61
+ nylonMaterial: '尼龙',
62
+ materialcotton: '棉',
63
+ materialwool: '羊毛',
64
+ materialpolyester: '涤纶',
65
+ materialnylon: '尼龙',
66
+ twist: '捻向',
67
+ irregularTwist: '不规则',
68
+ sTwist: 'S',
69
+ zTwist: 'Z',
70
+ diameter: '直径',
71
+ polarResponse: '偏光响应',
72
+ dyeProfile: '染料特征',
73
+ indigoDye: '靛蓝',
74
+ crimsonDye: '绯红',
75
+ navyDye: '藏青',
76
+ violetDye: '紫罗兰',
77
+ dyeindigo: '靛蓝',
78
+ dyecrimson: '绯红',
79
+ dyenavy: '藏青',
80
+ dyeviolet: '紫罗兰',
81
+ knownSample: '已知比对样本',
82
+ questionedCotton: '检材蓝色棉纤维',
83
+ suspectCotton: '嫌疑人衬衫棉纤维',
84
+ wool: '红色羊毛衫纤维',
85
+ polyester: '藏青色涤纶夹克纤维',
86
+ nylon: '紫罗兰色尼龙里料纤维',
87
+ focus: '微调焦距',
88
+ polarizer: '起偏镜角度',
89
+ morphology: '形态学',
90
+ spectrum: '紫外-可见',
91
+ birefringence: '双折射',
92
+ combinedMatch: '综合比对',
93
+ uvVisTitle: '紫外-可见吸光度叠加图',
94
+ statusStrong: '高度符合',
95
+ statusPartial: '比对不一致',
96
+ statusDifferent: '支持排除',
97
+ verdictStrong: '检材与已知纤维在直径、捻向、光学行为和染料光谱上高度符合。在实际案件中,应将其报告为支持存在共同纺织品来源的可能,而不是单一的身份确认。',
98
+ verdictPartial: '比对结果不一致。尽管部分种类特征符合,但形态、染料响应或双折射存在足够差异,在建立关联前需要进行更深入的检验。',
99
+ verdictDifferent: '纤维在关键种类特征上不符合。模拟结果支持排除该已知纺织品作为检材纤维来源的可能。',
100
+ disclaimer: '仅供教学模拟。真实的法医纤维比对需要经验证的显微镜检验、对照样本、案卷记录、防污染措施、同行评议以及具备资质的微量物证解读。',
101
+ },
102
+ seo: [
103
+ {
104
+ type: 'title',
105
+ text: '通过分视场显微镜及紫外-可见光谱进行法医纤维比对',
106
+ level: 2,
107
+ },
108
+ {
109
+ type: 'diagnostic',
110
+ variant: 'info',
111
+ icon: 'mdi:microscope',
112
+ badge: '教学模拟器',
113
+ title: '本虚拟纤维显微镜展示的内容',
114
+ html: '本模拟器还原了<strong>法医纤维比对</strong>的实际工作流程:描述检材纺织纤维特征、与已知参考样本比对、调节焦距、旋转偏振滤光片,并通过简化的紫外-可见吸光度曲线比较染料响应。它专门为学习微量物证的学生设计,不适用于实际案检决策。',
115
+ },
116
+ {
117
+ type: 'stats',
118
+ columns: 4,
119
+ items: [
120
+ { value: '2个视场', label: '分视场显微镜视图', icon: 'mdi:compare-horizontal' },
121
+ { value: '380-720 nm', label: '可见光谱范围', icon: 'mdi:chart-bell-curve' },
122
+ { value: '4个大类', label: '棉、羊毛、涤纶、尼龙', icon: 'mdi:tshirt-crew' },
123
+ { value: '0-180°', label: '起偏镜旋转', icon: 'mdi:rotate-3d-variant' },
124
+ ],
125
+ },
126
+ {
127
+ type: 'summary',
128
+ title: '纺织纤维比对的关键观察点',
129
+ items: [
130
+ '测量或估算纤维直径,同时记录沿纤维单丝的自然变异。',
131
+ '记录捻向、表面纹理、类似髓质的特征、消光剂颗粒以及可用的截面线索。',
132
+ '使用偏振光比较旋转时的双折射、消光行为以及亮度变化。',
133
+ '在显微镜下比对颜色,若案件需要更高的分辨度,则使用仪器分析染料信息。',
134
+ '报告发现是符合、排除还是无法得出结论;避免在缺乏支持的情况下使用暗示存在唯一衣服来源的表述。',
135
+ ],
136
+ },
137
+ {
138
+ type: 'table',
139
+ headers: ['观察点', '有助于回答什么问题', '局限性'],
140
+ rows: [
141
+ ['直径与形态', '纤维在物理上是否相似?', '制造偏差可能会导致不同衣物之间的特征发生重叠。'],
142
+ ['捻向与表面细节', '天然或合成特征是否符合?', '损伤、拉伸及制片过程可能会改变外观。'],
143
+ ['偏振光响应', '光学性质是否符合?', '双折射是种类证据,而不是唯一的特征标识。'],
144
+ ['紫外-可见染料光谱', '染料是否符合?', '相似的染料光谱可能接近;提取方法和仪器设置至关重要。'],
145
+ ],
146
+ },
147
+ {
148
+ type: 'comparative',
149
+ columns: 2,
150
+ items: [
151
+ {
152
+ title: '何种情况支持符合',
153
+ icon: 'mdi:check-circle-outline',
154
+ highlight: true,
155
+ description: '检材与已知纤维之间符合多个独立的种类特征。',
156
+ points: ['直径范围相当', '相同的纤维大类', '相似的偏光行为', '染料吸光度峰值重合'],
157
+ },
158
+ {
159
+ title: '何种情况支持排除',
160
+ icon: 'mdi:close-circle-outline',
161
+ description: '在若来源于共同纺织品则本应符合的特征上,出现了明确且可重复的差异。',
162
+ points: ['天然与合成材质不匹配', '捻向或表面形态不同', '染料光谱明显不同', '双折射响应不兼容'],
163
+ },
164
+ ],
165
+ },
166
+ {
167
+ type: 'title',
168
+ text: '模拟模型的工作原理',
169
+ level: 3,
170
+ },
171
+ {
172
+ type: 'paragraph',
173
+ html: '该模型存储了一个小型参考数据库,包含直径、捻向、纤维类别、双折射、染料家族及紫外-可见吸光度数据点。检材纤维可由用户自行配置,因此比对评分响应的是案件设定,而非固定的教材样本。显微图像视图会响应焦距和起偏镜角度,以便学生直观理解光学设置的重要性。',
174
+ },
175
+ {
176
+ type: 'paragraph',
177
+ html: '起偏镜控制对于合成纤维尤其有用。涤纶和尼龙被分配了较强的双折射数值,因此随着起偏镜旋转,它们的亮度变化更为明显。天然棉和羊毛也会有响应,但模拟效果较为微妙。这体现了分子定向和纤维结构影响光学行为的教学理念。',
178
+ },
179
+ {
180
+ type: 'paragraph',
181
+ html: '紫外-可见图表是简化的叠加图,并非分光光度计的替代品。在真实的微量物证检验中,染料比对可能需要显微分光光度法、薄层色谱法、提取化学、参考对照样本和记录的不确定度。该图表旨在讲授为何在显微镜下看起来相似的两根纤维仍可通过染料化学性质进行区分。',
182
+ },
183
+ {
184
+ type: 'paragraph',
185
+ html: '法医鉴定人员必须记录纤维分析过程的每一步。这包括拍摄详细照片、记录测量数据以及维护所有物理证据的监管链。模拟器有助于学生理解法医实际检案中系统记录和客观比对的重要性。通过调节不同的设置,用户可以了解光路、样本厚度以及染料浓度如何影响光谱曲线和物理外观。',
186
+ },
187
+ {
188
+ type: 'paragraph',
189
+ html: '法医鉴定人员必须记录纤维分析过程的每一步。这包括拍摄详细照片、记录测量数据以及维护所有物理证据的监管链。模拟器有助于学生理解法医实际检案中系统记录和客观比对的重要性。通过调节不同的设置,用户可以了解光路、样本厚度以及染料浓度如何影响光谱曲线和物理外观。',
190
+ },
191
+ {
192
+ type: 'glossary',
193
+ items: [
194
+ { term: '检材纤维', definition: '从现场、被害人、嫌疑人、工具、车辆或其他物品上提取的、来源不明的纤维。' },
195
+ { term: '已知纤维', definition: '从已知纺织品或物体上收集的参考纤维,用于与检材样本进行比对。' },
196
+ { term: '双折射', definition: '由于材料方向或偏振状态的不同,导致其对光的折射率发生改变的光学行为。' },
197
+ { term: '紫外-可见光谱', definition: '显示染料或材料对不同波长光线吸收强度的图线。' },
198
+ { term: '种类特征', definition: '能将证据与一组可能来源建立关联(而非确定唯一源)的属性。' },
199
+ ],
200
+ },
201
+ {
202
+ type: 'tip',
203
+ title: '给学生的解读提示',
204
+ html: '高相似度评分应解读为<strong>符合</strong>,而非同一。当显微镜检查、化学分析、转移背景、遗留时限、提取方法及防污染控制均指向同一方向时,微量物证的证明力最强。',
205
+ },
206
+ {
207
+ type: 'diagnostic',
208
+ variant: 'warning',
209
+ icon: 'mdi:scale-balance',
210
+ badge: 'Forensic caution',
211
+ title: '使结论保持在证据限度内',
212
+ html: '浏览器显微镜无法替代真实的纤维比对鉴定。正式报告应描述观察结果、比对依据、局限性及支持程度,而不应夸大源归属结论。',
213
+ },
214
+ ],
215
+ faq,
216
+ bibliography,
217
+ howTo,
218
+ schemas: [
219
+ {
220
+ '@context': 'https://schema.org',
221
+ '@type': 'SoftwareApplication',
222
+ name: title,
223
+ description,
224
+ applicationCategory: 'ForensicApplication',
225
+ operatingSystem: 'Any',
226
+ },
227
+ {
228
+ '@context': 'https://schema.org',
229
+ '@type': 'FAQPage',
230
+ mainEntity: faq.map((item) => ({
231
+ '@type': 'Question',
232
+ name: item.question,
233
+ acceptedAnswer: {
234
+ '@type': 'Answer',
235
+ text: item.answer,
236
+ },
237
+ })),
238
+ },
239
+ {
240
+ '@context': 'https://schema.org',
241
+ '@type': 'HowTo',
242
+ name: title,
243
+ step: howTo.map((step) => ({
244
+ '@type': 'HowToStep',
245
+ name: step.name,
246
+ text: step.text,
247
+ })),
248
+ },
249
+ ],
250
+ };
@@ -0,0 +1,11 @@
1
+ import { forensicFiberComparisonMicroscope } from './entry';
2
+ import type { ToolDefinition } from '../../types';
3
+
4
+ export * from './entry';
5
+
6
+ export const FORENSIC_FIBER_COMPARISON_MICROSCOPE_TOOL: ToolDefinition = {
7
+ entry: forensicFiberComparisonMicroscope,
8
+ Component: () => import('./component.astro'),
9
+ SEOComponent: () => import('./seo.astro'),
10
+ BibliographyComponent: () => import('./bibliography.astro'),
11
+ };
@@ -0,0 +1,244 @@
1
+ export type FiberReference = 'questionedCotton' | 'suspectCotton' | 'wool' | 'polyester' | 'nylon';
2
+ export type FiberMaterial = 'cotton' | 'wool' | 'polyester' | 'nylon';
3
+ export type FiberTwist = 'S' | 'Z' | 'irregular';
4
+ export type DyePreset = 'indigo' | 'crimson' | 'navy' | 'violet';
5
+
6
+ export interface SpectrumPoint {
7
+ wavelength: number;
8
+ absorbance: number;
9
+ }
10
+
11
+ export interface FiberProfile {
12
+ id: string;
13
+ material: FiberMaterial;
14
+ origin: 'questioned' | 'known';
15
+ diameterMicrons: number;
16
+ twist: FiberTwist;
17
+ birefringence: number;
18
+ natural: boolean;
19
+ dyeFamily: string;
20
+ spectrum: SpectrumPoint[];
21
+ }
22
+
23
+ export interface FiberComparisonInput {
24
+ left: FiberReference | FiberProfile;
25
+ right: FiberReference;
26
+ focus: number;
27
+ polarizationDegrees: number;
28
+ }
29
+
30
+ export interface FiberComparisonResult {
31
+ left: FiberProfile;
32
+ right: FiberProfile;
33
+ morphologyScore: number;
34
+ spectrumScore: number;
35
+ combinedScore: number;
36
+ birefringenceContrast: number;
37
+ visualSharpness: number;
38
+ verdictKey: 'verdictStrong' | 'verdictPartial' | 'verdictDifferent';
39
+ }
40
+
41
+ export const FIBER_DATABASE: Record<FiberReference, FiberProfile> = {
42
+ questionedCotton: {
43
+ id: 'questionedCotton',
44
+ material: 'cotton',
45
+ origin: 'questioned',
46
+ diameterMicrons: 18,
47
+ twist: 'irregular',
48
+ birefringence: 0.032,
49
+ natural: true,
50
+ dyeFamily: 'indigo reactive blue',
51
+ spectrum: [
52
+ { wavelength: 380, absorbance: 0.18 },
53
+ { wavelength: 430, absorbance: 0.32 },
54
+ { wavelength: 480, absorbance: 0.74 },
55
+ { wavelength: 540, absorbance: 0.58 },
56
+ { wavelength: 600, absorbance: 0.22 },
57
+ { wavelength: 660, absorbance: 0.12 },
58
+ { wavelength: 720, absorbance: 0.08 },
59
+ ],
60
+ },
61
+ suspectCotton: {
62
+ id: 'suspectCotton',
63
+ material: 'cotton',
64
+ origin: 'known',
65
+ diameterMicrons: 19,
66
+ twist: 'irregular',
67
+ birefringence: 0.031,
68
+ natural: true,
69
+ dyeFamily: 'indigo reactive blue',
70
+ spectrum: [
71
+ { wavelength: 380, absorbance: 0.2 },
72
+ { wavelength: 430, absorbance: 0.34 },
73
+ { wavelength: 480, absorbance: 0.71 },
74
+ { wavelength: 540, absorbance: 0.56 },
75
+ { wavelength: 600, absorbance: 0.24 },
76
+ { wavelength: 660, absorbance: 0.13 },
77
+ { wavelength: 720, absorbance: 0.09 },
78
+ ],
79
+ },
80
+ wool: {
81
+ id: 'wool',
82
+ material: 'wool',
83
+ origin: 'known',
84
+ diameterMicrons: 27,
85
+ twist: 'S',
86
+ birefringence: 0.018,
87
+ natural: true,
88
+ dyeFamily: 'acid crimson',
89
+ spectrum: [
90
+ { wavelength: 380, absorbance: 0.12 },
91
+ { wavelength: 430, absorbance: 0.24 },
92
+ { wavelength: 480, absorbance: 0.38 },
93
+ { wavelength: 540, absorbance: 0.69 },
94
+ { wavelength: 600, absorbance: 0.82 },
95
+ { wavelength: 660, absorbance: 0.42 },
96
+ { wavelength: 720, absorbance: 0.18 },
97
+ ],
98
+ },
99
+ polyester: {
100
+ id: 'polyester',
101
+ material: 'polyester',
102
+ origin: 'known',
103
+ diameterMicrons: 14,
104
+ twist: 'Z',
105
+ birefringence: 0.055,
106
+ natural: false,
107
+ dyeFamily: 'disperse navy',
108
+ spectrum: [
109
+ { wavelength: 380, absorbance: 0.24 },
110
+ { wavelength: 430, absorbance: 0.61 },
111
+ { wavelength: 480, absorbance: 0.86 },
112
+ { wavelength: 540, absorbance: 0.5 },
113
+ { wavelength: 600, absorbance: 0.18 },
114
+ { wavelength: 660, absorbance: 0.1 },
115
+ { wavelength: 720, absorbance: 0.07 },
116
+ ],
117
+ },
118
+ nylon: {
119
+ id: 'nylon',
120
+ material: 'nylon',
121
+ origin: 'known',
122
+ diameterMicrons: 16,
123
+ twist: 'Z',
124
+ birefringence: 0.047,
125
+ natural: false,
126
+ dyeFamily: 'acid violet',
127
+ spectrum: [
128
+ { wavelength: 380, absorbance: 0.2 },
129
+ { wavelength: 430, absorbance: 0.28 },
130
+ { wavelength: 480, absorbance: 0.4 },
131
+ { wavelength: 540, absorbance: 0.77 },
132
+ { wavelength: 600, absorbance: 0.62 },
133
+ { wavelength: 660, absorbance: 0.25 },
134
+ { wavelength: 720, absorbance: 0.11 },
135
+ ],
136
+ },
137
+ };
138
+
139
+ export const DYE_SPECTRA: Record<DyePreset, { dyeFamily: string; spectrum: SpectrumPoint[] }> = {
140
+ indigo: {
141
+ dyeFamily: 'indigo reactive blue',
142
+ spectrum: FIBER_DATABASE.questionedCotton.spectrum,
143
+ },
144
+ crimson: {
145
+ dyeFamily: 'acid crimson',
146
+ spectrum: FIBER_DATABASE.wool.spectrum,
147
+ },
148
+ navy: {
149
+ dyeFamily: 'disperse navy',
150
+ spectrum: FIBER_DATABASE.polyester.spectrum,
151
+ },
152
+ violet: {
153
+ dyeFamily: 'acid violet',
154
+ spectrum: FIBER_DATABASE.nylon.spectrum,
155
+ },
156
+ };
157
+
158
+ export interface QuestionedFiberInput {
159
+ material: FiberMaterial;
160
+ diameterMicrons: number;
161
+ twist: FiberTwist;
162
+ birefringence: number;
163
+ dyePreset: DyePreset;
164
+ }
165
+
166
+ function clamp(value: number, min: number, max: number): number {
167
+ return Math.max(min, Math.min(max, value));
168
+ }
169
+
170
+ function scoreByDistance(distance: number, fullScale: number): number {
171
+ return Math.round(clamp(100 - (distance / fullScale) * 100, 0, 100));
172
+ }
173
+
174
+ export class FiberComparisonMicroscope {
175
+ compare(input: FiberComparisonInput): FiberComparisonResult {
176
+ const left = this.resolveLeftProfile(input.left);
177
+ const right = this.resolveRightProfile(input.right);
178
+ const morphologyScore = this.calculateMorphologyScore(left, right);
179
+ const spectrumScore = this.calculateSpectrumScore(left.spectrum, right.spectrum);
180
+ const combinedScore = Math.round((morphologyScore * 0.48) + (spectrumScore * 0.52));
181
+ const birefringenceContrast = Number(Math.abs(left.birefringence - right.birefringence).toFixed(3));
182
+ const focus = clamp(input.focus, 0, 100);
183
+ const visualSharpness = Math.round(clamp(100 - Math.abs(focus - 62) * 2.1, 10, 100));
184
+
185
+ return {
186
+ left,
187
+ right,
188
+ morphologyScore,
189
+ spectrumScore,
190
+ combinedScore,
191
+ birefringenceContrast,
192
+ visualSharpness,
193
+ verdictKey: this.verdictForScore(combinedScore),
194
+ };
195
+ }
196
+
197
+ createQuestionedFiber(input: QuestionedFiberInput): FiberProfile {
198
+ const dye = DYE_SPECTRA[input.dyePreset] ?? DYE_SPECTRA.indigo;
199
+ return {
200
+ id: 'customQuestioned',
201
+ material: input.material,
202
+ origin: 'questioned',
203
+ diameterMicrons: clamp(input.diameterMicrons, 8, 36),
204
+ twist: input.twist,
205
+ birefringence: Number(clamp(input.birefringence, 0.01, 0.065).toFixed(3)),
206
+ natural: input.material === 'cotton' || input.material === 'wool',
207
+ dyeFamily: dye.dyeFamily,
208
+ spectrum: dye.spectrum,
209
+ };
210
+ }
211
+
212
+ private calculateSpectrumScore(left: SpectrumPoint[], right: SpectrumPoint[]): number {
213
+ const totalDifference = left.reduce((sum, point, index) => sum + Math.abs(point.absorbance - (right[index]?.absorbance ?? 0)), 0);
214
+ return scoreByDistance(totalDifference, 2.1);
215
+ }
216
+
217
+ private resolveLeftProfile(left: FiberReference | FiberProfile): FiberProfile {
218
+ if (typeof left === 'string') return FIBER_DATABASE[left] ?? FIBER_DATABASE.questionedCotton;
219
+ return left;
220
+ }
221
+
222
+ private resolveRightProfile(right: FiberReference): FiberProfile {
223
+ return FIBER_DATABASE[right] ?? FIBER_DATABASE.suspectCotton;
224
+ }
225
+
226
+ private calculateMorphologyScore(left: FiberProfile, right: FiberProfile): number {
227
+ const diameterScore = scoreByDistance(Math.abs(left.diameterMicrons - right.diameterMicrons), 18);
228
+ const twistScore = left.twist === right.twist ? 100 : 52;
229
+ const materialScore = this.materialScore(left, right);
230
+ return Math.round((diameterScore * 0.42) + (twistScore * 0.24) + (materialScore * 0.34));
231
+ }
232
+
233
+ private materialScore(left: FiberProfile, right: FiberProfile): number {
234
+ if (left.material === right.material) return 100;
235
+ if (left.natural === right.natural) return 68;
236
+ return 34;
237
+ }
238
+
239
+ private verdictForScore(score: number): FiberComparisonResult['verdictKey'] {
240
+ if (score >= 82) return 'verdictStrong';
241
+ if (score >= 55) return 'verdictPartial';
242
+ return 'verdictDifferent';
243
+ }
244
+ }