chaffjs 0.30.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 (530) 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/time-lengths.d.ts.map +1 -1
  26. package/dist/derived/time-lengths.js +12 -9
  27. package/dist/derived/time-lengths.js.map +1 -1
  28. package/dist/derived/use-interval.d.ts +33 -0
  29. package/dist/derived/use-interval.d.ts.map +1 -0
  30. package/dist/derived/use-interval.js +93 -0
  31. package/dist/derived/use-interval.js.map +1 -0
  32. package/dist/detectors/announced-count.d.ts.map +1 -1
  33. package/dist/detectors/announced-count.js +1 -0
  34. package/dist/detectors/announced-count.js.map +1 -1
  35. package/dist/detectors/capacity-runtime.d.ts +7 -0
  36. package/dist/detectors/capacity-runtime.d.ts.map +1 -0
  37. package/dist/detectors/capacity-runtime.js +98 -0
  38. package/dist/detectors/capacity-runtime.js.map +1 -0
  39. package/dist/detectors/contact-variant.d.ts +3 -0
  40. package/dist/detectors/contact-variant.d.ts.map +1 -0
  41. package/dist/detectors/contact-variant.js +21 -0
  42. package/dist/detectors/contact-variant.js.map +1 -0
  43. package/dist/detectors/count-over-capacity.d.ts +4 -0
  44. package/dist/detectors/count-over-capacity.d.ts.map +1 -0
  45. package/dist/detectors/count-over-capacity.js +26 -0
  46. package/dist/detectors/count-over-capacity.js.map +1 -0
  47. package/dist/detectors/date-outside-period.d.ts.map +1 -1
  48. package/dist/detectors/date-outside-period.js +2 -1
  49. package/dist/detectors/date-outside-period.js.map +1 -1
  50. package/dist/detectors/date-stamp-order.d.ts +3 -0
  51. package/dist/detectors/date-stamp-order.d.ts.map +1 -0
  52. package/dist/detectors/date-stamp-order.js +24 -0
  53. package/dist/detectors/date-stamp-order.js.map +1 -0
  54. package/dist/detectors/defined-term-form.d.ts.map +1 -1
  55. package/dist/detectors/defined-term-form.js +57 -7
  56. package/dist/detectors/defined-term-form.js.map +1 -1
  57. package/dist/detectors/definition-use.d.ts +1 -1
  58. package/dist/detectors/definition-use.d.ts.map +1 -1
  59. package/dist/detectors/definition-use.js +4 -2
  60. package/dist/detectors/definition-use.js.map +1 -1
  61. package/dist/detectors/derived-numbers.d.ts +9 -1
  62. package/dist/detectors/derived-numbers.d.ts.map +1 -1
  63. package/dist/detectors/derived-numbers.js +2 -2
  64. package/dist/detectors/derived-numbers.js.map +1 -1
  65. package/dist/detectors/due-date.d.ts.map +1 -1
  66. package/dist/detectors/due-date.js +6 -0
  67. package/dist/detectors/due-date.js.map +1 -1
  68. package/dist/detectors/en-shape.d.ts.map +1 -1
  69. package/dist/detectors/en-shape.js +7 -4
  70. package/dist/detectors/en-shape.js.map +1 -1
  71. package/dist/detectors/event-deadline.d.ts +3 -0
  72. package/dist/detectors/event-deadline.d.ts.map +1 -0
  73. package/dist/detectors/event-deadline.js +24 -0
  74. package/dist/detectors/event-deadline.js.map +1 -0
  75. package/dist/detectors/fact-consistency.d.ts +1 -1
  76. package/dist/detectors/fact-consistency.d.ts.map +1 -1
  77. package/dist/detectors/fact-consistency.js +52 -3
  78. package/dist/detectors/fact-consistency.js.map +1 -1
  79. package/dist/detectors/heading-case.d.ts +5 -2
  80. package/dist/detectors/heading-case.d.ts.map +1 -1
  81. package/dist/detectors/heading-case.js +12 -4
  82. package/dist/detectors/heading-case.js.map +1 -1
  83. package/dist/detectors/heading-echo.d.ts +5 -0
  84. package/dist/detectors/heading-echo.d.ts.map +1 -1
  85. package/dist/detectors/heading-echo.js +14 -1
  86. package/dist/detectors/heading-echo.js.map +1 -1
  87. package/dist/detectors/layout-code.d.ts +5 -0
  88. package/dist/detectors/layout-code.d.ts.map +1 -0
  89. package/dist/detectors/layout-code.js +124 -0
  90. package/dist/detectors/layout-code.js.map +1 -0
  91. package/dist/detectors/measured-facts.d.ts +8 -0
  92. package/dist/detectors/measured-facts.d.ts.map +1 -0
  93. package/dist/detectors/measured-facts.js +18 -0
  94. package/dist/detectors/measured-facts.js.map +1 -0
  95. package/dist/detectors/name-variant-model-codes.d.ts +11 -0
  96. package/dist/detectors/name-variant-model-codes.d.ts.map +1 -0
  97. package/dist/detectors/name-variant-model-codes.js +21 -0
  98. package/dist/detectors/name-variant-model-codes.js.map +1 -0
  99. package/dist/detectors/name-variant.d.ts.map +1 -1
  100. package/dist/detectors/name-variant.js +25 -4
  101. package/dist/detectors/name-variant.js.map +1 -1
  102. package/dist/detectors/orthographic-variant.d.ts.map +1 -1
  103. package/dist/detectors/orthographic-variant.js +62 -7
  104. package/dist/detectors/orthographic-variant.js.map +1 -1
  105. package/dist/detectors/orthography.d.ts.map +1 -1
  106. package/dist/detectors/orthography.js +12 -6
  107. package/dist/detectors/orthography.js.map +1 -1
  108. package/dist/detectors/quantity-range.d.ts +3 -0
  109. package/dist/detectors/quantity-range.d.ts.map +1 -0
  110. package/dist/detectors/quantity-range.js +35 -0
  111. package/dist/detectors/quantity-range.js.map +1 -0
  112. package/dist/detectors/range-band.d.ts +5 -0
  113. package/dist/detectors/range-band.d.ts.map +1 -0
  114. package/dist/detectors/range-band.js +80 -0
  115. package/dist/detectors/range-band.js.map +1 -0
  116. package/dist/detectors/registry/capacity-runtime-mismatch.d.ts +3 -0
  117. package/dist/detectors/registry/capacity-runtime-mismatch.d.ts.map +1 -0
  118. package/dist/detectors/registry/capacity-runtime-mismatch.js +3 -0
  119. package/dist/detectors/registry/capacity-runtime-mismatch.js.map +1 -0
  120. package/dist/detectors/registry/contact-variant.d.ts +3 -0
  121. package/dist/detectors/registry/contact-variant.d.ts.map +1 -0
  122. package/dist/detectors/registry/contact-variant.js +3 -0
  123. package/dist/detectors/registry/contact-variant.js.map +1 -0
  124. package/dist/detectors/registry/count-over-capacity.d.ts +3 -0
  125. package/dist/detectors/registry/count-over-capacity.d.ts.map +1 -0
  126. package/dist/detectors/registry/count-over-capacity.js +3 -0
  127. package/dist/detectors/registry/count-over-capacity.js.map +1 -0
  128. package/dist/detectors/registry/date-stamp-order.d.ts +3 -0
  129. package/dist/detectors/registry/date-stamp-order.d.ts.map +1 -0
  130. package/dist/detectors/registry/date-stamp-order.js +3 -0
  131. package/dist/detectors/registry/date-stamp-order.js.map +1 -0
  132. package/dist/detectors/registry/event-deadline.d.ts +3 -0
  133. package/dist/detectors/registry/event-deadline.d.ts.map +1 -0
  134. package/dist/detectors/registry/event-deadline.js +3 -0
  135. package/dist/detectors/registry/event-deadline.js.map +1 -0
  136. package/dist/detectors/registry/interval-count-mismatch.d.ts +3 -0
  137. package/dist/detectors/registry/interval-count-mismatch.d.ts.map +1 -0
  138. package/dist/detectors/registry/interval-count-mismatch.js +3 -0
  139. package/dist/detectors/registry/interval-count-mismatch.js.map +1 -0
  140. package/dist/detectors/registry/layout-code-mismatch.d.ts +3 -0
  141. package/dist/detectors/registry/layout-code-mismatch.d.ts.map +1 -0
  142. package/dist/detectors/registry/layout-code-mismatch.js +3 -0
  143. package/dist/detectors/registry/layout-code-mismatch.js.map +1 -0
  144. package/dist/detectors/registry/quantity-range.d.ts +3 -0
  145. package/dist/detectors/registry/quantity-range.d.ts.map +1 -0
  146. package/dist/detectors/registry/quantity-range.js +3 -0
  147. package/dist/detectors/registry/quantity-range.js.map +1 -0
  148. package/dist/detectors/registry/range-band-mismatch.d.ts +3 -0
  149. package/dist/detectors/registry/range-band-mismatch.d.ts.map +1 -0
  150. package/dist/detectors/registry/range-band-mismatch.js +3 -0
  151. package/dist/detectors/registry/range-band-mismatch.js.map +1 -0
  152. package/dist/detectors/registry/rent-multiple-mismatch.d.ts +3 -0
  153. package/dist/detectors/registry/rent-multiple-mismatch.d.ts.map +1 -0
  154. package/dist/detectors/registry/rent-multiple-mismatch.js +3 -0
  155. package/dist/detectors/registry/rent-multiple-mismatch.js.map +1 -0
  156. package/dist/detectors/registry/scaled-quantity-mismatch.d.ts +3 -0
  157. package/dist/detectors/registry/scaled-quantity-mismatch.d.ts.map +1 -0
  158. package/dist/detectors/registry/scaled-quantity-mismatch.js +3 -0
  159. package/dist/detectors/registry/scaled-quantity-mismatch.js.map +1 -0
  160. package/dist/detectors/registry/step-time-sum-mismatch.d.ts +3 -0
  161. package/dist/detectors/registry/step-time-sum-mismatch.d.ts.map +1 -0
  162. package/dist/detectors/registry/step-time-sum-mismatch.js +3 -0
  163. package/dist/detectors/registry/step-time-sum-mismatch.js.map +1 -0
  164. package/dist/detectors/registry/time-range-reversed.d.ts +3 -0
  165. package/dist/detectors/registry/time-range-reversed.d.ts.map +1 -0
  166. package/dist/detectors/registry/time-range-reversed.js +3 -0
  167. package/dist/detectors/registry/time-range-reversed.js.map +1 -0
  168. package/dist/detectors/registry/unlisted-item-used.d.ts +3 -0
  169. package/dist/detectors/registry/unlisted-item-used.d.ts.map +1 -0
  170. package/dist/detectors/registry/unlisted-item-used.js +3 -0
  171. package/dist/detectors/registry/unlisted-item-used.js.map +1 -0
  172. package/dist/detectors/registry/walk-time-distance-mismatch.d.ts +3 -0
  173. package/dist/detectors/registry/walk-time-distance-mismatch.d.ts.map +1 -0
  174. package/dist/detectors/registry/walk-time-distance-mismatch.js +3 -0
  175. package/dist/detectors/registry/walk-time-distance-mismatch.js.map +1 -0
  176. package/dist/detectors/rent-multiple-mismatch.d.ts +3 -0
  177. package/dist/detectors/rent-multiple-mismatch.d.ts.map +1 -0
  178. package/dist/detectors/rent-multiple-mismatch.js +39 -0
  179. package/dist/detectors/rent-multiple-mismatch.js.map +1 -0
  180. package/dist/detectors/scaled-quantity.d.ts +3 -0
  181. package/dist/detectors/scaled-quantity.d.ts.map +1 -0
  182. package/dist/detectors/scaled-quantity.js +37 -0
  183. package/dist/detectors/scaled-quantity.js.map +1 -0
  184. package/dist/detectors/signals.d.ts.map +1 -1
  185. package/dist/detectors/signals.js +3 -1
  186. package/dist/detectors/signals.js.map +1 -1
  187. package/dist/detectors/step-time-sum.d.ts +3 -0
  188. package/dist/detectors/step-time-sum.d.ts.map +1 -0
  189. package/dist/detectors/step-time-sum.js +46 -0
  190. package/dist/detectors/step-time-sum.js.map +1 -0
  191. package/dist/detectors/structure-tree.d.ts +2 -0
  192. package/dist/detectors/structure-tree.d.ts.map +1 -1
  193. package/dist/detectors/structure-tree.js +22 -5
  194. package/dist/detectors/structure-tree.js.map +1 -1
  195. package/dist/detectors/structure.js +1 -1
  196. package/dist/detectors/structure.js.map +1 -1
  197. package/dist/detectors/technical-docs.d.ts +5 -0
  198. package/dist/detectors/technical-docs.d.ts.map +1 -1
  199. package/dist/detectors/technical-docs.js +12 -0
  200. package/dist/detectors/technical-docs.js.map +1 -1
  201. package/dist/detectors/time-order.d.ts +1 -0
  202. package/dist/detectors/time-order.d.ts.map +1 -1
  203. package/dist/detectors/time-order.js +3 -1
  204. package/dist/detectors/time-order.js.map +1 -1
  205. package/dist/detectors/unit-consistency.d.ts +1 -1
  206. package/dist/detectors/unit-consistency.d.ts.map +1 -1
  207. package/dist/detectors/unit-consistency.js +46 -38
  208. package/dist/detectors/unit-consistency.js.map +1 -1
  209. package/dist/detectors/unlisted-items.d.ts +3 -0
  210. package/dist/detectors/unlisted-items.d.ts.map +1 -0
  211. package/dist/detectors/unlisted-items.js +57 -0
  212. package/dist/detectors/unlisted-items.js.map +1 -0
  213. package/dist/detectors/use-count-words.d.ts +8 -0
  214. package/dist/detectors/use-count-words.d.ts.map +1 -0
  215. package/dist/detectors/use-count-words.js +20 -0
  216. package/dist/detectors/use-count-words.js.map +1 -0
  217. package/dist/detectors/use-interval.d.ts +3 -0
  218. package/dist/detectors/use-interval.d.ts.map +1 -0
  219. package/dist/detectors/use-interval.js +39 -0
  220. package/dist/detectors/use-interval.js.map +1 -0
  221. package/dist/detectors/walk-time-distance-mismatch.d.ts +4 -0
  222. package/dist/detectors/walk-time-distance-mismatch.d.ts.map +1 -0
  223. package/dist/detectors/walk-time-distance-mismatch.js +38 -0
  224. package/dist/detectors/walk-time-distance-mismatch.js.map +1 -0
  225. package/dist/facts/approximate-tolerance.d.ts +7 -0
  226. package/dist/facts/approximate-tolerance.d.ts.map +1 -0
  227. package/dist/facts/approximate-tolerance.js +16 -0
  228. package/dist/facts/approximate-tolerance.js.map +1 -0
  229. package/dist/facts/item-amounts.d.ts +18 -0
  230. package/dist/facts/item-amounts.d.ts.map +1 -0
  231. package/dist/facts/item-amounts.js +67 -0
  232. package/dist/facts/item-amounts.js.map +1 -0
  233. package/dist/facts/labelled-facts.d.ts +4 -0
  234. package/dist/facts/labelled-facts.d.ts.map +1 -1
  235. package/dist/facts/labelled-facts.js +21 -6
  236. package/dist/facts/labelled-facts.js.map +1 -1
  237. package/dist/facts/measure-units.d.ts +5 -0
  238. package/dist/facts/measure-units.d.ts.map +1 -0
  239. package/dist/facts/measure-units.js +28 -0
  240. package/dist/facts/measure-units.js.map +1 -0
  241. package/dist/facts/measures.d.ts +23 -4
  242. package/dist/facts/measures.d.ts.map +1 -1
  243. package/dist/facts/measures.js +68 -19
  244. package/dist/facts/measures.js.map +1 -1
  245. package/dist/facts/retention-periods.d.ts +51 -0
  246. package/dist/facts/retention-periods.d.ts.map +1 -0
  247. package/dist/facts/retention-periods.js +148 -0
  248. package/dist/facts/retention-periods.js.map +1 -0
  249. package/dist/facts/unit-conflicts.d.ts +3 -2
  250. package/dist/facts/unit-conflicts.d.ts.map +1 -1
  251. package/dist/facts/unit-conflicts.js +15 -7
  252. package/dist/facts/unit-conflicts.js.map +1 -1
  253. package/dist/facts/unit-equivalents.d.ts +13 -0
  254. package/dist/facts/unit-equivalents.d.ts.map +1 -0
  255. package/dist/facts/unit-equivalents.js +56 -0
  256. package/dist/facts/unit-equivalents.js.map +1 -0
  257. package/dist/facts/unit-tolerance.d.ts +4 -0
  258. package/dist/facts/unit-tolerance.d.ts.map +1 -0
  259. package/dist/facts/unit-tolerance.js +13 -0
  260. package/dist/facts/unit-tolerance.js.map +1 -0
  261. package/dist/facts/unit-word.d.ts +8 -0
  262. package/dist/facts/unit-word.d.ts.map +1 -0
  263. package/dist/facts/unit-word.js +34 -0
  264. package/dist/facts/unit-word.js.map +1 -0
  265. package/dist/facts/written-precision.d.ts +10 -0
  266. package/dist/facts/written-precision.d.ts.map +1 -0
  267. package/dist/facts/written-precision.js +20 -0
  268. package/dist/facts/written-precision.js.map +1 -0
  269. package/dist/kana-spelling.d.ts +12 -0
  270. package/dist/kana-spelling.d.ts.map +1 -1
  271. package/dist/kana-spelling.js +23 -0
  272. package/dist/kana-spelling.js.map +1 -1
  273. package/dist/model-codes.d.ts +23 -0
  274. package/dist/model-codes.d.ts.map +1 -0
  275. package/dist/model-codes.js +81 -0
  276. package/dist/model-codes.js.map +1 -0
  277. package/dist/name-spelling-chars.d.ts +16 -0
  278. package/dist/name-spelling-chars.d.ts.map +1 -0
  279. package/dist/name-spelling-chars.js +14 -0
  280. package/dist/name-spelling-chars.js.map +1 -0
  281. package/dist/name-variants.d.ts +3 -2
  282. package/dist/name-variants.d.ts.map +1 -1
  283. package/dist/name-variants.js +4 -7
  284. package/dist/name-variants.js.map +1 -1
  285. package/dist/phone-numbers.d.ts.map +1 -1
  286. package/dist/phone-numbers.js +22 -5
  287. package/dist/phone-numbers.js.map +1 -1
  288. package/dist/place-names.d.ts +12 -5
  289. package/dist/place-names.d.ts.map +1 -1
  290. package/dist/place-names.js +34 -14
  291. package/dist/place-names.js.map +1 -1
  292. package/dist/plugin.d.ts +5 -0
  293. package/dist/plugin.d.ts.map +1 -1
  294. package/dist/preamble-paragraphs.d.ts +2 -1
  295. package/dist/preamble-paragraphs.d.ts.map +1 -1
  296. package/dist/preamble-paragraphs.js +16 -1
  297. package/dist/preamble-paragraphs.js.map +1 -1
  298. package/dist/profile/parse.d.ts.map +1 -1
  299. package/dist/profile/parse.js +11 -0
  300. package/dist/profile/parse.js.map +1 -1
  301. package/dist/spacing-code.d.ts +6 -0
  302. package/dist/spacing-code.d.ts.map +1 -0
  303. package/dist/spacing-code.js +16 -0
  304. package/dist/spacing-code.js.map +1 -0
  305. package/dist/structure/amount-range.d.ts +2 -2
  306. package/dist/structure/amount-range.d.ts.map +1 -1
  307. package/dist/structure/amount-range.js +12 -5
  308. package/dist/structure/amount-range.js.map +1 -1
  309. package/dist/structure/capacity-count.d.ts +59 -0
  310. package/dist/structure/capacity-count.d.ts.map +1 -0
  311. package/dist/structure/capacity-count.js +118 -0
  312. package/dist/structure/capacity-count.js.map +1 -0
  313. package/dist/structure/counted-amounts.d.ts +6 -0
  314. package/dist/structure/counted-amounts.d.ts.map +1 -1
  315. package/dist/structure/counted-amounts.js +4 -4
  316. package/dist/structure/counted-amounts.js.map +1 -1
  317. package/dist/structure/date-outside-period.d.ts +3 -1
  318. package/dist/structure/date-outside-period.d.ts.map +1 -1
  319. package/dist/structure/date-outside-period.js +8 -5
  320. package/dist/structure/date-outside-period.js.map +1 -1
  321. package/dist/structure/date-range.d.ts +1 -0
  322. package/dist/structure/date-range.d.ts.map +1 -1
  323. package/dist/structure/date-range.js +1 -1
  324. package/dist/structure/date-range.js.map +1 -1
  325. package/dist/structure/deadline-after-event.d.ts +23 -0
  326. package/dist/structure/deadline-after-event.d.ts.map +1 -0
  327. package/dist/structure/deadline-after-event.js +42 -0
  328. package/dist/structure/deadline-after-event.js.map +1 -0
  329. package/dist/structure/definition-use.d.ts +1 -1
  330. package/dist/structure/definition-use.d.ts.map +1 -1
  331. package/dist/structure/definition-use.js +55 -2
  332. package/dist/structure/definition-use.js.map +1 -1
  333. package/dist/structure/due-date.d.ts.map +1 -1
  334. package/dist/structure/due-date.js +5 -27
  335. package/dist/structure/due-date.js.map +1 -1
  336. package/dist/structure/labelled-date-order.d.ts +38 -0
  337. package/dist/structure/labelled-date-order.d.ts.map +1 -0
  338. package/dist/structure/labelled-date-order.js +41 -0
  339. package/dist/structure/labelled-date-order.js.map +1 -0
  340. package/dist/structure/layout-codes.d.ts +68 -0
  341. package/dist/structure/layout-codes.d.ts.map +1 -0
  342. package/dist/structure/layout-codes.js +161 -0
  343. package/dist/structure/layout-codes.js.map +1 -0
  344. package/dist/structure/measured-amounts.d.ts +17 -0
  345. package/dist/structure/measured-amounts.d.ts.map +1 -0
  346. package/dist/structure/measured-amounts.js +110 -0
  347. package/dist/structure/measured-amounts.js.map +1 -0
  348. package/dist/structure/quantity-range.d.ts +8 -0
  349. package/dist/structure/quantity-range.d.ts.map +1 -0
  350. package/dist/structure/quantity-range.js +36 -0
  351. package/dist/structure/quantity-range.js.map +1 -0
  352. package/dist/structure/range-bands.d.ts +75 -0
  353. package/dist/structure/range-bands.d.ts.map +1 -0
  354. package/dist/structure/range-bands.js +244 -0
  355. package/dist/structure/range-bands.js.map +1 -0
  356. package/dist/structure/rent-multiple.d.ts +76 -0
  357. package/dist/structure/rent-multiple.d.ts.map +1 -0
  358. package/dist/structure/rent-multiple.js +186 -0
  359. package/dist/structure/rent-multiple.js.map +1 -0
  360. package/dist/structure/scaled-quantities.d.ts +50 -0
  361. package/dist/structure/scaled-quantities.d.ts.map +1 -0
  362. package/dist/structure/scaled-quantities.js +153 -0
  363. package/dist/structure/scaled-quantities.js.map +1 -0
  364. package/dist/structure/stamp-order.d.ts +16 -0
  365. package/dist/structure/stamp-order.d.ts.map +1 -0
  366. package/dist/structure/stamp-order.js +78 -0
  367. package/dist/structure/stamp-order.js.map +1 -0
  368. package/dist/structure/step-times.d.ts +49 -0
  369. package/dist/structure/step-times.d.ts.map +1 -0
  370. package/dist/structure/step-times.js +128 -0
  371. package/dist/structure/step-times.js.map +1 -0
  372. package/dist/structure/term-deadline.d.ts +19 -0
  373. package/dist/structure/term-deadline.d.ts.map +1 -0
  374. package/dist/structure/term-deadline.js +28 -0
  375. package/dist/structure/term-deadline.js.map +1 -0
  376. package/dist/structure/time-range.d.ts +16 -0
  377. package/dist/structure/time-range.d.ts.map +1 -0
  378. package/dist/structure/time-range.js +57 -0
  379. package/dist/structure/time-range.js.map +1 -0
  380. package/dist/structure/total-line.d.ts +16 -0
  381. package/dist/structure/total-line.d.ts.map +1 -0
  382. package/dist/structure/total-line.js +44 -0
  383. package/dist/structure/total-line.js.map +1 -0
  384. package/dist/structure/total.d.ts +7 -1
  385. package/dist/structure/total.d.ts.map +1 -1
  386. package/dist/structure/total.js +28 -5
  387. package/dist/structure/total.js.map +1 -1
  388. package/dist/structure/unlisted-items.d.ts +70 -0
  389. package/dist/structure/unlisted-items.d.ts.map +1 -0
  390. package/dist/structure/unlisted-items.js +152 -0
  391. package/dist/structure/unlisted-items.js.map +1 -0
  392. package/dist/structure/walk-time.d.ts +87 -0
  393. package/dist/structure/walk-time.d.ts.map +1 -0
  394. package/dist/structure/walk-time.js +195 -0
  395. package/dist/structure/walk-time.js.map +1 -0
  396. package/package.json +3 -3
  397. package/profiles/listing.yaml +20 -0
  398. package/rules/announced-count-mismatch.yaml +2 -2
  399. package/rules/capacity-runtime-mismatch.yaml +66 -0
  400. package/rules/contact-variant.yaml +76 -0
  401. package/rules/count-over-capacity.yaml +55 -0
  402. package/rules/date-outside-period.yaml +4 -4
  403. package/rules/date-stamp-order.yaml +82 -0
  404. package/rules/deadline-after-event.yaml +67 -0
  405. package/rules/defined-name-repeated.yaml +2 -2
  406. package/rules/due-before-issue.yaml +11 -7
  407. package/rules/fact-conflict.yaml +9 -4
  408. package/rules/heading-echo.yaml +2 -2
  409. package/rules/interval-count-mismatch.yaml +54 -0
  410. package/rules/latin-spacing.yaml +2 -2
  411. package/rules/layout-code-mismatch.yaml +68 -0
  412. package/rules/name-variant.yaml +8 -2
  413. package/rules/no-em-dash.yaml +2 -2
  414. package/rules/preamble-length.yaml +2 -2
  415. package/rules/quantity-range-reversed.yaml +53 -0
  416. package/rules/range-band-mismatch.yaml +70 -0
  417. package/rules/rent-multiple-mismatch.yaml +61 -0
  418. package/rules/scaled-quantity-mismatch.yaml +69 -0
  419. package/rules/step-time-sum-mismatch.yaml +65 -0
  420. package/rules/time-range-reversed.yaml +48 -0
  421. package/rules/title-case-consistency.yaml +2 -2
  422. package/rules/total-mismatch.yaml +2 -2
  423. package/rules/unit-mismatch.yaml +12 -4
  424. package/rules/unlisted-item-used.yaml +90 -0
  425. package/rules/unused-definition.yaml +2 -2
  426. package/rules/use-before-definition.yaml +2 -2
  427. package/rules/walk-time-distance-mismatch.yaml +55 -0
  428. package/src/announced-count.ts +6 -3
  429. package/src/contacts/addresses.ts +179 -0
  430. package/src/contacts/emails.ts +63 -0
  431. package/src/contacts/variants.ts +33 -0
  432. package/src/derived/battery-runtime.ts +284 -0
  433. package/src/derived/period-counts.ts +178 -0
  434. package/src/derived/time-lengths.ts +12 -9
  435. package/src/derived/use-interval.ts +151 -0
  436. package/src/detectors/announced-count.ts +1 -0
  437. package/src/detectors/capacity-runtime.ts +128 -0
  438. package/src/detectors/contact-variant.ts +24 -0
  439. package/src/detectors/count-over-capacity.ts +37 -0
  440. package/src/detectors/date-outside-period.ts +2 -1
  441. package/src/detectors/date-stamp-order.ts +28 -0
  442. package/src/detectors/defined-term-form.ts +63 -7
  443. package/src/detectors/definition-use.ts +6 -2
  444. package/src/detectors/derived-numbers.ts +3 -3
  445. package/src/detectors/due-date.ts +10 -2
  446. package/src/detectors/en-shape.ts +11 -4
  447. package/src/detectors/event-deadline.ts +29 -0
  448. package/src/detectors/fact-consistency.ts +64 -3
  449. package/src/detectors/heading-case.ts +13 -4
  450. package/src/detectors/heading-echo.ts +17 -1
  451. package/src/detectors/layout-code.ts +174 -0
  452. package/src/detectors/measured-facts.ts +20 -0
  453. package/src/detectors/name-variant-model-codes.ts +27 -0
  454. package/src/detectors/name-variant.ts +32 -6
  455. package/src/detectors/orthographic-variant.ts +78 -9
  456. package/src/detectors/orthography.ts +16 -6
  457. package/src/detectors/quantity-range.ts +47 -0
  458. package/src/detectors/range-band.ts +125 -0
  459. package/src/detectors/registry/capacity-runtime-mismatch.ts +4 -0
  460. package/src/detectors/registry/contact-variant.ts +4 -0
  461. package/src/detectors/registry/count-over-capacity.ts +4 -0
  462. package/src/detectors/registry/date-stamp-order.ts +4 -0
  463. package/src/detectors/registry/event-deadline.ts +4 -0
  464. package/src/detectors/registry/interval-count-mismatch.ts +4 -0
  465. package/src/detectors/registry/layout-code-mismatch.ts +4 -0
  466. package/src/detectors/registry/quantity-range.ts +4 -0
  467. package/src/detectors/registry/range-band-mismatch.ts +4 -0
  468. package/src/detectors/registry/rent-multiple-mismatch.ts +4 -0
  469. package/src/detectors/registry/scaled-quantity-mismatch.ts +4 -0
  470. package/src/detectors/registry/step-time-sum-mismatch.ts +4 -0
  471. package/src/detectors/registry/time-range-reversed.ts +4 -0
  472. package/src/detectors/registry/unlisted-item-used.ts +4 -0
  473. package/src/detectors/registry/walk-time-distance-mismatch.ts +4 -0
  474. package/src/detectors/rent-multiple-mismatch.ts +56 -0
  475. package/src/detectors/scaled-quantity.ts +49 -0
  476. package/src/detectors/signals.ts +3 -1
  477. package/src/detectors/step-time-sum.ts +55 -0
  478. package/src/detectors/structure-tree.ts +30 -5
  479. package/src/detectors/structure.ts +1 -1
  480. package/src/detectors/technical-docs.ts +12 -0
  481. package/src/detectors/time-order.ts +5 -1
  482. package/src/detectors/unit-consistency.ts +50 -38
  483. package/src/detectors/unlisted-items.ts +72 -0
  484. package/src/detectors/use-count-words.ts +32 -0
  485. package/src/detectors/use-interval.ts +51 -0
  486. package/src/detectors/walk-time-distance-mismatch.ts +45 -0
  487. package/src/facts/approximate-tolerance.ts +19 -0
  488. package/src/facts/item-amounts.ts +94 -0
  489. package/src/facts/labelled-facts.ts +29 -7
  490. package/src/facts/measure-units.ts +31 -0
  491. package/src/facts/measures.ts +113 -25
  492. package/src/facts/retention-periods.ts +216 -0
  493. package/src/facts/unit-conflicts.ts +22 -9
  494. package/src/facts/unit-equivalents.ts +70 -0
  495. package/src/facts/unit-tolerance.ts +17 -0
  496. package/src/facts/unit-word.ts +35 -0
  497. package/src/facts/written-precision.ts +23 -0
  498. package/src/kana-spelling.ts +25 -0
  499. package/src/model-codes.ts +104 -0
  500. package/src/name-spelling-chars.ts +34 -0
  501. package/src/name-variants.ts +8 -7
  502. package/src/phone-numbers.ts +26 -6
  503. package/src/place-names.ts +53 -14
  504. package/src/plugin.ts +2 -0
  505. package/src/preamble-paragraphs.ts +21 -2
  506. package/src/profile/parse.ts +10 -0
  507. package/src/spacing-code.ts +26 -0
  508. package/src/structure/amount-range.ts +16 -7
  509. package/src/structure/capacity-count.ts +196 -0
  510. package/src/structure/counted-amounts.ts +5 -4
  511. package/src/structure/date-outside-period.ts +11 -6
  512. package/src/structure/date-range.ts +1 -1
  513. package/src/structure/deadline-after-event.ts +67 -0
  514. package/src/structure/definition-use.ts +59 -2
  515. package/src/structure/due-date.ts +8 -32
  516. package/src/structure/labelled-date-order.ts +84 -0
  517. package/src/structure/layout-codes.ts +210 -0
  518. package/src/structure/measured-amounts.ts +138 -0
  519. package/src/structure/quantity-range.ts +54 -0
  520. package/src/structure/range-bands.ts +316 -0
  521. package/src/structure/rent-multiple.ts +277 -0
  522. package/src/structure/scaled-quantities.ts +225 -0
  523. package/src/structure/stamp-order.ts +100 -0
  524. package/src/structure/step-times.ts +169 -0
  525. package/src/structure/term-deadline.ts +35 -0
  526. package/src/structure/time-range.ts +64 -0
  527. package/src/structure/total-line.ts +63 -0
  528. package/src/structure/total.ts +41 -6
  529. package/src/structure/unlisted-items.ts +207 -0
  530. package/src/structure/walk-time.ts +285 -0
@@ -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
  });
@@ -0,0 +1,207 @@
1
+ // unlisted-item-used: a numbered step that uses an ingredient or a tool the document's own list of what is needed (材料,
2
+ // 用意するもの, Ingredients, What you need) does not name. Only a noun in a use frame is read: the object of a use verb
3
+ // (塩を加える, add salt), and, when the document lists tools, the thing used (紙やすりで磨く, with a multi-tool) or put into
4
+ // (バケツに沈める, in a bucket). A noun is listed when the document has written it before the step (the list, the title, an
5
+ // earlier step), whole or by its last word; a noun always at hand (水, 手, water) is never reported. The words are the
6
+ // language package's (item-list-heading, item-use, item-amount, item-at-hand, enumeration-joiner).
7
+
8
+ export type ItemToken = { readonly surface: string; readonly pos: string; readonly lemma: string; readonly start: number; readonly end: number };
9
+
10
+ /** A word that marks a use frame. after: it follows the noun (を, に, で); before: it precedes it (add, with, in). */
11
+ export type UseMarker = { readonly word: string; readonly group: UseGroup; readonly position: "before" | "after" };
12
+
13
+ export type UseGroup = "object" | "place" | "instrument";
14
+
15
+ export type ItemWords = {
16
+ readonly ingredientHeadings: readonly string[];
17
+ readonly toolHeadings: readonly string[];
18
+ readonly markers: readonly UseMarker[];
19
+ /** The verbs that must follow an after-marker of each group; a group with none takes any verb. */
20
+ readonly verbs: Readonly<Partial<Record<UseGroup, readonly string[]>>>;
21
+ /** Words right after a verb (ず, ない) or right before it (not) that deny the use. */
22
+ readonly negations: readonly string[];
23
+ /** Verbs the tagger may read as nouns (stir, knead): a coordination ends at one. */
24
+ readonly actions: readonly string[];
25
+ readonly joiners: readonly string[];
26
+ /** A word after which the thing itself starts again (30 ml of soy sauce). */
27
+ readonly partitives: readonly string[];
28
+ readonly amounts: readonly string[];
29
+ readonly atHand: readonly string[];
30
+ };
31
+
32
+ export type ItemStep = { readonly start: number; readonly tokens: readonly ItemToken[] };
33
+
34
+ export type UnlistedItem = { readonly offset: number; readonly item: string; readonly list: string };
35
+
36
+ type ItemList = { readonly heading: string; readonly tools: boolean };
37
+
38
+ type Candidate = { readonly tokens: readonly ItemToken[]; readonly group: UseGroup };
39
+
40
+ const NOUN_POS = new Set(["NOUN", "PROPN"]);
41
+ const SKIPPED_POS = new Set(["DET", "ADJ", "NUM"]);
42
+ const CLAUSE_OPENER_POS = new Set(["PUNCT", "CCONJ", "ADV", "SCONJ", "X"]);
43
+ const HAS_DIGIT = /\p{Nd}/u;
44
+ const HEADING_TAIL_OPENER = /[((::【[]/u;
45
+
46
+ const lower = (text: string): string => text.toLowerCase();
47
+
48
+ /** A heading's words before a bracket or a colon, in lower case: 材料(2~3人分) is 材料. */
49
+ export const headingKey = (heading: string): string => lower((heading.split(HEADING_TAIL_OPENER)[0] ?? "").trim());
50
+
51
+ /** Spaces, hyphens and case are not a different word: multi-tool is multitool. */
52
+ export const squeezed = (text: string): string => lower(text).replace(/[\s\-‐]/gu, "");
53
+
54
+ const listKind = (heading: string, words: ItemWords): ItemList | undefined => {
55
+ const key = headingKey(heading);
56
+ const tools = words.toolHeadings.some((word) => lower(word) === key);
57
+ if (!tools && !words.ingredientHeadings.some((word) => lower(word) === key)) return undefined;
58
+ return { heading: heading.trim(), tools };
59
+ };
60
+
61
+ export const itemLists = (headings: readonly string[], words: ItemWords): ItemList[] => headings.flatMap((heading) => listKind(heading, words) ?? []);
62
+
63
+ const isWord = (token: ItemToken | undefined, words: readonly string[]): boolean =>
64
+ token !== undefined && words.some((word) => word === token.surface || word === lower(token.surface) || word === token.lemma);
65
+
66
+ const isAmount = (token: ItemToken, words: ItemWords): boolean => HAS_DIGIT.test(token.surface) || isWord(token, words.amounts);
67
+
68
+ const isNoun = (token: ItemToken | undefined): boolean => token !== undefined && NOUN_POS.has(token.pos);
69
+
70
+ const isHyphen = (token: ItemToken | undefined): boolean => token !== undefined && /^[-‐]$/u.test(token.surface);
71
+
72
+ const BRACKET_PAIRS: Readonly<Record<string, string>> = { ")": "(", ")": "(", "」": "「", "]": "[" };
73
+
74
+ /** The index of the bracket that opens the aside closing at index, or -1 when it does not close one. */
75
+ const asideOpening = (tokens: readonly ItemToken[], index: number): number => {
76
+ const open = BRACKET_PAIRS[tokens[index]?.surface ?? ""];
77
+ if (open === undefined) return -1;
78
+ return tokens.findLastIndex((token, at) => at < index && token.surface === open);
79
+ };
80
+
81
+ type Reading = { readonly segments: readonly (readonly ItemToken[])[]; readonly current: readonly ItemToken[] };
82
+
83
+ const EMPTY_READING: Reading = { segments: [], current: [] };
84
+
85
+ const closed = (reading: Reading): Reading => (reading.current.length === 0 ? reading : { segments: [...reading.segments, reading.current], current: [] });
86
+
87
+ const stepBack = (tokens: readonly ItemToken[], index: number, reading: Reading, words: ItemWords): Reading => {
88
+ const token = tokens[index];
89
+ if (token === undefined) return closed(reading);
90
+ const aside = asideOpening(tokens, index);
91
+ if (aside >= 0) return stepBack(tokens, aside - 1, reading, words);
92
+ if (isWord(token, words.joiners)) return stepBack(tokens, index - 1, closed(reading), words);
93
+ if (isAmount(token, words)) return stepBack(tokens, index - 1, reading, words);
94
+ if (isNoun(token)) return stepBack(tokens, index - 1, { segments: reading.segments, current: [token, ...reading.current] }, words);
95
+ return closed(reading);
96
+ };
97
+
98
+ /** The nouns joined before an after-marker (合いびき肉、卵、塩、こしょうを), read backwards up to the first other word. */
99
+ export const phrasesBefore = (tokens: readonly ItemToken[], marker: number, words: ItemWords): (readonly ItemToken[])[] => [
100
+ ...stepBack(tokens, marker - 1, EMPTY_READING, words).segments,
101
+ ];
102
+
103
+ const isModifier = (token: ItemToken, words: ItemWords): boolean =>
104
+ SKIPPED_POS.has(token.pos) || isAmount(token, words) || (token.pos === "VERB" && token.surface.endsWith("ed"));
105
+
106
+ const isAction = (token: ItemToken, reading: Reading, words: ItemWords): boolean =>
107
+ reading.current.length === 0 && reading.segments.length > 0 && isWord(token, words.actions);
108
+
109
+ const stepOn = (tokens: readonly ItemToken[], index: number, reading: Reading, words: ItemWords): Reading => {
110
+ const token = tokens[index];
111
+ if (token === undefined) return closed(reading);
112
+ const next = (after: Reading): Reading => stepOn(tokens, index + 1, after, words);
113
+ if (isWord(token, words.partitives)) return next({ segments: reading.segments, current: [] });
114
+ if (isWord(token, words.joiners)) return next(closed(reading));
115
+ if (isNoun(token) && !isAction(token, reading, words)) return next({ segments: reading.segments, current: [...reading.current, token] });
116
+ if (isHyphen(token) && reading.current.length > 0 && isNoun(tokens[index + 1])) return next(reading);
117
+ if (reading.current.length === 0 && isModifier(token, words)) return next(reading);
118
+ return closed(reading);
119
+ };
120
+
121
+ /** The nouns joined after a before-marker (add the egg, salt and pepper), read forwards up to the first other word. */
122
+ export const phrasesAfter = (tokens: readonly ItemToken[], marker: number, words: ItemWords): (readonly ItemToken[])[] => [
123
+ ...stepOn(tokens, marker + 1, EMPTY_READING, words).segments,
124
+ ];
125
+
126
+ const markerLength = (tokens: readonly ItemToken[], index: number, word: string): number => {
127
+ const parts = word.split(" ");
128
+ return parts.every((part, at) => isWord(tokens[index + at], [part])) ? parts.length : 0;
129
+ };
130
+
131
+ const deniedAfter = (tokens: readonly ItemToken[], verb: number, words: ItemWords): boolean => isWord(tokens[verb + 1], words.negations);
132
+
133
+ const followedByUse = (tokens: readonly ItemToken[], index: number, group: UseGroup, words: ItemWords): boolean => {
134
+ const verb = tokens[index + 1];
135
+ if (verb === undefined || verb.pos !== "VERB") return false;
136
+ const verbs = words.verbs[group] ?? [];
137
+ return (verbs.length === 0 || isWord(verb, verbs)) && !deniedAfter(tokens, index + 1, words);
138
+ };
139
+
140
+ /** How far before a verb a negation still denies it (do not ever add). */
141
+ const NEGATION_REACH = 3;
142
+
143
+ const opensClause = (tokens: readonly ItemToken[], index: number, words: ItemWords): boolean => {
144
+ const previous = tokens[index - 1];
145
+ const denied = tokens.slice(Math.max(0, index - NEGATION_REACH), index).some((token) => isWord(token, words.negations));
146
+ return !denied && (previous === undefined || CLAUSE_OPENER_POS.has(previous.pos));
147
+ };
148
+
149
+ const candidatesAt = (tokens: readonly ItemToken[], index: number, marker: UseMarker, words: ItemWords): Candidate[] => {
150
+ if (marker.position === "after") {
151
+ if (tokens[index]?.pos !== "ADP" || !isWord(tokens[index], [marker.word]) || !followedByUse(tokens, index, marker.group, words)) return [];
152
+ return phrasesBefore(tokens, index, words).map((phrase) => ({ tokens: phrase, group: marker.group }));
153
+ }
154
+ const length = markerLength(tokens, index, marker.word);
155
+ const object = marker.group === "object";
156
+ if (length === 0 || (object && !opensClause(tokens, index, words))) return [];
157
+ // The thing a step puts in or works with comes before "of" (in a bucket of water); what it adds, after it (a pinch of salt).
158
+ const read = object ? words : { ...words, partitives: [] };
159
+ return phrasesAfter(tokens, index + length - 1, read).map((phrase) => ({ tokens: phrase, group: marker.group }));
160
+ };
161
+
162
+ /** Every noun phrase a step uses, in the order written. */
163
+ export const usedPhrases = (tokens: readonly ItemToken[], words: ItemWords): Candidate[] =>
164
+ tokens
165
+ .flatMap((_, index) => words.markers.flatMap((marker) => candidatesAt(tokens, index, marker, words)))
166
+ .toSorted((left, right) => (left.tokens[0]?.start ?? 0) - (right.tokens[0]?.start ?? 0));
167
+
168
+ const phraseText = (source: string, phrase: readonly ItemToken[]): string => source.slice(phrase[0]?.start ?? 0, phrase.at(-1)?.end ?? 0);
169
+
170
+ const isAtHand = (source: string, phrase: readonly ItemToken[], words: ItemWords): boolean => {
171
+ const text = squeezed(phraseText(source, phrase));
172
+ const last = phrase.at(-1);
173
+ return words.atHand.some((word) => text.endsWith(squeezed(word)) || (last !== undefined && lower(last.lemma) === lower(word)));
174
+ };
175
+
176
+ /** The document as written and as the tagger read it: the source, and every word with its lemma (berries is berry). */
177
+ export type ItemText = { readonly source: string; readonly words: readonly ItemToken[] };
178
+
179
+ /** Written before: the whole phrase, or its last word as written or as its lemma (eggs, egg). */
180
+ const isWrittenBefore = (text: ItemText, phrase: readonly ItemToken[]): boolean => {
181
+ const start = phrase[0]?.start ?? 0;
182
+ const earlier = squeezed(text.source.slice(0, start));
183
+ const last = phrase.at(-1);
184
+ const forms = [phraseText(text.source, phrase), last?.surface ?? "", last?.lemma ?? ""].map(squeezed).filter((form) => form !== "");
185
+ const lemma = lower(last?.lemma ?? "");
186
+ return forms.some((form) => earlier.includes(form)) || text.words.some((word) => word.start < start && lower(word.lemma) === lemma);
187
+ };
188
+
189
+ const unlistedIn = (text: ItemText, step: ItemStep, lists: readonly ItemList[], words: ItemWords): UnlistedItem[] => {
190
+ const tools = lists.some((list) => list.tools);
191
+ const listNames = lists.map((list) => list.heading).join(" / ");
192
+ return usedPhrases(step.tokens, words)
193
+ .filter((candidate) => (candidate.group === "object" || tools) && candidate.tokens.at(-1)?.pos === "NOUN")
194
+ .filter((candidate) => !isAtHand(text.source, candidate.tokens, words) && !isWrittenBefore(text, candidate.tokens))
195
+ .map((candidate) => ({ offset: candidate.tokens[0]?.start ?? step.start, item: phraseText(text.source, candidate.tokens), list: listNames }));
196
+ };
197
+
198
+ /**
199
+ * The things the numbered steps use that the document's list of what is needed does not name. Nothing when the document
200
+ * has no such list: a document that promises no list cannot leave something off it.
201
+ */
202
+ export const unlistedItems = (text: ItemText, headings: readonly string[], steps: readonly ItemStep[], words: ItemWords): UnlistedItem[] => {
203
+ const lists = itemLists(headings, words);
204
+ if (lists.length === 0) return [];
205
+ const found = steps.flatMap((step) => unlistedIn(text, step, lists, words));
206
+ return found.filter((item, index) => found.findIndex((other) => squeezed(other.item) === squeezed(item.item)) === index);
207
+ };
@@ -0,0 +1,285 @@
1
+ import type { StructureIssue } from "./issues.ts";
2
+
3
+ /**
4
+ * 書いた距離と合わない徒歩の分数(駅 徒歩3分(約600m)、a 3-minute walk (600 m))。徒歩の語に付いた分数と、そのすぐ前かすぐ後ろの
5
+ * 距離だけを組にし、文書の速さで歩いた分数と比べる。速さは文書に書いたもの(道路距離80mを1分、80 m per minute、5 km/h)を使い、
6
+ * 書いていなければ呼ぶ側が渡す速さ(文書の種類の速さ。不動産の広告は 80m で1分、切り上げ)を使う。どちらも無ければ速さを推し量らない。
7
+ * 約の付いた距離は、書いた一番下の桁の半分だけ幅を持たせる(約1.2km は 1.15〜1.25km)。小数の距離は書いた桁の半分の幅を持つ。
8
+ * 範囲(5〜7分)、上限(10分以内)、約の付いた分数は比べない。語は言語パッケージの語彙表から受け取る。
9
+ */
10
+
11
+ /** 語彙表の一語。position は数のどちら側に書くか。weight は単位の倍率(長さは m、時間は秒)。 */
12
+ export type WalkMark = {
13
+ readonly pattern: string;
14
+ readonly position?: "before" | "after" | undefined;
15
+ readonly weight?: number | undefined;
16
+ };
17
+
18
+ export type WalkWords = {
19
+ /** 分数を徒歩の時間にする語(徒歩、walk)。position は分数のどちら側か。 */
20
+ readonly walks: readonly WalkMark[];
21
+ /** 徒歩の語と分数の間に挟んでよい語(徒歩で5分の で)。 */
22
+ readonly gaps: readonly string[];
23
+ /** 分数を上限にする語(以内、within)。position は分数のどちら側か。 */
24
+ readonly bounds: readonly WalkMark[];
25
+ /** 二つの数を範囲にする語(5から7分、8 to 10 minutes)。〜 や - の記号は語彙表に書かなくても読む。 */
26
+ readonly ranges: readonly string[];
27
+ /** 約や上限の語と数の間で読み飛ばす語(about a 5-minute walk の a)。 */
28
+ readonly fillers: readonly string[];
29
+ /** 長さの単位。weight は m への倍率。 */
30
+ readonly lengths: readonly WalkMark[];
31
+ /** 時間の単位。weight は秒への倍率。分より短い単位は分数に使わない。 */
32
+ readonly times: readonly WalkMark[];
33
+ /** 数を概数にする語(約、about)。 */
34
+ readonly hedges: readonly string[];
35
+ /** 長さを速さにする語(を1分、per minute、/h、分速)。weight はその時間の秒。 */
36
+ readonly perTimes: readonly WalkMark[];
37
+ /** 速さの行にあれば、端数を切り上げると言う語(切り上げ、rounded up)。 */
38
+ readonly roundUps: readonly string[];
39
+ /** 速さの行にあれば、切り上げの語があっても切り上げではないと言う語(切り上げません、not rounded up)。 */
40
+ readonly notRoundUps: readonly string[];
41
+ };
42
+
43
+ /** 1分に歩く m と、端数の扱い。up は切り上げだけ、any は切り上げでも切り捨てでも合う。 */
44
+ export type WalkRate = { readonly metresPerMinute: number; readonly rounding: "up" | "any" };
45
+
46
+ /** 読んだ距離。metres は書いた値、margin は書いた桁から来る幅の半分(m)。 */
47
+ export type Distance = { readonly start: number; readonly end: number; readonly metres: number; readonly margin: number };
48
+
49
+ /** 徒歩の分数。lead は前に書いた徒歩の語の始まり(徒歩5分 の 徒歩)、後ろに書くなら数の始まり。 */
50
+ type Minutes = { readonly start: number; readonly end: number; readonly lead: number; readonly value: number };
51
+
52
+ type Reading = { readonly start: number; readonly end: number; readonly digits: string; readonly unit: WalkMark; readonly unitEnd: number };
53
+
54
+ const DIGIT = "[0-90-9]";
55
+ const NUMBER = new RegExp(`(?<!${DIGIT}|[.,,.])${DIGIT}+(?:[,,]${DIGIT}{3})*(?:[..]${DIGIT}+)?(?!${DIGIT}|[..,,]${DIGIT})`, "gu");
56
+ const LETTER = /[A-Za-z]/u;
57
+ /** 単位のすぐ後ろにあれば、その単位ではない(m のあとの in、㎡ の 2、m² の ²)。 */
58
+ const UNIT_CONTINUES = /^[A-Za-z0-90-9²³]/u;
59
+ /** 数と単位の間に置けるもの(1.2 km、5-minute)。 */
60
+ const NUMBER_UNIT_GAP = /^[ \u00a0-]?/u;
61
+ /** 分数と後ろの徒歩の語の間に置けるもの(5 minutes' walk、5 min. walk)。 */
62
+ const MINUTES_WALK_GAP = /^[\s'’.]*/u;
63
+ /**
64
+ * 分数と距離の間に置けるもの。開き括弧か・か / の一つだけ(徒歩5分(400m)、400m(徒歩5分)、徒歩5分・400m)。
65
+ * 読点では組にしない(標高600m、徒歩3分 の 600m は道のりではない)。
66
+ */
67
+ const PAIR_GAP = /^\s*[(([[【・//]\s*$/u;
68
+ const RANGE_MARKS: readonly string[] = ["〜", "~", "~", "–", "—", "-"];
69
+ const DIGIT_START = new RegExp(`^${DIGIT}`, "u");
70
+ const DIGIT_END = new RegExp(`${DIGIT}$`, "u");
71
+ /** 浮動小数の誤差(1150 / 80 など)で切り上げが一つずれないための小さな数。 */
72
+ const EPSILON = 1e-9;
73
+ const MINUTE_SECONDS = 60;
74
+ /** 時速から出した速さ(4km/h は 66.66…m/分)を、言うときに小数一桁にする。 */
75
+ const RATE_DECIMALS = 10;
76
+
77
+ const normal = (text: string): string => text.normalize("NFKC").toLowerCase();
78
+
79
+ const valueOf = (digits: string): number => Number(digits.normalize("NFKC").replaceAll(",", ""));
80
+
81
+ const isWordEdge = (text: string, at: number): boolean => !(LETTER.test(text.charAt(at - 1)) && LETTER.test(text.charAt(at)));
82
+
83
+ const startsWithWord = (text: string, word: string): boolean => normal(text.slice(0, word.length)) === normal(word) && isWordEdge(text, word.length);
84
+
85
+ const endsWithWord = (text: string, word: string): boolean =>
86
+ text.length >= word.length && normal(text.slice(-word.length)) === normal(word) && isWordEdge(text, text.length - word.length);
87
+
88
+ /** 末尾の空白と、読み飛ばす語を除く(about a の a)。 */
89
+ const trimFillers = (text: string, fillers: readonly string[]): string => {
90
+ const trimmed = text.trimEnd();
91
+ const filler = fillers.find((word) => endsWithWord(trimmed, word));
92
+ return filler === undefined ? trimmed : trimFillers(trimmed.slice(0, -filler.length), fillers);
93
+ };
94
+
95
+ /** 位置 at から始まる単位。㎞ のような一字の単位も NFKC で照らす。長い語から。 */
96
+ const unitAt = (line: string, at: number, units: readonly WalkMark[]): { readonly unit: WalkMark; readonly end: number } | undefined =>
97
+ units
98
+ .toSorted((left, right) => right.pattern.length - left.pattern.length)
99
+ .flatMap((unit) =>
100
+ Array.from({ length: unit.pattern.length }, (_, index) => at + index + 1)
101
+ .filter((end) => normal(line.slice(at, end)) === normal(unit.pattern) && !UNIT_CONTINUES.test(line.slice(end)))
102
+ .map((end) => ({ unit, end })),
103
+ )[0];
104
+
105
+ /** 一行の中の、単位の付いた数。 */
106
+ const readingsIn = (line: string, units: readonly WalkMark[]): Reading[] =>
107
+ [...line.matchAll(NUMBER)].flatMap((match) => {
108
+ const numberEnd = match.index + match[0].length;
109
+ const unitStart = numberEnd + (NUMBER_UNIT_GAP.exec(line.slice(numberEnd))?.[0].length ?? 0);
110
+ const found = unitAt(line, unitStart, units);
111
+ return found === undefined ? [] : [{ start: match.index, end: numberEnd, digits: match[0], unit: found.unit, unitEnd: found.end }];
112
+ });
113
+
114
+ const endsWithAny = (text: string, words: readonly string[]): boolean => words.some((word) => endsWithWord(text, word));
115
+
116
+ /** 範囲の片側の数か(5〜7分 の 7、5分から10分 の 5分、400m〜600m の 400m、8 to 10 minutes の 10)。 */
117
+ const isRangeEnd = (line: string, reading: Reading, units: readonly WalkMark[], rangeWords: readonly string[]): boolean => {
118
+ const joins = [...RANGE_MARKS, ...rangeWords];
119
+ const after = line.slice(reading.unitEnd).trimStart();
120
+ const next = joins.find((word) => startsWithWord(after, word));
121
+ if (next !== undefined && DIGIT_START.test(after.slice(next.length).trimStart())) return true;
122
+ const before = line.slice(0, reading.start).trimEnd();
123
+ const previous = joins.find((word) => endsWithWord(before, word));
124
+ if (previous === undefined) return false;
125
+ const left = before.slice(0, -previous.length).trimEnd();
126
+ const unit = units.find((mark) => endsWithWord(left, mark.pattern));
127
+ return DIGIT_END.test(unit === undefined ? left : left.slice(0, -unit.pattern.length).trimEnd());
128
+ };
129
+
130
+ /** 数のすぐ前(読み飛ばす語の後ろ)に約の語があるか。 */
131
+ const isHedged = (line: string, start: number, words: WalkWords): boolean =>
132
+ endsWithAny(trimFillers(line.slice(0, start).replace(/[(([[]$/u, ""), words.fillers), words.hedges);
133
+
134
+ const boundBefore = (prefix: string, words: WalkWords): boolean =>
135
+ endsWithAny(
136
+ trimFillers(prefix, words.fillers),
137
+ words.bounds.filter((bound) => bound.position === "before").map((bound) => bound.pattern),
138
+ );
139
+
140
+ const boundAfter = (rest: string, words: WalkWords): boolean =>
141
+ words.bounds.filter((bound) => bound.position !== "before").some((bound) => startsWithWord(rest.trimStart(), bound.pattern));
142
+
143
+ /** 前に書く徒歩の語(徒歩5分、徒歩で5分)。あれば、その語の始まりの位置。 */
144
+ const walkBeforeStart = (prefix: string, words: WalkWords): number | undefined => {
145
+ const trimmed = prefix.trimEnd();
146
+ const gap = words.gaps.find((word) => trimmed.endsWith(word));
147
+ const head = gap === undefined ? trimmed : trimmed.slice(0, -gap.length).trimEnd();
148
+ const walk = words.walks.find((mark) => mark.position === "before" && endsWithWord(head, mark.pattern));
149
+ return walk === undefined ? undefined : head.length - walk.pattern.length;
150
+ };
151
+
152
+ /** 後ろに書く徒歩の語(a 5-minute walk)。あれば、その語の終わりの位置。 */
153
+ const walkAfterEnd = (line: string, from: number, words: WalkWords): number | undefined => {
154
+ const at = from + (MINUTES_WALK_GAP.exec(line.slice(from))?.[0].length ?? 0);
155
+ const walk = words.walks.find((mark) => mark.position === "after" && startsWithWord(line.slice(at), mark.pattern));
156
+ return walk === undefined ? undefined : at + walk.pattern.length;
157
+ };
158
+
159
+ const minuteUnits = (words: WalkWords): WalkMark[] => words.times.filter((unit) => (unit.weight ?? 0) >= MINUTE_SECONDS);
160
+
161
+ /** 一行の中の、徒歩の分数。範囲、上限、約の付いた分数は読まない。 */
162
+ export const walkMinutesIn = (line: string, words: WalkWords): Minutes[] =>
163
+ readingsIn(line, minuteUnits(words)).flatMap((reading) => {
164
+ const prefix = line.slice(0, reading.start);
165
+ const leadStart = walkBeforeStart(prefix, words);
166
+ const afterEnd = walkAfterEnd(line, reading.unitEnd, words);
167
+ if (leadStart === undefined && afterEnd === undefined) return [];
168
+ if (isRangeEnd(line, reading, words.times, words.ranges) || isHedged(line, reading.start, words)) return [];
169
+ if (boundBefore(prefix, words) || boundAfter(line.slice(afterEnd ?? reading.unitEnd), words)) return [];
170
+ const value = (valueOf(reading.digits) * (reading.unit.weight ?? 0)) / MINUTE_SECONDS;
171
+ if (!Number.isInteger(value) || value <= 0) return [];
172
+ return [{ start: reading.start, end: afterEnd ?? reading.unitEnd, lead: leadStart ?? reading.start, value }];
173
+ });
174
+
175
+ /** 書いた数の桁から来る幅の半分(書いた単位で)。小数は最後の桁、約の付いた整数は最後の 0 でない桁。それ以外は 0。 */
176
+ export const marginOf = (digits: string, hedged: boolean): number => {
177
+ const plain = digits.normalize("NFKC").replaceAll(",", "");
178
+ const point = plain.indexOf(".");
179
+ if (point !== -1) return 10 ** -(plain.length - point - 1) / 2;
180
+ if (!hedged) return 0;
181
+ const lastNonZero = [...plain].findLastIndex((digit) => digit !== "0");
182
+ const zeros = plain.length - 1 - lastNonZero;
183
+ return 10 ** zeros / 2;
184
+ };
185
+
186
+ /** 一行の中の距離。範囲の片側は読まない。 */
187
+ export const distancesIn = (line: string, words: WalkWords): Distance[] =>
188
+ readingsIn(line, words.lengths)
189
+ .filter((reading) => !isRangeEnd(line, reading, words.lengths, words.ranges))
190
+ .flatMap((reading) => {
191
+ const weight = reading.unit.weight ?? 0;
192
+ const metres = valueOf(reading.digits) * weight;
193
+ const margin = marginOf(reading.digits, isHedged(line, reading.start, words)) * weight;
194
+ return metres > 0 ? [{ start: reading.start, end: reading.unitEnd, metres, margin }] : [];
195
+ });
196
+
197
+ /**
198
+ * 分数と組になる距離。すぐ後ろ(徒歩5分(400m))を先に、無ければすぐ前(400m(徒歩5分))を見る。間に置けるのは記号と約の語だけ。
199
+ * 一つの距離は一つの分数とだけ組にする。
200
+ */
201
+ export const walkPairs = (line: string, words: WalkWords): { readonly minutes: Minutes; readonly distance: Distance }[] => {
202
+ const distances = distancesIn(line, words);
203
+ const between = (from: number, to: number): boolean => from <= to && PAIR_GAP.test(trimFillers(line.slice(from, to), [...words.hedges, ...words.fillers]));
204
+ const used = new Set<Distance>();
205
+ return walkMinutesIn(line, words).flatMap((minutes) => {
206
+ const after = distances.find((distance) => !used.has(distance) && between(minutes.end, distance.start));
207
+ const before = after ?? distances.findLast((distance) => !used.has(distance) && between(distance.end, minutes.lead));
208
+ if (before === undefined) return [];
209
+ used.add(before);
210
+ return [{ minutes, distance: before }];
211
+ });
212
+ };
213
+
214
+ /** 一行に書いた速さ(m/分)。距離のすぐ後ろ(80mを1分、80 m per minute)かすぐ前(分速80m)に時間の語がある距離。 */
215
+ export const ratesIn = (line: string, words: WalkWords): number[] =>
216
+ distancesIn(line, words).flatMap((distance) => {
217
+ const rest = line.slice(distance.end).trimStart();
218
+ const head = line.slice(0, distance.start).trimEnd();
219
+ const per =
220
+ words.perTimes.find((mark) => mark.position !== "before" && startsWithWord(rest, mark.pattern)) ??
221
+ words.perTimes.find((mark) => mark.position === "before" && endsWithWord(head, mark.pattern));
222
+ return per === undefined ? [] : [distance.metres / ((per.weight ?? MINUTE_SECONDS) / MINUTE_SECONDS)];
223
+ });
224
+
225
+ const containsAny = (text: string, words: readonly string[]): boolean => words.some((word) => normal(text).includes(normal(word)));
226
+
227
+ /** 速さを読む行。徒歩の語(Walking も walk の語として)を含む行。 */
228
+ const hasWalkWord = (line: string, words: WalkWords): boolean =>
229
+ containsAny(
230
+ line,
231
+ words.walks.map((walk) => walk.pattern),
232
+ );
233
+
234
+ const linesOf = (source: string): { readonly text: string; readonly start: number }[] => {
235
+ const starts = [0, ...[...source.matchAll(/\n/gu)].map((match) => match.index + 1)];
236
+ return starts.map((start, index) => ({ text: source.slice(start, (starts[index + 1] ?? source.length + 1) - 1), start }));
237
+ };
238
+
239
+ /**
240
+ * 文書の速さ。徒歩の語のある行に書いた速さが一つ(一つの値)だけなら、それを使う。端数は、その行に切り上げの語があれば切り上げだけ、
241
+ * 無ければどちらでもよい。二つ以上の値があれば、どの分数がどれで計ったか決められないので比べない。書いていなければ fallback。
242
+ */
243
+ export const statedRate = (source: string, words: WalkWords, fallback?: WalkRate): WalkRate | undefined => {
244
+ const stated = linesOf(source)
245
+ .filter((line) => hasWalkWord(line.text, words))
246
+ .flatMap((line) =>
247
+ ratesIn(line.text, words).map((rate) => ({ rate, up: containsAny(line.text, words.roundUps) && !containsAny(line.text, words.notRoundUps) })),
248
+ );
249
+ const first = stated[0];
250
+ if (first === undefined) return fallback;
251
+ if (stated.some((other) => other.rate !== first.rate)) return undefined;
252
+ return { metresPerMinute: first.rate, rounding: stated.some((other) => other.up) ? "up" : "any" };
253
+ };
254
+
255
+ /** その距離を歩いて合う分数の幅。margin の幅の上の端は含まない(1.25km は 1.3km と書く)。 */
256
+ export const expectedMinutes = (distance: Pick<Distance, "metres" | "margin">, rate: WalkRate): { readonly low: number; readonly high: number } => {
257
+ const low = (distance.metres - distance.margin) / rate.metresPerMinute;
258
+ const high = (distance.metres + distance.margin) / rate.metresPerMinute;
259
+ return { low: rate.rounding === "up" ? Math.ceil(low - EPSILON) : Math.floor(low + EPSILON), high: Math.ceil(high - EPSILON) };
260
+ };
261
+
262
+ const rangeText = (low: number, high: number): string => (low === high ? String(low) : `${String(low)}–${String(high)}`);
263
+
264
+ /** 距離と合わない徒歩の分数。分数の位置を指す。 */
265
+ export const walkTimeMismatches = (source: string, words: WalkWords, fallback?: WalkRate): StructureIssue[] => {
266
+ const rate = statedRate(source, words, fallback);
267
+ if (rate === undefined) return [];
268
+ return linesOf(source).flatMap((line) =>
269
+ walkPairs(line.text, words).flatMap(({ minutes, distance }) => {
270
+ const { low, high } = expectedMinutes(distance, rate);
271
+ if (minutes.value >= low && minutes.value <= high) return [];
272
+ return [
273
+ {
274
+ offset: line.start + minutes.lead,
275
+ values: {
276
+ minutes: line.text.slice(minutes.lead, minutes.end),
277
+ distance: line.text.slice(distance.start, distance.end),
278
+ expected: rangeText(Math.max(low, 1), Math.max(high, 1)),
279
+ rate: Math.round(rate.metresPerMinute * RATE_DECIMALS) / RATE_DECIMALS,
280
+ },
281
+ },
282
+ ];
283
+ }),
284
+ );
285
+ };