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
@@ -0,0 +1,316 @@
1
+ // range-band-mismatch: the bands of one table column or one list (7〜11歳, 12〜14歳, 15歳以上; under 7, 7 to 11, 12 and over)
2
+ // that overlap or leave a gap between neighbours. The words that bound a band and the units it is counted in come from the
3
+ // lexicons (range-band-word, range-band-unit, range-band-heading). Pure.
4
+
5
+ /** How an end of a band is written: 以上/以下 (inclusive), 未満/超 (exclusive), or only a range mark (7〜11, 7 to 11). */
6
+ export type BoundKind = "inclusive" | "exclusive" | "loose";
7
+
8
+ export type Bound = { readonly value: number; readonly kind: BoundKind; readonly decimals: number };
9
+
10
+ /** Integer units (ages, people, points) have no value between 11 and 12; continuous ones (kg, yen, hours) do. */
11
+ export type UnitKind = "integer" | "continuous";
12
+
13
+ export type Band = { readonly low: Bound | undefined; readonly high: Bound | undefined; readonly unit: string };
14
+
15
+ export type BandMarker = {
16
+ readonly pattern: string;
17
+ readonly side: "low" | "high";
18
+ readonly kind: "inclusive" | "exclusive";
19
+ readonly position: "before" | "after";
20
+ };
21
+
22
+ export type BandUnit = { readonly pattern: string; readonly kind: UnitKind; readonly position: "before" | "after" };
23
+
24
+ export type BandWords = {
25
+ /** Marks between the two ends (〜, –, to). Written after a number alone (12歳〜) it leaves the band open above. */
26
+ readonly connectors: readonly string[];
27
+ /** The marks that, before a number alone (〜6歳), close a band above. A hyphen is not one: "- 5" is a bullet or a minus. */
28
+ readonly openers: readonly string[];
29
+ readonly markers: readonly BandMarker[];
30
+ /** Words a band may open with that say nothing about its ends ("ages", "aged"). */
31
+ readonly leads: readonly string[];
32
+ readonly units: readonly BandUnit[];
33
+ /** Column headings that name what bare numbers count (年齢, Age), with the kind of that quantity. */
34
+ readonly headings: readonly { readonly pattern: string; readonly kind: UnitKind }[];
35
+ };
36
+
37
+ /** One band as written, with where it is. */
38
+ export type PlacedBand = { readonly band: Band; readonly start: number; readonly end: number; readonly text: string };
39
+
40
+ export type BandSlip = { readonly kind: "overlap" | "gap"; readonly earlier: PlacedBand; readonly later: PlacedBand };
41
+
42
+ const NUMBER = /^(?:\d{1,3}(?:,\d{3})+|\d+)(?:\.\d+)?/u;
43
+ const LATIN_END = /[a-z]$/u;
44
+ const LETTER = /^[a-z]/u;
45
+ const SPACE = /^\s*/u;
46
+ const EMPHASIS = /[*_`]/gu;
47
+
48
+ /** The text without a note in brackets at its end (15歳以上(成人)), and the note. */
49
+ const splitNote = (text: string): { readonly bare: string; readonly note: string | undefined } => {
50
+ const open = text.lastIndexOf("(");
51
+ if (!text.endsWith(")") || open === -1) return { bare: text, note: undefined };
52
+ return { bare: text.slice(0, open).trim(), note: text.slice(open + 1, -1).trim() };
53
+ };
54
+
55
+ const plainOf = (text: string): string => text.normalize("NFKC").toLowerCase().replace(EMPHASIS, "").trim();
56
+
57
+ export const normalizeBandText = (text: string): string => splitNote(plainOf(text)).bare;
58
+
59
+ const normalizeWord = (word: string): string => word.normalize("NFKC").toLowerCase();
60
+
61
+ /** A unit compared across bands: years and year, kg and kgs are the same unit. */
62
+ const unitKey = (unit: string): string => (unit.length > 2 && unit.endsWith("s") ? unit.slice(0, -1) : unit);
63
+
64
+ type Cursor = { readonly text: string; readonly at: number };
65
+
66
+ const skipSpace = (cursor: Cursor): Cursor => ({ text: cursor.text, at: cursor.at + (SPACE.exec(cursor.text.slice(cursor.at))?.[0].length ?? 0) });
67
+
68
+ /** The longest word at the cursor; a Latin word must end at a word edge (to is not the start of total). */
69
+ const wordAt = (cursor: Cursor, words: readonly string[]): string | undefined =>
70
+ words
71
+ .map(normalizeWord)
72
+ .filter((word) => word !== "" && cursor.text.startsWith(word, cursor.at))
73
+ .filter((word) => !LATIN_END.test(word) || !LETTER.test(cursor.text.slice(cursor.at + word.length)))
74
+ .reduce<string | undefined>((best, word) => (best === undefined || word.length > best.length ? word : best), undefined);
75
+
76
+ const advance = (cursor: Cursor, word: string | undefined): Cursor =>
77
+ word === undefined ? cursor : skipSpace({ text: cursor.text, at: cursor.at + word.length });
78
+
79
+ type Read<T> = { readonly value: T; readonly cursor: Cursor } | undefined;
80
+
81
+ const numberAt = (cursor: Cursor): Read<{ value: number; decimals: number }> => {
82
+ const written = NUMBER.exec(cursor.text.slice(cursor.at))?.[0];
83
+ if (written === undefined) return undefined;
84
+ const digits = written.replaceAll(",", "");
85
+ return { value: { value: Number(digits), decimals: digits.split(".")[1]?.length ?? 0 }, cursor: advance(cursor, written) };
86
+ };
87
+
88
+ const unitsAt = (cursor: Cursor, words: BandWords, position: "before" | "after"): Read<string | undefined> => {
89
+ const unit = wordAt(
90
+ cursor,
91
+ words.units.filter((entry) => entry.position === position).map((entry) => entry.pattern),
92
+ );
93
+ return { value: unit, cursor: advance(cursor, unit) };
94
+ };
95
+
96
+ const markerAt = (cursor: Cursor, words: BandWords, position: "before" | "after"): Read<BandMarker | undefined> => {
97
+ const markers = words.markers.filter((marker) => marker.position === position);
98
+ const word = wordAt(
99
+ cursor,
100
+ markers.map((marker) => marker.pattern),
101
+ );
102
+ return { value: markers.find((marker) => normalizeWord(marker.pattern) === word), cursor: advance(cursor, word) };
103
+ };
104
+
105
+ /** One number with the units written around it ($5, 7歳, 8 years). */
106
+ type Figure = { readonly value: number; readonly decimals: number; readonly units: readonly string[] };
107
+
108
+ const figureAt = (cursor: Cursor, words: BandWords): Read<Figure> => {
109
+ const prefix = unitsAt(cursor, words, "before");
110
+ const number = prefix === undefined ? undefined : numberAt(prefix.cursor);
111
+ const suffix = number === undefined ? undefined : unitsAt(number.cursor, words, "after");
112
+ if (prefix === undefined || number === undefined || suffix === undefined) return undefined;
113
+ const units = [prefix.value, suffix.value].filter((unit) => unit !== undefined);
114
+ return { value: { ...number.value, units }, cursor: suffix.cursor };
115
+ };
116
+
117
+ const boundOf = (figure: Figure, kind: BoundKind): Bound => ({ value: figure.value, kind, decimals: figure.decimals });
118
+
119
+ /** A band with the units it was written in. undefined when the text is not one band of one unit. */
120
+ type Parsed = { readonly low: Bound | undefined; readonly high: Bound | undefined; readonly units: readonly string[] };
121
+
122
+ const withMarker = (figure: Figure, marker: BandMarker): Parsed => ({
123
+ low: marker.side === "low" ? boundOf(figure, marker.kind) : undefined,
124
+ high: marker.side === "high" ? boundOf(figure, marker.kind) : undefined,
125
+ units: figure.units,
126
+ });
127
+
128
+ /** One end with its word: 15歳以上, 7 and over, under 7. */
129
+ const markedEnd = (cursor: Cursor, words: BandWords): Read<{ readonly parsed: Parsed; readonly side: "low" | "high" }> => {
130
+ const before = markerAt(cursor, words, "before");
131
+ const figure = before === undefined ? undefined : figureAt(before.cursor, words);
132
+ const after = figure === undefined ? undefined : markerAt(figure.cursor, words, "after");
133
+ if (before === undefined || figure === undefined || after === undefined) return undefined;
134
+ const marker = before.value ?? after.value;
135
+ if (marker === undefined || (before.value !== undefined && after.value !== undefined)) return undefined;
136
+ const unit = unitsAt(after.cursor, words, "after");
137
+ if (unit === undefined) return undefined;
138
+ const parsed = withMarker(figure.value, marker);
139
+ return {
140
+ value: { parsed: { ...parsed, units: [...parsed.units, ...(unit.value === undefined ? [] : [unit.value])] }, side: marker.side },
141
+ cursor: unit.cursor,
142
+ };
143
+ };
144
+
145
+ /** 15歳以上, under 7, and both ends with their words: 7歳以上12歳未満. */
146
+ const markedBand = (cursor: Cursor, words: BandWords): Read<Parsed> => {
147
+ const first = markedEnd(cursor, words);
148
+ if (first === undefined) return undefined;
149
+ const rest = first.value.side === "low" ? markedEnd(first.cursor, words) : undefined;
150
+ if (rest === undefined || rest.value.side !== "high") return { value: first.value.parsed, cursor: first.cursor };
151
+ const units = [...first.value.parsed.units, ...rest.value.parsed.units];
152
+ return { value: { low: first.value.parsed.low, high: rest.value.parsed.high, units }, cursor: rest.cursor };
153
+ };
154
+
155
+ /** 7〜11歳, 7 to 11, 12歳〜, 〜6歳. */
156
+ const spannedBand = (cursor: Cursor, words: BandWords): Read<Parsed> => {
157
+ const leading = wordAt(cursor, words.openers);
158
+ const first = figureAt(advance(cursor, leading), words);
159
+ if (first === undefined) return undefined;
160
+ if (leading !== undefined) return { value: { low: undefined, high: boundOf(first.value, "loose"), units: first.value.units }, cursor: first.cursor };
161
+ const connector = wordAt(first.cursor, words.connectors);
162
+ if (connector === undefined) return undefined;
163
+ const second = figureAt(advance(first.cursor, connector), words);
164
+ if (second === undefined)
165
+ return { value: { low: boundOf(first.value, "loose"), high: undefined, units: first.value.units }, cursor: advance(first.cursor, connector) };
166
+ return {
167
+ value: { low: boundOf(first.value, "loose"), high: boundOf(second.value, "loose"), units: [...first.value.units, ...second.value.units] },
168
+ cursor: second.cursor,
169
+ };
170
+ };
171
+
172
+ /** The band a cell or the head of a list item states, or undefined when the whole text is not one band in one unit. */
173
+ export const parseBand = (written: string, words: BandWords): Band | undefined => {
174
+ const text = normalizeBandText(written);
175
+ const start = skipSpace({ text, at: 0 });
176
+ const begun = advance(start, wordAt(start, words.leads));
177
+ const read = [begun, start]
178
+ .flatMap((cursor) => [markedBand(cursor, words), spannedBand(cursor, words)])
179
+ .find((reading) => reading !== undefined && reading.cursor.at === text.length);
180
+ if (read === undefined) return undefined;
181
+ const units = new Set(read.value.units.map(unitKey));
182
+ if (units.size > 1) return undefined;
183
+ const { low, high } = read.value;
184
+ if (low !== undefined && high !== undefined && low.value >= high.value) return undefined;
185
+ return { low, high, unit: [...units][0] ?? "" };
186
+ };
187
+
188
+ /** The kind of a unit, by the unit lexicon. */
189
+ export const unitKindOf = (unit: string, words: BandWords): UnitKind | undefined =>
190
+ words.units.find((entry) => unitKey(normalizeWord(entry.pattern)) === unit)?.kind;
191
+
192
+ /** What bare numbers under a column heading count: by a word of the heading lexicon, or a unit in brackets (体重(kg)). */
193
+ export const headingOf = (heading: string, words: BandWords): { readonly unit: string; readonly kind: UnitKind } | undefined => {
194
+ const { bare, note } = splitNote(plainOf(heading).replaceAll("[", "(").replaceAll("]", ")"));
195
+ const unit = note === undefined ? undefined : unitKey(note);
196
+ const unitKind = unit === undefined ? undefined : unitKindOf(unit, words);
197
+ if (unit !== undefined && unitKind !== undefined) return { unit, kind: unitKind };
198
+ const named = words.headings.find((entry) => normalizeWord(entry.pattern) === bare);
199
+ return named === undefined ? undefined : { unit: "", kind: named.kind };
200
+ };
201
+
202
+ type Span = { readonly low: number; readonly high: number };
203
+
204
+ const POSITIVE = Number.POSITIVE_INFINITY;
205
+ const NEGATIVE = Number.NEGATIVE_INFINITY;
206
+
207
+ /** The band as the values it holds. An integer band's exclusive end is the next whole number in (7歳未満 holds up to 6). */
208
+ const spanOf = (band: Band, integer: boolean): Span => {
209
+ const step = integer ? 1 : 0;
210
+ const inward = (bound: Bound): number => (bound.kind === "exclusive" ? step : 0);
211
+ return {
212
+ low: band.low === undefined ? NEGATIVE : band.low.value + inward(band.low),
213
+ high: band.high === undefined ? POSITIVE : band.high.value - inward(band.high),
214
+ };
215
+ };
216
+
217
+ /** Bands written in one direction, each starting and ending past the one before. Nested tiers (〜100万円, 〜200万円) are not. */
218
+ const orderOf = (spans: readonly Span[]): "up" | "down" | undefined => {
219
+ const pairs = spans.slice(1).flatMap((span, index): [Span, Span][] => {
220
+ const previous = spans[index];
221
+ return previous === undefined ? [] : [[previous, span]];
222
+ });
223
+ if (pairs.every(([a, b]) => a.low < b.low && a.high < b.high)) return "up";
224
+ if (pairs.every(([a, b]) => a.low > b.low && a.high > b.high)) return "down";
225
+ return undefined;
226
+ };
227
+
228
+ /** Smallest step the two numbers are written to (59 and 60: 1; 59.9 and 60: 0.1). */
229
+ const stepOf = (a: Bound, b: Bound): number => 10 ** -Math.max(a.decimals, b.decimals);
230
+
231
+ const ROUNDING = 1e-9;
232
+
233
+ type Meeting = "overlap" | "gap" | undefined;
234
+
235
+ /** Whole numbers: the next band starts one past the end of the one below. */
236
+ const integerJunction = (below: Band, above: Band): Meeting => {
237
+ const distance = spanOf(above, true).low - spanOf(below, true).high;
238
+ if (distance <= 0) return "overlap";
239
+ return distance > 1 ? "gap" : undefined;
240
+ };
241
+
242
+ /** At one number, an end included on both sides overlaps and one excluded on both sides leaves the number out. */
243
+ const sameNumber = (end: Bound, start: Bound): Meeting => {
244
+ if (start.kind === "inclusive" && end.kind === "inclusive") return "overlap";
245
+ return start.kind === "exclusive" && end.kind === "exclusive" ? "gap" : undefined;
246
+ };
247
+
248
+ /** Values in between: a shared end with a range mark, or ends a written step apart, are contiguous. */
249
+ const continuousJunction = (end: Bound, start: Bound): Meeting => {
250
+ if (start.value < end.value) return "overlap";
251
+ if (start.value === end.value) return sameNumber(end, start);
252
+ if (start.kind === "exclusive" || end.kind === "exclusive") return "gap";
253
+ return start.value - end.value > stepOf(start, end) + ROUNDING ? "gap" : undefined;
254
+ };
255
+
256
+ /** Where the band below ends and the band above starts. */
257
+ const junctionOf = (below: Band, above: Band, integer: boolean): Meeting => {
258
+ if (below.high === undefined || above.low === undefined) return undefined;
259
+ return integer ? integerJunction(below, above) : continuousJunction(below.high, above.low);
260
+ };
261
+
262
+ const widthOf = (span: Span, integer: boolean): number => span.high - span.low + (integer ? 1 : 0);
263
+
264
+ type Junction = { readonly kind: "overlap" | "gap"; readonly size: number; readonly narrower: number };
265
+
266
+ /** How much the bands overlap or leave out, next to the narrower of the two. */
267
+ const junctionSize = (kind: "overlap" | "gap", below: Span, above: Span, integer: boolean): Junction => {
268
+ const edge = integer ? 1 : 0;
269
+ const size = kind === "overlap" ? below.high - above.low + edge : above.low - below.high - edge;
270
+ return { kind, size, narrower: Math.min(widthOf(below, integer), widthOf(above, integer)) };
271
+ };
272
+
273
+ /** An overlap of more than half the narrower band is deliberate: a size guide whose bands share a range. */
274
+ const isDeliberate = (junction: Junction): boolean => junction.kind === "overlap" && junction.size * 2 > junction.narrower;
275
+
276
+ /**
277
+ * The overlaps and gaps between neighbouring bands of one column or list. The bands must all be counted in one unit (bare
278
+ * numbers take the column's), written in one direction; the slip is reported at the later of the two in the document. A run
279
+ * with one deliberate overlap is a scale meant to overlap, and two bands alone are reported only when the hole or overlap is
280
+ * no wider than the narrower band.
281
+ */
282
+ export const bandSlips = (bands: readonly PlacedBand[], kind: UnitKind): BandSlip[] => {
283
+ if (bands.length < 2) return [];
284
+ const integer = kind === "integer" && bands.every(({ band }) => [band.low, band.high].every((bound) => bound === undefined || bound.decimals === 0));
285
+ const order = orderOf(bands.map(({ band }) => spanOf(band, integer)));
286
+ if (order === undefined) return [];
287
+ const ascending = order === "up" ? [...bands] : [...bands].reverse();
288
+ const junctions = ascending.slice(1).flatMap((above, index) => {
289
+ const below = ascending[index];
290
+ const slip = below === undefined ? undefined : junctionOf(below.band, above.band, integer);
291
+ return below === undefined || slip === undefined
292
+ ? []
293
+ : [{ below, above, ...junctionSize(slip, spanOf(below.band, integer), spanOf(above.band, integer), integer) }];
294
+ });
295
+ if (junctions.some(isDeliberate)) return [];
296
+ const pair = bands.length === 2;
297
+ return junctions
298
+ .filter((junction) => !pair || junction.size <= junction.narrower)
299
+ .map(({ below, above, kind: slip }) => {
300
+ const [earlier, later] = below.start < above.start ? [below, above] : [above, below];
301
+ return { kind: slip, earlier, later };
302
+ });
303
+ };
304
+
305
+ /** The unit and kind of a run of bands: one unit for all (bare numbers take the heading's), or undefined. */
306
+ export const runKindOf = (
307
+ bands: readonly Band[],
308
+ heading: { readonly unit: string; readonly kind: UnitKind } | undefined,
309
+ words: BandWords,
310
+ ): UnitKind | undefined => {
311
+ const units = new Set([...bands.map((band) => band.unit), heading?.unit ?? ""].filter((unit) => unit !== ""));
312
+ if (units.size > 1) return undefined;
313
+ const unit = [...units][0];
314
+ if (unit === undefined) return heading?.kind;
315
+ return unitKindOf(unit, words);
316
+ };
@@ -0,0 +1,277 @@
1
+ import { escapeRegExp } from "../orthography.ts";
2
+ import type { StructureIssue } from "./issues.ts";
3
+
4
+ /**
5
+ * rent-multiple-mismatch: a deposit, key money or guarantee given both as a number of months of rent and as an amount that
6
+ * is not that many months of the listing's rent (敷金 2か月(98,000円) with 賃料 98,000円; "Deposit: Six months' rent
7
+ * ($145,000)" with Rent $21,500). The rent is the nearest one stated before the deposit (several listings on one page each
8
+ * state their own); a document whose deposits come before any rent uses the rent only when it states one. When the document
9
+ * also states a fee beside the rent (管理費, service charge), the multiple may be of the rent alone or of the rent and the
10
+ * fee, since listings do both and seldom say which. A months count without an amount, an amount without a months count, a
11
+ * range of months, and two different pairs in one statement are not compared. The words come from the lexicons. Pure.
12
+ */
13
+
14
+ /** One amount of money as read: its currency, its value, and the value of the word of scale written in it (万), if any. */
15
+ export type RentAmount = {
16
+ readonly offset: number;
17
+ readonly end: number;
18
+ readonly currency: string;
19
+ readonly value: number;
20
+ readonly scale: number | undefined;
21
+ };
22
+
23
+ export type RentMultipleWords = {
24
+ /** Labels of the monthly rent (賃料, Rent). */
25
+ readonly rents: readonly string[];
26
+ /** Labels of a monthly fee paid with the rent (管理費, Service charge). */
27
+ readonly fees: readonly string[];
28
+ /** Labels of an amount stated in months of rent (敷金, 礼金, Security deposit). */
29
+ readonly multiples: readonly string[];
30
+ /** The word of a month after a count (か月, months). */
31
+ readonly months: readonly string[];
32
+ /** Words that may stand between a label or a months count and its amount (は, 分, of, equal to). */
33
+ readonly links: readonly string[];
34
+ /** Words that say an amount is not per month (per week, 年額, per sq ft); a rent line with one is not read. */
35
+ readonly periods: readonly string[];
36
+ /** Words that make the amount something other than the plain multiple (税込, tax included). */
37
+ readonly skips: readonly string[];
38
+ /** Marks and words that join the two ends of a range (〜, to). */
39
+ readonly connectors: readonly string[];
40
+ /** Numbers written as words (six, 一) and their values. */
41
+ readonly numberWords: readonly { readonly word: string; readonly value: number }[];
42
+ };
43
+
44
+ /** An amount read as the interval its written digits allow (9.8万円 is anything from 97,500 to 98,500). */
45
+ export type Interval = { readonly low: number; readonly high: number };
46
+
47
+ /** A rent or fee as stated. range is undefined when the label has amounts that are not one monthly amount (a change, per week). */
48
+ type Stated = { readonly offset: number; readonly currency: string; readonly range: Interval | undefined; readonly written: string };
49
+
50
+ type Months = { readonly start: number; readonly end: number; readonly count: number };
51
+
52
+ /** Rounding to the currency's unit (a yen, a dollar) is allowed on top of the written precision. */
53
+ const UNIT_SLACK = 1;
54
+
55
+ const LATIN = /[A-Za-z]/u;
56
+ const SEPARATORS = /[\s||::==、,,・//'’()()[\][]-]/gu;
57
+ const PARENTHETICAL_WITHOUT_DIGITS = /[((][^()()0-90-9]*[))]/gu;
58
+ /** The possessive after a months count (2 months' rent, 1 month's rent). */
59
+ const POSSESSIVE = /['’]s?(?![A-Za-z])/gu;
60
+ const RANGE_START = /[0-90-9A-Za-z一二三四五六七八九十]\s*$/u;
61
+ const SENTENCE_END = /[。!?;;]|[.!?](?=\s|$)/u;
62
+ const DIGITS = "[0-90-9]+(?:[..][0-90-9]+)?";
63
+
64
+ const wordPattern = (word: string): string => {
65
+ const edgeStart = LATIN.test(word.charAt(0)) ? "(?<![A-Za-z])" : "";
66
+ const edgeEnd = LATIN.test(word.charAt(word.length - 1)) ? "(?![A-Za-z])" : "";
67
+ return `${edgeStart}${escapeRegExp(word)}${edgeEnd}`;
68
+ };
69
+
70
+ /** One alternation of the words, longest first so 月額賃料 wins over 賃料. Undefined when there are none. */
71
+ const alternation = (words: readonly string[]): string | undefined =>
72
+ words.length === 0
73
+ ? undefined
74
+ : words
75
+ .toSorted((left, right) => right.length - left.length)
76
+ .map(wordPattern)
77
+ .join("|");
78
+
79
+ const occurrences = (text: string, words: readonly string[]): { readonly start: number; readonly end: number }[] => {
80
+ const pattern = alternation(words);
81
+ if (pattern === undefined) return [];
82
+ return [...text.matchAll(new RegExp(pattern, "giu"))].map((match) => ({ start: match.index, end: match.index + match[0].length }));
83
+ };
84
+
85
+ const withoutWords = (text: string, words: readonly string[]): string => {
86
+ const pattern = alternation(words);
87
+ return pattern === undefined ? text : text.replace(new RegExp(pattern, "giu"), "");
88
+ };
89
+
90
+ /** Whether only separators, bracketed words and link words stand in the gap (「 | 」, " (monthly): ", "の", "'s rent ("). */
91
+ export const isPlainGap = (gap: string, words: readonly string[]): boolean =>
92
+ withoutWords(gap.replace(PARENTHETICAL_WITHOUT_DIGITS, "").replace(POSSESSIVE, ""), words).replace(SEPARATORS, "") === "";
93
+
94
+ const decimalsOf = (written: string): number => /[..]([0-90-9]+)/u.exec(written)?.[1]?.length ?? 0;
95
+
96
+ /** The interval an amount's digits allow: a word of scale (9.8万) leaves half its last digit either way; plain digits are exact. */
97
+ export const intervalOf = (amount: RentAmount, written: string): Interval => {
98
+ const half = amount.scale === undefined ? 0 : (amount.scale * 10 ** -decimalsOf(written)) / 2;
99
+ return { low: amount.value - half, high: amount.value + half };
100
+ };
101
+
102
+ const lineBounds = (text: string, offset: number): { readonly start: number; readonly end: number } => {
103
+ const end = text.indexOf("\n", offset);
104
+ return { start: text.lastIndexOf("\n", offset - 1) + 1, end: end === -1 ? text.length : end };
105
+ };
106
+
107
+ const amountsBetween = (amounts: readonly RentAmount[], start: number, end: number): RentAmount[] =>
108
+ amounts.filter((amount) => amount.offset >= start && amount.end <= end);
109
+
110
+ /** A label preceded by a months count (2 months' rent, First month's rent) names a multiple, not the rent. */
111
+ const followsMonths = (before: string, words: RentMultipleWords): boolean => {
112
+ const trimmed = before.replace(/['’]s?\s*$/u, "").trimEnd();
113
+ return words.months.some((month) => trimmed.toLowerCase().endsWith(month.toLowerCase()));
114
+ };
115
+
116
+ /** The second end of a range that starts with this amount (85,000円〜90,000円), if one follows. */
117
+ const rangeEnd = (text: string, first: RentAmount, next: RentAmount | undefined, words: RentMultipleWords): RentAmount | undefined => {
118
+ if (next === undefined || next.currency !== first.currency || next.value < first.value) return undefined;
119
+ const joint = text.slice(first.end, next.offset).trim();
120
+ return words.connectors.some((connector) => connector.toLowerCase() === joint.toLowerCase()) ? next : undefined;
121
+ };
122
+
123
+ /** Where a label's own amount must be: before the next label of any kind on its line (賃料 98,000円 管理費 6,000円). */
124
+ const labelledEnd = (text: string, from: number, lineEnd: number, words: RentMultipleWords): number => {
125
+ const [next] = occurrences(text.slice(from, lineEnd), [...words.rents, ...words.fees, ...words.multiples]);
126
+ return next === undefined ? lineEnd : from + next.start;
127
+ };
128
+
129
+ /**
130
+ * A rent or fee stated with one amount or one range of amounts. With a second amount (a change from one to another, two
131
+ * units) or a word of another period on the line (per week, 年額), it is stated but its monthly amount is not known.
132
+ */
133
+ const statedAt = (
134
+ text: string,
135
+ label: { readonly start: number; readonly end: number },
136
+ amounts: readonly RentAmount[],
137
+ words: RentMultipleWords,
138
+ ): Stated[] => {
139
+ const line = lineBounds(text, label.start);
140
+ const [first, second, ...rest] = amountsBetween(amounts, label.end, labelledEnd(text, label.end, line.end, words));
141
+ if (first === undefined || !isPlainGap(text.slice(label.end, first.offset), words.links)) return [];
142
+ if (followsMonths(text.slice(line.start, label.start), words)) return [];
143
+ const last = rangeEnd(text, first, second, words) ?? first;
144
+ const written = text.slice(first.offset, last.end);
145
+ const unclear = (second !== undefined && last !== second) || rest.length > 0 || occurrences(text.slice(line.start, line.end), words.periods).length > 0;
146
+ if (unclear) return [{ offset: label.start, currency: first.currency, range: undefined, written }];
147
+ const range = { low: intervalOf(first, text.slice(first.offset, first.end)).low, high: intervalOf(last, text.slice(last.offset, last.end)).high };
148
+ return [{ offset: label.start, currency: first.currency, range, written }];
149
+ };
150
+
151
+ /** Each label of the kind followed on its line by an amount (賃料 | 98,000円, Rent: $1,600 to $1,700), in document order. */
152
+ const statedAmounts = (text: string, labels: readonly string[], amounts: readonly RentAmount[], words: RentMultipleWords): Stated[] =>
153
+ occurrences(text, labels).flatMap((label) => statedAt(text, label, amounts, words));
154
+
155
+ const numberWordValue = (word: string, words: RentMultipleWords): number | undefined =>
156
+ words.numberWords.find((entry) => entry.word.toLowerCase() === word.toLowerCase())?.value;
157
+
158
+ const countOf = (written: string, words: RentMultipleWords): number => numberWordValue(written, words) ?? Number(written.normalize("NFKC"));
159
+
160
+ /** The months counts in a piece of text (2か月, Six months), with the offset of the piece. */
161
+ export const monthsIn = (piece: string, offset: number, words: RentMultipleWords): Months[] => {
162
+ const marker = alternation(words.months);
163
+ if (marker === undefined) return [];
164
+ const numberWords = alternation(words.numberWords.map((entry) => entry.word));
165
+ const count = numberWords === undefined ? DIGITS : `${DIGITS}|${numberWords}`;
166
+ const pattern = new RegExp(`(?<![0-90-9.,,.])(${count})\\s?(?:${marker})`, "giu");
167
+ return [...piece.matchAll(pattern)].map((match) => ({
168
+ start: offset + match.index,
169
+ end: offset + match.index + match[0].length,
170
+ count: countOf(match[1] ?? "", words),
171
+ }));
172
+ };
173
+
174
+ /** Whether a months count is the upper end of a range (1〜2か月). */
175
+ const endsRange = (text: string, months: Months, words: RentMultipleWords): boolean => {
176
+ const before = text.slice(Math.max(0, months.start - 12), months.start).trimEnd();
177
+ return words.connectors.some((connector) => before.endsWith(connector) && RANGE_START.test(before.slice(0, -connector.length)));
178
+ };
179
+
180
+ type Pair = { readonly months: Months; readonly amount: RentAmount };
181
+
182
+ const gapBetween = (text: string, months: Months, amount: RentAmount): string =>
183
+ amount.offset >= months.end ? text.slice(months.end, amount.offset) : text.slice(amount.end, months.start);
184
+
185
+ /** A months count and an amount standing together, either way round (1か月(98,000円), $3,200 (2 months' rent)). */
186
+ const pairsIn = (text: string, months: readonly Months[], amounts: readonly RentAmount[], words: RentMultipleWords): Pair[] => {
187
+ const between = [...words.links, ...words.rents];
188
+ return months.flatMap((count) =>
189
+ amounts
190
+ .filter((amount) => (amount.offset >= count.end || amount.end <= count.start) && isPlainGap(gapBetween(text, count, amount), between))
191
+ .map((amount) => ({ months: count, amount })),
192
+ );
193
+ };
194
+
195
+ type Statement = { readonly label: number; readonly pair: Pair };
196
+
197
+ /** Where the statement of one label ends: at the next label of its kind, the end of the sentence, or the end of the line. */
198
+ const statementEnd = (text: string, from: number, nextLabel: number | undefined): number => {
199
+ const line = lineBounds(text, from);
200
+ const sentence = SENTENCE_END.exec(text.slice(from, line.end));
201
+ return Math.min(line.end, nextLabel ?? line.end, sentence === null ? line.end : from + sentence.index);
202
+ };
203
+
204
+ /** One label's months count and amount, when the statement holds exactly one such pair and no word that makes it another. */
205
+ const statementAt = (
206
+ text: string,
207
+ label: { readonly start: number; readonly end: number },
208
+ next: number | undefined,
209
+ amounts: readonly RentAmount[],
210
+ words: RentMultipleWords,
211
+ ): Statement[] => {
212
+ const end = statementEnd(text, label.end, next);
213
+ const piece = text.slice(label.end, end);
214
+ if (occurrences(piece, words.skips).length > 0) return [];
215
+ const months = monthsIn(piece, label.end, words);
216
+ if (months.some((count) => endsRange(text, count, words))) return [];
217
+ const pairs = pairsIn(text, months, amountsBetween(amounts, label.end, end), words);
218
+ const [pair] = pairs;
219
+ const single = pairs.every((other) => other.months.count === pair?.months.count && other.amount.value === pair.amount.value);
220
+ return pair === undefined || !single ? [] : [{ label: label.start, pair }];
221
+ };
222
+
223
+ /** Every label of a multiple with one months count and one amount (敷金 | 2か月(98,000円)). */
224
+ const multipleStatements = (text: string, amounts: readonly RentAmount[], words: RentMultipleWords): Statement[] => {
225
+ const labels = occurrences(text, words.multiples);
226
+ return labels.flatMap((label, index) => statementAt(text, label, labels[index + 1]?.start, amounts, words));
227
+ };
228
+
229
+ /** The rent a statement at this offset is of: the nearest before it, or, before any, the one rent the document states. */
230
+ export const rentFor = <T extends Stated>(rents: readonly T[], offset: number): T | undefined => {
231
+ const before = rents.filter((rent) => rent.offset < offset).at(-1);
232
+ if (before !== undefined) return before;
233
+ const [first] = rents;
234
+ return rents.every((rent) => rent.range !== undefined && rent.range.low === first?.range?.low && rent.range.high === first.range.high) ? first : undefined;
235
+ };
236
+
237
+ /** The fees of the listing a rent belongs to: those after it and before the next rent; for the first rent, else those before it. */
238
+ export const feesFor = <T extends Stated>(rent: Stated, rents: readonly Stated[], fees: readonly T[]): T[] => {
239
+ const index = rents.indexOf(rent);
240
+ const next = rents[index + 1]?.offset ?? Number.POSITIVE_INFINITY;
241
+ const ofCurrency = fees.filter((fee) => fee.currency === rent.currency);
242
+ const after = ofCurrency.filter((fee) => fee.offset > rent.offset && fee.offset < next);
243
+ return after.length > 0 || index > 0 ? after : ofCurrency.filter((fee) => fee.offset < rent.offset);
244
+ };
245
+
246
+ const plus = (left: Interval, right: Interval): Interval => ({ low: left.low + right.low, high: left.high + right.high });
247
+
248
+ /** The monthly amounts a multiple may be of: the rent alone, the rent with each fee, and the rent with every fee. */
249
+ export const monthlyBases = (rent: Interval, fees: readonly Interval[]): Interval[] => {
250
+ const withEach = fees.map((fee) => plus(rent, fee));
251
+ const withAll = fees.length > 1 ? [fees.reduce(plus, rent)] : [];
252
+ return [rent, ...withEach, ...withAll];
253
+ };
254
+
255
+ /** Whether the amount is the count of months of one of the bases, within the written precision and a unit's rounding. */
256
+ export const isMultiple = (count: number, amount: Interval, bases: readonly Interval[]): boolean =>
257
+ bases.some((base) => count * base.low - UNIT_SLACK <= amount.high && amount.low <= count * base.high + UNIT_SLACK);
258
+
259
+ const mismatchOf = (text: string, statement: Statement, rents: readonly Stated[], fees: readonly Stated[]): StructureIssue[] => {
260
+ const rent = rentFor(rents, statement.label);
261
+ const { months, amount } = statement.pair;
262
+ if (rent?.range === undefined || rent.currency !== amount.currency) return [];
263
+ const feeRanges = feesFor(rent, rents, fees).map((fee) => fee.range);
264
+ if (!feeRanges.every((range) => range !== undefined)) return [];
265
+ const bases = monthlyBases(rent.range, feeRanges);
266
+ if (isMultiple(months.count, intervalOf(amount, text.slice(amount.offset, amount.end)), bases)) return [];
267
+ const values = { months: text.slice(months.start, months.end), amount: text.slice(amount.offset, amount.end), rent: rent.written };
268
+ return [{ offset: Math.min(months.start, amount.offset), values }];
269
+ };
270
+
271
+ /** Every multiple of rent whose amount is not that many months of the listing's rent (or of the rent and its fee). */
272
+ export const rentMultipleMismatches = (text: string, amounts: readonly RentAmount[], words: RentMultipleWords): StructureIssue[] => {
273
+ const rents = statedAmounts(text, words.rents, amounts, words);
274
+ if (rents.length === 0) return [];
275
+ const fees = statedAmounts(text, words.fees, amounts, words);
276
+ return multipleStatements(text, amounts, words).flatMap((statement) => mismatchOf(text, statement, rents, fees));
277
+ };