chaffjs 0.33.0 → 0.34.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 (467) hide show
  1. package/dist/api.d.ts +7 -1
  2. package/dist/api.d.ts.map +1 -1
  3. package/dist/api.js.map +1 -1
  4. package/dist/cli-text.d.ts.map +1 -1
  5. package/dist/cli-text.js +4 -2
  6. package/dist/cli-text.js.map +1 -1
  7. package/dist/commands/grade.d.ts.map +1 -1
  8. package/dist/commands/grade.js +66 -21
  9. package/dist/commands/grade.js.map +1 -1
  10. package/dist/company-kana-spelling.d.ts +6 -1
  11. package/dist/company-kana-spelling.d.ts.map +1 -1
  12. package/dist/company-kana-spelling.js +19 -2
  13. package/dist/company-kana-spelling.js.map +1 -1
  14. package/dist/company-name-ending.d.ts +7 -0
  15. package/dist/company-name-ending.d.ts.map +1 -0
  16. package/dist/company-name-ending.js +44 -0
  17. package/dist/company-name-ending.js.map +1 -0
  18. package/dist/company-names.d.ts +11 -3
  19. package/dist/company-names.d.ts.map +1 -1
  20. package/dist/company-names.js +27 -14
  21. package/dist/company-names.js.map +1 -1
  22. package/dist/custom/no-rewrite.d.ts +3 -0
  23. package/dist/custom/no-rewrite.d.ts.map +1 -0
  24. package/dist/custom/no-rewrite.js +3 -0
  25. package/dist/custom/no-rewrite.js.map +1 -0
  26. package/dist/custom/parse.d.ts.map +1 -1
  27. package/dist/custom/parse.js +14 -2
  28. package/dist/custom/parse.js.map +1 -1
  29. package/dist/detectors/age-words.d.ts +5 -0
  30. package/dist/detectors/age-words.d.ts.map +1 -0
  31. package/dist/detectors/age-words.js +8 -0
  32. package/dist/detectors/age-words.js.map +1 -0
  33. package/dist/detectors/annual-pay-mismatch.d.ts.map +1 -1
  34. package/dist/detectors/annual-pay-mismatch.js +2 -12
  35. package/dist/detectors/annual-pay-mismatch.js.map +1 -1
  36. package/dist/detectors/blank-structure.d.ts.map +1 -1
  37. package/dist/detectors/blank-structure.js +4 -3
  38. package/dist/detectors/blank-structure.js.map +1 -1
  39. package/dist/detectors/closing-balance-mismatch.d.ts.map +1 -1
  40. package/dist/detectors/closing-balance-mismatch.js +1 -0
  41. package/dist/detectors/closing-balance-mismatch.js.map +1 -1
  42. package/dist/detectors/code-fences.d.ts +5 -0
  43. package/dist/detectors/code-fences.d.ts.map +1 -1
  44. package/dist/detectors/code-fences.js +9 -0
  45. package/dist/detectors/code-fences.js.map +1 -1
  46. package/dist/detectors/condition-toki.d.ts +16 -0
  47. package/dist/detectors/condition-toki.d.ts.map +1 -0
  48. package/dist/detectors/condition-toki.js +42 -0
  49. package/dist/detectors/condition-toki.js.map +1 -0
  50. package/dist/detectors/conjugated-entry.d.ts +17 -0
  51. package/dist/detectors/conjugated-entry.d.ts.map +1 -0
  52. package/dist/detectors/conjugated-entry.js +50 -0
  53. package/dist/detectors/conjugated-entry.js.map +1 -0
  54. package/dist/detectors/content-morphemes.d.ts +2 -0
  55. package/dist/detectors/content-morphemes.d.ts.map +1 -1
  56. package/dist/detectors/content-morphemes.js +1 -1
  57. package/dist/detectors/content-morphemes.js.map +1 -1
  58. package/dist/detectors/cross-facts.d.ts.map +1 -1
  59. package/dist/detectors/cross-facts.js +2 -1
  60. package/dist/detectors/cross-facts.js.map +1 -1
  61. package/dist/detectors/custom.d.ts.map +1 -1
  62. package/dist/detectors/custom.js +8 -2
  63. package/dist/detectors/custom.js.map +1 -1
  64. package/dist/detectors/document-shape.d.ts.map +1 -1
  65. package/dist/detectors/document-shape.js +6 -0
  66. package/dist/detectors/document-shape.js.map +1 -1
  67. package/dist/detectors/due-date.d.ts.map +1 -1
  68. package/dist/detectors/due-date.js +12 -2
  69. package/dist/detectors/due-date.js.map +1 -1
  70. package/dist/detectors/fact-consistency.d.ts.map +1 -1
  71. package/dist/detectors/fact-consistency.js +12 -13
  72. package/dist/detectors/fact-consistency.js.map +1 -1
  73. package/dist/detectors/form-line.d.ts +9 -0
  74. package/dist/detectors/form-line.d.ts.map +1 -0
  75. package/dist/detectors/form-line.js +36 -0
  76. package/dist/detectors/form-line.js.map +1 -0
  77. package/dist/detectors/formula-amount.d.ts.map +1 -1
  78. package/dist/detectors/formula-amount.js +2 -12
  79. package/dist/detectors/formula-amount.js.map +1 -1
  80. package/dist/detectors/heading-claim.d.ts +14 -0
  81. package/dist/detectors/heading-claim.d.ts.map +1 -0
  82. package/dist/detectors/heading-claim.js +49 -0
  83. package/dist/detectors/heading-claim.js.map +1 -0
  84. package/dist/detectors/heading-echo.d.ts.map +1 -1
  85. package/dist/detectors/heading-echo.js +29 -1
  86. package/dist/detectors/heading-echo.js.map +1 -1
  87. package/dist/detectors/installment-total-mismatch.d.ts.map +1 -1
  88. package/dist/detectors/installment-total-mismatch.js +2 -12
  89. package/dist/detectors/installment-total-mismatch.js.map +1 -1
  90. package/dist/detectors/list-parallel.d.ts +5 -2
  91. package/dist/detectors/list-parallel.d.ts.map +1 -1
  92. package/dist/detectors/list-parallel.js +45 -7
  93. package/dist/detectors/list-parallel.js.map +1 -1
  94. package/dist/detectors/name-variant.d.ts.map +1 -1
  95. package/dist/detectors/name-variant.js +34 -7
  96. package/dist/detectors/name-variant.js.map +1 -1
  97. package/dist/detectors/number-unit.d.ts +10 -0
  98. package/dist/detectors/number-unit.d.ts.map +1 -1
  99. package/dist/detectors/number-unit.js +28 -8
  100. package/dist/detectors/number-unit.js.map +1 -1
  101. package/dist/detectors/orthography.d.ts.map +1 -1
  102. package/dist/detectors/orthography.js +16 -7
  103. package/dist/detectors/orthography.js.map +1 -1
  104. package/dist/detectors/per-unit-words.d.ts +7 -0
  105. package/dist/detectors/per-unit-words.d.ts.map +1 -0
  106. package/dist/detectors/per-unit-words.js +17 -0
  107. package/dist/detectors/per-unit-words.js.map +1 -0
  108. package/dist/detectors/period-multiple-mismatch.d.ts.map +1 -1
  109. package/dist/detectors/period-multiple-mismatch.js +2 -12
  110. package/dist/detectors/period-multiple-mismatch.js.map +1 -1
  111. package/dist/detectors/reference-number-variant.d.ts.map +1 -1
  112. package/dist/detectors/reference-number-variant.js +21 -3
  113. package/dist/detectors/reference-number-variant.js.map +1 -1
  114. package/dist/detectors/registry/condition-toki.d.ts +3 -0
  115. package/dist/detectors/registry/condition-toki.d.ts.map +1 -0
  116. package/dist/detectors/registry/condition-toki.js +3 -0
  117. package/dist/detectors/registry/condition-toki.js.map +1 -0
  118. package/dist/detectors/rent-multiple-mismatch.d.ts.map +1 -1
  119. package/dist/detectors/rent-multiple-mismatch.js +2 -12
  120. package/dist/detectors/rent-multiple-mismatch.js.map +1 -1
  121. package/dist/detectors/row-sentence-amounts.d.ts.map +1 -1
  122. package/dist/detectors/row-sentence-amounts.js +5 -10
  123. package/dist/detectors/row-sentence-amounts.js.map +1 -1
  124. package/dist/detectors/stated-amounts.d.ts +4 -0
  125. package/dist/detectors/stated-amounts.d.ts.map +1 -0
  126. package/dist/detectors/stated-amounts.js +12 -0
  127. package/dist/detectors/stated-amounts.js.map +1 -0
  128. package/dist/detectors/step-time-sum.d.ts.map +1 -1
  129. package/dist/detectors/step-time-sum.js +2 -1
  130. package/dist/detectors/step-time-sum.js.map +1 -1
  131. package/dist/detectors/structure-tree.d.ts.map +1 -1
  132. package/dist/detectors/structure-tree.js +3 -0
  133. package/dist/detectors/structure-tree.js.map +1 -1
  134. package/dist/detectors/team.d.ts.map +1 -1
  135. package/dist/detectors/team.js +10 -6
  136. package/dist/detectors/team.js.map +1 -1
  137. package/dist/detectors/time-order.d.ts.map +1 -1
  138. package/dist/detectors/time-order.js +17 -13
  139. package/dist/detectors/time-order.js.map +1 -1
  140. package/dist/detectors/unknown-word.d.ts +4 -1
  141. package/dist/detectors/unknown-word.d.ts.map +1 -1
  142. package/dist/detectors/unknown-word.js +18 -2
  143. package/dist/detectors/unknown-word.js.map +1 -1
  144. package/dist/detectors/use-interval.d.ts +1 -0
  145. package/dist/detectors/use-interval.d.ts.map +1 -1
  146. package/dist/detectors/use-interval.js +15 -10
  147. package/dist/detectors/use-interval.js.map +1 -1
  148. package/dist/detectors/use-maximum.d.ts.map +1 -1
  149. package/dist/detectors/use-maximum.js +3 -1
  150. package/dist/detectors/use-maximum.js.map +1 -1
  151. package/dist/extension/document-view.d.ts +2 -2
  152. package/dist/extension/document-view.d.ts.map +1 -1
  153. package/dist/extension/document-view.js +17 -8
  154. package/dist/extension/document-view.js.map +1 -1
  155. package/dist/extension/entry-genres.d.ts +5 -0
  156. package/dist/extension/entry-genres.d.ts.map +1 -0
  157. package/dist/extension/entry-genres.js +13 -0
  158. package/dist/extension/entry-genres.js.map +1 -0
  159. package/dist/extension/module-detector.d.ts.map +1 -1
  160. package/dist/extension/module-detector.js +4 -4
  161. package/dist/extension/module-detector.js.map +1 -1
  162. package/dist/extension/plugin-parse.d.ts +1 -1
  163. package/dist/extension/plugin-parse.d.ts.map +1 -1
  164. package/dist/extension/plugin-parse.js +24 -7
  165. package/dist/extension/plugin-parse.js.map +1 -1
  166. package/dist/extension/problem-text.d.ts.map +1 -1
  167. package/dist/extension/problem-text.js +2 -0
  168. package/dist/extension/problem-text.js.map +1 -1
  169. package/dist/facts/age-values.d.ts.map +1 -1
  170. package/dist/facts/age-values.js +20 -2
  171. package/dist/facts/age-values.js.map +1 -1
  172. package/dist/facts/column-units.d.ts +31 -0
  173. package/dist/facts/column-units.d.ts.map +1 -0
  174. package/dist/facts/column-units.js +37 -0
  175. package/dist/facts/column-units.js.map +1 -0
  176. package/dist/facts/fact-values.d.ts +4 -0
  177. package/dist/facts/fact-values.d.ts.map +1 -1
  178. package/dist/facts/fact-values.js +3 -1
  179. package/dist/facts/fact-values.js.map +1 -1
  180. package/dist/facts/labelled-facts.d.ts +10 -0
  181. package/dist/facts/labelled-facts.d.ts.map +1 -1
  182. package/dist/facts/labelled-facts.js +6 -1
  183. package/dist/facts/labelled-facts.js.map +1 -1
  184. package/dist/facts/per-unit.d.ts +36 -0
  185. package/dist/facts/per-unit.d.ts.map +1 -0
  186. package/dist/facts/per-unit.js +56 -0
  187. package/dist/facts/per-unit.js.map +1 -0
  188. package/dist/facts/row-sentence-amounts.d.ts +3 -12
  189. package/dist/facts/row-sentence-amounts.d.ts.map +1 -1
  190. package/dist/facts/row-sentence-amounts.js +1 -15
  191. package/dist/facts/row-sentence-amounts.js.map +1 -1
  192. package/dist/facts/table-facts.d.ts +1 -1
  193. package/dist/facts/table-facts.d.ts.map +1 -1
  194. package/dist/facts/table-facts.js +13 -9
  195. package/dist/facts/table-facts.js.map +1 -1
  196. package/dist/facts/term-values.d.ts +13 -1
  197. package/dist/facts/term-values.d.ts.map +1 -1
  198. package/dist/facts/term-values.js +71 -12
  199. package/dist/facts/term-values.js.map +1 -1
  200. package/dist/grade/baseline.d.ts +4 -2
  201. package/dist/grade/baseline.d.ts.map +1 -1
  202. package/dist/grade/baseline.js +13 -11
  203. package/dist/grade/baseline.js.map +1 -1
  204. package/dist/grade/expect-read.d.ts +16 -0
  205. package/dist/grade/expect-read.d.ts.map +1 -0
  206. package/dist/grade/expect-read.js +52 -0
  207. package/dist/grade/expect-read.js.map +1 -0
  208. package/dist/grade/expect.d.ts +50 -0
  209. package/dist/grade/expect.d.ts.map +1 -0
  210. package/dist/grade/expect.js +53 -0
  211. package/dist/grade/expect.js.map +1 -0
  212. package/dist/grade/grade-item.d.ts +1 -1
  213. package/dist/grade/grade-item.d.ts.map +1 -1
  214. package/dist/grade/grade-item.js +13 -3
  215. package/dist/grade/grade-item.js.map +1 -1
  216. package/dist/grade/item.d.ts +9 -1
  217. package/dist/grade/item.d.ts.map +1 -1
  218. package/dist/grade/item.js +32 -7
  219. package/dist/grade/item.js.map +1 -1
  220. package/dist/grade/result.d.ts +5 -0
  221. package/dist/grade/result.d.ts.map +1 -1
  222. package/dist/grade/results-read.d.ts.map +1 -1
  223. package/dist/grade/results-read.js +4 -2
  224. package/dist/grade/results-read.js.map +1 -1
  225. package/dist/grade/rubric-file.d.ts +23 -0
  226. package/dist/grade/rubric-file.d.ts.map +1 -0
  227. package/dist/grade/rubric-file.js +35 -0
  228. package/dist/grade/rubric-file.js.map +1 -0
  229. package/dist/grade/rubric.d.ts +2 -0
  230. package/dist/grade/rubric.d.ts.map +1 -1
  231. package/dist/grade/rubric.js +75 -47
  232. package/dist/grade/rubric.js.map +1 -1
  233. package/dist/grade/setup.d.ts +2 -1
  234. package/dist/grade/setup.d.ts.map +1 -1
  235. package/dist/grade/setup.js +2 -2
  236. package/dist/grade/setup.js.map +1 -1
  237. package/dist/grade/stamp.d.ts +2 -0
  238. package/dist/grade/stamp.d.ts.map +1 -1
  239. package/dist/grade/stamp.js +8 -2
  240. package/dist/grade/stamp.js.map +1 -1
  241. package/dist/grade/task-rubric.d.ts +16 -0
  242. package/dist/grade/task-rubric.d.ts.map +1 -0
  243. package/dist/grade/task-rubric.js +34 -0
  244. package/dist/grade/task-rubric.js.map +1 -0
  245. package/dist/grade/text.d.ts +5 -1
  246. package/dist/grade/text.d.ts.map +1 -1
  247. package/dist/grade/text.js +16 -4
  248. package/dist/grade/text.js.map +1 -1
  249. package/dist/name-char-reading.d.ts +5 -0
  250. package/dist/name-char-reading.d.ts.map +1 -1
  251. package/dist/name-char-reading.js +15 -0
  252. package/dist/name-char-reading.js.map +1 -1
  253. package/dist/name-end-letter.d.ts +19 -0
  254. package/dist/name-end-letter.d.ts.map +1 -0
  255. package/dist/name-end-letter.js +37 -0
  256. package/dist/name-end-letter.js.map +1 -0
  257. package/dist/name-neighbours.d.ts.map +1 -1
  258. package/dist/name-neighbours.js +2 -1
  259. package/dist/name-neighbours.js.map +1 -1
  260. package/dist/name-separator.d.ts +6 -0
  261. package/dist/name-separator.d.ts.map +1 -1
  262. package/dist/name-separator.js +29 -0
  263. package/dist/name-separator.js.map +1 -1
  264. package/dist/name-variants.d.ts +6 -3
  265. package/dist/name-variants.d.ts.map +1 -1
  266. package/dist/name-variants.js +37 -10
  267. package/dist/name-variants.js.map +1 -1
  268. package/dist/number-name.d.ts.map +1 -1
  269. package/dist/number-name.js +11 -2
  270. package/dist/number-name.js.map +1 -1
  271. package/dist/profile/cue-lines.d.ts +6 -0
  272. package/dist/profile/cue-lines.d.ts.map +1 -0
  273. package/dist/profile/cue-lines.js +13 -0
  274. package/dist/profile/cue-lines.js.map +1 -0
  275. package/dist/profile/parse.d.ts +1 -1
  276. package/dist/profile/parse.d.ts.map +1 -1
  277. package/dist/profile/parse.js +3 -2
  278. package/dist/profile/parse.js.map +1 -1
  279. package/dist/reference-numbers.d.ts +2 -0
  280. package/dist/reference-numbers.d.ts.map +1 -1
  281. package/dist/reference-numbers.js +1 -1
  282. package/dist/reference-numbers.js.map +1 -1
  283. package/dist/spaced-person-name.d.ts.map +1 -1
  284. package/dist/spaced-person-name.js +26 -1
  285. package/dist/spaced-person-name.js.map +1 -1
  286. package/dist/spacing-code.d.ts.map +1 -1
  287. package/dist/spacing-code.js +10 -1
  288. package/dist/spacing-code.js.map +1 -1
  289. package/dist/spacing-minority.d.ts +7 -0
  290. package/dist/spacing-minority.d.ts.map +1 -1
  291. package/dist/spacing-minority.js +14 -0
  292. package/dist/spacing-minority.js.map +1 -1
  293. package/dist/structure/closing-balance.d.ts +9 -0
  294. package/dist/structure/closing-balance.d.ts.map +1 -1
  295. package/dist/structure/closing-balance.js +34 -7
  296. package/dist/structure/closing-balance.js.map +1 -1
  297. package/dist/structure/definition-use.d.ts.map +1 -1
  298. package/dist/structure/definition-use.js +20 -1
  299. package/dist/structure/definition-use.js.map +1 -1
  300. package/dist/structure/document-context.d.ts +2 -0
  301. package/dist/structure/document-context.d.ts.map +1 -0
  302. package/dist/structure/document-context.js +42 -0
  303. package/dist/structure/document-context.js.map +1 -0
  304. package/dist/structure/fenced-lines.d.ts +7 -0
  305. package/dist/structure/fenced-lines.d.ts.map +1 -0
  306. package/dist/structure/fenced-lines.js +27 -0
  307. package/dist/structure/fenced-lines.js.map +1 -0
  308. package/dist/structure/labelled-date-order.d.ts +31 -9
  309. package/dist/structure/labelled-date-order.d.ts.map +1 -1
  310. package/dist/structure/labelled-date-order.js +60 -15
  311. package/dist/structure/labelled-date-order.js.map +1 -1
  312. package/dist/structure/labelled-order.d.ts +7 -1
  313. package/dist/structure/labelled-order.d.ts.map +1 -1
  314. package/dist/structure/labelled-order.js +10 -3
  315. package/dist/structure/labelled-order.js.map +1 -1
  316. package/dist/structure/line-amount-label.d.ts.map +1 -1
  317. package/dist/structure/line-amount-label.js +2 -1
  318. package/dist/structure/line-amount-label.js.map +1 -1
  319. package/dist/structure/line-amount.d.ts +0 -5
  320. package/dist/structure/line-amount.d.ts.map +1 -1
  321. package/dist/structure/line-amount.js +1 -10
  322. package/dist/structure/line-amount.js.map +1 -1
  323. package/dist/structure/prose-total.d.ts +8 -0
  324. package/dist/structure/prose-total.d.ts.map +1 -1
  325. package/dist/structure/prose-total.js +70 -2
  326. package/dist/structure/prose-total.js.map +1 -1
  327. package/dist/structure/runs.d.ts.map +1 -1
  328. package/dist/structure/runs.js +9 -1
  329. package/dist/structure/runs.js.map +1 -1
  330. package/dist/structure/signed-date-line.d.ts +15 -0
  331. package/dist/structure/signed-date-line.d.ts.map +1 -0
  332. package/dist/structure/signed-date-line.js +68 -0
  333. package/dist/structure/signed-date-line.js.map +1 -0
  334. package/dist/structure/tax.d.ts.map +1 -1
  335. package/dist/structure/tax.js +5 -3
  336. package/dist/structure/tax.js.map +1 -1
  337. package/dist/structure/total-line.d.ts.map +1 -1
  338. package/dist/structure/total-line.js +10 -6
  339. package/dist/structure/total-line.js.map +1 -1
  340. package/dist/structure/total.d.ts +2 -0
  341. package/dist/structure/total.d.ts.map +1 -1
  342. package/dist/structure/total.js +34 -3
  343. package/dist/structure/total.js.map +1 -1
  344. package/dist/subject-codes.d.ts +21 -0
  345. package/dist/subject-codes.d.ts.map +1 -0
  346. package/dist/subject-codes.js +79 -0
  347. package/dist/subject-codes.js.map +1 -0
  348. package/dist/unlabelled-references.d.ts +6 -2
  349. package/dist/unlabelled-references.d.ts.map +1 -1
  350. package/dist/unlabelled-references.js +11 -10
  351. package/dist/unlabelled-references.js.map +1 -1
  352. package/genres.yaml +17 -0
  353. package/package.json +3 -3
  354. package/profiles/listing.yaml +1 -1
  355. package/rules/closing-balance-mismatch.yaml +3 -3
  356. package/rules/due-before-issue.yaml +9 -5
  357. package/rules/fact-conflict.yaml +2 -2
  358. package/rules/heading-echo.yaml +2 -2
  359. package/rules/internal-jargon.yaml +2 -2
  360. package/rules/latin-spacing.yaml +4 -4
  361. package/rules/list-item-form-mix.yaml +1 -1
  362. package/rules/name-variant.yaml +5 -2
  363. package/rules/no-lead.yaml +2 -2
  364. package/rules/number-without-unit.yaml +2 -2
  365. package/rules/reference-number-variant.yaml +3 -3
  366. package/rules/toki-as-condition.yaml +71 -0
  367. package/rules/total-mismatch.yaml +4 -4
  368. package/rules/unknown-word.yaml +2 -2
  369. package/rules/use-before-definition.yaml +2 -2
  370. package/src/api.ts +11 -2
  371. package/src/cli-text.ts +4 -2
  372. package/src/commands/grade.ts +75 -23
  373. package/src/company-kana-spelling.ts +22 -2
  374. package/src/company-name-ending.ts +50 -0
  375. package/src/company-names.ts +52 -17
  376. package/src/custom/no-rewrite.ts +2 -0
  377. package/src/custom/parse.ts +18 -2
  378. package/src/detectors/age-words.ts +12 -0
  379. package/src/detectors/annual-pay-mismatch.ts +3 -18
  380. package/src/detectors/blank-structure.ts +4 -3
  381. package/src/detectors/closing-balance-mismatch.ts +1 -0
  382. package/src/detectors/code-fences.ts +10 -0
  383. package/src/detectors/condition-toki.ts +60 -0
  384. package/src/detectors/conjugated-entry.ts +53 -0
  385. package/src/detectors/content-morphemes.ts +2 -1
  386. package/src/detectors/cross-facts.ts +2 -1
  387. package/src/detectors/custom.ts +9 -2
  388. package/src/detectors/document-shape.ts +8 -0
  389. package/src/detectors/due-date.ts +17 -7
  390. package/src/detectors/fact-consistency.ts +13 -17
  391. package/src/detectors/form-line.ts +47 -0
  392. package/src/detectors/formula-amount.ts +2 -18
  393. package/src/detectors/heading-claim.ts +76 -0
  394. package/src/detectors/heading-echo.ts +32 -1
  395. package/src/detectors/installment-total-mismatch.ts +3 -18
  396. package/src/detectors/list-parallel.ts +46 -7
  397. package/src/detectors/name-variant.ts +45 -8
  398. package/src/detectors/number-unit.ts +35 -9
  399. package/src/detectors/orthography.ts +17 -8
  400. package/src/detectors/per-unit-words.ts +24 -0
  401. package/src/detectors/period-multiple-mismatch.ts +2 -18
  402. package/src/detectors/reference-number-variant.ts +27 -5
  403. package/src/detectors/registry/condition-toki.ts +4 -0
  404. package/src/detectors/rent-multiple-mismatch.ts +2 -18
  405. package/src/detectors/row-sentence-amounts.ts +5 -20
  406. package/src/detectors/stated-amounts.ts +20 -0
  407. package/src/detectors/step-time-sum.ts +2 -1
  408. package/src/detectors/structure-tree.ts +3 -0
  409. package/src/detectors/team.ts +13 -7
  410. package/src/detectors/time-order.ts +6 -3
  411. package/src/detectors/unknown-word.ts +20 -2
  412. package/src/detectors/use-interval.ts +8 -4
  413. package/src/detectors/use-maximum.ts +3 -1
  414. package/src/extension/document-view.ts +18 -9
  415. package/src/extension/entry-genres.ts +15 -0
  416. package/src/extension/module-detector.ts +9 -4
  417. package/src/extension/plugin-parse.ts +29 -7
  418. package/src/extension/problem-text.ts +4 -0
  419. package/src/facts/age-values.ts +24 -2
  420. package/src/facts/column-units.ts +67 -0
  421. package/src/facts/fact-values.ts +11 -1
  422. package/src/facts/labelled-facts.ts +16 -2
  423. package/src/facts/per-unit.ts +105 -0
  424. package/src/facts/row-sentence-amounts.ts +4 -26
  425. package/src/facts/table-facts.ts +7 -4
  426. package/src/facts/term-values.ts +94 -13
  427. package/src/grade/baseline.ts +20 -12
  428. package/src/grade/expect-read.ts +65 -0
  429. package/src/grade/expect.ts +74 -0
  430. package/src/grade/grade-item.ts +14 -3
  431. package/src/grade/item.ts +48 -8
  432. package/src/grade/result.ts +5 -0
  433. package/src/grade/results-read.ts +7 -2
  434. package/src/grade/rubric-file.ts +46 -0
  435. package/src/grade/rubric.ts +77 -47
  436. package/src/grade/setup.ts +3 -3
  437. package/src/grade/stamp.ts +10 -2
  438. package/src/grade/task-rubric.ts +39 -0
  439. package/src/grade/text.ts +27 -5
  440. package/src/name-char-reading.ts +16 -0
  441. package/src/name-end-letter.ts +52 -0
  442. package/src/name-neighbours.ts +2 -1
  443. package/src/name-separator.ts +32 -0
  444. package/src/name-variants.ts +54 -13
  445. package/src/number-name.ts +12 -3
  446. package/src/profile/cue-lines.ts +14 -0
  447. package/src/profile/parse.ts +3 -2
  448. package/src/reference-numbers.ts +1 -1
  449. package/src/spaced-person-name.ts +27 -2
  450. package/src/spacing-code.ts +13 -2
  451. package/src/spacing-minority.ts +17 -0
  452. package/src/structure/closing-balance.ts +46 -8
  453. package/src/structure/definition-use.ts +24 -1
  454. package/src/structure/document-context.ts +46 -0
  455. package/src/structure/fenced-lines.ts +28 -0
  456. package/src/structure/labelled-date-order.ts +105 -21
  457. package/src/structure/labelled-order.ts +21 -10
  458. package/src/structure/line-amount-label.ts +2 -1
  459. package/src/structure/line-amount.ts +1 -11
  460. package/src/structure/prose-total.ts +81 -2
  461. package/src/structure/runs.ts +10 -1
  462. package/src/structure/signed-date-line.ts +78 -0
  463. package/src/structure/tax.ts +6 -3
  464. package/src/structure/total-line.ts +12 -6
  465. package/src/structure/total.ts +42 -3
  466. package/src/subject-codes.ts +105 -0
  467. package/src/unlabelled-references.ts +24 -16
@@ -2,6 +2,7 @@ import type { Token } from "./plugin.ts";
2
2
  import { escapeRegExp } from "./orthography.ts";
3
3
  import { nameCueAt, type NameCue, type NameCues } from "./name-cue.ts";
4
4
  import { isNearSurname } from "./surname-near.ts";
5
+ import { isEndWordSlip, NO_WORD_USE, type WordUse } from "./name-end-letter.ts";
5
6
  import { foldedKeysOf, NO_SPELLING, type SpellingInput } from "./name-spelling-chars.ts";
6
7
  import { NO_CHAR_READINGS, readingOfWords } from "./name-char-reading.ts";
7
8
  import { isPersonRun } from "./name-person.ts";
@@ -15,7 +16,8 @@ import { heavierFirst, outweighs, type NameWeight } from "./name-weight.ts";
15
16
  * words は名前の語(記号を除く)。読みが同じ二つの名前が、どの語で違うかを見るため。person は人の名前と読めるもの(解析器が
16
17
  * 人名と読むか、敬称が付く)。cue は前後の語から見た名前らしさ(name-cue.ts)。ordinary は、ふつうの語でもある一語の名前で、
17
18
  * 文書がその形をどこでも名前に使わないもの(name-ordinary-word.ts)。labelled は、人を書く欄(氏名:、Contractor:)に書いた名前
18
- * (person-field.ts)。
19
+ * (person-field.ts)。givenNameReading は、空白を挟んだ姓と名の名を、名の中での字の読みで読み直した読み(name-char-reading.ts の
20
+ * givenNameReadingOf)。解析器が名を別の読みに切ったとき(建太 を タテフトシ)のもう一通りの読み。
19
21
  */
20
22
  export type NameMention = {
21
23
  readonly surface: string;
@@ -26,6 +28,7 @@ export type NameMention = {
26
28
  readonly cue?: NameCue;
27
29
  readonly ordinary?: boolean;
28
30
  readonly labelled?: boolean;
31
+ readonly givenNameReading?: string | undefined;
29
32
  };
30
33
 
31
34
  export type NameVariant = {
@@ -163,8 +166,12 @@ export const nameKey = (surface: string): string =>
163
166
  */
164
167
  export type NameEvidence = { readonly person: boolean; readonly named: boolean; readonly nameLike: boolean };
165
168
 
166
- /** readings は書き方の現れの読みのすべて。解析器は同じ字を所によって別に読む(佐伯 を サエキ と サイキ)。 */
167
- type Tally = NameEvidence & NameWeight & { readonly surface: string; readonly first: NameMention; readonly readings: readonly string[] };
169
+ /**
170
+ * readings は書き方の現れの読みのすべて。解析器は同じ字を所によって別に読む(佐伯 を サエキ と サイキ)。givenNameReadings は
171
+ * 現れの givenNameReading のすべて。
172
+ */
173
+ type Tally = NameEvidence &
174
+ NameWeight & { readonly surface: string; readonly first: NameMention; readonly readings: readonly string[]; readonly givenNameReadings: readonly string[] };
168
175
 
169
176
  const readingsWith = (readings: readonly string[], reading: string | undefined): readonly string[] =>
170
177
  reading === undefined || readings.includes(reading) ? readings : [...readings, reading];
@@ -184,11 +191,20 @@ const talliesOf = (mentions: readonly NameMention[]): Tally[] => {
184
191
  tallies.set(
185
192
  mention.surface,
186
193
  tally === undefined
187
- ? { surface: mention.surface, first: mention, count: 1, readings: readingsWith([], mention.reading), labelled: mention.labelled === true, ...evidence }
194
+ ? {
195
+ surface: mention.surface,
196
+ first: mention,
197
+ count: 1,
198
+ readings: readingsWith([], mention.reading),
199
+ givenNameReadings: readingsWith([], mention.givenNameReading),
200
+ labelled: mention.labelled === true,
201
+ ...evidence,
202
+ }
188
203
  : {
189
204
  ...tally,
190
205
  count: tally.count + 1,
191
206
  readings: readingsWith(tally.readings, mention.reading),
207
+ givenNameReadings: readingsWith(tally.givenNameReadings, mention.givenNameReading),
192
208
  person: tally.person || evidence.person,
193
209
  named: tally.named || evidence.named,
194
210
  nameLike: tally.nameLike || evidence.nameLike,
@@ -268,14 +284,17 @@ const isSwapOrReplace = (left: string, right: string): boolean => {
268
284
  const isInnerDrop = (longer: string, shorter: string): boolean =>
269
285
  [...longer].some((_char, index) => index > 0 && index < longer.length - 1 && longer.slice(0, index) + longer.slice(index + 1) === shorter);
270
286
 
271
- /** 二つの語が一字違いか。英字だけの、長さ 5 以上の語で。 */
272
- export const isNearWord = (left: string, right: string): boolean => {
273
- if (left === right || !LATIN_WORD.test(left) || !LATIN_WORD.test(right) || Math.min(left.length, right.length) < MIN_NEAR_LENGTH) return false;
287
+ /** 二つの英字だけの語が一字違いか(置き換え・隣どうしの入れ替え・中の抜け)。長さは問わない。 */
288
+ const isOneLetterApart = (left: string, right: string): boolean => {
289
+ if (left === right || !LATIN_WORD.test(left) || !LATIN_WORD.test(right)) return false;
274
290
  if (left.length === right.length) return isSwapOrReplace(left, right);
275
291
  if (left.length === right.length + 1) return isInnerDrop(left, right);
276
292
  return right.length === left.length + 1 && isInnerDrop(right, left);
277
293
  };
278
294
 
295
+ /** 二つの語が一字違いか。英字だけの、長さ 5 以上の語で。 */
296
+ export const isNearWord = (left: string, right: string): boolean => Math.min(left.length, right.length) >= MIN_NEAR_LENGTH && isOneLetterApart(left, right);
297
+
279
298
  /** 語の一つを伏せた形。ほかの語が同じ二つの名前は、同じ形を一つ持つ。 */
280
299
  const maskedKeys = (tally: Tally): string[] => {
281
300
  const words = wordsOf(tally.surface);
@@ -297,7 +316,24 @@ const isSlipOf = (tally: Tally, other: Tally): boolean => {
297
316
  return words !== undefined && isNearWord(...words);
298
317
  };
299
318
 
300
- const nearVariants = (tallies: readonly Tally[]): NameVariant[] => variantsIn(groupBy(tallies, maskedKeys), "near", isSlipOf, true);
319
+ /**
320
+ * 二語以上の名前の一語だけが、終わりの一字の抜けか足し(Rachel Whitmore と Rachel Whitmor)か、最後の語(姓)が四字でも一字違い
321
+ * (Daniel Reyes と Daniel Reys)で、相手のほうが重い(outweighs)。違う語のどちらかがふつうの語なら語の形の違い。
322
+ */
323
+ const isEndSlipOf =
324
+ (use: WordUse): Alike =>
325
+ (tally, other) => {
326
+ const [words, others] = [wordsOf(tally.surface), wordsOf(other.surface)];
327
+ const at = words.findIndex((word, index) => word !== others[index]);
328
+ const [word, usual] = [words[at], others[at]];
329
+ if (!outweighs(other, tally) || words.length < 2 || words.length !== others.length || word === undefined || usual === undefined) return false;
330
+ return !use.ordinary.has(word) && !use.ordinary.has(usual) && isEndWordSlip(words, at, usual, use, isOneLetterApart);
331
+ };
332
+
333
+ const nearVariants = (tallies: readonly Tally[], use: WordUse): NameVariant[] => {
334
+ const isEndSlip = isEndSlipOf(use);
335
+ return variantsIn(groupBy(tallies, maskedKeys), "near", (tally, other) => isSlipOf(tally, other) || isEndSlip(tally, other), true);
336
+ };
301
337
 
302
338
  /**
303
339
  * 名が同じで姓だけが二字ほど違う人の名前(Rachel Whitford と Rachel Whitfield)。一字違い(near)より遠いので、名と姓の二語以上
@@ -343,7 +379,7 @@ const oneCharApart = (left: string, right: string): boolean => {
343
379
 
344
380
  /**
345
381
  * 読みが同じ人の名前が、一字だけ違う(松本 と 松元)。読みの同じ別の姓(伊藤 と 伊東)もあるので、相手のほうが重い(outweighs)
346
- * ときに限る。
382
+ * ときに限る。名を名の中での字の読みで読み直した読み(健太 と 建太 の ケンタ)でも比べる。
347
383
  */
348
384
  const isPersonSlipOf = (tally: Tally, other: Tally): boolean => outweighs(other, tally) && oneCharApart(tally.surface, other.surface);
349
385
 
@@ -351,7 +387,7 @@ const personReadingVariants = (tallies: readonly Tally[]): NameVariant[] =>
351
387
  variantsIn(
352
388
  groupBy(
353
389
  tallies.filter((tally) => tally.person),
354
- (tally) => tally.readings,
390
+ (tally) => [...tally.readings, ...tally.givenNameReadings],
355
391
  ),
356
392
  "reading",
357
393
  isPersonSlipOf,
@@ -360,9 +396,14 @@ const personReadingVariants = (tallies: readonly Tally[]): NameVariant[] =>
360
396
 
361
397
  /**
362
398
  * 同じ名前の、少ないほうの書き方。書き方ごとに最初の現れを一つ。同じ現れを二つの見方が言えば、先の見方だけ。chars は字体の
363
- * 違う同じ字の組(語彙表 name-variant-char)。
399
+ * 違う同じ字の組(語彙表 name-variant-char)。use は文書での英字の語の使い方。
364
400
  */
365
- export const nameVariants = (mentions: readonly NameMention[], chars: VariantChars = new Map(), spelling: SpellingInput = NO_SPELLING): NameVariant[] => {
401
+ export const nameVariants = (
402
+ mentions: readonly NameMention[],
403
+ chars: VariantChars = new Map(),
404
+ spelling: SpellingInput = NO_SPELLING,
405
+ use: WordUse = NO_WORD_USE,
406
+ ): NameVariant[] => {
366
407
  const tallies = talliesOf(mentions);
367
408
  const found = [
368
409
  ...variantsIn(
@@ -378,7 +419,7 @@ export const nameVariants = (mentions: readonly NameMention[], chars: VariantCha
378
419
  "reading",
379
420
  oneWordApart,
380
421
  ),
381
- ...nearVariants(tallies),
422
+ ...nearVariants(tallies, use),
382
423
  ...surnameVariants(tallies),
383
424
  ...characterVariants(tallies, chars),
384
425
  ...personReadingVariants(tallies),
@@ -6,7 +6,7 @@ import type { Span, Token } from "./plugin.ts";
6
6
  * 「〒100-8916 東京都」の郵便番号、「紀尾井町1-3 東京ガーデンテラス」の番地は、後ろの空白が番号と題・項目の区切りで、「3回」「3 回」のような空け方の好みではない。
7
7
  * 空け方を数える材料にしない。
8
8
  *
9
- * 形で名前と読むのは六つだけ。0 で始まる組を含むハイフンつなぎの番号、※・〒や鉤括弧のすぐ後ろの番号、市・区・町まで下りた地名のすぐ後ろのハイフンつなぎの番号、
9
+ * 形で名前と読むのは七つだけ。桁の区切りの無い 6 桁以上の番号(口座番号 3056172)、0 で始まる組を含むハイフンつなぎの番号、※・〒や鉤括弧のすぐ後ろの番号、市・区・町まで下りた地名のすぐ後ろのハイフンつなぎの番号、
10
10
  * 文頭の階層つきの節番号(2.1、3.1.2)、続き番号になった行や番号の並ぶ箇条書きの頭の番号(白書の注、関連コード)、「問3」のように番号の前に書く語のすぐ後ろの番号。
11
11
  * 文頭でも「223 言語」「1ターン」は数量なので、階層の無い数は文頭というだけでは名前と読まない。
12
12
  * そのうえで、すぐ後ろ(空白 1 つまで)の語が数につく語(助数詞・数・助詞・助動詞)なら数量として数え続ける(「3-5 日」「26.7 万行」)。
@@ -53,8 +53,16 @@ const wordAfter = (tokens: readonly Token[], text: string, run: Span, base: numb
53
53
  return tokens.find((token) => token.span.start === base + next);
54
54
  };
55
55
 
56
- const isBoundToNumber = (token: Token): boolean =>
57
- token.features?.["NounType"] === "Class" || token.features?.["NumType"] === "Card" || BOUND_TO_NUMBER.has(token.pos);
56
+ /** 助数詞(NounType=Class)か数(NumType=Card)。 */
57
+ const isCounterOrNumber = (token: Token | undefined): boolean => token?.features?.["NounType"] === "Class" || token?.features?.["NumType"] === "Card";
58
+
59
+ const isBoundToNumber = (token: Token): boolean => isCounterOrNumber(token) || BOUND_TO_NUMBER.has(token.pos);
60
+
61
+ /**
62
+ * 桁の区切りの無い、6 桁以上の数字だけの番号(口座番号 3056172 を、普通 1234567)。この大きさの数量は桁を区切るか単位を付けて書くので、
63
+ * 助数詞か数が続くとき(1234567円、250000 人)だけ数量と読む。助詞が続いても番号のまま。
64
+ */
65
+ const PLAIN_CODE = /^\d{6,}$/u;
58
66
 
59
67
  /** 「1 又は 2」の接続詞は数どうしをつなぐが、続き番号の注の本文は「31 ただし、…」のように接続詞でも始まる。 */
60
68
  const CONJUNCTIONS = new Set(["CCONJ", "SCONJ"]);
@@ -221,6 +229,7 @@ export type NameContext = {
221
229
  export const isNumberName = (text: string, run: Span, tokens: readonly Token[] | undefined, base: number, context: NameContext = {}): boolean => {
222
230
  const inSequence = context.sequence?.has(base + run.start) ?? false;
223
231
  if (tokens === undefined) return false;
232
+ if (PLAIN_CODE.test(text.slice(run.start, run.end))) return !isCounterOrNumber(wordAfter(tokens, text, run, base));
224
233
  const shaped =
225
234
  inSequence ||
226
235
  isNameShaped(text, run) ||
@@ -0,0 +1,14 @@
1
+ /**
2
+ * 行頭の箇条の印。Markdown の印(- * + と 1. 1))は後ろに空白が要る。「・」は日本語で手で打つ箇条の印で、空白を置かずに続けて書く。
3
+ * 「**賃料**」の太字や「-5」は印ではない。
4
+ */
5
+ const LIST_MARKER = /^([ \t]*)(?:(?:[-*+]|\d{1,9}[.)])[ \t]+|・[ \t\u3000]*)/gmu;
6
+
7
+ /** 行頭で項目の名だけを包む太字(「**賃料**:」)。名の後ろにすぐ区切りが来るときだけで、文の中の太字は外さない。 */
8
+ const BOLD_LABEL = /^([ \t]*)\*\*([^*::||\n]+)\*\*(?=[ \t]*[||::])/gmu;
9
+
10
+ /**
11
+ * 内容から種類を選ぶ行を読むために、行頭の箇条の印と、項目の名を包む太字を外す。字下げは残す。
12
+ * 「- 賃料:85,000円」「・賃料:85,000円」「**賃料**:85,000円」も「賃料:85,000円」と同じ項目の行として数えるため。
13
+ */
14
+ export const withoutListMarkers = (source: string): string => source.replace(LIST_MARKER, "$1").replace(BOLD_LABEL, "$1$2");
@@ -1,4 +1,5 @@
1
1
  import type { DocumentProfile, RelativeVocabulary } from "../plugin.ts";
2
+ import { withoutListMarkers } from "./cue-lines.ts";
2
3
 
3
4
  /** profiles/*.yaml を一つ読んだもの。言語ごとの中身と、内容から選ぶための形を持つ。 */
4
5
  export type ProfileDefinition = {
@@ -137,10 +138,10 @@ export const parseProfile = (raw: unknown): ProfileDefinition | undefined => {
137
138
  return { id, name: texts(raw["name"]), detect, languages: languagesOf(id, raw) };
138
139
  };
139
140
 
140
- /** 行頭から数える。source 全体に m フラグで当てれば、行ごとに分けずに数えられる。 */
141
+ /** 行頭から数える。source 全体に m フラグで当てれば、行ごとに分けずに数えられる。箇条の印は行頭に数えない。 */
141
142
  export const detected = (definition: ProfileDefinition, source: string, language: string): boolean => {
142
143
  const detect = definition.detect[language];
143
144
  if (detect === undefined) return false;
144
- const count = [...source.matchAll(new RegExp(detect.line, "gmu"))].length;
145
+ const count = [...withoutListMarkers(source).matchAll(new RegExp(detect.line, "gmu"))].length;
145
146
  return count >= detect.minLines;
146
147
  };
@@ -18,7 +18,7 @@ export const MAX_CHARACTERS = 24;
18
18
  const DIGIT = /\d/gu;
19
19
  const SEPARATOR = /[-‐−-]/gu;
20
20
  /** Only spaces and a colon, comma or opening bracket between the label and its number (「予約番号:」, "booking number, "); not a sentence's end. */
21
- const LABEL_TO_NUMBER = /^[\s::,,、##((「『[[]{0,4}$/u;
21
+ export const LABEL_TO_NUMBER = /^[\s::,,、##((「『[[]{0,4}$/u;
22
22
 
23
23
  export const charactersOf = (written: string): string => written.normalize("NFKC").replaceAll(SEPARATOR, "").replaceAll(" ", "").toUpperCase();
24
24
 
@@ -4,7 +4,7 @@ import { isInPersonField as isFieldValue } from "./person-field.ts";
4
4
  // 空白を挟んで書いた姓と名(高瀬 誠、佐伯 未咲)を、解析器の品詞に頼らず、前後の語で人の名前と読む。解析器は姓を地名やふつうの
5
5
  // 名詞と読み、名を二語に切ることがあるので、姓と名を一つの名前にできない。後ろに敬称か肩書き(様、部長)が付くか、人を書く欄
6
6
  // (氏名:、承認者:)の行の終わりにあれば、人の名前と読む。欄に書いた名と同じ名を空白の後ろに書いたもの(受託者:西村 亮 の
7
- // 文書の 西邑 亮が)も、人の名前と読む。
7
+ // 文書の 西邑 亮が)も、人の名前と読む。名はかなで書いてもよい(松本 あかり 様)。
8
8
 
9
9
  /** suffixes は敬称(様、さん)、titles は名前のすぐ後ろの肩書き(部長)、labels は人を書く欄の語(氏名、承認者)。語彙表から。 */
10
10
  export type SpacedNameCues = { readonly suffixes: readonly string[]; readonly titles: readonly string[]; readonly labels: readonly string[] };
@@ -31,6 +31,13 @@ const LINE_MARKS = /^[\s>*#+-]*/u;
31
31
  const FIELD_END = /^[\u0020\u3000]*(?:[((][^(())\n]*[))][\u0020\u3000]*)?(?:\r?\n|$)/u;
32
32
 
33
33
  const isHan = (letter: string | undefined): boolean => letter !== undefined && HAN.test(letter);
34
+ const KANA = /^[\p{Script=Hiragana}\p{Script=Katakana}]$/u;
35
+ const LEADING_KANA = /^[\p{Script=Hiragana}\p{Script=Katakana}ー]+/u;
36
+ /** かなで書いた名の字数(あかり、エミリー)。一字のかな(ご担当 の ご)は名でない。 */
37
+ const MIN_KANA_GIVEN = 2;
38
+ const MAX_KANA_GIVEN = 5;
39
+
40
+ const isKana = (letter: string | undefined): boolean => letter !== undefined && KANA.test(letter);
34
41
 
35
42
  /** 人を書く欄の行(承認者:営業部長 高瀬 誠)で、姓が欄の語の後ろにあるか。 */
36
43
  const isInPersonField = (source: string, surnameStart: number, labels: readonly string[]): boolean => {
@@ -53,8 +60,23 @@ const givenBeforeSuffix = (run: string, after: string, words: readonly string[])
53
60
  });
54
61
  };
55
62
 
63
+ /**
64
+ * かなで書いた名(松本 あかり 様)。かなの連なりまるごとを名と読み、後ろに空白一つまでを挟んで敬称か肩書きが付くか、人を書く欄の
65
+ * 行の終わりにあるときだけ。かなの続きの中で敬称を切り出さない(みなさま を みな と さま にしない)。
66
+ */
67
+ const kanaGivenAt = (source: string, start: number, cues: SpacedNameCues, inField: boolean, fieldGivens: ReadonlySet<string>): string | undefined => {
68
+ const run = LEADING_KANA.exec(source.slice(start, start + LOOKBACK))?.[0];
69
+ const length = [...(run ?? "")].length;
70
+ if (run === undefined || length < MIN_KANA_GIVEN || length > MAX_KANA_GIVEN) return undefined;
71
+ const after = source.slice(start + run.length, start + run.length + LOOKBACK);
72
+ const rest = after.replace(ONE_SPACE, "");
73
+ if ([...cues.suffixes, ...cues.titles].some((word) => rest.startsWith(word))) return run;
74
+ return fieldGivens.has(run) || (inField && FIELD_END.test(after)) ? run : undefined;
75
+ };
76
+
56
77
  /** fieldGivens は、文書が人を書く欄に書いた名(亮)。その名と同じ漢字の連なりは、敬称が無くても名と読む。 */
57
78
  const givenAt = (source: string, start: number, cues: SpacedNameCues, inField: boolean, fieldGivens: ReadonlySet<string>): string | undefined => {
79
+ if (isKana(source.charAt(start))) return kanaGivenAt(source, start, cues, inField, fieldGivens);
58
80
  const run = LEADING_HAN.exec(source.slice(start, start + LOOKBACK))?.[0];
59
81
  if (run === undefined) return undefined;
60
82
  const after = source.slice(start + run.length, start + run.length + LOOKBACK);
@@ -73,10 +95,13 @@ const surnameBefore = (source: string, space: number, cues: SpacedNameCues): str
73
95
  return [...cues.titles, ...cues.suffixes].some((word) => run.endsWith(word)) ? undefined : run;
74
96
  };
75
97
 
98
+ /** 名の頭の字。漢字か、かな(松本 あかり)。 */
99
+ const isNameLetter = (letter: string | undefined): boolean => isHan(letter) || isKana(letter);
100
+
76
101
  const namesIn = (source: string, cues: SpacedNameCues, fieldGivens: ReadonlySet<string>): SpacedName[] =>
77
102
  [...source.matchAll(NAME_SPACE)].flatMap((match) => {
78
103
  const space = match.index;
79
- if (!isHan(LAST_LETTER.exec(source.slice(Math.max(0, space - 2), space))?.[0]) || !isHan(FIRST_LETTER.exec(source.slice(space + 1, space + 3))?.[0]))
104
+ if (!isHan(LAST_LETTER.exec(source.slice(Math.max(0, space - 2), space))?.[0]) || !isNameLetter(FIRST_LETTER.exec(source.slice(space + 1, space + 3))?.[0]))
80
105
  return [];
81
106
  const surname = surnameBefore(source, space, cues);
82
107
  if (surname === undefined) return [];
@@ -28,6 +28,15 @@ const HYPHENATED = new RegExp(
28
28
  "gu",
29
29
  );
30
30
  const CAPITALS_SERIAL = new RegExp(`(?<![A-Za-z0-9.${HYPHEN_CHARS}])[A-Z]+\\d{4,}(?![A-Za-z0-9${HYPHEN_CHARS}]|\\.[A-Za-z0-9])`, "gu");
31
+ /**
32
+ * A Japanese number plate after its place name: a class number of two or three digits, one hiragana and a serial
33
+ * (330 ね 48-15, 500み・・12). A one-digit class number, or a serial that a kanji or katakana word follows, is a reference
34
+ * such as 「表3の12-15行」「表30の12-34行」, not a plate. The spaces inside a plate are its layout, and the one before it ends the place name.
35
+ */
36
+ const PLATE = new RegExp(
37
+ `(?<![\\d${HYPHEN_CHARS}])\\d{2,3}[ \\u3000]?\\p{Script=Hiragana}[ \\u3000]?(?:\\d{1,2}[${HYPHEN_CHARS}]\\d{2}|[・・]{1,3}\\d{1,3})(?![\\d${HYPHEN_CHARS}\\p{Script=Han}\\p{Script=Katakana}])`,
38
+ "gu",
39
+ );
31
40
  const HAS_LETTER = /[A-Za-z]/u;
32
41
  const SERIAL_DIGITS = /\d{3}/u;
33
42
 
@@ -40,8 +49,10 @@ export const addressSpans = (text: string): Span[] => spansOf(text, ASCII_RUN).f
40
49
 
41
50
  const isIdentifier = (word: string): boolean => HAS_LETTER.test(word) && SERIAL_DIGITS.test(word);
42
51
 
43
- export const identifierSpans = (text: string): Span[] =>
44
- [...spansOf(text, HYPHENATED), ...spansOf(text, CAPITALS_SERIAL)].filter((span) => isIdentifier(text.slice(span.start, span.end)));
52
+ export const identifierSpans = (text: string): Span[] => [
53
+ ...[...spansOf(text, HYPHENATED), ...spansOf(text, CAPITALS_SERIAL)].filter((span) => isIdentifier(text.slice(span.start, span.end))),
54
+ ...spansOf(text, PLATE),
55
+ ];
45
56
 
46
57
  /** Whether the character at `at` is inside one of the spans. */
47
58
  export const isInsideSpan = (spans: readonly Span[], at: number): boolean => spans.some((span) => span.start <= at && at < span.end);
@@ -25,3 +25,20 @@ export const minorityReport = <T extends { readonly spaced: boolean }>(ofKind: r
25
25
  const spaced = ofKind.filter((entry) => entry.spaced).length;
26
26
  return { mode: "mixed", first, odd: odd.length, spaced, touching: ofKind.length - spaced };
27
27
  };
28
+
29
+ /** 同数のとき、書き方が二通りあると 1 件で言う最少の数。1 対 1 は一つずつの書き違いとも区別がつかないので言わない。 */
30
+ const MIN_TIE = 2;
31
+
32
+ /**
33
+ * 二通りが同じ数なら少ないほうは無く、1 箇所ずつ「違う」と指すとどちらかの側に立つ。MIN_TIE 対 MIN_TIE 以上なら書き方が二通りあることを 1 件で言い、
34
+ * それより少なければ言わない。同数でなければ report のまま。
35
+ */
36
+ export const withoutSideOnTie = <T extends { readonly spaced: boolean }>(
37
+ report: MinorityReport<T> | undefined,
38
+ ofKind: readonly T[],
39
+ ): MinorityReport<T> | undefined => {
40
+ if (report?.mode !== "each" || report.odd.length * 2 !== ofKind.length) return report;
41
+ const first = ofKind[0];
42
+ if (report.odd.length < MIN_TIE || first === undefined) return undefined;
43
+ return { mode: "mixed", first, odd: report.odd.length, spaced: report.odd.length, touching: report.odd.length };
44
+ };
@@ -11,7 +11,11 @@ import { amountOf, isExact, shown, type LabelledLine, type NetAmount, type NetCe
11
11
  export const BALANCE_ROLES = ["opening", "inflow", "outflow", "closing"] as const;
12
12
  export type BalanceRole = (typeof BALANCE_ROLES)[number];
13
13
 
14
- export type BalanceWords = Pick<NetWords, "magnitudes" | "percentUnits"> & { readonly labels: Readonly<Record<BalanceRole, readonly string[]>> };
14
+ export type BalanceWords = Pick<NetWords, "magnitudes" | "percentUnits"> & {
15
+ readonly labels: Readonly<Record<BalanceRole, readonly string[]>>;
16
+ /** Words that make a label's note the date the balance is taken on (時点, as of): such a note is not a unit. */
17
+ readonly asOf?: readonly string[] | undefined;
18
+ };
15
19
 
16
20
  export type BalanceAmount = NetAmount & { readonly role: BalanceRole };
17
21
 
@@ -54,12 +58,46 @@ export const closingMismatch = (amounts: readonly BalanceAmount[], words: Balanc
54
58
  return { offset: closing.start, values: { written: closing.written, expected: shown(expected, closing, decimals) } };
55
59
  };
56
60
 
57
- const labelledCells = (labels: readonly NetCell[], values: readonly NetCell[], words: BalanceWords): BalanceAmount[] =>
61
+ const DIGIT = /\d/u;
62
+
63
+ /** A note naming the date a balance is taken on ((2026年6月30日時点), (as of June 30, 2026)): a date, not a unit. */
64
+ export const isDatedNote = (note: string, asOf: readonly string[]): boolean => {
65
+ const key = keyOf(note);
66
+ return DIGIT.test(key) && asOf.some((word) => key.includes(keyOf(word)));
67
+ };
68
+
69
+ const CURRENCY_CODE = /\b[A-Z]{3}\b/u;
70
+
71
+ /** A note that names a scale or a currency besides the date ((thousand yen, as of June 30, 2026)) still names a unit. */
72
+ const namesUnitToo = (note: string, words: BalanceWords): boolean =>
73
+ CURRENCY_CODE.test(note.normalize("NFKC")) || words.magnitudes.some((word) => keyOf(note).includes(keyOf(word)));
74
+
75
+ /** The note of a label that may give its amount a unit: a note that only dates it gives none, so dated and undated rows compare. */
76
+ const unitNoteOf = (label: string, words: BalanceWords): string => {
77
+ const note = noteOf(label);
78
+ return isDatedNote(note, words.asOf ?? []) && !namesUnitToo(note, words) ? "" : note;
79
+ };
80
+
81
+ /** A column heading that is a word with no digit or bracket (ポイント, Points): the unit of the bare numbers under it. */
82
+ const UNIT_HEADING = /^[^\d()[\]()]+$/u;
83
+ const BARE_MARKS = "||";
84
+
85
+ /**
86
+ * A bare number under a heading naming its unit compares with the cells that write the unit out (14,310 under ポイント,
87
+ * against 4,960ポイント). Its marks stay empty, so it is still read as possibly rounded.
88
+ */
89
+ export const withHeadingUnit = (amount: NetAmount, heading: string | undefined): NetAmount => {
90
+ const unit = keyOf(heading ?? "");
91
+ if (amount.prefix + amount.suffix !== "" || !UNIT_HEADING.test(unit)) return amount;
92
+ return { ...amount, unit: `|${unit}|${amount.unit.slice(BARE_MARKS.length)}` };
93
+ };
94
+
95
+ const labelledCells = (labels: readonly NetCell[], values: readonly NetCell[], words: BalanceWords, heading?: string): BalanceAmount[] =>
58
96
  labels.flatMap((label, column) => {
59
97
  const role = balanceRoleOf(label.text, words);
60
98
  const value = values[column];
61
- const amount = role === undefined || value === undefined ? undefined : amountOf(value.text, value.start, noteOf(label.text), words);
62
- return role === undefined || amount === undefined ? [] : [{ ...amount, role }];
99
+ const amount = role === undefined || value === undefined ? undefined : amountOf(value.text, value.start, unitNoteOf(label.text, words), words);
100
+ return role === undefined || amount === undefined ? [] : [{ ...withHeadingUnit(amount, heading), role }];
63
101
  });
64
102
 
65
103
  const issuesOf = (scopes: readonly (readonly BalanceAmount[])[], words: BalanceWords): NetIssue[] =>
@@ -75,14 +113,14 @@ const byColumns = (header: readonly NetCell[], rows: readonly (readonly NetCell[
75
113
  words,
76
114
  );
77
115
 
78
- /** A table whose rows are labelled in their first cell: each column after the labels is one scope. */
79
- const byRows = (rows: readonly (readonly NetCell[])[], words: BalanceWords): NetIssue[] => {
116
+ /** A table whose rows are labelled in their first cell: each column after the labels is one scope, under its heading. */
117
+ const byRows = (header: readonly NetCell[], rows: readonly (readonly NetCell[])[], words: BalanceWords): NetIssue[] => {
80
118
  const labelled = rows.filter((row) => row[0] !== undefined && balanceRoleOf(row[0].text, words) !== undefined);
81
119
  const width = labelled[0]?.length ?? 0;
82
120
  if (labelled.length === 0 || labelled.some((row) => row.length !== width)) return [];
83
121
  const columns = Array.from({ length: width - 1 }, (_, index) => index + 1);
84
122
  return issuesOf(
85
- columns.map((column) => labelled.flatMap((row) => labelledCells(row.slice(0, 1), row.slice(column, column + 1), words))),
123
+ columns.map((column) => labelled.flatMap((row) => labelledCells(row.slice(0, 1), row.slice(column, column + 1), words, header[column]?.text))),
86
124
  words,
87
125
  );
88
126
  };
@@ -92,7 +130,7 @@ const byRows = (rows: readonly (readonly NetCell[])[], words: BalanceWords): Net
92
130
  * four totals in its header or its first column, so it gives no scope; running-balance-mismatch reads it row by row.
93
131
  */
94
132
  export const tableClosingMismatches = (header: readonly NetCell[], rows: readonly (readonly NetCell[])[], words: BalanceWords): NetIssue[] => {
95
- const issues = [...byColumns(header, rows, words), ...byRows(rows, words)];
133
+ const issues = [...byColumns(header, rows, words), ...byRows(header, rows, words)];
96
134
  return issues.filter((issue, index) => issues.findIndex((other) => other.offset === issue.offset) === index);
97
135
  };
98
136
 
@@ -200,6 +200,27 @@ const echoesLongName = (texts: readonly BodyText[], defined: DefinedTerm, offset
200
200
  return name.endsWith(defined.term) && sharedTail(name, written) >= defined.term.length + MIN_ECHO;
201
201
  };
202
202
 
203
+ /** What may follow a word inside a name up to a definition's bracket: capitalised words (Services Agreement), or kanji and katakana (本サービス利用契約). */
204
+ const NAME_RUN = /^(?:['’]?s)?(?:\s+\p{Lu}[\p{L}\p{N}&.'’-]*)*$|^[\p{Script=Han}\p{Script=Katakana}ー・・]*$/u;
205
+
206
+ /**
207
+ * 別の語を定義する括弧の前の名前の中の現れ(This Services Agreement (this "Agreement") の Services、
208
+ * 本サービス利用契約(以下「本契約」という。)の本サービス)。文書が自分に付けた名前で、後で定義する語の使用ではない。
209
+ */
210
+ const insideDefiningName = (texts: readonly BodyText[], inline: readonly DefinedTerm[], defined: DefinedTerm, offset: number): boolean => {
211
+ const body = sentenceAt(texts, offset);
212
+ if (body === undefined) return false;
213
+ const from = offset + defined.term.length;
214
+ const bodyEnd = body.start + body.text.length;
215
+ return inline.some(
216
+ (other) =>
217
+ other.term !== defined.term &&
218
+ other.span.start >= from &&
219
+ other.span.start <= bodyEnd &&
220
+ NAME_RUN.test(nameBefore(body.text.slice(from - body.start, other.span.start - body.start))),
221
+ );
222
+ };
223
+
203
224
  const CAPITAL_OR_NON_LATIN = /\p{Lu}|[^\p{Script=Latin}\p{N}\s\p{P}]/u;
204
225
 
205
226
  /**
@@ -226,6 +247,7 @@ export const usesBeforeDefinition = (
226
247
  groupsOf(terms)
227
248
  .filter((group) => group.first.inline && isDistinctive(group.first.term))
228
249
  .flatMap(({ first: defined, spans }) => {
250
+ const inline = terms.filter((term) => term.inline);
229
251
  const defining = sentenceAt(texts, defined.span.start)?.start ?? defined.span.start;
230
252
  const uses = usesOf(defined.term, texts, spans, "exact", defined.bare === true ? mentioned : NEVER_MENTIONED);
231
253
  const first = uses.find(
@@ -234,7 +256,8 @@ export const usesBeforeDefinition = (
234
256
  !isTitle(offset) &&
235
257
  !continuesInHan(texts, defined.term, offset) &&
236
258
  !insideKatakanaName(texts, defined.term, offset) &&
237
- !echoesLongName(texts, defined, offset),
259
+ !echoesLongName(texts, defined, offset) &&
260
+ !insideDefiningName(texts, inline, defined, offset),
238
261
  );
239
262
  return first !== undefined && first < defining ? [{ term: defined, offset: first }] : [];
240
263
  });
@@ -0,0 +1,46 @@
1
+ import { linesOf } from "./lines.ts";
2
+ import { withoutEdgeMarks } from "../facts/trim-marks.ts";
3
+ import { leadingText } from "./total.ts";
4
+
5
+ /**
6
+ * Whether a document says what kind of document it is with one of the words: its title contains one (会議室ご利用御見積書,
7
+ * Meeting Room Quote), or a field's label is one (見積番号:MR-0733, Quote number: Q-1). The title is the first line with
8
+ * text after any front matter, and the first heading; a table row is neither. A label only starting with a word does not
9
+ * count (Withholding tax: on an invoice), nor does a word in a sentence. Width and case are ignored. Pure.
10
+ */
11
+
12
+ const LABEL_SEPARATOR = /[::|]/u;
13
+ const HEADING = /^[ \t]*#+[ \t]*/u;
14
+ const TABLE_ROW = /^[ \t]*\|/u;
15
+ const FRONT_MATTER_FENCE = /^---[ \t]*$/u;
16
+
17
+ const keyOf = (text: string): string => text.normalize("NFKC").toLowerCase().replace(/\s+/gu, " ").trim();
18
+
19
+ /** The lines after a front matter block (--- … --- at the very top); all of them when there is none. */
20
+ const afterFrontMatter = (lines: readonly string[]): readonly string[] => {
21
+ if (!FRONT_MATTER_FENCE.test(lines[0] ?? "")) return lines;
22
+ const close = lines.findIndex((line, index) => index > 0 && FRONT_MATTER_FENCE.test(line));
23
+ return close === -1 ? lines : lines.slice(close + 1);
24
+ };
25
+
26
+ const titlesOf = (lines: readonly string[]): string[] => {
27
+ const first = lines.find((line) => line.trim().length > 0 && !TABLE_ROW.test(line));
28
+ const heading = lines.find((line) => HEADING.test(line));
29
+ return [first, heading].flatMap((line) => (line === undefined ? [] : [keyOf(line.replace(HEADING, ""))]));
30
+ };
31
+
32
+ const labelOf = (line: string): string | undefined => {
33
+ const lead = leadingText(line);
34
+ const separator = LABEL_SEPARATOR.exec(lead);
35
+ return separator === null ? undefined : keyOf(withoutEdgeMarks(lead.slice(0, separator.index)));
36
+ };
37
+
38
+ export const namesDocumentKind = (source: string, words: readonly string[]): boolean => {
39
+ const keys = words.map(keyOf);
40
+ const lines = afterFrontMatter(linesOf(source).map((line) => line.text));
41
+ if (titlesOf(lines).some((title) => keys.some((key) => title.includes(key)))) return true;
42
+ return lines.some((line) => {
43
+ const label = labelOf(line);
44
+ return label !== undefined && keys.includes(label);
45
+ });
46
+ };
@@ -0,0 +1,28 @@
1
+ import { codeFences } from "../detectors/code-fences.ts";
2
+
3
+ const FENCE_MARK = /`{3}|~{3}/u;
4
+
5
+ const NONE: ReadonlySet<number> = new Set();
6
+
7
+ /** The index of the line holding an offset of the joined lines. */
8
+ const lineIndexAt = (starts: readonly number[], offset: number): number => starts.findLastIndex((start) => start <= offset);
9
+
10
+ /**
11
+ * The indexes of the lines of fenced code blocks (``` or ~~~), the fences included. A table or a list written in one is an
12
+ * example, not the document's own values. The blocks are the ones the Markdown parser reads (code-fences.ts): a fence in
13
+ * a list item is one, a line indented as code or a fence in a quotation is not, and an unclosed fence runs to the end.
14
+ */
15
+ export const fencedLineIndexes = (texts: readonly string[]): ReadonlySet<number> => {
16
+ if (!texts.some((text) => FENCE_MARK.test(text))) return NONE;
17
+ const starts: number[] = [];
18
+ texts.reduce((start, text) => {
19
+ starts.push(start);
20
+ return start + text.length + 1;
21
+ }, 0);
22
+ const fenced = new Set<number>();
23
+ codeFences(texts.join("\n")).forEach((fence) => {
24
+ const last = lineIndexAt(starts, Math.max(fence.start, fence.end - 1));
25
+ for (let index = lineIndexAt(starts, fence.start); index <= last; index += 1) fenced.add(index);
26
+ });
27
+ return fenced;
28
+ };