chaffjs 0.29.0 → 0.31.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 (681) hide show
  1. package/dist/announced-count.d.ts +2 -0
  2. package/dist/announced-count.d.ts.map +1 -1
  3. package/dist/announced-count.js +4 -3
  4. package/dist/announced-count.js.map +1 -1
  5. package/dist/contacts/addresses.d.ts +21 -0
  6. package/dist/contacts/addresses.d.ts.map +1 -0
  7. package/dist/contacts/addresses.js +144 -0
  8. package/dist/contacts/addresses.js.map +1 -0
  9. package/dist/contacts/emails.d.ts +11 -0
  10. package/dist/contacts/emails.d.ts.map +1 -0
  11. package/dist/contacts/emails.js +51 -0
  12. package/dist/contacts/emails.js.map +1 -0
  13. package/dist/contacts/variants.d.ts +14 -0
  14. package/dist/contacts/variants.d.ts.map +1 -0
  15. package/dist/contacts/variants.js +25 -0
  16. package/dist/contacts/variants.js.map +1 -0
  17. package/dist/derived/battery-runtime.d.ts +85 -0
  18. package/dist/derived/battery-runtime.d.ts.map +1 -0
  19. package/dist/derived/battery-runtime.js +217 -0
  20. package/dist/derived/battery-runtime.js.map +1 -0
  21. package/dist/derived/period-counts.d.ts +48 -0
  22. package/dist/derived/period-counts.d.ts.map +1 -0
  23. package/dist/derived/period-counts.js +128 -0
  24. package/dist/derived/period-counts.js.map +1 -0
  25. package/dist/derived/session-hours.d.ts +26 -0
  26. package/dist/derived/session-hours.d.ts.map +1 -0
  27. package/dist/derived/session-hours.js +29 -0
  28. package/dist/derived/session-hours.js.map +1 -0
  29. package/dist/derived/time-lengths.d.ts +37 -0
  30. package/dist/derived/time-lengths.d.ts.map +1 -0
  31. package/dist/derived/time-lengths.js +171 -0
  32. package/dist/derived/time-lengths.js.map +1 -0
  33. package/dist/derived/unit-counts.d.ts +7 -0
  34. package/dist/derived/unit-counts.d.ts.map +1 -0
  35. package/dist/derived/unit-counts.js +13 -0
  36. package/dist/derived/unit-counts.js.map +1 -0
  37. package/dist/derived/use-interval.d.ts +33 -0
  38. package/dist/derived/use-interval.d.ts.map +1 -0
  39. package/dist/derived/use-interval.js +93 -0
  40. package/dist/derived/use-interval.js.map +1 -0
  41. package/dist/derived/working-hours.d.ts +36 -0
  42. package/dist/derived/working-hours.d.ts.map +1 -0
  43. package/dist/derived/working-hours.js +55 -0
  44. package/dist/derived/working-hours.js.map +1 -0
  45. package/dist/detectors/acronym-expansion.d.ts +1 -1
  46. package/dist/detectors/acronym-expansion.d.ts.map +1 -1
  47. package/dist/detectors/acronym-expansion.js +46 -2
  48. package/dist/detectors/acronym-expansion.js.map +1 -1
  49. package/dist/detectors/announced-count.d.ts.map +1 -1
  50. package/dist/detectors/announced-count.js +1 -0
  51. package/dist/detectors/announced-count.js.map +1 -1
  52. package/dist/detectors/capacity-runtime.d.ts +7 -0
  53. package/dist/detectors/capacity-runtime.d.ts.map +1 -0
  54. package/dist/detectors/capacity-runtime.js +98 -0
  55. package/dist/detectors/capacity-runtime.js.map +1 -0
  56. package/dist/detectors/capitalised-term.d.ts +44 -0
  57. package/dist/detectors/capitalised-term.d.ts.map +1 -0
  58. package/dist/detectors/capitalised-term.js +101 -0
  59. package/dist/detectors/capitalised-term.js.map +1 -0
  60. package/dist/detectors/contact-variant.d.ts +3 -0
  61. package/dist/detectors/contact-variant.d.ts.map +1 -0
  62. package/dist/detectors/contact-variant.js +21 -0
  63. package/dist/detectors/contact-variant.js.map +1 -0
  64. package/dist/detectors/count-over-capacity.d.ts +4 -0
  65. package/dist/detectors/count-over-capacity.d.ts.map +1 -0
  66. package/dist/detectors/count-over-capacity.js +26 -0
  67. package/dist/detectors/count-over-capacity.js.map +1 -0
  68. package/dist/detectors/date-outside-period.d.ts.map +1 -1
  69. package/dist/detectors/date-outside-period.js +5 -1
  70. package/dist/detectors/date-outside-period.js.map +1 -1
  71. package/dist/detectors/date-stamp-order.d.ts +3 -0
  72. package/dist/detectors/date-stamp-order.d.ts.map +1 -0
  73. package/dist/detectors/date-stamp-order.js +24 -0
  74. package/dist/detectors/date-stamp-order.js.map +1 -0
  75. package/dist/detectors/defined-term-form.d.ts.map +1 -1
  76. package/dist/detectors/defined-term-form.js +57 -7
  77. package/dist/detectors/defined-term-form.js.map +1 -1
  78. package/dist/detectors/definition-use.d.ts +1 -1
  79. package/dist/detectors/definition-use.d.ts.map +1 -1
  80. package/dist/detectors/definition-use.js +4 -2
  81. package/dist/detectors/definition-use.js.map +1 -1
  82. package/dist/detectors/derived-numbers.d.ts +11 -1
  83. package/dist/detectors/derived-numbers.d.ts.map +1 -1
  84. package/dist/detectors/derived-numbers.js +74 -20
  85. package/dist/detectors/derived-numbers.js.map +1 -1
  86. package/dist/detectors/due-date.d.ts.map +1 -1
  87. package/dist/detectors/due-date.js +6 -0
  88. package/dist/detectors/due-date.js.map +1 -1
  89. package/dist/detectors/en-shape.d.ts.map +1 -1
  90. package/dist/detectors/en-shape.js +7 -4
  91. package/dist/detectors/en-shape.js.map +1 -1
  92. package/dist/detectors/event-deadline.d.ts +3 -0
  93. package/dist/detectors/event-deadline.d.ts.map +1 -0
  94. package/dist/detectors/event-deadline.js +24 -0
  95. package/dist/detectors/event-deadline.js.map +1 -0
  96. package/dist/detectors/fact-consistency.d.ts +1 -1
  97. package/dist/detectors/fact-consistency.d.ts.map +1 -1
  98. package/dist/detectors/fact-consistency.js +52 -3
  99. package/dist/detectors/fact-consistency.js.map +1 -1
  100. package/dist/detectors/heading-case.d.ts +5 -2
  101. package/dist/detectors/heading-case.d.ts.map +1 -1
  102. package/dist/detectors/heading-case.js +12 -4
  103. package/dist/detectors/heading-case.js.map +1 -1
  104. package/dist/detectors/heading-echo.d.ts +5 -0
  105. package/dist/detectors/heading-echo.d.ts.map +1 -1
  106. package/dist/detectors/heading-echo.js +14 -1
  107. package/dist/detectors/heading-echo.js.map +1 -1
  108. package/dist/detectors/layout-code.d.ts +5 -0
  109. package/dist/detectors/layout-code.d.ts.map +1 -0
  110. package/dist/detectors/layout-code.js +124 -0
  111. package/dist/detectors/layout-code.js.map +1 -0
  112. package/dist/detectors/measured-facts.d.ts +8 -0
  113. package/dist/detectors/measured-facts.d.ts.map +1 -0
  114. package/dist/detectors/measured-facts.js +18 -0
  115. package/dist/detectors/measured-facts.js.map +1 -0
  116. package/dist/detectors/name-variant-model-codes.d.ts +11 -0
  117. package/dist/detectors/name-variant-model-codes.d.ts.map +1 -0
  118. package/dist/detectors/name-variant-model-codes.js +21 -0
  119. package/dist/detectors/name-variant-model-codes.js.map +1 -0
  120. package/dist/detectors/name-variant.d.ts.map +1 -1
  121. package/dist/detectors/name-variant.js +78 -10
  122. package/dist/detectors/name-variant.js.map +1 -1
  123. package/dist/detectors/orthographic-variant.d.ts.map +1 -1
  124. package/dist/detectors/orthographic-variant.js +62 -7
  125. package/dist/detectors/orthographic-variant.js.map +1 -1
  126. package/dist/detectors/orthography.d.ts.map +1 -1
  127. package/dist/detectors/orthography.js +12 -6
  128. package/dist/detectors/orthography.js.map +1 -1
  129. package/dist/detectors/party-order.d.ts +3 -0
  130. package/dist/detectors/party-order.d.ts.map +1 -0
  131. package/dist/detectors/party-order.js +46 -0
  132. package/dist/detectors/party-order.js.map +1 -0
  133. package/dist/detectors/quantity-range.d.ts +3 -0
  134. package/dist/detectors/quantity-range.d.ts.map +1 -0
  135. package/dist/detectors/quantity-range.js +35 -0
  136. package/dist/detectors/quantity-range.js.map +1 -0
  137. package/dist/detectors/range-band.d.ts +5 -0
  138. package/dist/detectors/range-band.d.ts.map +1 -0
  139. package/dist/detectors/range-band.js +80 -0
  140. package/dist/detectors/range-band.js.map +1 -0
  141. package/dist/detectors/registry/capacity-runtime-mismatch.d.ts +3 -0
  142. package/dist/detectors/registry/capacity-runtime-mismatch.d.ts.map +1 -0
  143. package/dist/detectors/registry/capacity-runtime-mismatch.js +3 -0
  144. package/dist/detectors/registry/capacity-runtime-mismatch.js.map +1 -0
  145. package/dist/detectors/registry/connection-time-short.d.ts +3 -0
  146. package/dist/detectors/registry/connection-time-short.d.ts.map +1 -0
  147. package/dist/detectors/registry/connection-time-short.js +3 -0
  148. package/dist/detectors/registry/connection-time-short.js.map +1 -0
  149. package/dist/detectors/registry/contact-variant.d.ts +3 -0
  150. package/dist/detectors/registry/contact-variant.d.ts.map +1 -0
  151. package/dist/detectors/registry/contact-variant.js +3 -0
  152. package/dist/detectors/registry/contact-variant.js.map +1 -0
  153. package/dist/detectors/registry/count-over-capacity.d.ts +3 -0
  154. package/dist/detectors/registry/count-over-capacity.d.ts.map +1 -0
  155. package/dist/detectors/registry/count-over-capacity.js +3 -0
  156. package/dist/detectors/registry/count-over-capacity.js.map +1 -0
  157. package/dist/detectors/registry/date-stamp-order.d.ts +3 -0
  158. package/dist/detectors/registry/date-stamp-order.d.ts.map +1 -0
  159. package/dist/detectors/registry/date-stamp-order.js +3 -0
  160. package/dist/detectors/registry/date-stamp-order.js.map +1 -0
  161. package/dist/detectors/registry/duration-product-mismatch.d.ts +3 -0
  162. package/dist/detectors/registry/duration-product-mismatch.d.ts.map +1 -0
  163. package/dist/detectors/registry/duration-product-mismatch.js +3 -0
  164. package/dist/detectors/registry/duration-product-mismatch.js.map +1 -0
  165. package/dist/detectors/registry/event-deadline.d.ts +3 -0
  166. package/dist/detectors/registry/event-deadline.d.ts.map +1 -0
  167. package/dist/detectors/registry/event-deadline.js +3 -0
  168. package/dist/detectors/registry/event-deadline.js.map +1 -0
  169. package/dist/detectors/registry/interval-count-mismatch.d.ts +3 -0
  170. package/dist/detectors/registry/interval-count-mismatch.d.ts.map +1 -0
  171. package/dist/detectors/registry/interval-count-mismatch.js +3 -0
  172. package/dist/detectors/registry/interval-count-mismatch.js.map +1 -0
  173. package/dist/detectors/registry/layout-code-mismatch.d.ts +3 -0
  174. package/dist/detectors/registry/layout-code-mismatch.d.ts.map +1 -0
  175. package/dist/detectors/registry/layout-code-mismatch.js +3 -0
  176. package/dist/detectors/registry/layout-code-mismatch.js.map +1 -0
  177. package/dist/detectors/registry/party-order.d.ts +3 -0
  178. package/dist/detectors/registry/party-order.d.ts.map +1 -0
  179. package/dist/detectors/registry/party-order.js +3 -0
  180. package/dist/detectors/registry/party-order.js.map +1 -0
  181. package/dist/detectors/registry/quantity-range.d.ts +3 -0
  182. package/dist/detectors/registry/quantity-range.d.ts.map +1 -0
  183. package/dist/detectors/registry/quantity-range.js +3 -0
  184. package/dist/detectors/registry/quantity-range.js.map +1 -0
  185. package/dist/detectors/registry/range-band-mismatch.d.ts +3 -0
  186. package/dist/detectors/registry/range-band-mismatch.d.ts.map +1 -0
  187. package/dist/detectors/registry/range-band-mismatch.js +3 -0
  188. package/dist/detectors/registry/range-band-mismatch.js.map +1 -0
  189. package/dist/detectors/registry/rent-multiple-mismatch.d.ts +3 -0
  190. package/dist/detectors/registry/rent-multiple-mismatch.d.ts.map +1 -0
  191. package/dist/detectors/registry/rent-multiple-mismatch.js +3 -0
  192. package/dist/detectors/registry/rent-multiple-mismatch.js.map +1 -0
  193. package/dist/detectors/registry/requirement-lists.d.ts +3 -0
  194. package/dist/detectors/registry/requirement-lists.d.ts.map +1 -0
  195. package/dist/detectors/registry/requirement-lists.js +3 -0
  196. package/dist/detectors/registry/requirement-lists.js.map +1 -0
  197. package/dist/detectors/registry/return-type.d.ts +3 -0
  198. package/dist/detectors/registry/return-type.d.ts.map +1 -0
  199. package/dist/detectors/registry/return-type.js +3 -0
  200. package/dist/detectors/registry/return-type.js.map +1 -0
  201. package/dist/detectors/registry/scaled-quantity-mismatch.d.ts +3 -0
  202. package/dist/detectors/registry/scaled-quantity-mismatch.d.ts.map +1 -0
  203. package/dist/detectors/registry/scaled-quantity-mismatch.js +3 -0
  204. package/dist/detectors/registry/scaled-quantity-mismatch.js.map +1 -0
  205. package/dist/detectors/registry/step-time-sum-mismatch.d.ts +3 -0
  206. package/dist/detectors/registry/step-time-sum-mismatch.d.ts.map +1 -0
  207. package/dist/detectors/registry/step-time-sum-mismatch.js +3 -0
  208. package/dist/detectors/registry/step-time-sum-mismatch.js.map +1 -0
  209. package/dist/detectors/registry/time-range-reversed.d.ts +3 -0
  210. package/dist/detectors/registry/time-range-reversed.d.ts.map +1 -0
  211. package/dist/detectors/registry/time-range-reversed.js +3 -0
  212. package/dist/detectors/registry/time-range-reversed.js.map +1 -0
  213. package/dist/detectors/registry/unlisted-item-used.d.ts +3 -0
  214. package/dist/detectors/registry/unlisted-item-used.d.ts.map +1 -0
  215. package/dist/detectors/registry/unlisted-item-used.js +3 -0
  216. package/dist/detectors/registry/unlisted-item-used.js.map +1 -0
  217. package/dist/detectors/registry/walk-time-distance-mismatch.d.ts +3 -0
  218. package/dist/detectors/registry/walk-time-distance-mismatch.d.ts.map +1 -0
  219. package/dist/detectors/registry/walk-time-distance-mismatch.js +3 -0
  220. package/dist/detectors/registry/walk-time-distance-mismatch.js.map +1 -0
  221. package/dist/detectors/rent-multiple-mismatch.d.ts +3 -0
  222. package/dist/detectors/rent-multiple-mismatch.d.ts.map +1 -0
  223. package/dist/detectors/rent-multiple-mismatch.js +39 -0
  224. package/dist/detectors/rent-multiple-mismatch.js.map +1 -0
  225. package/dist/detectors/requirement-lists.d.ts +3 -0
  226. package/dist/detectors/requirement-lists.d.ts.map +1 -0
  227. package/dist/detectors/requirement-lists.js +46 -0
  228. package/dist/detectors/requirement-lists.js.map +1 -0
  229. package/dist/detectors/return-type.d.ts +3 -0
  230. package/dist/detectors/return-type.d.ts.map +1 -0
  231. package/dist/detectors/return-type.js +24 -0
  232. package/dist/detectors/return-type.js.map +1 -0
  233. package/dist/detectors/scaled-quantity.d.ts +3 -0
  234. package/dist/detectors/scaled-quantity.d.ts.map +1 -0
  235. package/dist/detectors/scaled-quantity.js +37 -0
  236. package/dist/detectors/scaled-quantity.js.map +1 -0
  237. package/dist/detectors/signals.d.ts.map +1 -1
  238. package/dist/detectors/signals.js +3 -1
  239. package/dist/detectors/signals.js.map +1 -1
  240. package/dist/detectors/step-time-sum.d.ts +3 -0
  241. package/dist/detectors/step-time-sum.d.ts.map +1 -0
  242. package/dist/detectors/step-time-sum.js +46 -0
  243. package/dist/detectors/step-time-sum.js.map +1 -0
  244. package/dist/detectors/structure-tree.d.ts +2 -0
  245. package/dist/detectors/structure-tree.d.ts.map +1 -1
  246. package/dist/detectors/structure-tree.js +31 -6
  247. package/dist/detectors/structure-tree.js.map +1 -1
  248. package/dist/detectors/structure.js +1 -1
  249. package/dist/detectors/structure.js.map +1 -1
  250. package/dist/detectors/technical-docs.d.ts +5 -0
  251. package/dist/detectors/technical-docs.d.ts.map +1 -1
  252. package/dist/detectors/technical-docs.js +12 -0
  253. package/dist/detectors/technical-docs.js.map +1 -1
  254. package/dist/detectors/time-order.d.ts +2 -0
  255. package/dist/detectors/time-order.d.ts.map +1 -1
  256. package/dist/detectors/time-order.js +33 -5
  257. package/dist/detectors/time-order.js.map +1 -1
  258. package/dist/detectors/undefined-term.d.ts.map +1 -1
  259. package/dist/detectors/undefined-term.js +19 -3
  260. package/dist/detectors/undefined-term.js.map +1 -1
  261. package/dist/detectors/unit-consistency.d.ts +1 -1
  262. package/dist/detectors/unit-consistency.d.ts.map +1 -1
  263. package/dist/detectors/unit-consistency.js +46 -38
  264. package/dist/detectors/unit-consistency.js.map +1 -1
  265. package/dist/detectors/unlisted-items.d.ts +3 -0
  266. package/dist/detectors/unlisted-items.d.ts.map +1 -0
  267. package/dist/detectors/unlisted-items.js +57 -0
  268. package/dist/detectors/unlisted-items.js.map +1 -0
  269. package/dist/detectors/use-count-words.d.ts +8 -0
  270. package/dist/detectors/use-count-words.d.ts.map +1 -0
  271. package/dist/detectors/use-count-words.js +20 -0
  272. package/dist/detectors/use-count-words.js.map +1 -0
  273. package/dist/detectors/use-interval.d.ts +3 -0
  274. package/dist/detectors/use-interval.d.ts.map +1 -0
  275. package/dist/detectors/use-interval.js +39 -0
  276. package/dist/detectors/use-interval.js.map +1 -0
  277. package/dist/detectors/walk-time-distance-mismatch.d.ts +4 -0
  278. package/dist/detectors/walk-time-distance-mismatch.d.ts.map +1 -0
  279. package/dist/detectors/walk-time-distance-mismatch.js +38 -0
  280. package/dist/detectors/walk-time-distance-mismatch.js.map +1 -0
  281. package/dist/document.d.ts.map +1 -1
  282. package/dist/document.js +4 -1
  283. package/dist/document.js.map +1 -1
  284. package/dist/facts/approximate-tolerance.d.ts +7 -0
  285. package/dist/facts/approximate-tolerance.d.ts.map +1 -0
  286. package/dist/facts/approximate-tolerance.js +16 -0
  287. package/dist/facts/approximate-tolerance.js.map +1 -0
  288. package/dist/facts/item-amounts.d.ts +18 -0
  289. package/dist/facts/item-amounts.d.ts.map +1 -0
  290. package/dist/facts/item-amounts.js +67 -0
  291. package/dist/facts/item-amounts.js.map +1 -0
  292. package/dist/facts/labelled-facts.d.ts +4 -0
  293. package/dist/facts/labelled-facts.d.ts.map +1 -1
  294. package/dist/facts/labelled-facts.js +21 -6
  295. package/dist/facts/labelled-facts.js.map +1 -1
  296. package/dist/facts/measure-units.d.ts +5 -0
  297. package/dist/facts/measure-units.d.ts.map +1 -0
  298. package/dist/facts/measure-units.js +28 -0
  299. package/dist/facts/measure-units.js.map +1 -0
  300. package/dist/facts/measures.d.ts +23 -4
  301. package/dist/facts/measures.d.ts.map +1 -1
  302. package/dist/facts/measures.js +68 -19
  303. package/dist/facts/measures.js.map +1 -1
  304. package/dist/facts/retention-periods.d.ts +51 -0
  305. package/dist/facts/retention-periods.d.ts.map +1 -0
  306. package/dist/facts/retention-periods.js +148 -0
  307. package/dist/facts/retention-periods.js.map +1 -0
  308. package/dist/facts/table-facts.d.ts +2 -0
  309. package/dist/facts/table-facts.d.ts.map +1 -1
  310. package/dist/facts/table-facts.js +2 -0
  311. package/dist/facts/table-facts.js.map +1 -1
  312. package/dist/facts/unit-conflicts.d.ts +3 -2
  313. package/dist/facts/unit-conflicts.d.ts.map +1 -1
  314. package/dist/facts/unit-conflicts.js +15 -7
  315. package/dist/facts/unit-conflicts.js.map +1 -1
  316. package/dist/facts/unit-equivalents.d.ts +13 -0
  317. package/dist/facts/unit-equivalents.d.ts.map +1 -0
  318. package/dist/facts/unit-equivalents.js +56 -0
  319. package/dist/facts/unit-equivalents.js.map +1 -0
  320. package/dist/facts/unit-tolerance.d.ts +4 -0
  321. package/dist/facts/unit-tolerance.d.ts.map +1 -0
  322. package/dist/facts/unit-tolerance.js +13 -0
  323. package/dist/facts/unit-tolerance.js.map +1 -0
  324. package/dist/facts/unit-word.d.ts +8 -0
  325. package/dist/facts/unit-word.d.ts.map +1 -0
  326. package/dist/facts/unit-word.js +34 -0
  327. package/dist/facts/unit-word.js.map +1 -0
  328. package/dist/facts/written-precision.d.ts +10 -0
  329. package/dist/facts/written-precision.d.ts.map +1 -0
  330. package/dist/facts/written-precision.js +20 -0
  331. package/dist/facts/written-precision.js.map +1 -0
  332. package/dist/kana-spelling.d.ts +12 -0
  333. package/dist/kana-spelling.d.ts.map +1 -1
  334. package/dist/kana-spelling.js +23 -0
  335. package/dist/kana-spelling.js.map +1 -1
  336. package/dist/model-codes.d.ts +23 -0
  337. package/dist/model-codes.d.ts.map +1 -0
  338. package/dist/model-codes.js +81 -0
  339. package/dist/model-codes.js.map +1 -0
  340. package/dist/name-spelling-chars.d.ts +16 -0
  341. package/dist/name-spelling-chars.d.ts.map +1 -0
  342. package/dist/name-spelling-chars.js +14 -0
  343. package/dist/name-spelling-chars.js.map +1 -0
  344. package/dist/name-variants.d.ts +8 -2
  345. package/dist/name-variants.d.ts.map +1 -1
  346. package/dist/name-variants.js +72 -7
  347. package/dist/name-variants.js.map +1 -1
  348. package/dist/name-word-order.d.ts +49 -0
  349. package/dist/name-word-order.d.ts.map +1 -0
  350. package/dist/name-word-order.js +114 -0
  351. package/dist/name-word-order.js.map +1 -0
  352. package/dist/phone-numbers.d.ts.map +1 -1
  353. package/dist/phone-numbers.js +22 -5
  354. package/dist/phone-numbers.js.map +1 -1
  355. package/dist/place-names.d.ts +12 -5
  356. package/dist/place-names.d.ts.map +1 -1
  357. package/dist/place-names.js +34 -14
  358. package/dist/place-names.js.map +1 -1
  359. package/dist/plugin.d.ts +12 -0
  360. package/dist/plugin.d.ts.map +1 -1
  361. package/dist/preamble-paragraphs.d.ts +2 -1
  362. package/dist/preamble-paragraphs.d.ts.map +1 -1
  363. package/dist/preamble-paragraphs.js +16 -1
  364. package/dist/preamble-paragraphs.js.map +1 -1
  365. package/dist/profile/parse.d.ts.map +1 -1
  366. package/dist/profile/parse.js +11 -0
  367. package/dist/profile/parse.js.map +1 -1
  368. package/dist/return-type.d.ts +33 -0
  369. package/dist/return-type.d.ts.map +1 -0
  370. package/dist/return-type.js +302 -0
  371. package/dist/return-type.js.map +1 -0
  372. package/dist/spacing-code.d.ts +6 -0
  373. package/dist/spacing-code.d.ts.map +1 -0
  374. package/dist/spacing-code.js +16 -0
  375. package/dist/spacing-code.js.map +1 -0
  376. package/dist/structure/amount-range.d.ts +2 -2
  377. package/dist/structure/amount-range.d.ts.map +1 -1
  378. package/dist/structure/amount-range.js +12 -5
  379. package/dist/structure/amount-range.js.map +1 -1
  380. package/dist/structure/capacity-count.d.ts +59 -0
  381. package/dist/structure/capacity-count.d.ts.map +1 -0
  382. package/dist/structure/capacity-count.js +118 -0
  383. package/dist/structure/capacity-count.js.map +1 -0
  384. package/dist/structure/connection-times.d.ts +56 -0
  385. package/dist/structure/connection-times.d.ts.map +1 -0
  386. package/dist/structure/connection-times.js +165 -0
  387. package/dist/structure/connection-times.js.map +1 -0
  388. package/dist/structure/counted-amounts.d.ts +6 -0
  389. package/dist/structure/counted-amounts.d.ts.map +1 -1
  390. package/dist/structure/counted-amounts.js +4 -4
  391. package/dist/structure/counted-amounts.js.map +1 -1
  392. package/dist/structure/date-order-tie.d.ts +13 -0
  393. package/dist/structure/date-order-tie.d.ts.map +1 -0
  394. package/dist/structure/date-order-tie.js +61 -0
  395. package/dist/structure/date-order-tie.js.map +1 -0
  396. package/dist/structure/date-order.d.ts +2 -2
  397. package/dist/structure/date-order.d.ts.map +1 -1
  398. package/dist/structure/date-order.js +26 -9
  399. package/dist/structure/date-order.js.map +1 -1
  400. package/dist/structure/date-outside-period.d.ts +8 -0
  401. package/dist/structure/date-outside-period.d.ts.map +1 -1
  402. package/dist/structure/date-outside-period.js +52 -3
  403. package/dist/structure/date-outside-period.js.map +1 -1
  404. package/dist/structure/date-range.d.ts +6 -2
  405. package/dist/structure/date-range.d.ts.map +1 -1
  406. package/dist/structure/date-range.js +37 -9
  407. package/dist/structure/date-range.js.map +1 -1
  408. package/dist/structure/deadline-after-event.d.ts +23 -0
  409. package/dist/structure/deadline-after-event.d.ts.map +1 -0
  410. package/dist/structure/deadline-after-event.js +42 -0
  411. package/dist/structure/deadline-after-event.js.map +1 -0
  412. package/dist/structure/definition-use.d.ts +1 -1
  413. package/dist/structure/definition-use.d.ts.map +1 -1
  414. package/dist/structure/definition-use.js +55 -2
  415. package/dist/structure/definition-use.js.map +1 -1
  416. package/dist/structure/due-date.d.ts.map +1 -1
  417. package/dist/structure/due-date.js +5 -27
  418. package/dist/structure/due-date.js.map +1 -1
  419. package/dist/structure/labelled-date-order.d.ts +38 -0
  420. package/dist/structure/labelled-date-order.d.ts.map +1 -0
  421. package/dist/structure/labelled-date-order.js +41 -0
  422. package/dist/structure/labelled-date-order.js.map +1 -0
  423. package/dist/structure/layout-codes.d.ts +68 -0
  424. package/dist/structure/layout-codes.d.ts.map +1 -0
  425. package/dist/structure/layout-codes.js +161 -0
  426. package/dist/structure/layout-codes.js.map +1 -0
  427. package/dist/structure/leg-times.d.ts +2 -0
  428. package/dist/structure/leg-times.d.ts.map +1 -1
  429. package/dist/structure/leg-times.js +13 -0
  430. package/dist/structure/leg-times.js.map +1 -1
  431. package/dist/structure/measured-amounts.d.ts +17 -0
  432. package/dist/structure/measured-amounts.d.ts.map +1 -0
  433. package/dist/structure/measured-amounts.js +110 -0
  434. package/dist/structure/measured-amounts.js.map +1 -0
  435. package/dist/structure/party-order.d.ts +33 -0
  436. package/dist/structure/party-order.d.ts.map +1 -0
  437. package/dist/structure/party-order.js +79 -0
  438. package/dist/structure/party-order.js.map +1 -0
  439. package/dist/structure/quantity-range.d.ts +8 -0
  440. package/dist/structure/quantity-range.d.ts.map +1 -0
  441. package/dist/structure/quantity-range.js +36 -0
  442. package/dist/structure/quantity-range.js.map +1 -0
  443. package/dist/structure/range-bands.d.ts +75 -0
  444. package/dist/structure/range-bands.d.ts.map +1 -0
  445. package/dist/structure/range-bands.js +244 -0
  446. package/dist/structure/range-bands.js.map +1 -0
  447. package/dist/structure/rent-multiple.d.ts +76 -0
  448. package/dist/structure/rent-multiple.d.ts.map +1 -0
  449. package/dist/structure/rent-multiple.js +186 -0
  450. package/dist/structure/rent-multiple.js.map +1 -0
  451. package/dist/structure/requirement-lists.d.ts +37 -0
  452. package/dist/structure/requirement-lists.d.ts.map +1 -0
  453. package/dist/structure/requirement-lists.js +89 -0
  454. package/dist/structure/requirement-lists.js.map +1 -0
  455. package/dist/structure/scaled-quantities.d.ts +50 -0
  456. package/dist/structure/scaled-quantities.d.ts.map +1 -0
  457. package/dist/structure/scaled-quantities.js +153 -0
  458. package/dist/structure/scaled-quantities.js.map +1 -0
  459. package/dist/structure/stamp-order.d.ts +16 -0
  460. package/dist/structure/stamp-order.d.ts.map +1 -0
  461. package/dist/structure/stamp-order.js +78 -0
  462. package/dist/structure/stamp-order.js.map +1 -0
  463. package/dist/structure/step-times.d.ts +49 -0
  464. package/dist/structure/step-times.d.ts.map +1 -0
  465. package/dist/structure/step-times.js +128 -0
  466. package/dist/structure/step-times.js.map +1 -0
  467. package/dist/structure/term-deadline.d.ts +19 -0
  468. package/dist/structure/term-deadline.d.ts.map +1 -0
  469. package/dist/structure/term-deadline.js +28 -0
  470. package/dist/structure/term-deadline.js.map +1 -0
  471. package/dist/structure/time-order.d.ts +21 -0
  472. package/dist/structure/time-order.d.ts.map +1 -1
  473. package/dist/structure/time-order.js +5 -6
  474. package/dist/structure/time-order.js.map +1 -1
  475. package/dist/structure/time-range.d.ts +16 -0
  476. package/dist/structure/time-range.d.ts.map +1 -0
  477. package/dist/structure/time-range.js +57 -0
  478. package/dist/structure/time-range.js.map +1 -0
  479. package/dist/structure/total-line.d.ts +16 -0
  480. package/dist/structure/total-line.d.ts.map +1 -0
  481. package/dist/structure/total-line.js +44 -0
  482. package/dist/structure/total-line.js.map +1 -0
  483. package/dist/structure/total.d.ts +7 -1
  484. package/dist/structure/total.d.ts.map +1 -1
  485. package/dist/structure/total.js +28 -5
  486. package/dist/structure/total.js.map +1 -1
  487. package/dist/structure/unlisted-items.d.ts +70 -0
  488. package/dist/structure/unlisted-items.d.ts.map +1 -0
  489. package/dist/structure/unlisted-items.js +152 -0
  490. package/dist/structure/unlisted-items.js.map +1 -0
  491. package/dist/structure/version-order.d.ts +2 -0
  492. package/dist/structure/version-order.d.ts.map +1 -1
  493. package/dist/structure/version-order.js +10 -0
  494. package/dist/structure/version-order.js.map +1 -1
  495. package/dist/structure/walk-time.d.ts +87 -0
  496. package/dist/structure/walk-time.d.ts.map +1 -0
  497. package/dist/structure/walk-time.js +195 -0
  498. package/dist/structure/walk-time.js.map +1 -0
  499. package/dist/surname-near.d.ts +6 -0
  500. package/dist/surname-near.d.ts.map +1 -0
  501. package/dist/surname-near.js +31 -0
  502. package/dist/surname-near.js.map +1 -0
  503. package/dist/table-cells.d.ts +13 -0
  504. package/dist/table-cells.d.ts.map +1 -0
  505. package/dist/table-cells.js +36 -0
  506. package/dist/table-cells.js.map +1 -0
  507. package/dist/table-names.d.ts +42 -0
  508. package/dist/table-names.d.ts.map +1 -0
  509. package/dist/table-names.js +94 -0
  510. package/dist/table-names.js.map +1 -0
  511. package/package.json +3 -3
  512. package/profiles/listing.yaml +20 -0
  513. package/rules/announced-count-mismatch.yaml +2 -2
  514. package/rules/capacity-runtime-mismatch.yaml +66 -0
  515. package/rules/connection-time-short.yaml +52 -0
  516. package/rules/contact-variant.yaml +76 -0
  517. package/rules/count-over-capacity.yaml +55 -0
  518. package/rules/date-outside-period.yaml +4 -4
  519. package/rules/date-stamp-order.yaml +82 -0
  520. package/rules/deadline-after-event.yaml +67 -0
  521. package/rules/defined-name-repeated.yaml +2 -2
  522. package/rules/due-before-issue.yaml +11 -7
  523. package/rules/duration-mismatch.yaml +7 -4
  524. package/rules/duration-product-mismatch.yaml +73 -0
  525. package/rules/fact-conflict.yaml +9 -4
  526. package/rules/heading-echo.yaml +2 -2
  527. package/rules/interval-count-mismatch.yaml +54 -0
  528. package/rules/latin-spacing.yaml +2 -2
  529. package/rules/layout-code-mismatch.yaml +68 -0
  530. package/rules/name-variant.yaml +14 -2
  531. package/rules/no-em-dash.yaml +2 -2
  532. package/rules/party-order.yaml +84 -0
  533. package/rules/preamble-length.yaml +2 -2
  534. package/rules/quantity-range-reversed.yaml +53 -0
  535. package/rules/range-band-mismatch.yaml +70 -0
  536. package/rules/rent-multiple-mismatch.yaml +61 -0
  537. package/rules/required-listed-as-preferred.yaml +102 -0
  538. package/rules/return-type-mismatch.yaml +79 -0
  539. package/rules/scaled-quantity-mismatch.yaml +69 -0
  540. package/rules/step-time-sum-mismatch.yaml +65 -0
  541. package/rules/time-range-reversed.yaml +48 -0
  542. package/rules/title-case-consistency.yaml +2 -2
  543. package/rules/total-mismatch.yaml +2 -2
  544. package/rules/undefined-term.yaml +21 -7
  545. package/rules/unit-mismatch.yaml +12 -4
  546. package/rules/unlisted-item-used.yaml +90 -0
  547. package/rules/unused-definition.yaml +2 -2
  548. package/rules/use-before-definition.yaml +2 -2
  549. package/rules/walk-time-distance-mismatch.yaml +55 -0
  550. package/src/announced-count.ts +6 -3
  551. package/src/contacts/addresses.ts +179 -0
  552. package/src/contacts/emails.ts +63 -0
  553. package/src/contacts/variants.ts +33 -0
  554. package/src/derived/battery-runtime.ts +284 -0
  555. package/src/derived/period-counts.ts +178 -0
  556. package/src/derived/session-hours.ts +62 -0
  557. package/src/derived/time-lengths.ts +229 -0
  558. package/src/derived/unit-counts.ts +18 -0
  559. package/src/derived/use-interval.ts +151 -0
  560. package/src/derived/working-hours.ts +97 -0
  561. package/src/detectors/acronym-expansion.ts +57 -2
  562. package/src/detectors/announced-count.ts +1 -0
  563. package/src/detectors/capacity-runtime.ts +128 -0
  564. package/src/detectors/capitalised-term.ts +147 -0
  565. package/src/detectors/contact-variant.ts +24 -0
  566. package/src/detectors/count-over-capacity.ts +37 -0
  567. package/src/detectors/date-outside-period.ts +5 -1
  568. package/src/detectors/date-stamp-order.ts +28 -0
  569. package/src/detectors/defined-term-form.ts +63 -7
  570. package/src/detectors/definition-use.ts +6 -2
  571. package/src/detectors/derived-numbers.ts +97 -24
  572. package/src/detectors/due-date.ts +10 -2
  573. package/src/detectors/en-shape.ts +11 -4
  574. package/src/detectors/event-deadline.ts +29 -0
  575. package/src/detectors/fact-consistency.ts +64 -3
  576. package/src/detectors/heading-case.ts +13 -4
  577. package/src/detectors/heading-echo.ts +17 -1
  578. package/src/detectors/layout-code.ts +174 -0
  579. package/src/detectors/measured-facts.ts +20 -0
  580. package/src/detectors/name-variant-model-codes.ts +27 -0
  581. package/src/detectors/name-variant.ts +99 -15
  582. package/src/detectors/orthographic-variant.ts +78 -9
  583. package/src/detectors/orthography.ts +16 -6
  584. package/src/detectors/party-order.ts +50 -0
  585. package/src/detectors/quantity-range.ts +47 -0
  586. package/src/detectors/range-band.ts +125 -0
  587. package/src/detectors/registry/capacity-runtime-mismatch.ts +4 -0
  588. package/src/detectors/registry/connection-time-short.ts +4 -0
  589. package/src/detectors/registry/contact-variant.ts +4 -0
  590. package/src/detectors/registry/count-over-capacity.ts +4 -0
  591. package/src/detectors/registry/date-stamp-order.ts +4 -0
  592. package/src/detectors/registry/duration-product-mismatch.ts +4 -0
  593. package/src/detectors/registry/event-deadline.ts +4 -0
  594. package/src/detectors/registry/interval-count-mismatch.ts +4 -0
  595. package/src/detectors/registry/layout-code-mismatch.ts +4 -0
  596. package/src/detectors/registry/party-order.ts +4 -0
  597. package/src/detectors/registry/quantity-range.ts +4 -0
  598. package/src/detectors/registry/range-band-mismatch.ts +4 -0
  599. package/src/detectors/registry/rent-multiple-mismatch.ts +4 -0
  600. package/src/detectors/registry/requirement-lists.ts +4 -0
  601. package/src/detectors/registry/return-type.ts +4 -0
  602. package/src/detectors/registry/scaled-quantity-mismatch.ts +4 -0
  603. package/src/detectors/registry/step-time-sum-mismatch.ts +4 -0
  604. package/src/detectors/registry/time-range-reversed.ts +4 -0
  605. package/src/detectors/registry/unlisted-item-used.ts +4 -0
  606. package/src/detectors/registry/walk-time-distance-mismatch.ts +4 -0
  607. package/src/detectors/rent-multiple-mismatch.ts +56 -0
  608. package/src/detectors/requirement-lists.ts +65 -0
  609. package/src/detectors/return-type.ts +30 -0
  610. package/src/detectors/scaled-quantity.ts +49 -0
  611. package/src/detectors/signals.ts +3 -1
  612. package/src/detectors/step-time-sum.ts +55 -0
  613. package/src/detectors/structure-tree.ts +42 -6
  614. package/src/detectors/structure.ts +1 -1
  615. package/src/detectors/technical-docs.ts +12 -0
  616. package/src/detectors/time-order.ts +45 -6
  617. package/src/detectors/undefined-term.ts +20 -3
  618. package/src/detectors/unit-consistency.ts +50 -38
  619. package/src/detectors/unlisted-items.ts +72 -0
  620. package/src/detectors/use-count-words.ts +32 -0
  621. package/src/detectors/use-interval.ts +51 -0
  622. package/src/detectors/walk-time-distance-mismatch.ts +45 -0
  623. package/src/document.ts +4 -1
  624. package/src/facts/approximate-tolerance.ts +19 -0
  625. package/src/facts/item-amounts.ts +94 -0
  626. package/src/facts/labelled-facts.ts +29 -7
  627. package/src/facts/measure-units.ts +31 -0
  628. package/src/facts/measures.ts +113 -25
  629. package/src/facts/retention-periods.ts +216 -0
  630. package/src/facts/table-facts.ts +3 -0
  631. package/src/facts/unit-conflicts.ts +22 -9
  632. package/src/facts/unit-equivalents.ts +70 -0
  633. package/src/facts/unit-tolerance.ts +17 -0
  634. package/src/facts/unit-word.ts +35 -0
  635. package/src/facts/written-precision.ts +23 -0
  636. package/src/kana-spelling.ts +25 -0
  637. package/src/model-codes.ts +104 -0
  638. package/src/name-spelling-chars.ts +34 -0
  639. package/src/name-variants.ts +83 -7
  640. package/src/name-word-order.ts +149 -0
  641. package/src/phone-numbers.ts +26 -6
  642. package/src/place-names.ts +53 -14
  643. package/src/plugin.ts +7 -0
  644. package/src/preamble-paragraphs.ts +21 -2
  645. package/src/profile/parse.ts +10 -0
  646. package/src/return-type.ts +361 -0
  647. package/src/spacing-code.ts +26 -0
  648. package/src/structure/amount-range.ts +16 -7
  649. package/src/structure/capacity-count.ts +196 -0
  650. package/src/structure/connection-times.ts +226 -0
  651. package/src/structure/counted-amounts.ts +5 -4
  652. package/src/structure/date-order-tie.ts +86 -0
  653. package/src/structure/date-order.ts +41 -14
  654. package/src/structure/date-outside-period.ts +77 -7
  655. package/src/structure/date-range.ts +45 -9
  656. package/src/structure/deadline-after-event.ts +67 -0
  657. package/src/structure/definition-use.ts +59 -2
  658. package/src/structure/due-date.ts +8 -32
  659. package/src/structure/labelled-date-order.ts +84 -0
  660. package/src/structure/layout-codes.ts +210 -0
  661. package/src/structure/leg-times.ts +15 -0
  662. package/src/structure/measured-amounts.ts +138 -0
  663. package/src/structure/party-order.ts +115 -0
  664. package/src/structure/quantity-range.ts +54 -0
  665. package/src/structure/range-bands.ts +316 -0
  666. package/src/structure/rent-multiple.ts +277 -0
  667. package/src/structure/requirement-lists.ts +145 -0
  668. package/src/structure/scaled-quantities.ts +225 -0
  669. package/src/structure/stamp-order.ts +100 -0
  670. package/src/structure/step-times.ts +169 -0
  671. package/src/structure/term-deadline.ts +35 -0
  672. package/src/structure/time-order.ts +8 -7
  673. package/src/structure/time-range.ts +64 -0
  674. package/src/structure/total-line.ts +63 -0
  675. package/src/structure/total.ts +41 -6
  676. package/src/structure/unlisted-items.ts +207 -0
  677. package/src/structure/version-order.ts +10 -0
  678. package/src/structure/walk-time.ts +285 -0
  679. package/src/surname-near.ts +31 -0
  680. package/src/table-cells.ts +41 -0
  681. package/src/table-names.ts +113 -0
@@ -0,0 +1,100 @@
1
+ import type { DatedSpan } from "./date-range.ts";
2
+ import type { StructureIssue } from "./issues.ts";
3
+ import { linesOf } from "./lines.ts";
4
+
5
+ /**
6
+ * A document's stamp dates out of order: an established date (2024年10月1日 制定, Established: April 1, 2023) later than an
7
+ * update (2024年9月1日 最終改定, Last updated: September 1, 2024). A stamp line (or one part of a line that holds several dates)
8
+ * holds one full date and, once the date is taken out, nothing but one stamp word (with colons, brackets, emphasis or a list
9
+ * marker around it). Only stamps written together are compared. Both stamps are reported, each naming the other: nothing tells
10
+ * which of the two dates is the wrong one. Pure; the stamp words come from the caller.
11
+ */
12
+ export type StampWords = { readonly established: readonly string[]; readonly updated: readonly string[] };
13
+
14
+ type Stamp = { readonly kind: "established" | "updated"; readonly date: DatedSpan; readonly line: number };
15
+
16
+ const FULL_DATE = /^\d{4}-\d{2}-\d{2}$/u;
17
+ /** How many lines apart two stamps may be and still be one document's stamp block; further apart, they date two documents. */
18
+ const MAX_LINE_GAP = 6;
19
+ /** What may stand around a stamp word: spaces, colons, brackets, table bars, Markdown emphasis and list markers. */
20
+ const AROUND_WORD = /[\s::*_()()[\]「」【】||\-‐−・]/u;
21
+ /** A part of a line between a wide space, a tab or a bar. */
22
+ const FIELD = /[^\t\u3000||]+/gu;
23
+
24
+ const bareWord = (text: string): string => {
25
+ const chars = [...text];
26
+ const first = chars.findIndex((char) => !AROUND_WORD.test(char));
27
+ const last = chars.findLastIndex((char) => !AROUND_WORD.test(char));
28
+ return first === -1
29
+ ? ""
30
+ : chars
31
+ .slice(first, last + 1)
32
+ .join("")
33
+ .toLowerCase();
34
+ };
35
+
36
+ const kindOf = (word: string, words: StampWords): Stamp["kind"] | undefined => {
37
+ const isOne = (list: readonly string[]): boolean => list.some((entry) => entry.toLowerCase() === word);
38
+ if (isOne(words.established)) return "established";
39
+ return isOne(words.updated) ? "updated" : undefined;
40
+ };
41
+
42
+ type Field = { readonly start: number; readonly end: number; readonly line: number };
43
+
44
+ const datesIn = (dates: readonly DatedSpan[], field: Field): DatedSpan[] => dates.filter((date) => date.offset >= field.start && date.end <= field.end);
45
+
46
+ /** A line, or, when it holds more than one date, its parts between wide spaces, tabs and bars (作成:平成16年2月4日, a wide space, 最終改定:令和3年11月19日). */
47
+ const fieldsOf = (source: string, dates: readonly DatedSpan[]): Field[] =>
48
+ linesOf(source).flatMap((line) => {
49
+ const whole = { start: line.start, end: line.start + line.text.length, line: line.number };
50
+ if (datesIn(dates, whole).length < 2) return [whole];
51
+ return [...line.text.matchAll(FIELD)].map((match) => ({
52
+ start: line.start + match.index,
53
+ end: line.start + match.index + match[0].length,
54
+ line: line.number,
55
+ }));
56
+ });
57
+
58
+ /** The stamps of a text: a line or field holding one full date and one stamp word, nothing else. */
59
+ const stampsOf = (source: string, dates: readonly DatedSpan[], words: StampWords): Stamp[] =>
60
+ fieldsOf(source, dates).flatMap((field) => {
61
+ const [date, ...more] = datesIn(dates, field);
62
+ if (date === undefined || more.length > 0 || !FULL_DATE.test(date.value)) return [];
63
+ const rest = `${source.slice(field.start, date.offset)} ${source.slice(date.end, field.end)}`;
64
+ const kind = kindOf(bareWord(rest), words);
65
+ return kind === undefined ? [] : [{ kind, date, line: field.line }];
66
+ });
67
+
68
+ /** Stamps written together, each within MAX_LINE_GAP lines of the one before: one document's stamp block. */
69
+ const blocksOf = (stamps: readonly Stamp[]): Stamp[][] =>
70
+ stamps.reduce<Stamp[][]>((blocks, stamp, index) => {
71
+ const previous = stamps[index - 1];
72
+ const last = blocks.at(-1);
73
+ if (previous === undefined || last === undefined || stamp.line - previous.line > MAX_LINE_GAP) return [...blocks, [stamp]];
74
+ return [...blocks.slice(0, -1), [...last, stamp]];
75
+ }, []);
76
+
77
+ const valuesOf = (established: Stamp, updated: Stamp): Record<string, string> => ({ established: established.date.value, updated: updated.date.value });
78
+
79
+ /** In one block: each established stamp later than an update, against the latest such update, and each such update against the first such established stamp. */
80
+ const blockIssues = (block: readonly Stamp[]): StructureIssue[] => {
81
+ const established = block.filter((stamp) => stamp.kind === "established");
82
+ const updates = block.filter((stamp) => stamp.kind === "updated").toSorted((a, b) => b.date.value.localeCompare(a.date.value));
83
+ const ofEstablished = established.flatMap((stamp) => {
84
+ const latest = updates.find((updated) => updated.date.value < stamp.date.value);
85
+ return latest === undefined ? [] : [{ offset: stamp.date.offset, values: valuesOf(stamp, latest) }];
86
+ });
87
+ const ofUpdates = updates.flatMap((updated) => {
88
+ const later = established.find((stamp) => stamp.date.value > updated.date.value);
89
+ return later === undefined ? [] : [{ offset: updated.date.offset, values: { ...valuesOf(later, updated), side: "updated" } }];
90
+ });
91
+ return [...ofEstablished, ...ofUpdates];
92
+ };
93
+
94
+ /** Each established stamp later than an update of its block, and each such update, the one naming the other. */
95
+ export const stampOrderIssues = (source: string, dates: readonly DatedSpan[], words: StampWords): StructureIssue[] => {
96
+ if (words.established.length === 0 || words.updated.length === 0) return [];
97
+ return blocksOf(stampsOf(source, dates, words))
98
+ .flatMap(blockIssues)
99
+ .toSorted((a, b) => a.offset - b.offset);
100
+ };
@@ -0,0 +1,169 @@
1
+ import type { Span } from "../plugin.ts";
2
+ import { labelOf, timeLengths, type LengthWords, type TimeLength } from "../derived/time-lengths.ts";
3
+ import { isWordAt, type Mark } from "./time-marks.ts";
4
+ import { linesOf } from "./lines.ts";
5
+
6
+ /**
7
+ * A stated total time (所要時間:80分, Total time: 1 hr 20 min) against the sum of the times written in the numbered steps
8
+ * ((10分), bake for 40 minutes). The words come from the language packages; nothing here knows a language. Pure.
9
+ */
10
+ export type StepTimeWords = {
11
+ readonly lengths: LengthWords;
12
+ /** Labels of a total time (所要時間, Total time), written before the length. */
13
+ readonly totals: readonly Mark[];
14
+ /** Marks of a rough figure (約, about, 程度): the total or a step that carries one is compared with a margin. */
15
+ readonly approximate: readonly Mark[];
16
+ /** Marks of a time that is not how long the step takes (10分後, every 5 minutes, after 10 minutes). */
17
+ readonly notOwn: readonly Mark[];
18
+ /** What joins the two ends of a range (10〜15分, 10 to 15 minutes): a step or total with a range is not read. */
19
+ readonly rangeJoiners: readonly string[];
20
+ };
21
+
22
+ /** A length of time with whether it was written as a rough figure. */
23
+ export type ReadTime = Span & { readonly minutes: number; readonly approximate: boolean };
24
+
25
+ export type StepTimeSlip = {
26
+ readonly total: ReadTime;
27
+ readonly sum: number;
28
+ readonly steps: number;
29
+ /** How far the sum may be from the total and still agree, in minutes: 0 unless a rough figure is involved. */
30
+ readonly margin: number;
31
+ };
32
+
33
+ /** A rough total agrees with a sum within this share of the total, and never less than ROUGH_MINUTES. */
34
+ export const ROUGH_SHARE = 0.1;
35
+ export const ROUGH_MINUTES = 5;
36
+ const MIN_STEPS = 2;
37
+
38
+ /** 1. or 1) with a space after it (so 1.5 hours is no step); the full-width 1.or 1)may run straight on. */
39
+ const STEP_ITEM = /^\s*\p{Nd}{1,3}(?:[.)]\s|[.)])/u;
40
+ const INDENTED = /^\s+\S/u;
41
+
42
+ type ListRead = { readonly lists: Span[][]; readonly open: boolean; readonly lastLine: number };
43
+
44
+ /** One line's place in the numbered lists: a new item, a line under the last item, a blank, or the end of the list. */
45
+ const readLine = (read: ListRead, line: { readonly text: string; readonly start: number; readonly number: number }): ListRead => {
46
+ const end = line.start + line.text.length;
47
+ const list = read.lists[read.lists.length - 1];
48
+ const last = list?.[list.length - 1];
49
+ if (STEP_ITEM.test(line.text)) {
50
+ if (read.open && list !== undefined) list.push({ start: line.start, end });
51
+ else read.lists.push([{ start: line.start, end }]);
52
+ return { lists: read.lists, open: true, lastLine: line.number };
53
+ }
54
+ if (line.text.trim() === "") return read;
55
+ if (read.open && list !== undefined && last !== undefined && INDENTED.test(line.text)) {
56
+ if (read.lastLine === line.number - 1) list[list.length - 1] = { start: last.start, end };
57
+ return { ...read, lastLine: line.number };
58
+ }
59
+ return { ...read, open: false };
60
+ };
61
+
62
+ /** The numbered lists of the source, each a run of items with only blank or indented lines between them. An item holds its line and the indented lines right under it. */
63
+ export const stepLists = (source: string): Span[][] => linesOf(source).reduce<ListRead>(readLine, { lists: [], open: false, lastLine: 0 }).lists;
64
+
65
+ /** Every numbered step of the source. */
66
+ export const stepItems = (source: string): Span[] => stepLists(source).flat();
67
+
68
+ const lower = (text: string): string => text.toLowerCase();
69
+
70
+ const sideOf = (marks: readonly Mark[], side: "before" | "after"): string[] =>
71
+ marks
72
+ .filter((mark) => (mark.position ?? "before") === side)
73
+ .map((mark) => lower(mark.pattern))
74
+ .filter((pattern) => pattern !== "")
75
+ .toSorted((left, right) => right.length - left.length);
76
+
77
+ /** The mark written right before at (white space between only), and where it starts. */
78
+ const markEndingAt = (text: string, at: number, patterns: readonly string[]): number | undefined => {
79
+ const head = lower(text.slice(0, at)).trimEnd();
80
+ const found = patterns.find((pattern) => isWordAt(head, head.length - pattern.length, pattern));
81
+ return found === undefined ? undefined : head.length - found.length;
82
+ };
83
+
84
+ /** Whether a mark is written right after at (white space between only). */
85
+ const markStartingAt = (text: string, at: number, patterns: readonly string[]): boolean => {
86
+ const tail = lower(text.slice(at));
87
+ const gap = tail.length - tail.trimStart().length;
88
+ return patterns.some((pattern) => isWordAt(tail, gap, pattern));
89
+ };
90
+
91
+ const DIGIT_BEFORE = /\p{Nd}\s*$/u;
92
+
93
+ /** Whether the length is the second end of a range: a joiner right before it, after a number, a number word or a unit (10〜15分, one to two hours, 10 minutes to 15 minutes). */
94
+ const endsRange = (text: string, at: number, words: StepTimeWords): boolean => {
95
+ const start = markEndingAt(text, at, words.rangeJoiners.map(lower));
96
+ if (start === undefined) return false;
97
+ const { numberWords, hourUnits, minuteUnits } = words.lengths;
98
+ const ends = [...numberWords, ...hourUnits, ...minuteUnits].map(lower).filter((word) => word !== "");
99
+ return DIGIT_BEFORE.test(text.slice(0, start)) || markEndingAt(text, start, ends) !== undefined;
100
+ };
101
+
102
+ type Reading = { readonly time: ReadTime; readonly from: number; readonly own: boolean; readonly range: boolean };
103
+
104
+ /** Each length in text, with where its rough-figure mark starts, whether it is the step's own time, and whether it ends a range. */
105
+ const readingsOf = (text: string, words: StepTimeWords): Reading[] =>
106
+ timeLengths(text, words.lengths).map((length: TimeLength): Reading => {
107
+ const roughBefore = markEndingAt(text, length.start, sideOf(words.approximate, "before"));
108
+ const roughAfter = markStartingAt(text, length.end, sideOf(words.approximate, "after"));
109
+ const from = roughBefore ?? length.start;
110
+ const notOwn =
111
+ markEndingAt(text, from, sideOf(words.notOwn, "before")) !== undefined ||
112
+ (!roughAfter && markStartingAt(text, length.end, sideOf(words.notOwn, "after")));
113
+ return {
114
+ time: { start: length.start, end: length.end, minutes: length.minutes, approximate: roughBefore !== undefined || roughAfter },
115
+ from,
116
+ own: !notOwn,
117
+ range: endsRange(text, length.start, words),
118
+ };
119
+ });
120
+
121
+ /** How long one step takes: its one own length of time. undefined when it has none, more than one, or a range. */
122
+ export const stepTime = (text: string, words: StepTimeWords): ReadTime | undefined => {
123
+ const readings = readingsOf(text, words);
124
+ if (readings.some((reading) => reading.range)) return undefined;
125
+ const own = readings.filter((reading) => reading.own);
126
+ return own.length === 1 ? own[0]?.time : undefined;
127
+ };
128
+
129
+ const outside = (offset: number, items: readonly Span[]): boolean => !items.some((item) => offset >= item.start && offset < item.end);
130
+
131
+ /** The totals written on one line. A line with a range (Total time: 10 to 15 minutes) states no exact total. */
132
+ const totalsOn = (line: { readonly text: string; readonly start: number }, words: StepTimeWords): ReadTime[] => {
133
+ const readings = readingsOf(line.text, words);
134
+ if (readings.some((reading) => reading.range)) return [];
135
+ return readings.flatMap((reading): ReadTime[] => {
136
+ const label = labelOf(line.text, { start: reading.from, end: reading.time.end }, words.totals);
137
+ if (label === undefined || !reading.own) return [];
138
+ return [{ ...reading.time, start: line.start + reading.time.start, end: line.start + reading.time.end }];
139
+ });
140
+ };
141
+
142
+ /** The total time the source states outside its steps: a length with a total label right before it. Only one value is read. */
143
+ export const statedTotal = (source: string, items: readonly Span[], words: StepTimeWords): ReadTime | undefined => {
144
+ const totals = linesOf(source)
145
+ .filter((line) => outside(line.start, items))
146
+ .flatMap((line) => totalsOn(line, words));
147
+ const values = new Set(totals.map((total) => total.minutes));
148
+ return values.size === 1 ? totals[0] : undefined;
149
+ };
150
+
151
+ /** The margin a rough figure allows: ROUGH_SHARE of the total, at least ROUGH_MINUTES. */
152
+ export const marginOf = (total: number, rough: boolean): number => (rough ? Math.max(ROUGH_MINUTES, total * ROUGH_SHARE) : 0);
153
+
154
+ /**
155
+ * The stated total against the sum of the steps' times, when the document has one numbered list, every step's time is readable,
156
+ * and they do not agree. Two lists may be two procedures, or one of them no procedure, so a document with two is not read.
157
+ */
158
+ export const stepTimeSlip = (source: string, words: StepTimeWords): StepTimeSlip | undefined => {
159
+ const lists = stepLists(source);
160
+ const [items] = lists;
161
+ if (lists.length !== 1 || items === undefined || items.length < MIN_STEPS) return undefined;
162
+ const total = statedTotal(source, items, words);
163
+ if (total === undefined) return undefined;
164
+ const times = items.map((item) => stepTime(source.slice(item.start, item.end), words));
165
+ if (times.some((time) => time === undefined)) return undefined;
166
+ const sum = times.reduce((minutes, time) => minutes + (time?.minutes ?? 0), 0);
167
+ const margin = marginOf(total.minutes, total.approximate || times.some((time) => time?.approximate === true));
168
+ return Math.abs(sum - total.minutes) <= margin ? undefined : { total, sum, steps: items.length, margin };
169
+ };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * 文書の仕事の期間(開講期間、Term)と、後ろの節の締め切りの日付を比べた結果。語の有無は呼ぶ側が読んで渡す。
3
+ * 年の無い締め切りは、期間の始まりと終わりが同じ年のときだけその年と読む。年をまたぐ期間では、期間の外の月日は始まりの前とも終わりの後とも読める。
4
+ */
5
+ export type TermDates = { readonly start: string; readonly end: string };
6
+
7
+ /** 締め切りの日付を囲む項目か文の語: 締め切りの語、期間の外に置く語、期間の後に来てよい仕事の語(成績発表、再試験)。 */
8
+ export type DeadlineMarks = { readonly deadline: boolean; readonly aside: boolean; readonly afterTerm: boolean };
9
+
10
+ export type DeadlineVerdict = "late" | "within" | "before" | "no-year" | "not-deadline" | "aside" | "after-term";
11
+
12
+ const YEAR_LENGTH = "2026".length;
13
+ const DATED = /^\d{4}-\d{2}-\d{2}$/u;
14
+ const MONTH_DAY = /^\d{2}-\d{2}$/u;
15
+
16
+ /** 締め切りの日付を年月日で。年が無ければ、期間の始まりと終わりの年が同じときだけその年を足す。 */
17
+ export const deadlineDate = (value: string, term: TermDates): string | undefined => {
18
+ if (DATED.test(value)) return value;
19
+ if (!MONTH_DAY.test(value) || !DATED.test(term.start) || !DATED.test(term.end)) return undefined;
20
+ const year = term.start.slice(0, YEAR_LENGTH);
21
+ return year === term.end.slice(0, YEAR_LENGTH) ? `${year}-${value}` : undefined;
22
+ };
23
+
24
+ const placeOf = (date: string, term: TermDates): DeadlineVerdict => {
25
+ if (date > term.end) return "late";
26
+ return date < term.start ? "before" : "within";
27
+ };
28
+
29
+ export const deadlineVerdict = (value: string, term: TermDates, marks: DeadlineMarks): DeadlineVerdict => {
30
+ if (!marks.deadline) return "not-deadline";
31
+ if (marks.afterTerm) return "after-term";
32
+ if (marks.aside) return "aside";
33
+ const date = deadlineDate(value, term);
34
+ return date === undefined || !DATED.test(term.end) ? "no-year" : placeOf(date, term);
35
+ };
@@ -13,7 +13,7 @@ import { crossesZones, dayShiftOf, withoutDayShiftBefore, zonesIn, SECONDS_PER_D
13
13
  */
14
14
 
15
15
  /** 頭に時刻を書いた項目。seconds は 0 時からの秒、shift は翌日の印(1)か無印(0)。 */
16
- type Stop = {
16
+ export type Stop = {
17
17
  readonly offset: number;
18
18
  readonly written: string;
19
19
  readonly seconds: number;
@@ -130,11 +130,12 @@ const breaksIn = (day: readonly Stop[]): StructureIssue[] => {
130
130
  return values.slice(1).flatMap((value, index) => (value < (values[index] ?? "") ? issueAt(day, outOfPlace(values, index + 1, 1)) : []));
131
131
  };
132
132
 
133
- /** 一日の予定の時刻のうち、早い順から外れたもの。 */
134
- export const timeOrderBreaks = (source: string, times: readonly TimeSpan[], dates: readonly DateSpan[], words: TimeWords): StructureIssue[] => {
133
+ /** 一日の予定として並べた時刻を、日ごとに。並びの中で日付か時間帯が変われば別の日。 */
134
+ export const scheduleDays = (source: string, times: readonly TimeSpan[], dates: readonly DateSpan[], words: TimeWords): Stop[][] => {
135
135
  const inputs = { source, times, dates, words };
136
- return runsOf(source).flatMap((run) => {
137
- const stops = run.flatMap((line) => stopOf(line, inputs) ?? []);
138
- return daysOf(stops).flatMap(breaksIn);
139
- });
136
+ return runsOf(source).flatMap((run) => daysOf(run.flatMap((line) => stopOf(line, inputs) ?? [])));
140
137
  };
138
+
139
+ /** 一日の予定の時刻のうち、早い順から外れたもの。 */
140
+ export const timeOrderBreaks = (source: string, times: readonly TimeSpan[], dates: readonly DateSpan[], words: TimeWords): StructureIssue[] =>
141
+ scheduleDays(source, times, dates, words).flatMap(breaksIn);
@@ -0,0 +1,64 @@
1
+ import type { StructureIssue } from "./issues.ts";
2
+ import { isRange, type RangeWords } from "./date-range.ts";
3
+ import { dayShiftOf, type TimeSpan, type TimeWords } from "./time-marks.ts";
4
+
5
+ /**
6
+ * 時刻の範囲の終わりが始まりより前(14:00〜13:30、2:00–1:30 pm、午後2時から午後1時半まで)。隣り合う二つの時刻の間が
7
+ * 範囲の記号だけか、「から … まで」「from … to」のときを範囲と読む(date-range と同じ読み方)。変更の語のある文は読まない。
8
+ * 終わりに翌日の印(翌、+1、next day)があれば日をまたぐ範囲で、言わない。印が無くても、三時間以上戻る終わりは
9
+ * 夜をまたぐ範囲(22:00〜6:00 の夜勤、18:00〜9:00)か十二時間制の読み違い(9:00–5:00)と見て言わない。打ち間違いは一、二時間の戻り。
10
+ * 片方にだけ午前・午後(a.m.、p.m.)を書いた範囲は、書いていない側も同じと読む(2:00–1:30 pm は 14:00 から 13:30)。
11
+ * ただし正午をまたぐ書き方(8:00–6:00 pm)があるので、そのときは一時間未満の戻りだけを言う。
12
+ */
13
+
14
+ const SECONDS_PER_HOUR = 3600;
15
+ /** 書いたまま読んだ範囲で、打ち間違いと見る戻りの上限。 */
16
+ const SLIP_SECONDS = 3 * SECONDS_PER_HOUR;
17
+ /** 午前・午後を片方から借りた範囲で、打ち間違いと見る戻りの上限。 */
18
+ const BORROWED_SLIP_SECONDS = SECONDS_PER_HOUR;
19
+ const NOON_SECONDS = 12 * SECONDS_PER_HOUR;
20
+ const ONE_OCLOCK_SECONDS = SECONDS_PER_HOUR;
21
+ const LAST_TWELVE_HOUR_SECONDS = 13 * SECONDS_PER_HOUR;
22
+
23
+ const MERIDIEM = /[ap]\.?m\b|午前|午後/iu;
24
+
25
+ const hasMeridiem = (source: string, time: TimeSpan): boolean => MERIDIEM.test(source.slice(time.start, time.end));
26
+
27
+ /** 午前・午後を書かず、十二時間制でも読める時刻(1:00〜12:59)。 */
28
+ const isTwelveHour = (time: TimeSpan): boolean => time.seconds >= ONE_OCLOCK_SECONDS && time.seconds < LAST_TWELVE_HOUR_SECONDS;
29
+
30
+ /** 午前・午後を書かない時刻を、もう片方の午前・午後で読む。 */
31
+ const borrowed = (bare: number, written: number): number => {
32
+ const afternoon = written >= NOON_SECONDS;
33
+ if (afternoon && bare < NOON_SECONDS) return bare + NOON_SECONDS;
34
+ if (!afternoon && bare >= NOON_SECONDS) return bare - NOON_SECONDS;
35
+ return bare;
36
+ };
37
+
38
+ type Reading = { readonly from: number; readonly to: number; readonly borrowed: boolean };
39
+
40
+ /** 範囲の二つの時刻を秒で。片方にだけ午前・午後があれば、もう片方もそれで読む。 */
41
+ export const rangeReading = (source: string, start: TimeSpan, end: TimeSpan): Reading => {
42
+ const [startMarked, endMarked] = [hasMeridiem(source, start), hasMeridiem(source, end)];
43
+ if (endMarked && !startMarked && isTwelveHour(start)) return { from: borrowed(start.seconds, end.seconds), to: end.seconds, borrowed: true };
44
+ if (startMarked && !endMarked && isTwelveHour(end)) return { from: start.seconds, to: borrowed(end.seconds, start.seconds), borrowed: true };
45
+ return { from: start.seconds, to: end.seconds, borrowed: false };
46
+ };
47
+
48
+ /** 終わりが始まりより前で、その戻りが打ち間違いの幅に収まるか。 */
49
+ export const isReversed = (reading: Reading): boolean => {
50
+ const back = reading.from - reading.to;
51
+ return back > 0 && back < (reading.borrowed ? BORROWED_SLIP_SECONDS : SLIP_SECONDS);
52
+ };
53
+
54
+ const asDated = (time: TimeSpan): { offset: number; end: number; value: string } => ({ offset: time.start, end: time.end, value: "" });
55
+
56
+ /** 隣り合う二つの時刻のうち、範囲をなし、終わりが始まりより前のもの。範囲の書き出しを指し、書いたままの範囲を見せる。 */
57
+ export const reversedTimeRanges = (source: string, times: readonly TimeSpan[], range: RangeWords, words: TimeWords): StructureIssue[] =>
58
+ times.slice(1).flatMap((end, index) => {
59
+ const start = times[index];
60
+ if (start === undefined || !isRange(source, asDated(start), asDated(end), range, () => false)) return [];
61
+ if (dayShiftOf(source.slice(start.end, end.start), source, end.end, words) !== 0 || !isReversed(rangeReading(source, start, end))) return [];
62
+ const written = source.slice(start.start, end.end).replace(/\s+/gu, " ");
63
+ return [{ offset: start.start, values: { range: written, start: source.slice(start.start, start.end), end: source.slice(end.start, end.end) } }];
64
+ });
@@ -0,0 +1,63 @@
1
+ import { isTotalLabel, leadingText } from "./total.ts";
2
+ import { outsideBrackets } from "./connection-times.ts";
3
+
4
+ /** 合計の語に添える語。position が before なら合計の語の前だけ、after なら後ろだけ、無ければどちらにも置ける。 */
5
+ export type TotalQualifier = { readonly pattern: string; readonly position?: "before" | "after" | undefined };
6
+
7
+ /** labels: 合計の語(total-label)。qualifiers: 合計の語の前後に置いても合計の行のままの語(total-label-qualifier)。 */
8
+ export type TotalLineWords = { readonly labels: readonly string[]; readonly qualifiers: readonly TotalQualifier[] };
9
+
10
+ /** 見出しの終わり。区切り(: |)があればそこまで、無ければ金額の始まりまで。 */
11
+ const SEPARATOR = /[::|]/u;
12
+ const AMOUNT_START = /[$€£¥¥\p{N}]/u;
13
+ const EMPHASIS = /[*_]/gu;
14
+ const SPACES = /\s+/u;
15
+
16
+ const wordsOf = (text: string): string[] =>
17
+ text
18
+ .toLowerCase()
19
+ .split(SPACES)
20
+ .filter((word) => word.length > 0);
21
+
22
+ const labelText = (lead: string): string => {
23
+ const separator = lead.search(SEPARATOR);
24
+ if (separator !== -1) return lead.slice(0, separator);
25
+ const amount = lead.search(AMOUNT_START);
26
+ return amount === -1 ? lead : lead.slice(0, amount);
27
+ };
28
+
29
+ /** 行頭の見出しを語に分ける。括弧の注記((incl. tax))と強調の印は除く。 */
30
+ const labelWords = (text: string): string[] => wordsOf(outsideBrackets(labelText(leadingText(text))).replace(EMPHASIS, ""));
31
+
32
+ const startsAt = (words: readonly string[], label: readonly string[], index: number): boolean => label.every((word, offset) => words[index + offset] === word);
33
+
34
+ const allowedOn = (side: "before" | "after", qualifiers: readonly TotalQualifier[]): ReadonlySet<string> =>
35
+ new Set(
36
+ qualifiers.filter((qualifier) => qualifier.position !== (side === "before" ? "after" : "before")).map((qualifier) => qualifier.pattern.toLowerCase()),
37
+ );
38
+
39
+ /** 見出しの語のどこかに合計の語が一つ続けてあり、その前と後ろの語がどれも、そちら側に置ける添える語か。 */
40
+ const isQualifiedLabel = (words: readonly string[], label: readonly string[], qualifiers: readonly TotalQualifier[]): boolean => {
41
+ const before = allowedOn("before", qualifiers);
42
+ const after = allowedOn("after", qualifiers);
43
+ return (
44
+ label.length > 0 &&
45
+ words.some(
46
+ (_, index) =>
47
+ startsAt(words, label, index) &&
48
+ words.slice(0, index).every((word) => before.has(word)) &&
49
+ words.slice(index + label.length).every((word) => after.has(word)),
50
+ )
51
+ );
52
+ };
53
+
54
+ /**
55
+ * 合計の行か。合計の語で始まる行(Total、合計(月額))に加えて、合計の語に添える語だけを前後に置いた見出し
56
+ * (Total per month、Monthly total、Total due)も合計の行と読む。添える語でない語(Total area)が入れば読まない。
57
+ */
58
+ export const isTotalLine = (text: string, words: TotalLineWords): boolean => {
59
+ if (isTotalLabel(text, words.labels)) return true;
60
+ if (words.qualifiers.length === 0) return false;
61
+ const label = labelWords(text);
62
+ return words.labels.some((total) => isQualifiedLabel(label, wordsOf(total), words.qualifiers));
63
+ };
@@ -8,7 +8,14 @@ import { bareNumbersIn, CELL_SEPARATOR, NO_UNIT } from "./bare-numbers.ts";
8
8
  * 小計と税のあとの総計のように、足し方が何通りかある。どの足し方でも合わないときだけ言う。
9
9
  * 表の升に数だけを書いた列(単位は見出しにある)も足す。そのときは上の行の升がどれも数でなければ足さない。年や番号の列を足さないため。
10
10
  */
11
- export type Amount = { readonly offset: number; readonly end: number; readonly value: number; readonly unit: string };
11
+ export type Amount = {
12
+ readonly offset: number;
13
+ readonly end: number;
14
+ readonly value: number;
15
+ readonly unit: string;
16
+ /** 単位の付いた量(0.5 g)は、量の種類の一番小さい単位(mg)に直して足す。scale は書いた単位一つがその単位いくつか(g なら 1,000)。 */
17
+ readonly scale?: number;
18
+ };
12
19
 
13
20
  const CENTS = 100;
14
21
 
@@ -36,7 +43,7 @@ const isNegative = (source: string, line: Line, amount: Amount): boolean => {
36
43
  };
37
44
 
38
45
  /** 行の最初の文字列(表なら最初の列)から、強調の印と箇条書きの印を除いたもの。 */
39
- const leadingText = (text: string): string =>
46
+ export const leadingText = (text: string): string =>
40
47
  text
41
48
  .replace(/^[ \t]*(?:[-*+]|\d{1,3}[.)])?[ \t]*\|?[ \t]*/u, "")
42
49
  .replace(/^[*_]+/u, "")
@@ -58,8 +65,11 @@ const centsOf = (source: string, line: Line, amount: Amount): number | undefined
58
65
  return Math.round(amount.value * CENTS) * (isNegative(source, line, amount) ? -1 : 1);
59
66
  };
60
67
 
61
- const entryOf = (source: string, line: Line, amounts: readonly Amount[], labels: readonly string[]): Entry => ({
62
- label: isTotalLabel(source.slice(line.start, line.end), labels),
68
+ /** 行が合計の行か。語彙で決める判定は呼ぶ側が渡す。 */
69
+ export type IsTotalLine = (text: string) => boolean;
70
+
71
+ const entryOf = (source: string, line: Line, amounts: readonly Amount[], isTotal: IsTotalLine): Entry => ({
72
+ label: isTotal(source.slice(line.start, line.end)),
63
73
  amounts: amounts
64
74
  .filter((amount) => amount.offset >= line.start && amount.offset <= line.end)
65
75
  .map((amount) => ({ ...amount, column: columnOf(source, line, amount.offset), cents: centsOf(source, line, amount) })),
@@ -169,6 +179,30 @@ export const shownTotal = (source: string, total: Amount, sumCents: number): Rec
169
179
  const readsAsSum = (cell: Cell, writtenCents: number, above: RowsAbove): boolean =>
170
180
  cell.unit !== NO_UNIT || above.parts.some((part) => part < 0) || above.parts.every((part) => part < writtenCents);
171
181
 
182
+ /** 書いた数の小数の桁(0.25 g なら 2)。 */
183
+ const decimalsOf = (written: string): number => /[..](\d+)/u.exec(written.normalize("NFKC"))?.[1]?.length ?? 0;
184
+
185
+ /** 見せる和の小数の桁の上限。合計より細かい単位の内訳(1 g の合計に 250 mg の内訳)の和を、合計の単位で見せるため。 */
186
+ const MAX_SHOWN_DECIMALS = 3;
187
+
188
+ /** 換算の倍率が割り切れない単位(8 oz + 8 oz = 1 lb)で、丸めの差を合うとみなす割合。 */
189
+ const MEASURE_TOLERANCE = 1e-6;
190
+
191
+ /** 単位の付いた量の合計。和は合計に書いた単位と書き方で見せる(1 g の合計に 300 mg と 0.6 g の内訳なら 0.9 g)。 */
192
+ const measuredMismatch = (source: string, total: Placed, scale: number, sums: readonly number[], reported: number): StructureIssue[] => {
193
+ const number = source.slice(total.offset, total.end);
194
+ const digits = decimalsOf(number);
195
+ const written = total.value * CENTS;
196
+ if (sums.some((sum) => Math.abs(sum - written) <= Math.abs(written) * MEASURE_TOLERANCE)) return [];
197
+ const inTotalUnit = (cents: number): number => cents / CENTS / scale;
198
+ const sum = inTotalUnit(reported).toLocaleString("en-US", {
199
+ minimumFractionDigits: digits,
200
+ maximumFractionDigits: Math.max(digits, MAX_SHOWN_DECIMALS),
201
+ useGrouping: !UNGROUPED.test(number),
202
+ });
203
+ return [{ offset: total.offset, values: { written: number, sum: `${sum}${unitAfterNumber(number, total.unit)}` } }];
204
+ };
205
+
172
206
  const mismatchesIn = (source: string, entries: readonly Entry[]): StructureIssue[] =>
173
207
  entries.flatMap((entry, index) => {
174
208
  if (!entry.label) return [];
@@ -176,16 +210,17 @@ const mismatchesIn = (source: string, entries: readonly Entry[]): StructureIssue
176
210
  const writtenCents = valueIn(entry, total);
177
211
  const above = windowAbove(entries.slice(0, index), total);
178
212
  if (typeof writtenCents !== "number" || above === undefined || !enoughRows(above) || !readsAsSum(total, writtenCents, above)) return [];
213
+ if (total.scale !== undefined) return measuredMismatch(source, total, total.scale, candidateSums(above), reportedSum(above));
179
214
  if (candidateSums(above).includes(writtenCents)) return [];
180
215
  return [{ offset: total.offset, values: shownAmounts(source, total, reportedSum(above)) }];
181
216
  });
182
217
  });
183
218
 
184
- export const totalMismatches = (source: string, amounts: readonly Amount[], labels: readonly string[]): StructureIssue[] =>
219
+ export const totalMismatches = (source: string, amounts: readonly Amount[], isTotal: IsTotalLine): StructureIssue[] =>
185
220
  runsOf(source).flatMap((run) => {
186
221
  const inRun = [...amounts, ...bareNumbersIn(source, run)];
187
222
  return mismatchesIn(
188
223
  source,
189
- run.map((line) => entryOf(source, line, inRun, labels)),
224
+ run.map((line) => entryOf(source, line, inRun, isTotal)),
190
225
  );
191
226
  });