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
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { extname } from "node:path";
|
|
3
|
+
import { parse } from "yaml";
|
|
4
|
+
import { parseRubric, type Rubric, type RubricProblem } from "./rubric.ts";
|
|
5
|
+
|
|
6
|
+
// A rubric file given with `chaff grade --rubric <file>`: the schema of chaff.yaml's `grade:`, at the file's top level.
|
|
7
|
+
// Pure: the caller reads the file. Its paths drop the `grade.` prefix, because in the file they start at the top.
|
|
8
|
+
|
|
9
|
+
type Format = "yaml" | "json";
|
|
10
|
+
|
|
11
|
+
const FORMATS: Readonly<Record<string, Format>> = { ".yaml": "yaml", ".yml": "yaml", ".json": "json" };
|
|
12
|
+
|
|
13
|
+
export type RubricFileProblem =
|
|
14
|
+
{ readonly kind: "format" } | { readonly kind: "syntax"; readonly why: string } | { readonly kind: "rubric"; readonly problem: RubricProblem };
|
|
15
|
+
|
|
16
|
+
export type RubricFile = { readonly rubric: Rubric; readonly digest: string } | { readonly problems: readonly RubricFileProblem[] };
|
|
17
|
+
|
|
18
|
+
const formatOf = (path: string): Format | undefined => FORMATS[extname(path).toLowerCase()];
|
|
19
|
+
|
|
20
|
+
const syntaxOf = (content: string, format: Format): { readonly raw: unknown } | { readonly why: string } => {
|
|
21
|
+
try {
|
|
22
|
+
// An empty file reads as null in YAML but throws in JSON; both are an empty rubric, as a bare `grade:` is.
|
|
23
|
+
if (content.trim() === "") return { raw: null };
|
|
24
|
+
return { raw: format === "json" ? JSON.parse(content) : parse(content) };
|
|
25
|
+
} catch (error) {
|
|
26
|
+
return { why: error instanceof Error ? error.message : String(error) };
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** `grade.rules.x` in chaff.yaml is `rules.x` in a rubric file; `grade` itself is the file's top level, the empty path. */
|
|
31
|
+
export const filePathOf = (gradePath: string): string => (gradePath === "grade" ? "" : gradePath.replace(/^grade\./u, ""));
|
|
32
|
+
|
|
33
|
+
/** The sha256 of the file's bytes as read, recorded in the stamp so runs under different rubric files are not compared silently. */
|
|
34
|
+
export const rubricFileDigest = (content: string): string => `sha256:${createHash("sha256").update(content).digest("hex")}`;
|
|
35
|
+
|
|
36
|
+
/** The rubric a file holds, or every problem with it. An empty file is an empty rubric, as a bare `grade:` is. */
|
|
37
|
+
export const parseRubricFile = (path: string, content: string): RubricFile => {
|
|
38
|
+
const format = formatOf(path);
|
|
39
|
+
if (format === undefined) return { problems: [{ kind: "format" }] };
|
|
40
|
+
const read = syntaxOf(content, format);
|
|
41
|
+
if ("why" in read) return { problems: [{ kind: "syntax", why: read.why }] };
|
|
42
|
+
const parsed = parseRubric(read.raw ?? null);
|
|
43
|
+
if ("problems" in parsed)
|
|
44
|
+
return { problems: parsed.problems.map((problem) => ({ kind: "rubric", problem: { ...problem, path: filePathOf(problem.path) } })) };
|
|
45
|
+
return { rubric: parsed.rubric ?? { rules: {} }, digest: rubricFileDigest(content) };
|
|
46
|
+
};
|
package/src/grade/rubric.ts
CHANGED
|
@@ -31,6 +31,8 @@ export type Rubric = {
|
|
|
31
31
|
readonly citations?: CitationLimits | undefined;
|
|
32
32
|
readonly contexts?: ContextLimits | undefined;
|
|
33
33
|
readonly penalty?: number | undefined;
|
|
34
|
+
/** Each task's own rubric, as written: what it does not write comes from the top level (task-rubric.ts). */
|
|
35
|
+
readonly tasks?: Readonly<Record<string, Rubric>> | undefined;
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
/** What a value under `grade:` should have been. */
|
|
@@ -78,10 +80,10 @@ const ruleLimit = (raw: unknown, path: string): Read<RuleLimit> => {
|
|
|
78
80
|
};
|
|
79
81
|
};
|
|
80
82
|
|
|
81
|
-
const rulesOf = (raw: unknown): Read<Record<string, RuleLimit>> => {
|
|
83
|
+
const rulesOf = (raw: unknown, base: string): Read<Record<string, RuleLimit>> => {
|
|
82
84
|
if (raw === undefined) return ok({});
|
|
83
|
-
if (!isRecord(raw)) return { value: {}, problems: [problem(
|
|
84
|
-
const read = Object.entries(raw).map(([id, limit]) => ({ id, read: ruleLimit(limit,
|
|
85
|
+
if (!isRecord(raw)) return { value: {}, problems: [problem(`${base}.rules`, "map", raw)] };
|
|
86
|
+
const read = Object.entries(raw).map(([id, limit]) => ({ id, read: ruleLimit(limit, `${base}.rules.${id}`) }));
|
|
85
87
|
return { value: Object.fromEntries(read.map((entry) => [entry.id, entry.read.value])), problems: read.flatMap((entry) => entry.read.problems) };
|
|
86
88
|
};
|
|
87
89
|
|
|
@@ -98,13 +100,14 @@ const kindsOf = (raw: unknown, path: string): Read<AtomKind[]> => {
|
|
|
98
100
|
return kinds.length === entries.length ? ok(kinds) : { value: kinds, problems: [problem(path, "kinds", raw)] };
|
|
99
101
|
};
|
|
100
102
|
|
|
101
|
-
const factsOf = (raw: unknown): Read<FactLimits | undefined> => {
|
|
103
|
+
const factsOf = (raw: unknown, base: string): Read<FactLimits | undefined> => {
|
|
102
104
|
if (raw === undefined) return ok(undefined);
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
const
|
|
105
|
+
const path = `${base}.facts`;
|
|
106
|
+
if (!isRecord(raw)) return { value: undefined, problems: [problem(path, "map", raw)] };
|
|
107
|
+
const dropped = optional(raw, "dropped", path, "count");
|
|
108
|
+
const added = optional(raw, "added", path, "count");
|
|
109
|
+
const allowDropped = kindsOf(raw["allow_dropped"], `${path}.allow_dropped`);
|
|
110
|
+
const allowAdded = kindsOf(raw["allow_added"], `${path}.allow_added`);
|
|
108
111
|
return {
|
|
109
112
|
value: { dropped: dropped.value, added: added.value, allowDropped: allowDropped.value, allowAdded: allowAdded.value },
|
|
110
113
|
problems: [
|
|
@@ -112,7 +115,7 @@ const factsOf = (raw: unknown): Read<FactLimits | undefined> => {
|
|
|
112
115
|
...added.problems,
|
|
113
116
|
...allowDropped.problems,
|
|
114
117
|
...allowAdded.problems,
|
|
115
|
-
...unknownKeys(raw, ["dropped", "added", "allow_dropped", "allow_added"],
|
|
118
|
+
...unknownKeys(raw, ["dropped", "added", "allow_dropped", "allow_added"], path),
|
|
116
119
|
],
|
|
117
120
|
};
|
|
118
121
|
};
|
|
@@ -125,62 +128,51 @@ const requiredOf = (raw: Record<string, unknown>, path: string): Read<boolean> =
|
|
|
125
128
|
: { value: false, problems: [problem(`${path}.required`, "boolean", required)] };
|
|
126
129
|
};
|
|
127
130
|
|
|
128
|
-
const citationsOf = (raw: unknown): Read<CitationLimits | undefined> => {
|
|
131
|
+
const citationsOf = (raw: unknown, base: string): Read<CitationLimits | undefined> => {
|
|
129
132
|
if (raw === undefined) return ok(undefined);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
+
const path = `${base}.citations`;
|
|
134
|
+
if (!isRecord(raw)) return { value: undefined, problems: [problem(path, "map", raw)] };
|
|
135
|
+
const failed = optional(raw, "failed", path, "count");
|
|
136
|
+
const required = requiredOf(raw, path);
|
|
133
137
|
return {
|
|
134
138
|
value: { failed: failed.value, required: required.value },
|
|
135
|
-
problems: [...failed.problems, ...required.problems, ...unknownKeys(raw, ["failed", "required"],
|
|
139
|
+
problems: [...failed.problems, ...required.problems, ...unknownKeys(raw, ["failed", "required"], path)],
|
|
136
140
|
};
|
|
137
141
|
};
|
|
138
142
|
|
|
139
|
-
const contextsOf = (raw: unknown): Read<ContextLimits | undefined> => {
|
|
143
|
+
const contextsOf = (raw: unknown, base: string): Read<ContextLimits | undefined> => {
|
|
140
144
|
if (raw === undefined) return ok(undefined);
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
const
|
|
145
|
+
const path = `${base}.contexts`;
|
|
146
|
+
if (!isRecord(raw)) return { value: undefined, problems: [problem(path, "map", raw)] };
|
|
147
|
+
const unsupported = optional(raw, "unsupported", path, "count");
|
|
148
|
+
const allowUnsupported = kindsOf(raw["allow_unsupported"], `${path}.allow_unsupported`);
|
|
149
|
+
const required = requiredOf(raw, path);
|
|
145
150
|
return {
|
|
146
151
|
value: { unsupported: unsupported.value, allowUnsupported: allowUnsupported.value, required: required.value },
|
|
147
152
|
problems: [
|
|
148
153
|
...unsupported.problems,
|
|
149
154
|
...allowUnsupported.problems,
|
|
150
155
|
...required.problems,
|
|
151
|
-
...unknownKeys(raw, ["unsupported", "allow_unsupported", "required"],
|
|
156
|
+
...unknownKeys(raw, ["unsupported", "allow_unsupported", "required"], path),
|
|
152
157
|
],
|
|
153
158
|
};
|
|
154
159
|
};
|
|
155
160
|
|
|
156
|
-
|
|
161
|
+
/** The keys a task's rubric may write: every top-level key but `tasks`, so a task cannot hold tasks of its own. */
|
|
162
|
+
const TASK_KEYS: readonly string[] = ["rules", "required_sections", "facts", "citations", "contexts", "penalty"];
|
|
157
163
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
const
|
|
168
|
-
const facts = factsOf(raw["facts"]);
|
|
169
|
-
const citations = citationsOf(raw["citations"]);
|
|
170
|
-
const contexts = contextsOf(raw["contexts"]);
|
|
171
|
-
const penalty = optional(raw, "penalty", "grade", "number");
|
|
172
|
-
const problems = [
|
|
173
|
-
...rules.problems,
|
|
174
|
-
...sections.problems,
|
|
175
|
-
...facts.problems,
|
|
176
|
-
...citations.problems,
|
|
177
|
-
...contexts.problems,
|
|
178
|
-
...penalty.problems,
|
|
179
|
-
...unknownKeys(raw, RUBRIC_KEYS, "grade"),
|
|
180
|
-
];
|
|
181
|
-
if (problems.length > 0) return { problems };
|
|
164
|
+
const RUBRIC_KEYS: readonly string[] = [...TASK_KEYS, "tasks"];
|
|
165
|
+
|
|
166
|
+
/** One rubric's keys under `base` (`grade`, or `grade.tasks.<name>`); a key not among `keys` is a problem. */
|
|
167
|
+
const rubricAt = (raw: Record<string, unknown>, base: string, keys: readonly string[]): Read<Rubric> => {
|
|
168
|
+
const rules = rulesOf(raw["rules"], base);
|
|
169
|
+
const sections = wordsOf(raw["required_sections"], `${base}.required_sections`);
|
|
170
|
+
const facts = factsOf(raw["facts"], base);
|
|
171
|
+
const citations = citationsOf(raw["citations"], base);
|
|
172
|
+
const contexts = contextsOf(raw["contexts"], base);
|
|
173
|
+
const penalty = optional(raw, "penalty", base, "number");
|
|
182
174
|
return {
|
|
183
|
-
|
|
175
|
+
value: {
|
|
184
176
|
rules: rules.value,
|
|
185
177
|
requiredSections: sections.value,
|
|
186
178
|
facts: facts.value,
|
|
@@ -188,5 +180,43 @@ export const parseRubric = (raw: unknown): { readonly rubric: Rubric | undefined
|
|
|
188
180
|
contexts: contexts.value,
|
|
189
181
|
penalty: penalty.value,
|
|
190
182
|
},
|
|
183
|
+
problems: [
|
|
184
|
+
...rules.problems,
|
|
185
|
+
...sections.problems,
|
|
186
|
+
...facts.problems,
|
|
187
|
+
...citations.problems,
|
|
188
|
+
...contexts.problems,
|
|
189
|
+
...penalty.problems,
|
|
190
|
+
...unknownKeys(raw, keys, base),
|
|
191
|
+
],
|
|
191
192
|
};
|
|
192
193
|
};
|
|
194
|
+
|
|
195
|
+
/** One task's rubric. A bare `<name>:` (null) writes nothing, so the task inherits the whole top-level rubric. */
|
|
196
|
+
const taskOf = (raw: unknown, path: string): Read<Rubric> => {
|
|
197
|
+
if (raw === null) return ok({ rules: {} });
|
|
198
|
+
if (!isRecord(raw)) return { value: { rules: {} }, problems: [problem(path, "map", raw)] };
|
|
199
|
+
return rubricAt(raw, path, TASK_KEYS);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const tasksOf = (raw: unknown): Read<Record<string, Rubric> | undefined> => {
|
|
203
|
+
if (raw === undefined) return ok(undefined);
|
|
204
|
+
if (!isRecord(raw)) return { value: undefined, problems: [problem("grade.tasks", "map", raw)] };
|
|
205
|
+
const read = Object.entries(raw).map(([name, task]) => ({ name, read: taskOf(task, `grade.tasks.${name}`) }));
|
|
206
|
+
return { value: Object.fromEntries(read.map((entry) => [entry.name, entry.read.value])), problems: read.flatMap((entry) => entry.read.problems) };
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* The rubric, or every problem in it. No `grade:` key is no rubric: the default pass or fail applies (spec §29.3). A bare
|
|
211
|
+
* `grade:` (null) is an empty rubric, so writing the key alone still turns the default off, as the spec says it does.
|
|
212
|
+
*/
|
|
213
|
+
export const parseRubric = (raw: unknown): { readonly rubric: Rubric | undefined } | { readonly problems: readonly RubricProblem[] } => {
|
|
214
|
+
if (raw === undefined) return { rubric: undefined };
|
|
215
|
+
if (raw === null) return { rubric: { rules: {} } };
|
|
216
|
+
if (!isRecord(raw)) return { problems: [problem("grade", "map", raw)] };
|
|
217
|
+
const top = rubricAt(raw, "grade", RUBRIC_KEYS);
|
|
218
|
+
const tasks = tasksOf(raw["tasks"]);
|
|
219
|
+
const problems = [...top.problems, ...tasks.problems];
|
|
220
|
+
if (problems.length > 0) return { problems };
|
|
221
|
+
return { rubric: tasks.value === undefined ? top.value : { ...top.value, tasks: tasks.value } };
|
|
222
|
+
};
|
package/src/grade/setup.ts
CHANGED
|
@@ -28,16 +28,16 @@ const ruleSetOf = async (config: Config, languages: readonly string[]): Promise<
|
|
|
28
28
|
};
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
/** A run's settings, rubric and stamp. `languages`: those the items name, beyond the bundled ones. Throws when a language package cannot be loaded. */
|
|
31
|
+
/** A run's settings, rubric and stamp. `run.rubricFile`: a --rubric file's digest, when the rubric came from one. `languages`: those the items name, beyond the bundled ones. Throws when a language package cannot be loaded. */
|
|
32
32
|
export const gradeSetup = async (
|
|
33
33
|
config: Config,
|
|
34
|
-
run: { readonly experimental: boolean; readonly genre: string | undefined },
|
|
34
|
+
run: { readonly experimental: boolean; readonly genre: string | undefined; readonly rubricFile?: string | undefined },
|
|
35
35
|
languages: readonly string[],
|
|
36
36
|
rubric: Rubric | undefined,
|
|
37
37
|
): Promise<GradeSetup> => {
|
|
38
38
|
// chaff.yaml's language and its by_path languages are loaded here too, so a missing package stops the run before grading.
|
|
39
39
|
const named = [config.language, ...config.byPath.map((rule) => rule.language)].filter((language) => language !== undefined);
|
|
40
40
|
const all = [...new Set([...BUNDLED_LANGUAGES, ...named, ...languages])].toSorted(compareText);
|
|
41
|
-
const settings = { config, experimental: run.experimental, genre: run.genre };
|
|
41
|
+
const settings = { config, experimental: run.experimental, genre: run.genre, rubricFile: run.rubricFile };
|
|
42
42
|
return { ...settings, rubric, stamp: stampOf(VERSION_LINES, await ruleSetOf(config, all), settings) };
|
|
43
43
|
};
|
package/src/grade/stamp.ts
CHANGED
|
@@ -20,6 +20,15 @@ export type GradeSettings = {
|
|
|
20
20
|
readonly experimental: boolean;
|
|
21
21
|
/** --genre, which wins over chaff.yaml for every item that does not name its own. */
|
|
22
22
|
readonly genre: string | undefined;
|
|
23
|
+
/** The digest of a --rubric file, which replaces chaff.yaml's `grade:` for the run. */
|
|
24
|
+
readonly rubricFile?: string | undefined;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** The rubric a run grades by: a --rubric file by its digest, else chaff.yaml's `grade:`. */
|
|
28
|
+
const gradeOf = (settings: GradeSettings): unknown => {
|
|
29
|
+
if (settings.rubricFile !== undefined) return { file: settings.rubricFile };
|
|
30
|
+
// A bare `grade:` reads as null and is an empty rubric, which grades otherwise than no `grade:` at all.
|
|
31
|
+
return settings.config.grade === undefined ? "none" : settings.config.grade;
|
|
23
32
|
};
|
|
24
33
|
|
|
25
34
|
/** The parts of chaff.yaml that change a finding, a fact or a verdict. Paths and the AI judge do not. */
|
|
@@ -40,8 +49,7 @@ export const settingsOf = (settings: GradeSettings): Record<string, unknown> =>
|
|
|
40
49
|
requiredSections: config.requiredSections,
|
|
41
50
|
names: config.names,
|
|
42
51
|
byPath: config.byPath,
|
|
43
|
-
|
|
44
|
-
grade: config.grade === undefined ? "none" : config.grade,
|
|
52
|
+
grade: gradeOf(settings),
|
|
45
53
|
};
|
|
46
54
|
};
|
|
47
55
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Rubric } from "./rubric.ts";
|
|
2
|
+
|
|
3
|
+
// The rubric one item is graded by: the top-level rubric with its task's rubric laid over it. Pure.
|
|
4
|
+
// A key the task writes replaces the top-level key whole (`facts`, `citations`, `contexts` are not merged field by
|
|
5
|
+
// field); a key it does not write is inherited. `rules` is merged per rule id: the task's entry for an id replaces the
|
|
6
|
+
// top-level entry for that id, and every other top-level id still applies.
|
|
7
|
+
|
|
8
|
+
/** The task names a rubric defines; none without a rubric or without `tasks`. */
|
|
9
|
+
export const taskNamesOf = (rubric: Rubric | undefined): readonly string[] => Object.keys(rubric?.tasks ?? {});
|
|
10
|
+
|
|
11
|
+
/** Each rule id the rubric names and where, from the rubric's top level: `rules`, or `tasks.<name>.rules`. */
|
|
12
|
+
export const rubricRuleEntries = (rubric: Rubric | undefined): readonly { readonly path: string; readonly id: string }[] => [
|
|
13
|
+
...Object.keys(rubric?.rules ?? {}).map((id) => ({ path: "rules", id })),
|
|
14
|
+
...Object.entries(rubric?.tasks ?? {}).flatMap(([name, task]) => Object.keys(task.rules).map((id) => ({ path: `tasks.${name}.rules`, id }))),
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const overlay = (top: Rubric, task: Rubric): Rubric => ({
|
|
18
|
+
rules: { ...top.rules, ...task.rules },
|
|
19
|
+
requiredSections: task.requiredSections ?? top.requiredSections,
|
|
20
|
+
facts: task.facts ?? top.facts,
|
|
21
|
+
citations: task.citations ?? top.citations,
|
|
22
|
+
contexts: task.contexts ?? top.contexts,
|
|
23
|
+
penalty: task.penalty ?? top.penalty,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The rubric for an item of `task`. Without a task, the rubric as it is. A task the rubric does not define is refused
|
|
28
|
+
* when the items are read, so here it cannot happen; it throws rather than fall back to the top level silently.
|
|
29
|
+
*/
|
|
30
|
+
export const rubricForTask = (rubric: Rubric | undefined, task: string | undefined): Rubric | undefined => {
|
|
31
|
+
if (task === undefined) return rubric;
|
|
32
|
+
const own = rubric?.tasks?.[task];
|
|
33
|
+
if (rubric === undefined || own === undefined) throw new Error(`chaff grade: no rubric for task "${task}"`);
|
|
34
|
+
return overlay(rubric, own);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** The rule ids an output of `task` is graded by: whose rise in that output is a regression against a baseline. */
|
|
38
|
+
export const taskRuleIds = (rubric: Rubric | undefined, task: string | undefined): ReadonlySet<string> =>
|
|
39
|
+
new Set(Object.keys(rubricForTask(rubric, task)?.rules ?? {}));
|
package/src/grade/text.ts
CHANGED
|
@@ -11,7 +11,11 @@ export type GradeText = {
|
|
|
11
11
|
readonly contextsUnread: (kind: string, reason: string) => string;
|
|
12
12
|
readonly uncheckedSentences: (count: number) => string;
|
|
13
13
|
readonly unknownRule: string;
|
|
14
|
-
|
|
14
|
+
/** A problem under chaff.yaml's `grade:`, or, given `file`, under a --rubric file's top level. */
|
|
15
|
+
readonly rubricProblem: (problem: RubricProblem, file?: string) => string;
|
|
16
|
+
/** A --rubric file whose extension is none of .yaml, .yml, .json. */
|
|
17
|
+
readonly rubricFormat: (file: string) => string;
|
|
18
|
+
readonly rubricSyntax: (file: string, why: string) => string;
|
|
15
19
|
readonly penalty: (total: number) => string;
|
|
16
20
|
/** The outputs at each level of the AI-likeness quick score. */
|
|
17
21
|
readonly aiScore: (low: number, medium: number, high: number, notScored: number) => string;
|
|
@@ -55,6 +59,12 @@ const PROBLEM_JA: Readonly<Record<ItemProblemKind, (problem: ItemProblem) => str
|
|
|
55
59
|
"which-source": (problem) => `${lineJa(problem)}番地 ${problem.detail ?? ""} の引用に source がありません。原文が二つ以上あるときは名前を書いてください`,
|
|
56
60
|
"unknown-language": (problem) => `${lineJa(problem)}language "${problem.detail ?? ""}" は読めません`,
|
|
57
61
|
"unknown-genre": (problem) => `${lineJa(problem)}genre "${problem.detail ?? ""}" は chaff の知らないジャンルです(chaff genres で一覧)`,
|
|
62
|
+
"not-expect": (problem) => `${lineJa(problem)}${problem.detail ?? ""} は { "facts": ["…"], "absent": ["…"] } の形で、空でない文字列の並びを書いてください`,
|
|
63
|
+
"expect-no-fact": (problem) =>
|
|
64
|
+
`${lineJa(problem)}${problem.detail ?? ""} から事実を読めません(数・日付・時刻・URL・コード・名前・引用のどれか 1 つを書いてください)`,
|
|
65
|
+
"expect-several-facts": (problem) =>
|
|
66
|
+
`${lineJa(problem)}${problem.detail ?? ""} は事実を 2 つ以上含みます(compare が 1 つの事実と読む書き方で、1 項目に 1 つ書いてください)`,
|
|
67
|
+
"unknown-task": (problem) => `${lineJa(problem)}task "${problem.detail ?? ""}" は採点基準の tasks にありません`,
|
|
58
68
|
empty: () => "採点する出力が 1 行もありません",
|
|
59
69
|
};
|
|
60
70
|
|
|
@@ -74,6 +84,12 @@ const PROBLEM_EN: typeof PROBLEM_JA = {
|
|
|
74
84
|
"which-source": (problem) => `${lineEn(problem)}the citation of ${problem.detail ?? ""} names no source; with two or more sources, name one`,
|
|
75
85
|
"unknown-language": (problem) => `${lineEn(problem)}language "${problem.detail ?? ""}" cannot be read`,
|
|
76
86
|
"unknown-genre": (problem) => `${lineEn(problem)}genre "${problem.detail ?? ""}" is not one chaff knows (chaff genres lists them)`,
|
|
87
|
+
"not-expect": (problem) => `${lineEn(problem)}${problem.detail ?? ""} must be { "facts": ["…"], "absent": ["…"] } with non-empty strings`,
|
|
88
|
+
"expect-no-fact": (problem) =>
|
|
89
|
+
`${lineEn(problem)}${problem.detail ?? ""} states no fact chaff can read (write one number, date, time, URL, code, name or quotation)`,
|
|
90
|
+
"expect-several-facts": (problem) =>
|
|
91
|
+
`${lineEn(problem)}${problem.detail ?? ""} states more than one fact (write one per entry, in a form compare reads as one fact)`,
|
|
92
|
+
"unknown-task": (problem) => `${lineEn(problem)}task "${problem.detail ?? ""}" is not among the rubric's tasks`,
|
|
77
93
|
empty: () => "no output to grade: the file has no item",
|
|
78
94
|
};
|
|
79
95
|
|
|
@@ -103,14 +119,17 @@ const aside = (open: string, inner: string, close: string): string => (inner ===
|
|
|
103
119
|
export const GRADE_TEXT: Texts<GradeText> = {
|
|
104
120
|
ja: {
|
|
105
121
|
usage:
|
|
106
|
-
"使い方: chaff grade <items.jsonl> [--out <results.jsonl>] [--baseline <前の results.jsonl>] [--allow-stamp-mismatch] [--variant-key <欄の名前>] [--format text|json|markdown] [--json] [--compact] [--experimental] [--genre <ジャンル>]",
|
|
122
|
+
"使い方: chaff grade <items.jsonl> [--rubric <rubric.yaml|.json>] [--out <results.jsonl>] [--baseline <前の results.jsonl>] [--allow-stamp-mismatch] [--variant-key <欄の名前>] [--format text|json|markdown] [--json] [--compact] [--experimental] [--genre <ジャンル>]",
|
|
107
123
|
noReference: "reference が無い(事実は reference と照らす)",
|
|
108
124
|
noCitations: "citations が無い(chaff は出力から引用を推測しない)",
|
|
109
125
|
noContexts: "contexts が無い(回答の事実は、検索で取った一節と照らす)",
|
|
110
126
|
contextsUnread: (kind, reason) => `contexts と照らすとき、${kind} を読み切れなかった(${reason})。その種類の事実は、一節にあっても見つからないことがある`,
|
|
111
127
|
uncheckedSentences: (count) => `数・日付・名前・引用の無い文 ${String(count)} 件は、contexts に支えられているかを照らしていない(意味を読む必要がある)`,
|
|
112
128
|
unknownRule: "grade: に書かれているが、chaff の知らないルール",
|
|
113
|
-
rubricProblem: (problem) =>
|
|
129
|
+
rubricProblem: (problem, file) =>
|
|
130
|
+
`${file ?? "chaff.yaml"} の ${problem.path === "" ? "最上位" : problem.path} は${EXPECTED_JA[problem.expected]}で書いてください(書かれていたのは ${problem.written})`,
|
|
131
|
+
rubricFormat: (file) => `${file}: --rubric のファイルは .yaml・.yml・.json のどれかにしてください`,
|
|
132
|
+
rubricSyntax: (file, why) => `${file} を採点の基準として読めませんでした: ${why}`,
|
|
114
133
|
penalty: (total) => `減点の和: ${String(total)}`,
|
|
115
134
|
aiScore: (low, medium, high, notScored) =>
|
|
116
135
|
`AI らしさ: 低 ${String(low)}、中 ${String(medium)}、高 ${String(high)}、測っていない ${String(notScored)}(書いたのが AI かどうかの判定ではありません。合否には入りません)`,
|
|
@@ -135,7 +154,7 @@ export const GRADE_TEXT: Texts<GradeText> = {
|
|
|
135
154
|
},
|
|
136
155
|
en: {
|
|
137
156
|
usage:
|
|
138
|
-
"usage: chaff grade <items.jsonl> [--out <results.jsonl>] [--baseline <earlier results.jsonl>] [--allow-stamp-mismatch] [--variant-key <field>] [--format text|json|markdown] [--json] [--compact] [--experimental] [--genre <genre>]",
|
|
157
|
+
"usage: chaff grade <items.jsonl> [--rubric <rubric.yaml|.json>] [--out <results.jsonl>] [--baseline <earlier results.jsonl>] [--allow-stamp-mismatch] [--variant-key <field>] [--format text|json|markdown] [--json] [--compact] [--experimental] [--genre <genre>]",
|
|
139
158
|
noReference: "no reference given (facts are checked against a reference)",
|
|
140
159
|
noCitations: "no citations given (chaff does not guess quotations from the output)",
|
|
141
160
|
noContexts: "no contexts given (the output's facts are checked against retrieved passages)",
|
|
@@ -144,7 +163,10 @@ export const GRADE_TEXT: Texts<GradeText> = {
|
|
|
144
163
|
uncheckedSentences: (count) =>
|
|
145
164
|
`${String(count)} ${count === 1 ? "sentence states" : "sentences state"} no number, date, name or quotation, so their support by contexts is not checked (it needs reading)`,
|
|
146
165
|
unknownRule: "named under grade: but not a rule chaff knows",
|
|
147
|
-
rubricProblem: (problem) =>
|
|
166
|
+
rubricProblem: (problem, file) =>
|
|
167
|
+
`${file ?? "chaff.yaml"}: ${problem.path === "" ? "the top level" : problem.path} must be ${EXPECTED_EN[problem.expected]} (found ${problem.written})`,
|
|
168
|
+
rubricFormat: (file) => `${file}: a --rubric file must end in .yaml, .yml or .json`,
|
|
169
|
+
rubricSyntax: (file, why) => `Could not read ${file} as a rubric: ${why}`,
|
|
148
170
|
penalty: (total) => `Penalty points: ${String(total)}`,
|
|
149
171
|
aiScore: (low, medium, high, notScored) =>
|
|
150
172
|
`AI-likeness: low ${String(low)}, medium ${String(medium)}, high ${String(high)}, not scored ${String(notScored)} (not a verdict on whether AI wrote them; not part of pass or fail)`,
|
package/src/name-char-reading.ts
CHANGED
|
@@ -22,3 +22,19 @@ export const readingOfWords = (words: readonly ReadToken[], chars: CharReadings)
|
|
|
22
22
|
const readings = words.map((token) => tokenReadingOf(token, chars));
|
|
23
23
|
return words.length > 0 && readings.every(isRead) ? readings.join("") : undefined;
|
|
24
24
|
};
|
|
25
|
+
|
|
26
|
+
/** 名の字の読みで読み直す、名の字数の下限。一字の名(太 を フトシ)は、二字の名の中の読み(健太 の タ)で読まない。 */
|
|
27
|
+
const MIN_GIVEN_LETTERS = 2;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 名をもう一通りに読んだもの。解析器は名を別の読みに切ることがある(建太 を タテ と フトシ)。字がどれも givenChars(名の中での
|
|
31
|
+
* 字の読み)にある語はその字の読みをつなぎ、ほかの語は tokenReadingOf で読む。一字の名と、読めない語のある名は読まない。
|
|
32
|
+
*/
|
|
33
|
+
export const givenNameReadingOf = (words: readonly ReadToken[], givenChars: CharReadings, chars: CharReadings): string | undefined => {
|
|
34
|
+
if (words.reduce((letters, token) => letters + [...token.surface].length, 0) < MIN_GIVEN_LETTERS) return undefined;
|
|
35
|
+
const readings = words.map((token) => {
|
|
36
|
+
const letters = [...token.surface].map((letter) => givenChars.get(letter));
|
|
37
|
+
return letters.length > 0 && letters.every(isRead) ? letters.join("") : tokenReadingOf(token, chars);
|
|
38
|
+
});
|
|
39
|
+
return readings.every(isRead) ? readings.join("") : undefined;
|
|
40
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// 名前の語の終わりの一字の抜けか足し(Whitmore と Whitmor、Hollybrook と Hollybrooke)。ふつうの語なら語の形の違い
|
|
2
|
+
// (Service と Services、America と American)なので、どちらも名前にしか使わない語に限る。
|
|
3
|
+
|
|
4
|
+
const LATIN_WORD = /^[a-z]+$/u;
|
|
5
|
+
const VOWEL = /[aeiouy]$/u;
|
|
6
|
+
|
|
7
|
+
/** 足すと別の語の形になる字。s は複数と所有(Rental と Rentals)。 */
|
|
8
|
+
const PLURAL_LETTER = "s";
|
|
9
|
+
/** 母音で終わる語に足すと、その土地の人や言葉の語になる字(Korea と Korean、Rica と Rican)。 */
|
|
10
|
+
const DEMONYM_LETTER = "n";
|
|
11
|
+
|
|
12
|
+
/** 比べる語の長さ。三字の語(Ann と Anne)は別の名前のことが多い。 */
|
|
13
|
+
const MIN_END_LETTER_LENGTH = 4;
|
|
14
|
+
/** 終わりの一字の抜けか足しを、名前の最後でない語で見る長さ(Hollybrook と Hollybrooke)。 */
|
|
15
|
+
const MIN_END_SLIP_ANYWHERE = 8;
|
|
16
|
+
|
|
17
|
+
const isFormLetter = (shorter: string, added: string): boolean => added === PLURAL_LETTER || (added === DEMONYM_LETTER && VOWEL.test(shorter));
|
|
18
|
+
|
|
19
|
+
/** 二つの英字の語が、終わりの一字の抜けか足しだけ違うか。足した字が語の形を作る字(s、母音の後ろの n)なら違うと見ない。 */
|
|
20
|
+
export const isEndLetterApart = (left: string, right: string): boolean => {
|
|
21
|
+
const [shorter, longer] = left.length < right.length ? [left, right] : [right, left];
|
|
22
|
+
if (!LATIN_WORD.test(shorter) || !LATIN_WORD.test(longer) || shorter.length < MIN_END_LETTER_LENGTH) return false;
|
|
23
|
+
return longer.length === shorter.length + 1 && longer.startsWith(shorter) && !isFormLetter(shorter, longer.slice(shorter.length));
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 文書での語の使い方(小文字で)。ordinary は名前と読んだ語のうち小文字で書けばふつうの語(Rental、Green)、common は文書が
|
|
28
|
+
* 小文字で書くか、品詞解析が普通名詞と読む語(hurricane、lake)。
|
|
29
|
+
*/
|
|
30
|
+
export type WordUse = { readonly ordinary: ReadonlySet<string>; readonly common: ReadonlySet<string> };
|
|
31
|
+
|
|
32
|
+
export const NO_WORD_USE: WordUse = { ordinary: new Set(), common: new Set() };
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 名前の語(words、小文字)の at の語を usual と書いた名前があるとき、その違いを書き損じと見るか。違う語が最後の語(姓)なら、
|
|
36
|
+
* 終わりの一字の抜けか足しか、四字以上の一字違い(oneLetterApart)で、前の語がどれも文書の中で普通名詞として書かない語(Rachel、Mr)
|
|
37
|
+
* のとき。Hurricane Hanna と Hurricane Hannah は名前の違う二つの嵐。最後でない語なら、八字以上の終わりの一字の抜けか足しで、名前の
|
|
38
|
+
* 最後の語がふつうの語(Residences、Products)のとき。人の名の終わりの一字だけ違う名前(Christian と Christiane)は別の人のことが多い。
|
|
39
|
+
*/
|
|
40
|
+
export const isEndWordSlip = (
|
|
41
|
+
words: readonly string[],
|
|
42
|
+
at: number,
|
|
43
|
+
usual: string,
|
|
44
|
+
use: WordUse,
|
|
45
|
+
oneLetterApart: (left: string, right: string) => boolean,
|
|
46
|
+
): boolean => {
|
|
47
|
+
const word = words[at] ?? "";
|
|
48
|
+
const shorter = Math.min(word.length, usual.length);
|
|
49
|
+
if (at < words.length - 1) return shorter >= MIN_END_SLIP_ANYWHERE && use.ordinary.has(words.at(-1) ?? "") && isEndLetterApart(word, usual);
|
|
50
|
+
if (words.slice(0, at).some((before) => use.common.has(before))) return false;
|
|
51
|
+
return isEndLetterApart(word, usual) || (shorter >= MIN_END_LETTER_LENGTH && oneLetterApart(word, usual));
|
|
52
|
+
};
|
package/src/name-neighbours.ts
CHANGED
|
@@ -47,11 +47,12 @@ export const withKnownNeighbours = (mentions: readonly NameMention[], source: st
|
|
|
47
47
|
surface: `${before} ${mention.surface}`,
|
|
48
48
|
offset: mention.offset - 1 - before.length,
|
|
49
49
|
reading: undefined,
|
|
50
|
+
givenNameReading: undefined,
|
|
50
51
|
words: [before, ...mention.words],
|
|
51
52
|
};
|
|
52
53
|
}
|
|
53
54
|
if (after !== undefined && known.has(`${mention.surface} ${after}`) && !taken(end + 1, end + 1 + after.length)) {
|
|
54
|
-
return { ...mention, surface: `${mention.surface} ${after}`, reading: undefined, words: [...mention.words, after] };
|
|
55
|
+
return { ...mention, surface: `${mention.surface} ${after}`, reading: undefined, givenNameReading: undefined, words: [...mention.words, after] };
|
|
55
56
|
}
|
|
56
57
|
return mention;
|
|
57
58
|
});
|
package/src/name-separator.ts
CHANGED
|
@@ -34,6 +34,38 @@ const isNameLine = (lineTokens: readonly Token[]): boolean => lineTokens.length
|
|
|
34
34
|
const isOrganizationName = (word: string, wordTokens: readonly Token[], words: OrganizationWords): boolean =>
|
|
35
35
|
words.units.has(wordTokens.at(-1)?.surface ?? "") || words.forms.some((form) => word.length > form.length && (word.startsWith(form) || word.endsWith(form)));
|
|
36
36
|
|
|
37
|
+
/** 行の頭の項目名とコロン(所属:、振込先:)。項目名は空白も数字も含まない短い語。 */
|
|
38
|
+
const FIELD_LABEL = /^[ \t]*(?:[-*+][ \t]+)?[^\s::。\d]{1,12}[::][ \t\u3000]*/u;
|
|
39
|
+
|
|
40
|
+
/** 文の終わり。値を文で書いた行(会場:本社 5階で行います。)の空白は、文の中の空け方として数える。 */
|
|
41
|
+
const SENTENCE_END = /[。!?]/u;
|
|
42
|
+
|
|
43
|
+
/** 値を句で書いた行(目標:参加者 100人を想定)に出る品詞。名前と番号を並べた値には出ない。 */
|
|
44
|
+
const CLAUSE_POS = new Set(["VERB", "AUX", "ADP"]);
|
|
45
|
+
|
|
46
|
+
/** 数の前に書いて量の一部になる語(約 3 か月、およそ 3 日、全 5 店舗)や、文の中の語(本社は 5階)の品詞。 */
|
|
47
|
+
const QUANTITY_LEAD_POS = new Set(["ADJ", "ADV", "ADP", "AUX", "VERB", "SCONJ", "PART"]);
|
|
48
|
+
|
|
49
|
+
/** 値の組の最も短い長さ。1 字の語(計 5件)は量の前置き。 */
|
|
50
|
+
const MIN_PART_LENGTH = 2;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 「所属:経済学部 経済学科 1年」「物件:メゾンかえで 203号室」「振込先:みなと銀行 普通 1234567」のように、項目名とコロンの後ろに
|
|
54
|
+
* 値の組を空白で並べた行で、組と、数で始まる次の組のあいだの空白。値の区切りで、日本語と数字のあいだの空け方ではない。
|
|
55
|
+
* boundary は空けた数字の前の境目(text[offset] が空白)。tokens は文書全体の座標で、base は text の先頭の位置。
|
|
56
|
+
*/
|
|
57
|
+
export const isFieldSeparator = (text: string, boundary: Boundary, tokens: readonly Token[] | undefined, base: number): boolean => {
|
|
58
|
+
if (tokens === undefined || boundary.kind !== "before-digit" || !boundary.spaced) return false;
|
|
59
|
+
const line = lineAround(text, boundary.offset);
|
|
60
|
+
const label = FIELD_LABEL.exec(text.slice(line.start, line.end));
|
|
61
|
+
if (label === null || SENTENCE_END.test(text.slice(line.start, line.end))) return false;
|
|
62
|
+
const valueStart = line.start + label[0].length;
|
|
63
|
+
if (tokensIn(tokens, { start: valueStart, end: line.end }, base).some((token) => CLAUSE_POS.has(token.pos))) return false;
|
|
64
|
+
const partStart = Math.max(wordStart(text, boundary.offset), valueStart);
|
|
65
|
+
const partEnd = tokens.find((token) => token.span.end === base + boundary.offset);
|
|
66
|
+
return boundary.offset - partStart >= MIN_PART_LENGTH && partEnd !== undefined && !QUANTITY_LEAD_POS.has(partEnd.pos);
|
|
67
|
+
};
|
|
68
|
+
|
|
37
69
|
/**
|
|
38
70
|
* 「株式会社あおば電子 経営企画部 IR担当」「東京本社 PR室」のように、名前だけを空白で並べた行で、空白の両側がどちらも組織の名前のときの空白。
|
|
39
71
|
* 名前と名前の区切りで、英字の前後の空け方ではない。文の中の空白(この IT システムは)や、組織の名前でない語のあいだ(生成AI 活用事例、Patch リリース)は区切りと読まない。
|