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.
- package/dist/api.d.ts +7 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js.map +1 -1
- package/dist/cli-text.d.ts.map +1 -1
- package/dist/cli-text.js +4 -2
- package/dist/cli-text.js.map +1 -1
- package/dist/commands/grade.d.ts.map +1 -1
- package/dist/commands/grade.js +66 -21
- package/dist/commands/grade.js.map +1 -1
- package/dist/company-kana-spelling.d.ts +6 -1
- package/dist/company-kana-spelling.d.ts.map +1 -1
- package/dist/company-kana-spelling.js +19 -2
- package/dist/company-kana-spelling.js.map +1 -1
- package/dist/company-name-ending.d.ts +7 -0
- package/dist/company-name-ending.d.ts.map +1 -0
- package/dist/company-name-ending.js +44 -0
- package/dist/company-name-ending.js.map +1 -0
- package/dist/company-names.d.ts +11 -3
- package/dist/company-names.d.ts.map +1 -1
- package/dist/company-names.js +27 -14
- package/dist/company-names.js.map +1 -1
- package/dist/custom/no-rewrite.d.ts +3 -0
- package/dist/custom/no-rewrite.d.ts.map +1 -0
- package/dist/custom/no-rewrite.js +3 -0
- package/dist/custom/no-rewrite.js.map +1 -0
- package/dist/custom/parse.d.ts.map +1 -1
- package/dist/custom/parse.js +14 -2
- package/dist/custom/parse.js.map +1 -1
- package/dist/detectors/age-words.d.ts +5 -0
- package/dist/detectors/age-words.d.ts.map +1 -0
- package/dist/detectors/age-words.js +8 -0
- package/dist/detectors/age-words.js.map +1 -0
- package/dist/detectors/annual-pay-mismatch.d.ts.map +1 -1
- package/dist/detectors/annual-pay-mismatch.js +2 -12
- package/dist/detectors/annual-pay-mismatch.js.map +1 -1
- package/dist/detectors/blank-structure.d.ts.map +1 -1
- package/dist/detectors/blank-structure.js +4 -3
- package/dist/detectors/blank-structure.js.map +1 -1
- package/dist/detectors/closing-balance-mismatch.d.ts.map +1 -1
- package/dist/detectors/closing-balance-mismatch.js +1 -0
- package/dist/detectors/closing-balance-mismatch.js.map +1 -1
- package/dist/detectors/code-fences.d.ts +5 -0
- package/dist/detectors/code-fences.d.ts.map +1 -1
- package/dist/detectors/code-fences.js +9 -0
- package/dist/detectors/code-fences.js.map +1 -1
- package/dist/detectors/condition-toki.d.ts +16 -0
- package/dist/detectors/condition-toki.d.ts.map +1 -0
- package/dist/detectors/condition-toki.js +42 -0
- package/dist/detectors/condition-toki.js.map +1 -0
- package/dist/detectors/conjugated-entry.d.ts +17 -0
- package/dist/detectors/conjugated-entry.d.ts.map +1 -0
- package/dist/detectors/conjugated-entry.js +50 -0
- package/dist/detectors/conjugated-entry.js.map +1 -0
- package/dist/detectors/content-morphemes.d.ts +2 -0
- package/dist/detectors/content-morphemes.d.ts.map +1 -1
- package/dist/detectors/content-morphemes.js +1 -1
- package/dist/detectors/content-morphemes.js.map +1 -1
- package/dist/detectors/cross-facts.d.ts.map +1 -1
- package/dist/detectors/cross-facts.js +2 -1
- package/dist/detectors/cross-facts.js.map +1 -1
- package/dist/detectors/custom.d.ts.map +1 -1
- package/dist/detectors/custom.js +8 -2
- package/dist/detectors/custom.js.map +1 -1
- package/dist/detectors/document-shape.d.ts.map +1 -1
- package/dist/detectors/document-shape.js +6 -0
- package/dist/detectors/document-shape.js.map +1 -1
- package/dist/detectors/due-date.d.ts.map +1 -1
- package/dist/detectors/due-date.js +12 -2
- package/dist/detectors/due-date.js.map +1 -1
- package/dist/detectors/fact-consistency.d.ts.map +1 -1
- package/dist/detectors/fact-consistency.js +12 -13
- package/dist/detectors/fact-consistency.js.map +1 -1
- package/dist/detectors/form-line.d.ts +9 -0
- package/dist/detectors/form-line.d.ts.map +1 -0
- package/dist/detectors/form-line.js +36 -0
- package/dist/detectors/form-line.js.map +1 -0
- package/dist/detectors/formula-amount.d.ts.map +1 -1
- package/dist/detectors/formula-amount.js +2 -12
- package/dist/detectors/formula-amount.js.map +1 -1
- package/dist/detectors/heading-claim.d.ts +14 -0
- package/dist/detectors/heading-claim.d.ts.map +1 -0
- package/dist/detectors/heading-claim.js +49 -0
- package/dist/detectors/heading-claim.js.map +1 -0
- package/dist/detectors/heading-echo.d.ts.map +1 -1
- package/dist/detectors/heading-echo.js +29 -1
- package/dist/detectors/heading-echo.js.map +1 -1
- package/dist/detectors/installment-total-mismatch.d.ts.map +1 -1
- package/dist/detectors/installment-total-mismatch.js +2 -12
- package/dist/detectors/installment-total-mismatch.js.map +1 -1
- package/dist/detectors/list-parallel.d.ts +5 -2
- package/dist/detectors/list-parallel.d.ts.map +1 -1
- package/dist/detectors/list-parallel.js +45 -7
- package/dist/detectors/list-parallel.js.map +1 -1
- package/dist/detectors/name-variant.d.ts.map +1 -1
- package/dist/detectors/name-variant.js +34 -7
- package/dist/detectors/name-variant.js.map +1 -1
- package/dist/detectors/number-unit.d.ts +10 -0
- package/dist/detectors/number-unit.d.ts.map +1 -1
- package/dist/detectors/number-unit.js +28 -8
- package/dist/detectors/number-unit.js.map +1 -1
- package/dist/detectors/orthography.d.ts.map +1 -1
- package/dist/detectors/orthography.js +16 -7
- package/dist/detectors/orthography.js.map +1 -1
- package/dist/detectors/per-unit-words.d.ts +7 -0
- package/dist/detectors/per-unit-words.d.ts.map +1 -0
- package/dist/detectors/per-unit-words.js +17 -0
- package/dist/detectors/per-unit-words.js.map +1 -0
- package/dist/detectors/period-multiple-mismatch.d.ts.map +1 -1
- package/dist/detectors/period-multiple-mismatch.js +2 -12
- package/dist/detectors/period-multiple-mismatch.js.map +1 -1
- package/dist/detectors/reference-number-variant.d.ts.map +1 -1
- package/dist/detectors/reference-number-variant.js +21 -3
- package/dist/detectors/reference-number-variant.js.map +1 -1
- package/dist/detectors/registry/condition-toki.d.ts +3 -0
- package/dist/detectors/registry/condition-toki.d.ts.map +1 -0
- package/dist/detectors/registry/condition-toki.js +3 -0
- package/dist/detectors/registry/condition-toki.js.map +1 -0
- package/dist/detectors/rent-multiple-mismatch.d.ts.map +1 -1
- package/dist/detectors/rent-multiple-mismatch.js +2 -12
- package/dist/detectors/rent-multiple-mismatch.js.map +1 -1
- package/dist/detectors/row-sentence-amounts.d.ts.map +1 -1
- package/dist/detectors/row-sentence-amounts.js +5 -10
- package/dist/detectors/row-sentence-amounts.js.map +1 -1
- package/dist/detectors/stated-amounts.d.ts +4 -0
- package/dist/detectors/stated-amounts.d.ts.map +1 -0
- package/dist/detectors/stated-amounts.js +12 -0
- package/dist/detectors/stated-amounts.js.map +1 -0
- package/dist/detectors/step-time-sum.d.ts.map +1 -1
- package/dist/detectors/step-time-sum.js +2 -1
- package/dist/detectors/step-time-sum.js.map +1 -1
- package/dist/detectors/structure-tree.d.ts.map +1 -1
- package/dist/detectors/structure-tree.js +3 -0
- package/dist/detectors/structure-tree.js.map +1 -1
- package/dist/detectors/team.d.ts.map +1 -1
- package/dist/detectors/team.js +10 -6
- package/dist/detectors/team.js.map +1 -1
- package/dist/detectors/time-order.d.ts.map +1 -1
- package/dist/detectors/time-order.js +17 -13
- package/dist/detectors/time-order.js.map +1 -1
- package/dist/detectors/unknown-word.d.ts +4 -1
- package/dist/detectors/unknown-word.d.ts.map +1 -1
- package/dist/detectors/unknown-word.js +18 -2
- package/dist/detectors/unknown-word.js.map +1 -1
- package/dist/detectors/use-interval.d.ts +1 -0
- package/dist/detectors/use-interval.d.ts.map +1 -1
- package/dist/detectors/use-interval.js +15 -10
- package/dist/detectors/use-interval.js.map +1 -1
- package/dist/detectors/use-maximum.d.ts.map +1 -1
- package/dist/detectors/use-maximum.js +3 -1
- package/dist/detectors/use-maximum.js.map +1 -1
- package/dist/extension/document-view.d.ts +2 -2
- package/dist/extension/document-view.d.ts.map +1 -1
- package/dist/extension/document-view.js +17 -8
- package/dist/extension/document-view.js.map +1 -1
- package/dist/extension/entry-genres.d.ts +5 -0
- package/dist/extension/entry-genres.d.ts.map +1 -0
- package/dist/extension/entry-genres.js +13 -0
- package/dist/extension/entry-genres.js.map +1 -0
- package/dist/extension/module-detector.d.ts.map +1 -1
- package/dist/extension/module-detector.js +4 -4
- package/dist/extension/module-detector.js.map +1 -1
- package/dist/extension/plugin-parse.d.ts +1 -1
- package/dist/extension/plugin-parse.d.ts.map +1 -1
- package/dist/extension/plugin-parse.js +24 -7
- package/dist/extension/plugin-parse.js.map +1 -1
- package/dist/extension/problem-text.d.ts.map +1 -1
- package/dist/extension/problem-text.js +2 -0
- package/dist/extension/problem-text.js.map +1 -1
- package/dist/facts/age-values.d.ts.map +1 -1
- package/dist/facts/age-values.js +20 -2
- package/dist/facts/age-values.js.map +1 -1
- package/dist/facts/column-units.d.ts +31 -0
- package/dist/facts/column-units.d.ts.map +1 -0
- package/dist/facts/column-units.js +37 -0
- package/dist/facts/column-units.js.map +1 -0
- package/dist/facts/fact-values.d.ts +4 -0
- package/dist/facts/fact-values.d.ts.map +1 -1
- package/dist/facts/fact-values.js +3 -1
- package/dist/facts/fact-values.js.map +1 -1
- package/dist/facts/labelled-facts.d.ts +10 -0
- package/dist/facts/labelled-facts.d.ts.map +1 -1
- package/dist/facts/labelled-facts.js +6 -1
- package/dist/facts/labelled-facts.js.map +1 -1
- package/dist/facts/per-unit.d.ts +36 -0
- package/dist/facts/per-unit.d.ts.map +1 -0
- package/dist/facts/per-unit.js +56 -0
- package/dist/facts/per-unit.js.map +1 -0
- package/dist/facts/row-sentence-amounts.d.ts +3 -12
- package/dist/facts/row-sentence-amounts.d.ts.map +1 -1
- package/dist/facts/row-sentence-amounts.js +1 -15
- package/dist/facts/row-sentence-amounts.js.map +1 -1
- package/dist/facts/table-facts.d.ts +1 -1
- package/dist/facts/table-facts.d.ts.map +1 -1
- package/dist/facts/table-facts.js +13 -9
- package/dist/facts/table-facts.js.map +1 -1
- package/dist/facts/term-values.d.ts +13 -1
- package/dist/facts/term-values.d.ts.map +1 -1
- package/dist/facts/term-values.js +71 -12
- package/dist/facts/term-values.js.map +1 -1
- package/dist/grade/baseline.d.ts +4 -2
- package/dist/grade/baseline.d.ts.map +1 -1
- package/dist/grade/baseline.js +13 -11
- package/dist/grade/baseline.js.map +1 -1
- package/dist/grade/expect-read.d.ts +16 -0
- package/dist/grade/expect-read.d.ts.map +1 -0
- package/dist/grade/expect-read.js +52 -0
- package/dist/grade/expect-read.js.map +1 -0
- package/dist/grade/expect.d.ts +50 -0
- package/dist/grade/expect.d.ts.map +1 -0
- package/dist/grade/expect.js +53 -0
- package/dist/grade/expect.js.map +1 -0
- package/dist/grade/grade-item.d.ts +1 -1
- package/dist/grade/grade-item.d.ts.map +1 -1
- package/dist/grade/grade-item.js +13 -3
- package/dist/grade/grade-item.js.map +1 -1
- package/dist/grade/item.d.ts +9 -1
- package/dist/grade/item.d.ts.map +1 -1
- package/dist/grade/item.js +32 -7
- package/dist/grade/item.js.map +1 -1
- package/dist/grade/result.d.ts +5 -0
- package/dist/grade/result.d.ts.map +1 -1
- package/dist/grade/results-read.d.ts.map +1 -1
- package/dist/grade/results-read.js +4 -2
- package/dist/grade/results-read.js.map +1 -1
- package/dist/grade/rubric-file.d.ts +23 -0
- package/dist/grade/rubric-file.d.ts.map +1 -0
- package/dist/grade/rubric-file.js +35 -0
- package/dist/grade/rubric-file.js.map +1 -0
- package/dist/grade/rubric.d.ts +2 -0
- package/dist/grade/rubric.d.ts.map +1 -1
- package/dist/grade/rubric.js +75 -47
- package/dist/grade/rubric.js.map +1 -1
- package/dist/grade/setup.d.ts +2 -1
- package/dist/grade/setup.d.ts.map +1 -1
- package/dist/grade/setup.js +2 -2
- package/dist/grade/setup.js.map +1 -1
- package/dist/grade/stamp.d.ts +2 -0
- package/dist/grade/stamp.d.ts.map +1 -1
- package/dist/grade/stamp.js +8 -2
- package/dist/grade/stamp.js.map +1 -1
- package/dist/grade/task-rubric.d.ts +16 -0
- package/dist/grade/task-rubric.d.ts.map +1 -0
- package/dist/grade/task-rubric.js +34 -0
- package/dist/grade/task-rubric.js.map +1 -0
- package/dist/grade/text.d.ts +5 -1
- package/dist/grade/text.d.ts.map +1 -1
- package/dist/grade/text.js +16 -4
- package/dist/grade/text.js.map +1 -1
- package/dist/name-char-reading.d.ts +5 -0
- package/dist/name-char-reading.d.ts.map +1 -1
- package/dist/name-char-reading.js +15 -0
- package/dist/name-char-reading.js.map +1 -1
- package/dist/name-end-letter.d.ts +19 -0
- package/dist/name-end-letter.d.ts.map +1 -0
- package/dist/name-end-letter.js +37 -0
- package/dist/name-end-letter.js.map +1 -0
- package/dist/name-neighbours.d.ts.map +1 -1
- package/dist/name-neighbours.js +2 -1
- package/dist/name-neighbours.js.map +1 -1
- package/dist/name-separator.d.ts +6 -0
- package/dist/name-separator.d.ts.map +1 -1
- package/dist/name-separator.js +29 -0
- package/dist/name-separator.js.map +1 -1
- package/dist/name-variants.d.ts +6 -3
- package/dist/name-variants.d.ts.map +1 -1
- package/dist/name-variants.js +37 -10
- package/dist/name-variants.js.map +1 -1
- package/dist/number-name.d.ts.map +1 -1
- package/dist/number-name.js +11 -2
- package/dist/number-name.js.map +1 -1
- package/dist/profile/cue-lines.d.ts +6 -0
- package/dist/profile/cue-lines.d.ts.map +1 -0
- package/dist/profile/cue-lines.js +13 -0
- package/dist/profile/cue-lines.js.map +1 -0
- package/dist/profile/parse.d.ts +1 -1
- package/dist/profile/parse.d.ts.map +1 -1
- package/dist/profile/parse.js +3 -2
- package/dist/profile/parse.js.map +1 -1
- package/dist/reference-numbers.d.ts +2 -0
- package/dist/reference-numbers.d.ts.map +1 -1
- package/dist/reference-numbers.js +1 -1
- package/dist/reference-numbers.js.map +1 -1
- package/dist/spaced-person-name.d.ts.map +1 -1
- package/dist/spaced-person-name.js +26 -1
- package/dist/spaced-person-name.js.map +1 -1
- package/dist/spacing-code.d.ts.map +1 -1
- package/dist/spacing-code.js +10 -1
- package/dist/spacing-code.js.map +1 -1
- package/dist/spacing-minority.d.ts +7 -0
- package/dist/spacing-minority.d.ts.map +1 -1
- package/dist/spacing-minority.js +14 -0
- package/dist/spacing-minority.js.map +1 -1
- package/dist/structure/closing-balance.d.ts +9 -0
- package/dist/structure/closing-balance.d.ts.map +1 -1
- package/dist/structure/closing-balance.js +34 -7
- package/dist/structure/closing-balance.js.map +1 -1
- package/dist/structure/definition-use.d.ts.map +1 -1
- package/dist/structure/definition-use.js +20 -1
- package/dist/structure/definition-use.js.map +1 -1
- package/dist/structure/document-context.d.ts +2 -0
- package/dist/structure/document-context.d.ts.map +1 -0
- package/dist/structure/document-context.js +42 -0
- package/dist/structure/document-context.js.map +1 -0
- package/dist/structure/fenced-lines.d.ts +7 -0
- package/dist/structure/fenced-lines.d.ts.map +1 -0
- package/dist/structure/fenced-lines.js +27 -0
- package/dist/structure/fenced-lines.js.map +1 -0
- package/dist/structure/labelled-date-order.d.ts +31 -9
- package/dist/structure/labelled-date-order.d.ts.map +1 -1
- package/dist/structure/labelled-date-order.js +60 -15
- package/dist/structure/labelled-date-order.js.map +1 -1
- package/dist/structure/labelled-order.d.ts +7 -1
- package/dist/structure/labelled-order.d.ts.map +1 -1
- package/dist/structure/labelled-order.js +10 -3
- package/dist/structure/labelled-order.js.map +1 -1
- package/dist/structure/line-amount-label.d.ts.map +1 -1
- package/dist/structure/line-amount-label.js +2 -1
- package/dist/structure/line-amount-label.js.map +1 -1
- package/dist/structure/line-amount.d.ts +0 -5
- package/dist/structure/line-amount.d.ts.map +1 -1
- package/dist/structure/line-amount.js +1 -10
- package/dist/structure/line-amount.js.map +1 -1
- package/dist/structure/prose-total.d.ts +8 -0
- package/dist/structure/prose-total.d.ts.map +1 -1
- package/dist/structure/prose-total.js +70 -2
- package/dist/structure/prose-total.js.map +1 -1
- package/dist/structure/runs.d.ts.map +1 -1
- package/dist/structure/runs.js +9 -1
- package/dist/structure/runs.js.map +1 -1
- package/dist/structure/signed-date-line.d.ts +15 -0
- package/dist/structure/signed-date-line.d.ts.map +1 -0
- package/dist/structure/signed-date-line.js +68 -0
- package/dist/structure/signed-date-line.js.map +1 -0
- package/dist/structure/tax.d.ts.map +1 -1
- package/dist/structure/tax.js +5 -3
- package/dist/structure/tax.js.map +1 -1
- package/dist/structure/total-line.d.ts.map +1 -1
- package/dist/structure/total-line.js +10 -6
- package/dist/structure/total-line.js.map +1 -1
- package/dist/structure/total.d.ts +2 -0
- package/dist/structure/total.d.ts.map +1 -1
- package/dist/structure/total.js +34 -3
- package/dist/structure/total.js.map +1 -1
- package/dist/subject-codes.d.ts +21 -0
- package/dist/subject-codes.d.ts.map +1 -0
- package/dist/subject-codes.js +79 -0
- package/dist/subject-codes.js.map +1 -0
- package/dist/unlabelled-references.d.ts +6 -2
- package/dist/unlabelled-references.d.ts.map +1 -1
- package/dist/unlabelled-references.js +11 -10
- package/dist/unlabelled-references.js.map +1 -1
- package/genres.yaml +17 -0
- package/package.json +3 -3
- package/profiles/listing.yaml +1 -1
- package/rules/closing-balance-mismatch.yaml +3 -3
- package/rules/due-before-issue.yaml +9 -5
- package/rules/fact-conflict.yaml +2 -2
- package/rules/heading-echo.yaml +2 -2
- package/rules/internal-jargon.yaml +2 -2
- package/rules/latin-spacing.yaml +4 -4
- package/rules/list-item-form-mix.yaml +1 -1
- package/rules/name-variant.yaml +5 -2
- package/rules/no-lead.yaml +2 -2
- package/rules/number-without-unit.yaml +2 -2
- package/rules/reference-number-variant.yaml +3 -3
- package/rules/toki-as-condition.yaml +71 -0
- package/rules/total-mismatch.yaml +4 -4
- package/rules/unknown-word.yaml +2 -2
- package/rules/use-before-definition.yaml +2 -2
- package/src/api.ts +11 -2
- package/src/cli-text.ts +4 -2
- package/src/commands/grade.ts +75 -23
- package/src/company-kana-spelling.ts +22 -2
- package/src/company-name-ending.ts +50 -0
- package/src/company-names.ts +52 -17
- package/src/custom/no-rewrite.ts +2 -0
- package/src/custom/parse.ts +18 -2
- package/src/detectors/age-words.ts +12 -0
- package/src/detectors/annual-pay-mismatch.ts +3 -18
- package/src/detectors/blank-structure.ts +4 -3
- package/src/detectors/closing-balance-mismatch.ts +1 -0
- package/src/detectors/code-fences.ts +10 -0
- package/src/detectors/condition-toki.ts +60 -0
- package/src/detectors/conjugated-entry.ts +53 -0
- package/src/detectors/content-morphemes.ts +2 -1
- package/src/detectors/cross-facts.ts +2 -1
- package/src/detectors/custom.ts +9 -2
- package/src/detectors/document-shape.ts +8 -0
- package/src/detectors/due-date.ts +17 -7
- package/src/detectors/fact-consistency.ts +13 -17
- package/src/detectors/form-line.ts +47 -0
- package/src/detectors/formula-amount.ts +2 -18
- package/src/detectors/heading-claim.ts +76 -0
- package/src/detectors/heading-echo.ts +32 -1
- package/src/detectors/installment-total-mismatch.ts +3 -18
- package/src/detectors/list-parallel.ts +46 -7
- package/src/detectors/name-variant.ts +45 -8
- package/src/detectors/number-unit.ts +35 -9
- package/src/detectors/orthography.ts +17 -8
- package/src/detectors/per-unit-words.ts +24 -0
- package/src/detectors/period-multiple-mismatch.ts +2 -18
- package/src/detectors/reference-number-variant.ts +27 -5
- package/src/detectors/registry/condition-toki.ts +4 -0
- package/src/detectors/rent-multiple-mismatch.ts +2 -18
- package/src/detectors/row-sentence-amounts.ts +5 -20
- package/src/detectors/stated-amounts.ts +20 -0
- package/src/detectors/step-time-sum.ts +2 -1
- package/src/detectors/structure-tree.ts +3 -0
- package/src/detectors/team.ts +13 -7
- package/src/detectors/time-order.ts +6 -3
- package/src/detectors/unknown-word.ts +20 -2
- package/src/detectors/use-interval.ts +8 -4
- package/src/detectors/use-maximum.ts +3 -1
- package/src/extension/document-view.ts +18 -9
- package/src/extension/entry-genres.ts +15 -0
- package/src/extension/module-detector.ts +9 -4
- package/src/extension/plugin-parse.ts +29 -7
- package/src/extension/problem-text.ts +4 -0
- package/src/facts/age-values.ts +24 -2
- package/src/facts/column-units.ts +67 -0
- package/src/facts/fact-values.ts +11 -1
- package/src/facts/labelled-facts.ts +16 -2
- package/src/facts/per-unit.ts +105 -0
- package/src/facts/row-sentence-amounts.ts +4 -26
- package/src/facts/table-facts.ts +7 -4
- package/src/facts/term-values.ts +94 -13
- package/src/grade/baseline.ts +20 -12
- package/src/grade/expect-read.ts +65 -0
- package/src/grade/expect.ts +74 -0
- package/src/grade/grade-item.ts +14 -3
- package/src/grade/item.ts +48 -8
- package/src/grade/result.ts +5 -0
- package/src/grade/results-read.ts +7 -2
- package/src/grade/rubric-file.ts +46 -0
- package/src/grade/rubric.ts +77 -47
- package/src/grade/setup.ts +3 -3
- package/src/grade/stamp.ts +10 -2
- package/src/grade/task-rubric.ts +39 -0
- package/src/grade/text.ts +27 -5
- package/src/name-char-reading.ts +16 -0
- package/src/name-end-letter.ts +52 -0
- package/src/name-neighbours.ts +2 -1
- package/src/name-separator.ts +32 -0
- package/src/name-variants.ts +54 -13
- package/src/number-name.ts +12 -3
- package/src/profile/cue-lines.ts +14 -0
- package/src/profile/parse.ts +3 -2
- package/src/reference-numbers.ts +1 -1
- package/src/spaced-person-name.ts +27 -2
- package/src/spacing-code.ts +13 -2
- package/src/spacing-minority.ts +17 -0
- package/src/structure/closing-balance.ts +46 -8
- package/src/structure/definition-use.ts +24 -1
- package/src/structure/document-context.ts +46 -0
- package/src/structure/fenced-lines.ts +28 -0
- package/src/structure/labelled-date-order.ts +105 -21
- package/src/structure/labelled-order.ts +21 -10
- package/src/structure/line-amount-label.ts +2 -1
- package/src/structure/line-amount.ts +1 -11
- package/src/structure/prose-total.ts +81 -2
- package/src/structure/runs.ts +10 -1
- package/src/structure/signed-date-line.ts +78 -0
- package/src/structure/tax.ts +6 -3
- package/src/structure/total-line.ts +12 -6
- package/src/structure/total.ts +42 -3
- package/src/subject-codes.ts +105 -0
- package/src/unlabelled-references.ts +24 -16
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { namesDocumentKind } from "./document-context.ts";
|
|
1
2
|
import type { DatedPoint } from "./due-date.ts";
|
|
2
3
|
import type { StructureIssue } from "./issues.ts";
|
|
3
4
|
import type { Line } from "./lines.ts";
|
|
4
5
|
import { labelPairsOf, pointsOutOfOrder, type OrderLabel, type PairWords, type PointOf } from "./labelled-order.ts";
|
|
5
6
|
import { TABLE_ROW } from "./runs.ts";
|
|
7
|
+
import { isSignedDateLine } from "./signed-date-line.ts";
|
|
6
8
|
import { statedPeriod, type DateMention, type PeriodWords } from "./stated-period.ts";
|
|
7
9
|
|
|
8
10
|
/**
|
|
@@ -10,11 +12,13 @@ import { statedPeriod, type DateMention, type PeriodWords } from "./stated-perio
|
|
|
10
12
|
* earlier-date word and one starting with a later-date word, each with one full date on it. A later date before the
|
|
11
13
|
* first earlier date, and written near it, is out of order. A line with a passed word (期限切れ, overdue) reports on a
|
|
12
14
|
* date rather than setting one and is not read. A span word (対象期間, Reporting period) labels a period, and the
|
|
13
|
-
* period's end is its date. Pure.
|
|
15
|
+
* period's end is its date, or its start for a span word in fromStart. Pure.
|
|
14
16
|
*/
|
|
15
17
|
export type DateOrderWords = PairWords & {
|
|
16
18
|
/** The words whose line states a period rather than one date (labels), with the range signs and names to read it. */
|
|
17
19
|
readonly spans?: PeriodWords | undefined;
|
|
20
|
+
/** The span words whose period's start is the date: the later date may fall anywhere in the period (a pay date in its pay period). */
|
|
21
|
+
readonly fromStart?: readonly string[] | undefined;
|
|
18
22
|
};
|
|
19
23
|
|
|
20
24
|
/** A date from the tree; end is where its text ends, needed only to read a period. */
|
|
@@ -30,12 +34,14 @@ const FULL_DATE = /^\d{4}-\d{2}-\d{2}$/u;
|
|
|
30
34
|
const mentionsOf = (dates: readonly OrderDate[]): DateMention[] => dates.flatMap((date) => (date.end === undefined ? [] : [{ ...date, end: date.end }]));
|
|
31
35
|
|
|
32
36
|
/**
|
|
33
|
-
* The end of the period a span word's line states (対象期間:2026年4月1日〜9月30日
|
|
34
|
-
* A period without a year, or a line that states no period, gives undefined. tableOf gives
|
|
35
|
-
* (| 対象期間 | 2026年4月1日〜9月30日 |), read when the row is the table's only row labelled
|
|
36
|
-
* both Reporting period and Period covered rows lists periods. A row's label cell is the
|
|
37
|
+
* The start or the end of the period a span word's line states (対象期間:2026年4月1日〜9月30日 starts 2026-04-01 and
|
|
38
|
+
* ends 2026-09-30), with its year. A period without a year, or a line that states no period, gives undefined. tableOf gives
|
|
39
|
+
* the table the line is a row of (| 対象期間 | 2026年4月1日〜9月30日 |), read when the row is the table's only row labelled
|
|
40
|
+
* with any span word: a table with both Reporting period and Period covered rows lists periods. A row's label cell is the
|
|
41
|
+
* span word itself, so all of them are passed.
|
|
37
42
|
*/
|
|
38
|
-
|
|
43
|
+
const periodBoundOf = (
|
|
44
|
+
bound: "start" | "end",
|
|
39
45
|
line: Line,
|
|
40
46
|
label: string,
|
|
41
47
|
dates: readonly OrderDate[],
|
|
@@ -44,24 +50,54 @@ export const periodEndOf = (
|
|
|
44
50
|
): DatedPoint | undefined => {
|
|
45
51
|
const labels = TABLE_ROW.test(line.text) ? spans.labels : [label];
|
|
46
52
|
const period = statedPeriod(line.text, line.start, mentionsOf(dates), { ...spans, labels }, tableOf);
|
|
47
|
-
return period === undefined || !FULL_DATE.test(period
|
|
53
|
+
return period === undefined || !FULL_DATE.test(period[bound]) ? undefined : { offset: line.start, value: period[bound] };
|
|
48
54
|
};
|
|
49
55
|
|
|
50
|
-
|
|
56
|
+
/** The end of the period a span word's line states. */
|
|
57
|
+
export const periodEndOf = (
|
|
58
|
+
line: Line,
|
|
59
|
+
label: string,
|
|
60
|
+
dates: readonly OrderDate[],
|
|
61
|
+
spans: PeriodWords,
|
|
62
|
+
tableOf?: () => readonly string[],
|
|
63
|
+
): DatedPoint | undefined => periodBoundOf("end", line, label, dates, spans, tableOf);
|
|
64
|
+
|
|
65
|
+
/** The start of the period a span word's line states. */
|
|
66
|
+
export const periodStartOf = (
|
|
67
|
+
line: Line,
|
|
68
|
+
label: string,
|
|
69
|
+
dates: readonly OrderDate[],
|
|
70
|
+
spans: PeriodWords,
|
|
71
|
+
tableOf?: () => readonly string[],
|
|
72
|
+
): DatedPoint | undefined => periodBoundOf("start", line, label, dates, spans, tableOf);
|
|
73
|
+
|
|
74
|
+
const oneDateOf = (line: Line, dates: readonly OrderDate[]): OrderDate | undefined => {
|
|
51
75
|
const end = line.start + line.text.length;
|
|
52
76
|
const onLine = dates.filter((date) => date.offset >= line.start && date.offset <= end && FULL_DATE.test(date.value));
|
|
53
77
|
return onLine.length === 1 ? onLine[0] : undefined;
|
|
54
78
|
};
|
|
55
79
|
|
|
80
|
+
/** The one date on a signature line (2026年4月12日 会計監査 戸川 隆): the line holds that date, the role and a name, nothing else. */
|
|
81
|
+
const signedDateOf = (line: Line, role: string, dates: readonly OrderDate[]): DatedPoint | undefined => {
|
|
82
|
+
const date = oneDateOf(line, dates);
|
|
83
|
+
if (date?.end === undefined) return undefined;
|
|
84
|
+
const span = { start: date.offset - line.start, end: date.end - line.start };
|
|
85
|
+
return isSignedDateLine(line.text, role, span) ? date : undefined;
|
|
86
|
+
};
|
|
87
|
+
|
|
56
88
|
const dateOf =
|
|
57
|
-
(dates: readonly OrderDate[], spans: PeriodWords | undefined): PointOf =>
|
|
89
|
+
(dates: readonly OrderDate[], spans: PeriodWords | undefined, fromStart: readonly string[], signers: readonly string[]): PointOf =>
|
|
58
90
|
(line, label, tableOf) => {
|
|
91
|
+
if (signers.includes(label)) {
|
|
92
|
+
const date = signedDateOf(line, label, dates);
|
|
93
|
+
return date === undefined ? undefined : { point: date, span: false };
|
|
94
|
+
}
|
|
59
95
|
if (spans === undefined || !spans.labels.includes(label)) {
|
|
60
96
|
const date = oneDateOf(line, dates);
|
|
61
97
|
return date === undefined ? undefined : { point: date, span: false };
|
|
62
98
|
}
|
|
63
|
-
const
|
|
64
|
-
return
|
|
99
|
+
const bound = fromStart.includes(label) ? periodStartOf(line, label, dates, spans, tableOf) : periodEndOf(line, label, dates, spans, tableOf);
|
|
100
|
+
return bound === undefined ? undefined : { point: bound, span: true };
|
|
65
101
|
};
|
|
66
102
|
|
|
67
103
|
/**
|
|
@@ -70,18 +106,59 @@ const dateOf =
|
|
|
70
106
|
* record's. A later date written once (an issue date below the records) belongs to the whole document and keeps the first.
|
|
71
107
|
*/
|
|
72
108
|
export const datesOutOfOrder = (source: string, dates: readonly OrderDate[], words: DateOrderWords): OutOfOrder[] =>
|
|
73
|
-
pointsOutOfOrder(source, words, dateOf(dates, words.spans)).map(({ earlier, later }) => ({
|
|
109
|
+
pointsOutOfOrder(source, words, dateOf(dates, words.spans, words.fromStart ?? [], words.signers ?? [])).map(({ earlier, later }) => ({
|
|
74
110
|
earlier: { label: earlier.label, date: earlier.point, line: earlier.line, span: earlier.span },
|
|
75
111
|
later: { label: later.label, date: later.point, line: later.line, span: later.span },
|
|
76
112
|
}));
|
|
77
113
|
|
|
78
|
-
/** An out-of-order pair; period is set when the earlier date is
|
|
79
|
-
export type PairedIssue = StructureIssue & { readonly period: boolean };
|
|
114
|
+
/** An out-of-order pair; period is set when the earlier date is a stated period's end, or its start when fromStart is set. */
|
|
115
|
+
export type PairedIssue = StructureIssue & { readonly period: boolean; readonly fromStart?: boolean | undefined };
|
|
116
|
+
|
|
117
|
+
/** The span words with the range signs and names to read them, and the dated-span lexicon's entries they come from. */
|
|
118
|
+
export type PairSpans = PeriodWords & { readonly entries: readonly OrderLabel[] };
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The span words a dated-span lexicon reads by the period's start in one group: its entries naming that group with
|
|
122
|
+
* position before. An entry without a group is read by the period's end in every group.
|
|
123
|
+
*/
|
|
124
|
+
export const spanStartsOf = (spanLabels: readonly OrderLabel[], group: string): string[] =>
|
|
125
|
+
spanLabels.filter((label) => label.group === group && label.position === "before").map((label) => label.pattern);
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The span words to read periods with, the whole document (its headings too) to read the words naming its kind, and the
|
|
129
|
+
* role words whose signature line gives a group's later date (a dated-signer lexicon's entries with position after).
|
|
130
|
+
*/
|
|
131
|
+
export type PairedReading = {
|
|
132
|
+
readonly spans?: PairSpans | undefined;
|
|
133
|
+
readonly whole?: string | undefined;
|
|
134
|
+
readonly signers?: readonly OrderLabel[] | undefined;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const signersOf = (signers: readonly OrderLabel[], group: string): string[] =>
|
|
138
|
+
signers.filter((label) => label.group === group && label.position === "after").map((label) => label.pattern);
|
|
139
|
+
|
|
140
|
+
/** The words a group names its kind of document with: the group's entries without a position (見積, Quote). */
|
|
141
|
+
const contextOf = (labels: readonly OrderLabel[], group: string): string[] =>
|
|
142
|
+
labels.filter((label) => label.group === group && label.position === undefined).map((label) => label.pattern);
|
|
143
|
+
|
|
144
|
+
const sameDates = (one: PairedIssue, other: PairedIssue): boolean =>
|
|
145
|
+
one.offset === other.offset && one.values["later"] === other.values["later"] && one.values["earlier"] === other.values["earlier"];
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The issues, two groups comparing one later date with the same earlier date reported once (a leaver's issue date before
|
|
149
|
+
* both the leaving date and the end of the period covered, which is the same day). Different earlier dates are kept.
|
|
150
|
+
*/
|
|
151
|
+
const withoutRepeats = (issues: readonly PairedIssue[]): PairedIssue[] =>
|
|
152
|
+
issues.filter((issue, index) => issues.findIndex((other) => sameDates(other, issue)) === index);
|
|
80
153
|
|
|
81
154
|
/**
|
|
82
155
|
* Every pair of a dated-pair lexicon: the entries of one group with position before label the earlier date, those with
|
|
83
156
|
* position after the later one. The values name both labels, so the message can quote them. The issue is on whichever
|
|
84
157
|
* of the two dates is written second: a reader meets the contradiction there (a cancellation deadline below check-in).
|
|
158
|
+
* A group with entries without a position is read only in a document whose title or a field's label names one of them
|
|
159
|
+
* (namesDocumentKind, over reading.whole: the document with its headings, which source blanks out): ご利用日 before 有効期限 is a
|
|
160
|
+
* quote's mistake and a points statement's normal order.
|
|
161
|
+
* reading.spans.entries are the dated-span lexicon's entries, for the span words a group reads by the period's start.
|
|
85
162
|
*/
|
|
86
163
|
export const pairedDatesOutOfOrder = (
|
|
87
164
|
source: string,
|
|
@@ -89,12 +166,19 @@ export const pairedDatesOutOfOrder = (
|
|
|
89
166
|
labels: readonly OrderLabel[],
|
|
90
167
|
passed: readonly string[],
|
|
91
168
|
maxLineGap: number,
|
|
92
|
-
spans
|
|
169
|
+
{ spans, whole = source, signers = [] }: PairedReading = {},
|
|
93
170
|
): PairedIssue[] =>
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
171
|
+
withoutRepeats(
|
|
172
|
+
labelPairsOf(labels).flatMap((pair) => {
|
|
173
|
+
const context = contextOf(labels, pair.group);
|
|
174
|
+
if (context.length > 0 && !namesDocumentKind(whole, context)) return [];
|
|
175
|
+
const fromStart = spanStartsOf(spans?.entries ?? [], pair.group);
|
|
176
|
+
const words = { ...pair, passed, maxLineGap, spans, fromStart, signers: signersOf(signers, pair.group) };
|
|
177
|
+
return datesOutOfOrder(source, dates, words).map(({ earlier, later }) => ({
|
|
178
|
+
offset: Math.max(later.date.offset, earlier.date.offset),
|
|
179
|
+
values: { later: later.date.value, earlier: earlier.date.value, later_label: later.label, earlier_label: earlier.label },
|
|
180
|
+
period: earlier.span,
|
|
181
|
+
...(earlier.span && fromStart.includes(earlier.label) ? { fromStart: true } : {}),
|
|
182
|
+
}));
|
|
183
|
+
}),
|
|
100
184
|
);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { linesOf, type Line } from "./lines.ts";
|
|
2
2
|
import { tableAround } from "./key-value-row.ts";
|
|
3
|
+
import { signerRoleOf } from "./signed-date-line.ts";
|
|
3
4
|
import { isTotalLabel } from "./total.ts";
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -18,6 +19,11 @@ export type PairWords = {
|
|
|
18
19
|
readonly passed: readonly string[];
|
|
19
20
|
/** How many lines from the earlier point a later point may be and still be read as the same record's. */
|
|
20
21
|
readonly maxLineGap: number;
|
|
22
|
+
/**
|
|
23
|
+
* Role words whose line, when no label heads it, may give a later point (a signature: 2026年4月12日 会計監査 戸川 隆).
|
|
24
|
+
* pointOf decides whether the line is one. A signature closes the whole document, so it is read at any distance.
|
|
25
|
+
*/
|
|
26
|
+
readonly signers?: readonly string[] | undefined;
|
|
21
27
|
};
|
|
22
28
|
|
|
23
29
|
/** A labelled point: the word that labels it, as the lexicon writes it. span is set when the point ends a stated period. */
|
|
@@ -28,17 +34,24 @@ export type PointPair = { readonly earlier: LabelledPoint; readonly later: Label
|
|
|
28
34
|
/** The point a labelled line gives, or undefined when it gives none. tableOf gives the table the line is a row of. */
|
|
29
35
|
export type PointOf = (line: Line, label: string, tableOf: () => readonly string[]) => Pick<LabelledPoint, "point" | "span"> | undefined;
|
|
30
36
|
|
|
31
|
-
type Read = LabelledPoint & { readonly kind: "earlier" | "later" };
|
|
37
|
+
type Read = LabelledPoint & { readonly kind: "earlier" | "later"; readonly signed?: boolean | undefined };
|
|
32
38
|
|
|
33
39
|
const labelOf = (text: string, words: readonly string[]): string | undefined => words.find((word) => isTotalLabel(text, [word]));
|
|
34
40
|
|
|
35
|
-
const
|
|
41
|
+
const signerOf = (text: string, words: PairWords): Pick<Read, "kind" | "label" | "signed"> | undefined => {
|
|
42
|
+
const role = signerRoleOf(text, words.signers ?? []);
|
|
43
|
+
return role === undefined ? undefined : { kind: "later", label: role, signed: true };
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const kindOf = (text: string, words: PairWords): Pick<Read, "kind" | "label" | "signed"> | undefined => {
|
|
36
47
|
const earlier = labelOf(text, words.earlier);
|
|
37
48
|
if (earlier !== undefined) return { kind: "earlier", label: earlier };
|
|
38
49
|
const later = labelOf(text, words.later);
|
|
39
|
-
return later === undefined ?
|
|
50
|
+
return later === undefined ? signerOf(text, words) : { kind: "later", label: later };
|
|
40
51
|
};
|
|
41
52
|
|
|
53
|
+
const withinGap = (later: Read, earlier: Read, maxLineGap: number): boolean => later.signed === true || Math.abs(later.line - earlier.line) < maxLineGap;
|
|
54
|
+
|
|
42
55
|
const labelledPoints = (source: string, words: PairWords, pointOf: PointOf): Read[] => {
|
|
43
56
|
const lines = linesOf(source);
|
|
44
57
|
const texts = lines.map((line) => line.text);
|
|
@@ -75,7 +88,7 @@ export type PairReading = {
|
|
|
75
88
|
const nearestOutOfOrder = (labelled: readonly Read[], earliers: readonly Read[], maxLineGap: number): PointPair[] =>
|
|
76
89
|
labelled.flatMap((later) => {
|
|
77
90
|
const earlier = later.kind === "later" ? nearestEarlier(earliers, later) : undefined;
|
|
78
|
-
return earlier !== undefined && later.point.value < earlier.point.value &&
|
|
91
|
+
return earlier !== undefined && later.point.value < earlier.point.value && withinGap(later, earlier, maxLineGap) ? [{ earlier, later }] : [];
|
|
79
92
|
});
|
|
80
93
|
|
|
81
94
|
/**
|
|
@@ -89,10 +102,7 @@ const firstOutOfOrder = (labelled: readonly Read[], earliers: readonly Read[], m
|
|
|
89
102
|
const records = labelled.filter((entry) => entry.kind === "later").length > 1;
|
|
90
103
|
const beforeOwnRecord = (later: Read): boolean => !records || later.point.value < (nearestEarlier(earliers, later) ?? earlier).point.value;
|
|
91
104
|
return labelled
|
|
92
|
-
.filter(
|
|
93
|
-
(entry) =>
|
|
94
|
-
entry.kind === "later" && entry.point.value < earlier.point.value && Math.abs(entry.line - earlier.line) < maxLineGap && beforeOwnRecord(entry),
|
|
95
|
-
)
|
|
105
|
+
.filter((entry) => entry.kind === "later" && entry.point.value < earlier.point.value && withinGap(entry, earlier, maxLineGap) && beforeOwnRecord(entry))
|
|
96
106
|
.map((later) => ({ earlier, later }));
|
|
97
107
|
};
|
|
98
108
|
|
|
@@ -108,8 +118,8 @@ export const pointsOutOfOrder = (source: string, words: PairWords, pointOf: Poin
|
|
|
108
118
|
/** A lexicon entry naming its pair (group) and its side: before for the earlier point, after for the later one. */
|
|
109
119
|
export type OrderLabel = { readonly pattern: string; readonly group?: string | undefined; readonly position?: "before" | "after" | undefined };
|
|
110
120
|
|
|
111
|
-
/** One pair of a lexicon: the words of
|
|
112
|
-
export type LabelPair = { readonly earlier: string[]; readonly later: string[] };
|
|
121
|
+
/** One pair of a lexicon: its group, the words of the group that label the earlier point, and those that label the later one. */
|
|
122
|
+
export type LabelPair = { readonly group: string; readonly earlier: string[]; readonly later: string[] };
|
|
113
123
|
|
|
114
124
|
const sideOf = (labels: readonly OrderLabel[], group: string, position: "before" | "after"): string[] =>
|
|
115
125
|
labels.filter((label) => label.group === group && label.position === position).map((label) => label.pattern);
|
|
@@ -117,6 +127,7 @@ const sideOf = (labels: readonly OrderLabel[], group: string, position: "before"
|
|
|
117
127
|
/** Every pair a lexicon names, in the order its groups first appear. */
|
|
118
128
|
export const labelPairsOf = (labels: readonly OrderLabel[]): LabelPair[] =>
|
|
119
129
|
[...new Set(labels.flatMap((label) => (label.group === undefined ? [] : [label.group])))].map((group) => ({
|
|
130
|
+
group,
|
|
120
131
|
earlier: sideOf(labels, group, "before"),
|
|
121
132
|
later: sideOf(labels, group, "after"),
|
|
122
133
|
}));
|
|
@@ -3,7 +3,8 @@ import { linesOf, type Line } from "./lines.ts";
|
|
|
3
3
|
import { escapeRegExp } from "../orthography.ts";
|
|
4
4
|
import { roundedMatches } from "./tax.ts";
|
|
5
5
|
import { hasSuperscriptPower } from "../facts/superscript-power.ts";
|
|
6
|
-
import { cellNumber, cellsOf, plain, shownExpected,
|
|
6
|
+
import { cellNumber, cellsOf, plain, shownExpected, type Cell, type CellNumber } from "./line-amount.ts";
|
|
7
|
+
import { tablesOf } from "../facts/table-facts.ts";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* line-amount-mismatch, the product written in an item cell: 「| 会費(3,000円×24名) | 72,000円 |」,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { StructureIssue } from "./issues.ts";
|
|
2
2
|
import { linesOf, type Line } from "./lines.ts";
|
|
3
3
|
import { CELL_SEPARATOR } from "./bare-numbers.ts";
|
|
4
|
-
import {
|
|
4
|
+
import { tablesOf } from "../facts/table-facts.ts";
|
|
5
5
|
import { roundedMatches } from "./tax.ts";
|
|
6
6
|
import { holds } from "./change-rate-table.ts";
|
|
7
7
|
import { roleOf, type QuantityRole, type QuantityWord } from "./order-quantity.ts";
|
|
@@ -297,16 +297,6 @@ const rowIssue = (row: Line, table: Table, words: LineAmountWords): StructureIss
|
|
|
297
297
|
return [{ offset: amount.offset, values }];
|
|
298
298
|
};
|
|
299
299
|
|
|
300
|
-
/** Each table: its header row and its body rows. */
|
|
301
|
-
export const tablesOf = (lines: readonly Line[]): { header: Line; rows: Line[] }[] =>
|
|
302
|
-
lines.flatMap((line, index) => {
|
|
303
|
-
const header = lines[index - 1];
|
|
304
|
-
if (!TABLE_RULE.test(line.text) || header === undefined || !header.text.includes("|")) return [];
|
|
305
|
-
const after = lines.slice(index + 1);
|
|
306
|
-
const end = after.findIndex((row) => !row.text.includes("|"));
|
|
307
|
-
return [{ header, rows: end === -1 ? after : after.slice(0, end) }];
|
|
308
|
-
});
|
|
309
|
-
|
|
310
300
|
export const lineAmountMismatches = (source: string, words: LineAmountWords): StructureIssue[] =>
|
|
311
301
|
tablesOf(linesOf(source)).flatMap(({ header, rows }) => {
|
|
312
302
|
const readings = [tableOf(cellsOf(header), words), packTableOf(cellsOf(header), words)];
|
|
@@ -5,6 +5,8 @@ import { shownTotal, type Amount } from "./total.ts";
|
|
|
5
5
|
* 文の中で書いた合計と内訳。表や箇条書きでなく、一つの文に並べた金額を読む。二つの形がある。
|
|
6
6
|
* 内訳のあとに合計: 「保守費60,000円、運用費30,000円とし、合計100,000円とする」 "$6,000 for hosting and $3,000 for support, for a total of $9,000"
|
|
7
7
|
* 合計のあとに内訳: 「月額150,000円(内訳:保守費90,000円、運用費60,000円)」 "costs $8 per month, made up of a base fee of $6 and a storage fee of $2"
|
|
8
|
+
* 合計の語のあとに内訳: 「総額12,000円:設計5,000円、開発6,000円」 "a total of $12,000: $5,000 for design and $6,000 for development"
|
|
9
|
+
* (内訳を始める記号や語 total-breakdown-lead が合計の金額のすぐ後ろにあるか、合計で終わる文の次の文が内訳の語で始まるとき)
|
|
8
10
|
* 合計の語(total-phrase)はすぐ後ろに合計の金額が来る語、内訳の語(breakdown-phrase)はすぐ後ろから内訳が始まる語。
|
|
9
11
|
* 内訳は合計と同じ単位の金額で、二つ以上。合計の語のすぐ前(内訳の語のすぐ後ろ)から続く並びのどれかの和が合計と合えば
|
|
10
12
|
* 合っているとみなし、どれとも合わないときだけ言う。文の中のほかの金額(単価・上限・遅延損害金)を内訳に数えて誤って言わないため。
|
|
@@ -16,6 +18,12 @@ export type TotalWords = {
|
|
|
16
18
|
readonly breakdowns: readonly string[];
|
|
17
19
|
readonly discounts: readonly string[];
|
|
18
20
|
readonly joiners: readonly string[];
|
|
21
|
+
/** 合計の金額のすぐ後ろに書いて、そこから内訳のすべてが続くことを言う記号や語(": $5,000 for design and …")。 */
|
|
22
|
+
readonly leads: readonly string[];
|
|
23
|
+
/** 合計と内訳のあいだにあれば、後ろの金額は合計の内訳のすべてとは限らないと読む語(including、うち、or)。 */
|
|
24
|
+
readonly partials: readonly string[];
|
|
25
|
+
/** 次の文で内訳の語の前に書いて、前の文の合計を指す語(This、It、その)。何も書かなくてもよい。 */
|
|
26
|
+
readonly backReferences: readonly string[];
|
|
19
27
|
};
|
|
20
28
|
|
|
21
29
|
type Span = { readonly start: number; readonly end: number };
|
|
@@ -124,12 +132,83 @@ const totalsBeforeParts = (source: string, sentence: Span, amounts: readonly Amo
|
|
|
124
132
|
});
|
|
125
133
|
};
|
|
126
134
|
|
|
135
|
+
/** 合計の金額と、後ろに続く内訳を始める記号や語のあいだに書いてよいもの: 空白と、読点一つ。 */
|
|
136
|
+
const isLeadGap = (gap: string): boolean => /^[、,,]?$/u.test(gap.trim());
|
|
137
|
+
/** 割合(40%)を並べた内訳は、金額が合計のどの部分かを言わないので比べない。 */
|
|
138
|
+
const PERCENT = /[%%]/u;
|
|
139
|
+
/** 合計の文と内訳の文のあいだに空行があれば、別の段落で、続く内訳とは読まない。 */
|
|
140
|
+
const PARAGRAPH_BREAK = /\n\s*\n/u;
|
|
141
|
+
|
|
142
|
+
/** 合計の語のすぐ後ろの金額。 */
|
|
143
|
+
const totalsAt = (source: string, markers: readonly Span[], amounts: readonly Amount[]): Amount[] =>
|
|
144
|
+
markers.flatMap((marker) => amounts.find((amount) => amount.offset >= marker.end && isTotalGap(gapBefore(source, marker.end, amount))) ?? []);
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 合計の後ろから始まる内訳のすべてを合計と比べる。内訳の金額がどれも合計と同じ単位で、合計から内訳の終わりまでに
|
|
148
|
+
* 一部だけを言う語(including、うち)も割合も無いときだけ。
|
|
149
|
+
*/
|
|
150
|
+
const fullBreakdown = (source: string, total: Amount, listed: readonly Amount[], from: number, words: TotalWords): StructureIssue[] => {
|
|
151
|
+
const last = listed.at(-1);
|
|
152
|
+
if (last === undefined || listed.some((part) => part.unit !== total.unit)) return [];
|
|
153
|
+
const between = source.slice(total.end, last.offset);
|
|
154
|
+
if (PERCENT.test(between) || phraseSpans(between, total.end, words.partials).length > 0) return [];
|
|
155
|
+
return mismatch(source, { total, parts: listed, from, nearEnd: false }, words.discounts);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/** 文の中で、合計の語の金額のすぐ後ろに内訳を始める記号や語(": ")があれば、その後ろから文の終わりか次の記号までが内訳。 */
|
|
159
|
+
const totalsThenLead = (source: string, sentence: Span, amounts: readonly Amount[], words: TotalWords): StructureIssue[] => {
|
|
160
|
+
const text = source.slice(sentence.start, sentence.end);
|
|
161
|
+
const markers = phraseSpans(text, sentence.start, words.totals);
|
|
162
|
+
const leads = phraseSpans(text, sentence.start, words.leads);
|
|
163
|
+
return totalsAt(source, markers, amounts).flatMap((total) => {
|
|
164
|
+
const lead = leads.find((span) => span.start >= total.end && isLeadGap(source.slice(total.end, span.start)));
|
|
165
|
+
if (lead === undefined) return [];
|
|
166
|
+
const end = Math.min(sentence.end, ...[...leads, ...markers].filter((span) => span.start >= lead.end).map((span) => span.start));
|
|
167
|
+
return fullBreakdown(source, total, inside(amounts, lead.end, end), lead.end, words);
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/** 文の最後の金額が合計の語のすぐ後ろの金額なら、その金額。 */
|
|
172
|
+
const closingTotal = (source: string, sentence: Span, amounts: readonly Amount[], words: TotalWords): Amount | undefined => {
|
|
173
|
+
const last = inside(amounts, sentence.start, sentence.end).at(-1);
|
|
174
|
+
const markers = phraseSpans(source.slice(sentence.start, sentence.end), sentence.start, words.totals);
|
|
175
|
+
return totalsAt(source, markers, amounts).find((total) => total === last);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/** 内訳の語の前に書いたものが、何も無いか前の合計を指す語だけ("This consists of" 「内訳は」「その内訳は」)。 */
|
|
179
|
+
const refersBack = (opening: string, backReferences: readonly string[]): boolean => {
|
|
180
|
+
const subject = opening.trim().toLowerCase();
|
|
181
|
+
return subject === "" || backReferences.some((word) => word.toLowerCase() === subject);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/** 合計で終わる文のすぐ次の文が、前の合計を指して内訳の語で始まれば(内訳は、This consists of)、その後ろの金額が内訳。 */
|
|
185
|
+
const totalThenNextSentence = (source: string, pair: readonly [Span, Span], amounts: readonly Amount[], words: TotalWords): StructureIssue[] => {
|
|
186
|
+
const [previous, next] = pair;
|
|
187
|
+
const total = closingTotal(source, previous, amounts, words);
|
|
188
|
+
if (total === undefined || PARAGRAPH_BREAK.test(source.slice(previous.end, next.start))) return [];
|
|
189
|
+
const text = source.slice(next.start, next.end);
|
|
190
|
+
const marker = phraseSpans(text, next.start, words.breakdowns)[0];
|
|
191
|
+
if (marker === undefined || !refersBack(source.slice(next.start, marker.start), words.backReferences)) return [];
|
|
192
|
+
const inNext = inside(amounts, next.start, next.end);
|
|
193
|
+
const end = phraseSpans(text, next.start, words.totals).find((span) => span.start >= marker.end)?.start ?? next.end;
|
|
194
|
+
return fullBreakdown(source, total, inside(inNext, marker.end, end), marker.end, words);
|
|
195
|
+
};
|
|
196
|
+
|
|
127
197
|
/** 文ごとに、書いた合計が内訳の和と合わない所。一つの金額は一度だけ言う。 */
|
|
128
198
|
export const proseTotalMismatches = (source: string, sentences: readonly Span[], amounts: readonly Amount[], words: TotalWords): StructureIssue[] => {
|
|
129
|
-
const
|
|
199
|
+
const inSentences = sentences.flatMap((sentence) => {
|
|
130
200
|
const inSentence = inside(amounts, sentence.start, sentence.end);
|
|
131
201
|
if (inSentence.length <= MIN_PARTS) return [];
|
|
132
|
-
return [
|
|
202
|
+
return [
|
|
203
|
+
...totalsAfterParts(source, sentence, inSentence, words),
|
|
204
|
+
...totalsBeforeParts(source, sentence, inSentence, words),
|
|
205
|
+
...totalsThenLead(source, sentence, inSentence, words),
|
|
206
|
+
];
|
|
207
|
+
});
|
|
208
|
+
const acrossSentences = sentences.flatMap((previous, index) => {
|
|
209
|
+
const next = sentences[index + 1];
|
|
210
|
+
return next === undefined ? [] : totalThenNextSentence(source, [previous, next], amounts, words);
|
|
133
211
|
});
|
|
212
|
+
const issues = [...inSentences, ...acrossSentences];
|
|
134
213
|
return issues.filter((issue, index) => issues.findIndex((other) => other.offset === issue.offset) === index);
|
|
135
214
|
};
|
package/src/structure/runs.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { fencedLineIndexes } from "./fenced-lines.ts";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* 箇条書きと表の行の並び。続いた箇条書きの同じ深さの項目か、続いた表の行を一つの並びとして読む。
|
|
3
5
|
* 並びの中の値(日付、金額)を比べる rule が共通に使う。
|
|
@@ -29,14 +31,21 @@ const pipelessTableLines = (texts: readonly string[]): Set<number> => {
|
|
|
29
31
|
return inTable;
|
|
30
32
|
};
|
|
31
33
|
|
|
34
|
+
/** コードブロックの中の箇条書きや表は例なので、並びにしない。 */
|
|
35
|
+
const kindAt = (texts: readonly string[], index: number, pipeless: ReadonlySet<number>, fenced: ReadonlySet<number>): Kind | undefined => {
|
|
36
|
+
if (fenced.has(index)) return undefined;
|
|
37
|
+
return pipeless.has(index) ? "table" : kindOf(texts[index] ?? "");
|
|
38
|
+
};
|
|
39
|
+
|
|
32
40
|
const linesOf = (source: string): Line[] => {
|
|
33
41
|
const texts = source.split("\n");
|
|
34
42
|
const pipeless = pipelessTableLines(texts);
|
|
43
|
+
const fenced = fencedLineIndexes(texts);
|
|
35
44
|
const found: Line[] = [];
|
|
36
45
|
let start = 0;
|
|
37
46
|
texts.forEach((text, index) => {
|
|
38
47
|
const indent = INDENT.exec(text)?.[0].length ?? 0;
|
|
39
|
-
const kind =
|
|
48
|
+
const kind = kindAt(texts, index, pipeless, fenced);
|
|
40
49
|
found.push({ start, end: start + text.length, kind, indent, rule: TABLE_RULE.test(text) });
|
|
41
50
|
start += text.length + 1;
|
|
42
51
|
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { escapeRegExp } from "../orthography.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A signature line that dates what its signer did: one date, one role word and the signer's name, and nothing else
|
|
5
|
+
* (2026年4月12日 会計監査 戸川 隆; Jane Doe, Honorary Auditor, April 12, 2026). A sentence (2026年3月26日の定期総会で承認),
|
|
6
|
+
* a line without a name, or a line naming two people is not one. Pure.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const SEPARATOR = /[\s,、,::]/u;
|
|
10
|
+
const MARK = /[,、,::]/u;
|
|
11
|
+
const SPACES = /\s+/u;
|
|
12
|
+
/** A Japanese name is one or two parts (戸川 隆, 戸川隆); an English one two or three (Jane Doe, Jane Q. Doe). */
|
|
13
|
+
const MAX_JA_NAME_PARTS = 2;
|
|
14
|
+
const MIN_EN_NAME_PARTS = 2;
|
|
15
|
+
const MAX_EN_NAME_PARTS = 3;
|
|
16
|
+
/** A Japanese name part is kanji or katakana: hiragana is a particle or a verb (田中が確認, に監査を受け). */
|
|
17
|
+
const JA_NAME_PART = /^[\p{Script=Han}\p{Script=Katakana}ー]{1,5}$/u;
|
|
18
|
+
/** An English name part is capitalised, or an initial with its full stop. */
|
|
19
|
+
const EN_NAME_PART = /^\p{Lu}(?:\.|[\p{L}'’-]+)$/u;
|
|
20
|
+
|
|
21
|
+
/** Where a date sits on the line: from start to end, counted from the line's start. */
|
|
22
|
+
export type LineSpan = { readonly start: number; readonly end: number };
|
|
23
|
+
|
|
24
|
+
const isEdge = (text: string, index: number): boolean => index < 0 || index >= text.length || SEPARATOR.test(text.charAt(index));
|
|
25
|
+
|
|
26
|
+
const standsAlone = (text: string, start: number, end: number): boolean => isEdge(text, start - 1) && isEdge(text, end);
|
|
27
|
+
|
|
28
|
+
const occurrences = (text: string, word: string): number[] => [...text.matchAll(new RegExp(escapeRegExp(word), "gu"))].map((match) => match.index);
|
|
29
|
+
|
|
30
|
+
/** The role word of roles standing alone on the line (between separators or at an edge), the longest first. */
|
|
31
|
+
export const signerRoleOf = (text: string, roles: readonly string[]): string | undefined =>
|
|
32
|
+
[...roles]
|
|
33
|
+
.sort((one, other) => other.length - one.length)
|
|
34
|
+
.find((role) => occurrences(text, role).some((start) => standsAlone(text, start, start + role.length)));
|
|
35
|
+
|
|
36
|
+
const isJaName = (parts: readonly string[]): boolean => parts.length <= MAX_JA_NAME_PARTS && parts.every((part) => JA_NAME_PART.test(part));
|
|
37
|
+
|
|
38
|
+
const isEnName = (parts: readonly string[]): boolean =>
|
|
39
|
+
parts.length >= MIN_EN_NAME_PARTS && parts.length <= MAX_EN_NAME_PARTS && parts.every((part) => EN_NAME_PART.test(part));
|
|
40
|
+
|
|
41
|
+
const blanked = (text: string, span: LineSpan): string => `${text.slice(0, span.start)}${" ".repeat(span.end - span.start)}${text.slice(span.end)}`;
|
|
42
|
+
|
|
43
|
+
/** Where the text that is not a separator starts and ends; undefined when there is none. */
|
|
44
|
+
const contentOf = (text: string): LineSpan | undefined => {
|
|
45
|
+
const units = text.split("");
|
|
46
|
+
const start = units.findIndex((unit) => !SEPARATOR.test(unit));
|
|
47
|
+
if (start < 0) return undefined;
|
|
48
|
+
const end = units.findLastIndex((unit) => !SEPARATOR.test(unit)) + 1;
|
|
49
|
+
return { start, end };
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** The text between the role and the name: the name sits wholly on one side of the role. */
|
|
53
|
+
const gapOf = (text: string, role: LineSpan, name: LineSpan): string | undefined => {
|
|
54
|
+
if (name.end <= role.start) return text.slice(name.end, role.start);
|
|
55
|
+
return name.start >= role.end ? text.slice(role.end, name.start) : undefined;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The line is a signature: the date and the role each stand alone, and what is left is one name, in one piece on one side
|
|
60
|
+
* of the role (山田太郎、佐藤花子 is two). An English name is set off from its role by a comma or a colon (Jane Doe, Auditor),
|
|
61
|
+
* so a title (Auditor Annual Report) is not one. The role is found once the date is blanked out, so a role word inside the
|
|
62
|
+
* date's text is not read.
|
|
63
|
+
*/
|
|
64
|
+
export const isSignedDateLine = (text: string, role: string, date: LineSpan): boolean => {
|
|
65
|
+
if (!standsAlone(text, date.start, date.end)) return false;
|
|
66
|
+
const withoutDate = blanked(text, date);
|
|
67
|
+
const roleStart = occurrences(withoutDate, role).find((start) => standsAlone(withoutDate, start, start + role.length));
|
|
68
|
+
if (roleStart === undefined) return false;
|
|
69
|
+
const roleSpan = { start: roleStart, end: roleStart + role.length };
|
|
70
|
+
const rest = blanked(withoutDate, roleSpan);
|
|
71
|
+
const name = contentOf(rest);
|
|
72
|
+
const gap = name === undefined ? undefined : gapOf(withoutDate, roleSpan, name);
|
|
73
|
+
if (name === undefined || gap === undefined) return false;
|
|
74
|
+
const nameText = rest.slice(name.start, name.end);
|
|
75
|
+
if (MARK.test(nameText)) return false;
|
|
76
|
+
const parts = nameText.split(SPACES);
|
|
77
|
+
return isJaName(parts) || (isEnName(parts) && MARK.test(gap));
|
|
78
|
+
};
|
package/src/structure/tax.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { StructureIssue } from "./issues.ts";
|
|
2
2
|
import { runsOf, type Line } from "./runs.ts";
|
|
3
|
-
import { columnOf, isTotalLabel, shownTotal, type Amount } from "./total.ts";
|
|
3
|
+
import { columnOf, isTotalLabel, shownTotal, withoutLeadingBlankCells, type Amount } from "./total.ts";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* 税の行と、その上の金額に率を掛けた額。表か箇条書きの並びで、税の語(消費税・Sales tax)で始まり率(10%)を書いた行の
|
|
@@ -42,8 +42,11 @@ const rateOf = (text: string): number | undefined => {
|
|
|
42
42
|
return Number.isFinite(rate) && rate > 0 && rate < MAX_RATE ? rate : undefined;
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
+
/** A tax or total row may leave its first cells blank and put its word in the first cell that is not (| | 小計 | 12,000円 |). */
|
|
46
|
+
const startsWithLabel = (row: Row, labels: readonly string[]): boolean => isTotalLabel(withoutLeadingBlankCells(row.text), labels);
|
|
47
|
+
|
|
45
48
|
const isTaxRow = (row: Row, words: TaxWords): boolean =>
|
|
46
|
-
|
|
49
|
+
startsWithLabel(row, words.labels) && !words.included.some((word) => row.text.toLowerCase().includes(word.toLowerCase()));
|
|
47
50
|
|
|
48
51
|
const isChargeRow = (row: Row, words: TaxWords): boolean => isTotalLabel(row.text, words.charges) && rateOf(row.text) !== undefined;
|
|
49
52
|
|
|
@@ -89,7 +92,7 @@ const basesOf = (source: string, above: readonly Row[], tax: Amount, column: num
|
|
|
89
92
|
const charges = valuesAt(source, above.slice(chargeStart), column, tax.unit);
|
|
90
93
|
if (charges === undefined) return [];
|
|
91
94
|
const before = above.slice(0, chargeStart);
|
|
92
|
-
const lastTotalIndex = before.findLastIndex((row) =>
|
|
95
|
+
const lastTotalIndex = before.findLastIndex((row) => startsWithLabel(row, words.totals));
|
|
93
96
|
const lastTotal = before[lastTotalIndex];
|
|
94
97
|
if (lastTotal !== undefined) {
|
|
95
98
|
const total = amountAt(source, lastTotal, column, tax.unit);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isTotalLabel, leadingText } from "./total.ts";
|
|
1
|
+
import { isTotalLabel, leadingText, withoutLeadingBlankCells } from "./total.ts";
|
|
2
2
|
import { CELL_SEPARATOR } from "./bare-numbers.ts";
|
|
3
3
|
import { outsideBrackets } from "./connection-times.ts";
|
|
4
4
|
|
|
@@ -45,7 +45,13 @@ const labelText = (lead: string): string => {
|
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
/** 行頭の見出しを語に分ける。括弧の注記((incl. tax))と強調の印は除く。 */
|
|
48
|
-
const
|
|
48
|
+
const leadingLabelWords = (text: string): string[] => wordsOf(outsideBrackets(labelText(leadingText(text))).replace(EMPHASIS, ""));
|
|
49
|
+
|
|
50
|
+
/** 行の見出しの語。表の行は、前の升がどれも空なら、最初の空でない升の見出しを読む。 */
|
|
51
|
+
const labelWords = (text: string): string[] => leadingLabelWords(withoutLeadingBlankCells(text));
|
|
52
|
+
|
|
53
|
+
/** 見出しの語で始まる行か。表の行は、前の升がどれも空なら、最初の空でない升に書いた見出しも読む(| | 合計 | 12,000円 |)。 */
|
|
54
|
+
const startsWithLabel = (text: string, labels: readonly string[]): boolean => isTotalLabel(withoutLeadingBlankCells(text), labels);
|
|
49
55
|
|
|
50
56
|
const startsAt = (words: readonly string[], label: readonly string[], index: number): boolean => label.every((word, offset) => words[index + offset] === word);
|
|
51
57
|
|
|
@@ -153,8 +159,8 @@ const isUnsummed = (nouns: readonly string[], unsummedNouns: readonly string[]):
|
|
|
153
159
|
*/
|
|
154
160
|
const isNamedTotal = (text: string, words: TotalLineWords, header: string): boolean => {
|
|
155
161
|
const label = labelWords(text).join(" ");
|
|
156
|
-
const firstColumn =
|
|
157
|
-
const columns = summedColumnHeaders(text, header).map(
|
|
162
|
+
const firstColumn = leadingLabelWords(header);
|
|
163
|
+
const columns = summedColumnHeaders(text, header).map(leadingLabelWords);
|
|
158
164
|
const namesTotal = (nouns: readonly string[]): boolean =>
|
|
159
165
|
!isUnsummed(nouns, words.unsummedNouns ?? []) &&
|
|
160
166
|
(namesEachOther(nouns, firstColumn) || (columns.length > 0 && columns.every((headerWords) => namesEachOther(nouns, headerWords))));
|
|
@@ -163,7 +169,7 @@ const isNamedTotal = (text: string, words: TotalLineWords, header: string): bool
|
|
|
163
169
|
|
|
164
170
|
/** 合計の語だけか、合計の語に添える語だけを前後に置いた見出しか。 */
|
|
165
171
|
const isPlainTotal = (text: string, words: TotalLineWords): boolean => {
|
|
166
|
-
if (
|
|
172
|
+
if (startsWithLabel(text, words.labels)) return true;
|
|
167
173
|
if (words.qualifiers.length === 0) return false;
|
|
168
174
|
const label = labelWords(text);
|
|
169
175
|
return words.labels.some((total) => isQualifiedLabel(label, wordsOf(total), words.qualifiers));
|
|
@@ -189,7 +195,7 @@ export const withoutPeriod = (label: string, qualifier: TotalQualifier): string
|
|
|
189
195
|
* 前の年の合計(前年合計、Prior-year total)は、期間の語が頭にも尻にも無いので読まない。
|
|
190
196
|
*/
|
|
191
197
|
export const isPeriodTotalLine = (text: string, words: TotalLineWords): boolean => {
|
|
192
|
-
if (
|
|
198
|
+
if (startsWithLabel(text, words.periodLabels ?? [])) return true;
|
|
193
199
|
const label = labelWords(text).join(" ");
|
|
194
200
|
return (words.periodQualifiers ?? []).some((qualifier) => {
|
|
195
201
|
const rest = withoutPeriod(label, qualifier);
|