chaffjs 0.34.0 → 0.35.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/capitals-passage.d.ts +3 -0
- package/dist/capitals-passage.d.ts.map +1 -0
- package/dist/capitals-passage.js +26 -0
- package/dist/capitals-passage.js.map +1 -0
- package/dist/coined-hyphen-name.d.ts +13 -0
- package/dist/coined-hyphen-name.d.ts.map +1 -0
- package/dist/coined-hyphen-name.js +21 -0
- package/dist/coined-hyphen-name.js.map +1 -0
- package/dist/company-names.d.ts +6 -1
- package/dist/company-names.d.ts.map +1 -1
- package/dist/company-names.js +22 -3
- package/dist/company-names.js.map +1 -1
- package/dist/detectors/acronym-context.d.ts +1 -1
- package/dist/detectors/acronym-context.d.ts.map +1 -1
- package/dist/detectors/acronym-context.js +4 -4
- package/dist/detectors/acronym-context.js.map +1 -1
- package/dist/detectors/acronym-definition.d.ts +13 -0
- package/dist/detectors/acronym-definition.d.ts.map +1 -0
- package/dist/detectors/acronym-definition.js +90 -0
- package/dist/detectors/acronym-definition.js.map +1 -0
- package/dist/detectors/acronym-lone.d.ts +13 -0
- package/dist/detectors/acronym-lone.d.ts.map +1 -0
- package/dist/detectors/acronym-lone.js +22 -0
- package/dist/detectors/acronym-lone.js.map +1 -0
- package/dist/detectors/acronym-name.d.ts +55 -0
- package/dist/detectors/acronym-name.d.ts.map +1 -0
- package/dist/detectors/acronym-name.js +75 -0
- package/dist/detectors/acronym-name.js.map +1 -0
- package/dist/detectors/capitalised-term.d.ts +14 -2
- package/dist/detectors/capitalised-term.d.ts.map +1 -1
- package/dist/detectors/capitalised-term.js +115 -4
- package/dist/detectors/capitalised-term.js.map +1 -1
- package/dist/detectors/caps-caption.d.ts +11 -0
- package/dist/detectors/caps-caption.d.ts.map +1 -0
- package/dist/detectors/caps-caption.js +51 -0
- package/dist/detectors/caps-caption.js.map +1 -0
- package/dist/detectors/char-shape.d.ts.map +1 -1
- package/dist/detectors/char-shape.js +47 -18
- package/dist/detectors/char-shape.js.map +1 -1
- package/dist/detectors/clause-shape.d.ts +1 -1
- package/dist/detectors/clause-shape.d.ts.map +1 -1
- package/dist/detectors/clause-shape.js +9 -4
- package/dist/detectors/clause-shape.js.map +1 -1
- package/dist/detectors/concrete-evidence.d.ts +13 -1
- package/dist/detectors/concrete-evidence.d.ts.map +1 -1
- package/dist/detectors/concrete-evidence.js +59 -0
- package/dist/detectors/concrete-evidence.js.map +1 -1
- package/dist/detectors/contraction-minority.d.ts +11 -0
- package/dist/detectors/contraction-minority.d.ts.map +1 -0
- package/dist/detectors/contraction-minority.js +10 -0
- package/dist/detectors/contraction-minority.js.map +1 -0
- package/dist/detectors/counter-edge.d.ts +5 -0
- package/dist/detectors/counter-edge.d.ts.map +1 -1
- package/dist/detectors/counter-edge.js +23 -0
- package/dist/detectors/counter-edge.js.map +1 -1
- package/dist/detectors/date-any-year.d.ts +6 -3
- package/dist/detectors/date-any-year.d.ts.map +1 -1
- package/dist/detectors/date-any-year.js +47 -10
- package/dist/detectors/date-any-year.js.map +1 -1
- package/dist/detectors/date-heading-year.d.ts +18 -0
- package/dist/detectors/date-heading-year.d.ts.map +1 -0
- package/dist/detectors/date-heading-year.js +38 -0
- package/dist/detectors/date-heading-year.js.map +1 -0
- package/dist/detectors/date-year.d.ts +12 -2
- package/dist/detectors/date-year.d.ts.map +1 -1
- package/dist/detectors/date-year.js +31 -6
- package/dist/detectors/date-year.js.map +1 -1
- package/dist/detectors/defined-term-form.d.ts.map +1 -1
- package/dist/detectors/defined-term-form.js +26 -5
- package/dist/detectors/defined-term-form.js.map +1 -1
- package/dist/detectors/due-date.d.ts.map +1 -1
- package/dist/detectors/due-date.js +6 -0
- package/dist/detectors/due-date.js.map +1 -1
- package/dist/detectors/en-shape.d.ts.map +1 -1
- package/dist/detectors/en-shape.js +5 -1
- package/dist/detectors/en-shape.js.map +1 -1
- package/dist/detectors/fact-consistency.d.ts.map +1 -1
- package/dist/detectors/fact-consistency.js +3 -2
- package/dist/detectors/fact-consistency.js.map +1 -1
- package/dist/detectors/gram-passages.d.ts +11 -0
- package/dist/detectors/gram-passages.d.ts.map +1 -0
- package/dist/detectors/gram-passages.js +26 -0
- package/dist/detectors/gram-passages.js.map +1 -0
- package/dist/detectors/heading-end-mark.d.ts +8 -3
- package/dist/detectors/heading-end-mark.d.ts.map +1 -1
- package/dist/detectors/heading-end-mark.js +18 -8
- package/dist/detectors/heading-end-mark.js.map +1 -1
- package/dist/detectors/heading-names.d.ts +7 -0
- package/dist/detectors/heading-names.d.ts.map +1 -0
- package/dist/detectors/heading-names.js +24 -0
- package/dist/detectors/heading-names.js.map +1 -0
- package/dist/detectors/heading-numbering.d.ts.map +1 -1
- package/dist/detectors/heading-numbering.js +7 -2
- package/dist/detectors/heading-numbering.js.map +1 -1
- package/dist/detectors/inch-mark.d.ts +10 -0
- package/dist/detectors/inch-mark.d.ts.map +1 -0
- package/dist/detectors/inch-mark.js +48 -0
- package/dist/detectors/inch-mark.js.map +1 -0
- package/dist/detectors/lexicon.d.ts +0 -1
- package/dist/detectors/lexicon.d.ts.map +1 -1
- package/dist/detectors/lexicon.js +29 -23
- package/dist/detectors/lexicon.js.map +1 -1
- package/dist/detectors/mark-shape.d.ts +6 -3
- package/dist/detectors/mark-shape.d.ts.map +1 -1
- package/dist/detectors/mark-shape.js +12 -6
- package/dist/detectors/mark-shape.js.map +1 -1
- package/dist/detectors/name-variant-apart.d.ts +27 -0
- package/dist/detectors/name-variant-apart.d.ts.map +1 -0
- package/dist/detectors/name-variant-apart.js +51 -0
- package/dist/detectors/name-variant-apart.js.map +1 -0
- package/dist/detectors/name-variant.d.ts.map +1 -1
- package/dist/detectors/name-variant.js +18 -11
- package/dist/detectors/name-variant.js.map +1 -1
- package/dist/detectors/numeral-word.d.ts +15 -0
- package/dist/detectors/numeral-word.d.ts.map +1 -0
- package/dist/detectors/numeral-word.js +26 -0
- package/dist/detectors/numeral-word.js.map +1 -0
- package/dist/detectors/organisation-run.d.ts +14 -0
- package/dist/detectors/organisation-run.d.ts.map +1 -0
- package/dist/detectors/organisation-run.js +73 -0
- package/dist/detectors/organisation-run.js.map +1 -0
- package/dist/detectors/orthographic-variant.d.ts.map +1 -1
- package/dist/detectors/orthographic-variant.js +13 -5
- package/dist/detectors/orthographic-variant.js.map +1 -1
- package/dist/detectors/outline-label.d.ts +14 -0
- package/dist/detectors/outline-label.d.ts.map +1 -0
- package/dist/detectors/outline-label.js +32 -0
- package/dist/detectors/outline-label.js.map +1 -0
- package/dist/detectors/page-marker.d.ts +6 -0
- package/dist/detectors/page-marker.d.ts.map +1 -0
- package/dist/detectors/page-marker.js +19 -0
- package/dist/detectors/page-marker.js.map +1 -0
- package/dist/detectors/parallel-definition.d.ts +16 -0
- package/dist/detectors/parallel-definition.d.ts.map +1 -0
- package/dist/detectors/parallel-definition.js +51 -0
- package/dist/detectors/parallel-definition.js.map +1 -0
- package/dist/detectors/reference-text.d.ts.map +1 -1
- package/dist/detectors/reference-text.js +1 -0
- package/dist/detectors/reference-text.js.map +1 -1
- package/dist/detectors/registry/unfilled-placeholder.d.ts.map +1 -1
- package/dist/detectors/registry/unfilled-placeholder.js +2 -2
- package/dist/detectors/registry/unfilled-placeholder.js.map +1 -1
- package/dist/detectors/registry/vague-density.js +1 -1
- package/dist/detectors/registry/vague-density.js.map +1 -1
- package/dist/detectors/request-owner.d.ts +6 -2
- package/dist/detectors/request-owner.d.ts.map +1 -1
- package/dist/detectors/request-owner.js +57 -7
- package/dist/detectors/request-owner.js.map +1 -1
- package/dist/detectors/requirement-smell.d.ts.map +1 -1
- package/dist/detectors/requirement-smell.js +10 -2
- package/dist/detectors/requirement-smell.js.map +1 -1
- package/dist/detectors/sentence-length.d.ts.map +1 -1
- package/dist/detectors/sentence-length.js +24 -16
- package/dist/detectors/sentence-length.js.map +1 -1
- package/dist/detectors/sentence-rhythm.d.ts.map +1 -1
- package/dist/detectors/sentence-rhythm.js +6 -3
- package/dist/detectors/sentence-rhythm.js.map +1 -1
- package/dist/detectors/series-label.d.ts +2 -1
- package/dist/detectors/series-label.d.ts.map +1 -1
- package/dist/detectors/series-label.js +10 -4
- package/dist/detectors/series-label.js.map +1 -1
- package/dist/detectors/signals.d.ts +4 -0
- package/dist/detectors/signals.d.ts.map +1 -1
- package/dist/detectors/signals.js +95 -36
- package/dist/detectors/signals.js.map +1 -1
- package/dist/detectors/structure-tree.d.ts.map +1 -1
- package/dist/detectors/structure-tree.js +8 -7
- package/dist/detectors/structure-tree.js.map +1 -1
- package/dist/detectors/template-sections.d.ts +14 -0
- package/dist/detectors/template-sections.d.ts.map +1 -0
- package/dist/detectors/template-sections.js +25 -0
- package/dist/detectors/template-sections.js.map +1 -0
- package/dist/detectors/titled-run.d.ts +22 -0
- package/dist/detectors/titled-run.d.ts.map +1 -0
- package/dist/detectors/titled-run.js +34 -0
- package/dist/detectors/titled-run.js.map +1 -0
- package/dist/detectors/unbalanced-bracket.d.ts.map +1 -1
- package/dist/detectors/unbalanced-bracket.js +4 -0
- package/dist/detectors/unbalanced-bracket.js.map +1 -1
- package/dist/detectors/undefined-term.d.ts.map +1 -1
- package/dist/detectors/undefined-term.js +8 -0
- package/dist/detectors/undefined-term.js.map +1 -1
- package/dist/detectors/unfilled-placeholder.d.ts +4 -0
- package/dist/detectors/unfilled-placeholder.d.ts.map +1 -0
- package/dist/detectors/unfilled-placeholder.js +9 -0
- package/dist/detectors/unfilled-placeholder.js.map +1 -0
- package/dist/detectors/vague-density.d.ts +5 -0
- package/dist/detectors/vague-density.d.ts.map +1 -0
- package/dist/detectors/vague-density.js +52 -0
- package/dist/detectors/vague-density.js.map +1 -0
- package/dist/facts/fact-scope.d.ts +10 -1
- package/dist/facts/fact-scope.d.ts.map +1 -1
- package/dist/facts/fact-scope.js +34 -3
- package/dist/facts/fact-scope.js.map +1 -1
- package/dist/facts/table-facts.d.ts +12 -2
- package/dist/facts/table-facts.d.ts.map +1 -1
- package/dist/facts/table-facts.js +22 -4
- package/dist/facts/table-facts.js.map +1 -1
- package/dist/grade-api.d.ts +9 -2
- package/dist/grade-api.d.ts.map +1 -1
- package/dist/grade-api.js +31 -15
- package/dist/grade-api.js.map +1 -1
- package/dist/heading-numbering.d.ts +5 -2
- package/dist/heading-numbering.d.ts.map +1 -1
- package/dist/heading-numbering.js +27 -6
- package/dist/heading-numbering.js.map +1 -1
- package/dist/name-end-letter.d.ts +5 -0
- package/dist/name-end-letter.d.ts.map +1 -1
- package/dist/name-end-letter.js +8 -0
- package/dist/name-end-letter.js.map +1 -1
- package/dist/name-given-before.d.ts +15 -0
- package/dist/name-given-before.d.ts.map +1 -0
- package/dist/name-given-before.js +30 -0
- package/dist/name-given-before.js.map +1 -0
- package/dist/name-separator.d.ts +2 -2
- package/dist/name-separator.d.ts.map +1 -1
- package/dist/name-separator.js +56 -5
- package/dist/name-separator.js.map +1 -1
- package/dist/name-variants.d.ts +3 -1
- package/dist/name-variants.d.ts.map +1 -1
- package/dist/name-variants.js +8 -7
- package/dist/name-variants.js.map +1 -1
- package/dist/name-word-order.d.ts +9 -5
- package/dist/name-word-order.d.ts.map +1 -1
- package/dist/name-word-order.js +47 -15
- package/dist/name-word-order.js.map +1 -1
- package/dist/nominal-stem.d.ts +13 -0
- package/dist/nominal-stem.d.ts.map +1 -0
- package/dist/nominal-stem.js +32 -0
- package/dist/nominal-stem.js.map +1 -0
- package/dist/sentence-load.d.ts +2 -1
- package/dist/sentence-load.d.ts.map +1 -1
- package/dist/sentence-load.js +6 -2
- package/dist/sentence-load.js.map +1 -1
- package/dist/structure/cited-document-section.d.ts +7 -0
- package/dist/structure/cited-document-section.d.ts.map +1 -0
- package/dist/structure/cited-document-section.js +33 -0
- package/dist/structure/cited-document-section.js.map +1 -0
- package/dist/structure/html-text-blocks.d.ts +8 -0
- package/dist/structure/html-text-blocks.d.ts.map +1 -0
- package/dist/structure/html-text-blocks.js +47 -0
- package/dist/structure/html-text-blocks.js.map +1 -0
- package/dist/structure/issues.d.ts +1 -1
- package/dist/structure/issues.d.ts.map +1 -1
- package/dist/structure/issues.js +19 -4
- package/dist/structure/issues.js.map +1 -1
- package/dist/structure/labelled-date-order.d.ts +4 -1
- package/dist/structure/labelled-date-order.d.ts.map +1 -1
- package/dist/structure/labelled-date-order.js +2 -2
- package/dist/structure/labelled-date-order.js.map +1 -1
- package/dist/structure/labelled-order.d.ts +3 -0
- package/dist/structure/labelled-order.d.ts.map +1 -1
- package/dist/structure/labelled-order.js +8 -3
- package/dist/structure/labelled-order.js.map +1 -1
- package/dist/structure/list-numbering.d.ts +20 -4
- package/dist/structure/list-numbering.d.ts.map +1 -1
- package/dist/structure/list-numbering.js +65 -9
- package/dist/structure/list-numbering.js.map +1 -1
- package/dist/structure/listed-paragraph.d.ts +16 -0
- package/dist/structure/listed-paragraph.d.ts.map +1 -0
- package/dist/structure/listed-paragraph.js +37 -0
- package/dist/structure/listed-paragraph.js.map +1 -0
- package/dist/structure/numbering.d.ts +3 -2
- package/dist/structure/numbering.d.ts.map +1 -1
- package/dist/structure/numbering.js +31 -6
- package/dist/structure/numbering.js.map +1 -1
- package/dist/structure/reference-text.d.ts +9 -1
- package/dist/structure/reference-text.d.ts.map +1 -1
- package/dist/structure/reference-text.js +21 -3
- package/dist/structure/reference-text.js.map +1 -1
- package/dist/structure/section-names.d.ts +12 -0
- package/dist/structure/section-names.d.ts.map +1 -0
- package/dist/structure/section-names.js +26 -0
- package/dist/structure/section-names.js.map +1 -0
- package/dist/structure/versioned-label.d.ts +16 -0
- package/dist/structure/versioned-label.d.ts.map +1 -0
- package/dist/structure/versioned-label.js +48 -0
- package/dist/structure/versioned-label.js.map +1 -0
- package/dist/structure/written-headings.d.ts +31 -0
- package/dist/structure/written-headings.d.ts.map +1 -0
- package/dist/structure/written-headings.js +119 -0
- package/dist/structure/written-headings.js.map +1 -0
- package/dist/structure/written-resolve.d.ts +12 -0
- package/dist/structure/written-resolve.d.ts.map +1 -0
- package/dist/structure/written-resolve.js +49 -0
- package/dist/structure/written-resolve.js.map +1 -0
- package/package.json +3 -3
- package/rules/all-caps-shouting.yaml +2 -2
- package/rules/concrete-evidence-density.yaml +2 -2
- package/rules/contraction-consistency.yaml +13 -13
- package/rules/dangling-reference.yaml +2 -2
- package/rules/date-without-year.yaml +2 -2
- package/rules/defined-name-repeated.yaml +2 -2
- package/rules/fact-conflict.yaml +2 -2
- package/rules/heading-end-punctuation-mix.yaml +2 -2
- package/rules/heading-numbering-mix.yaml +2 -2
- package/rules/latin-spacing.yaml +2 -2
- package/rules/max-kanji-continuous.yaml +5 -3
- package/rules/max-sentence-length.yaml +3 -2
- package/rules/name-variant.yaml +2 -2
- package/rules/ngram-repetition.yaml +2 -2
- package/rules/numbering-gap.yaml +2 -2
- package/rules/orthographic-variant.yaml +2 -2
- package/rules/reference-title-mismatch.yaml +2 -2
- package/rules/repeated-sentence-ending.yaml +3 -2
- package/rules/request-without-deadline.yaml +2 -2
- package/rules/requirement-smell.yaml +2 -2
- package/rules/sentence-rhythm.yaml +2 -2
- package/rules/title-case-consistency.yaml +2 -2
- package/rules/unbalanced-bracket.yaml +2 -2
- package/rules/undefined-acronym.yaml +11 -2
- package/rules/undefined-term.yaml +2 -2
- package/rules/unfilled-placeholder.yaml +2 -2
- package/rules/vague-word-density.yaml +2 -2
- package/src/capitals-passage.ts +31 -0
- package/src/coined-hyphen-name.ts +27 -0
- package/src/company-names.ts +24 -3
- package/src/detectors/acronym-context.ts +5 -6
- package/src/detectors/acronym-definition.ts +123 -0
- package/src/detectors/acronym-lone.ts +26 -0
- package/src/detectors/acronym-name.ts +117 -0
- package/src/detectors/capitalised-term.ts +154 -4
- package/src/detectors/caps-caption.ts +61 -0
- package/src/detectors/char-shape.ts +74 -22
- package/src/detectors/clause-shape.ts +14 -4
- package/src/detectors/concrete-evidence.ts +77 -1
- package/src/detectors/contraction-minority.ts +11 -0
- package/src/detectors/counter-edge.ts +23 -0
- package/src/detectors/date-any-year.ts +56 -10
- package/src/detectors/date-heading-year.ts +55 -0
- package/src/detectors/date-year.ts +54 -9
- package/src/detectors/defined-term-form.ts +26 -5
- package/src/detectors/due-date.ts +10 -1
- package/src/detectors/en-shape.ts +5 -1
- package/src/detectors/fact-consistency.ts +3 -2
- package/src/detectors/gram-passages.ts +36 -0
- package/src/detectors/heading-end-mark.ts +23 -10
- package/src/detectors/heading-names.ts +31 -0
- package/src/detectors/heading-numbering.ts +7 -2
- package/src/detectors/inch-mark.ts +49 -0
- package/src/detectors/lexicon.ts +32 -21
- package/src/detectors/mark-shape.ts +12 -6
- package/src/detectors/name-variant-apart.ts +63 -0
- package/src/detectors/name-variant.ts +30 -12
- package/src/detectors/numeral-word.ts +33 -0
- package/src/detectors/organisation-run.ts +86 -0
- package/src/detectors/orthographic-variant.ts +22 -6
- package/src/detectors/outline-label.ts +48 -0
- package/src/detectors/page-marker.ts +26 -0
- package/src/detectors/parallel-definition.ts +64 -0
- package/src/detectors/reference-text.ts +1 -0
- package/src/detectors/registry/unfilled-placeholder.ts +2 -2
- package/src/detectors/registry/vague-density.ts +1 -1
- package/src/detectors/request-owner.ts +71 -8
- package/src/detectors/requirement-smell.ts +15 -2
- package/src/detectors/sentence-length.ts +14 -6
- package/src/detectors/sentence-rhythm.ts +8 -4
- package/src/detectors/series-label.ts +13 -4
- package/src/detectors/signals.ts +94 -26
- package/src/detectors/structure-tree.ts +10 -8
- package/src/detectors/template-sections.ts +37 -0
- package/src/detectors/titled-run.ts +43 -0
- package/src/detectors/unbalanced-bracket.ts +3 -0
- package/src/detectors/undefined-term.ts +8 -0
- package/src/detectors/unfilled-placeholder.ts +10 -0
- package/src/detectors/vague-density.ts +68 -0
- package/src/facts/fact-scope.ts +53 -4
- package/src/facts/table-facts.ts +43 -6
- package/src/grade-api.ts +38 -16
- package/src/heading-numbering.ts +35 -8
- package/src/name-end-letter.ts +9 -0
- package/src/name-given-before.ts +40 -0
- package/src/name-separator.ts +66 -5
- package/src/name-variants.ts +21 -11
- package/src/name-word-order.ts +62 -17
- package/src/nominal-stem.ts +36 -0
- package/src/sentence-load.ts +7 -2
- package/src/structure/cited-document-section.ts +42 -0
- package/src/structure/html-text-blocks.ts +45 -0
- package/src/structure/issues.ts +19 -4
- package/src/structure/labelled-date-order.ts +5 -2
- package/src/structure/labelled-order.ts +12 -3
- package/src/structure/list-numbering.ts +100 -11
- package/src/structure/listed-paragraph.ts +45 -0
- package/src/structure/numbering.ts +48 -19
- package/src/structure/reference-text.ts +25 -3
- package/src/structure/section-names.ts +32 -0
- package/src/structure/versioned-label.ts +52 -0
- package/src/structure/written-headings.ts +137 -0
- package/src/structure/written-resolve.ts +57 -0
package/src/grade-api.ts
CHANGED
|
@@ -11,10 +11,13 @@ import { withStyle } from "./config/style.ts";
|
|
|
11
11
|
import { withExtensions } from "./extension/load.ts";
|
|
12
12
|
import { GENRES } from "./genre.ts";
|
|
13
13
|
import { gradeItem, type GradeSetup } from "./grade/grade-item.ts";
|
|
14
|
-
import {
|
|
14
|
+
import { expectProblems } from "./grade/expect-read.ts";
|
|
15
|
+
import type { GradeExpect } from "./grade/expect.ts";
|
|
16
|
+
import { readItem, type GradeItem } from "./grade/item.ts";
|
|
15
17
|
import type { GradeResult } from "./grade/result.ts";
|
|
16
18
|
import { parseRubric, type Rubric } from "./grade/rubric.ts";
|
|
17
19
|
import { gradeSetup } from "./grade/setup.ts";
|
|
20
|
+
import { taskNamesOf } from "./grade/task-rubric.ts";
|
|
18
21
|
import { GRADE_TEXT } from "./grade/text.ts";
|
|
19
22
|
import { compareVariantGroups, type VariantComparison } from "./grade/variants.ts";
|
|
20
23
|
import { variantGroupsOf, type VariantInput } from "./grade/variants-input.ts";
|
|
@@ -36,6 +39,7 @@ export type {
|
|
|
36
39
|
SupportedFact,
|
|
37
40
|
} from "./grade/result.ts";
|
|
38
41
|
export type { OutputSize } from "./grade/rates.ts";
|
|
42
|
+
export type { GradeExpect } from "./grade/expect.ts";
|
|
39
43
|
export type { AiLevelCounts, GradeAiScore } from "./grade/ai-score.ts";
|
|
40
44
|
export { toScorer, type ChaffScore } from "./grade/scorer.ts";
|
|
41
45
|
export type { Disagreement, VariantColumn, VariantComparison, VariantRates } from "./grade/variants.ts";
|
|
@@ -56,6 +60,10 @@ export type GradeOptions = {
|
|
|
56
60
|
readonly genre?: string;
|
|
57
61
|
/** The prompt, model or setting that produced the output, kept on the result for compareVariants(). */
|
|
58
62
|
readonly variant?: string;
|
|
63
|
+
/** The rubric task to grade under: one of chaff.yaml's `grade.tasks`. The top-level rubric when left out. */
|
|
64
|
+
readonly task?: string;
|
|
65
|
+
/** Facts the output must state (`facts`) and must not (`absent`), each one fact as `chaff compare` reads it. */
|
|
66
|
+
readonly expect?: Partial<GradeExpect>;
|
|
59
67
|
/** Run the experimental rules too, as --experimental does. chaff.yaml's `experimental` when left out. */
|
|
60
68
|
readonly experimental?: boolean;
|
|
61
69
|
/** chaff.yaml's path, or settings already read. Without it chaff's defaults apply and no file is read. */
|
|
@@ -125,36 +133,32 @@ const rubricOf = (config: Config): Rubric | undefined => {
|
|
|
125
133
|
*/
|
|
126
134
|
const setups = new WeakMap<Config, Map<string, Promise<GradeSetup>>>();
|
|
127
135
|
|
|
128
|
-
const setupFor = (config: Config, experimental: boolean, languages: readonly string[]): Promise<GradeSetup> => {
|
|
136
|
+
const setupFor = (config: Config, rubric: Rubric | undefined, experimental: boolean, languages: readonly string[]): Promise<GradeSetup> => {
|
|
129
137
|
const run = { experimental, genre: undefined };
|
|
130
138
|
const own = setups.get(config) ?? new Map<string, Promise<GradeSetup>>();
|
|
131
139
|
setups.set(config, own);
|
|
132
|
-
|
|
140
|
+
// The rubric is in the key: an item's task was checked against this rubric, so the setup must grade by the same one.
|
|
141
|
+
const key = `${String(experimental)}\n${languages.join(",")}\n${JSON.stringify(rubric) ?? ""}`;
|
|
133
142
|
const known = own.get(key);
|
|
134
143
|
if (known !== undefined) return known;
|
|
135
|
-
const made = remember(own, key, gradeSetup(config, run, languages,
|
|
144
|
+
const made = remember(own, key, gradeSetup(config, run, languages, rubric));
|
|
136
145
|
// A language package that failed to load may be installed before the next call: do not keep the failure.
|
|
137
146
|
made.catch(() => own.delete(key));
|
|
138
147
|
return made;
|
|
139
148
|
};
|
|
140
149
|
|
|
141
150
|
/** The setup, or GradeInputError when a language package cannot be loaded, as `chaff grade` ends with 2 then. */
|
|
142
|
-
const loadedSetup = async (config: Config, experimental: boolean, languages: readonly string[]): Promise<GradeSetup> => {
|
|
151
|
+
const loadedSetup = async (config: Config, rubric: Rubric | undefined, experimental: boolean, languages: readonly string[]): Promise<GradeSetup> => {
|
|
143
152
|
try {
|
|
144
|
-
return await setupFor(config, experimental, languages);
|
|
153
|
+
return await setupFor(config, rubric, experimental, languages);
|
|
145
154
|
} catch (error) {
|
|
146
155
|
if (error instanceof GradeInputError) throw error;
|
|
147
156
|
throw new GradeInputError([TEXT.cannotLoad(error instanceof Error ? error.message : String(error))]);
|
|
148
157
|
}
|
|
149
158
|
};
|
|
150
159
|
|
|
151
|
-
/**
|
|
152
|
-
|
|
153
|
-
* `sources`, and pass or fail by chaff.yaml's `grade:` or the default. Reads a file only when `config` is a path, and
|
|
154
|
-
* never writes one. Throws GradeInputError for an input or a chaff.yaml `chaff grade` would refuse with exit 2.
|
|
155
|
-
*/
|
|
156
|
-
export const grade = async (output: string, options: GradeOptions = {}): Promise<GradeResult> => {
|
|
157
|
-
const config = await configOf(options.config);
|
|
160
|
+
/** The options read as one line of `chaff grade`'s input, refused as that line would be. `tasks`: those the rubric defines. */
|
|
161
|
+
const itemOf = (output: string, options: GradeOptions, tasks: readonly string[]): GradeItem => {
|
|
158
162
|
const raw = {
|
|
159
163
|
id: options.id ?? "output",
|
|
160
164
|
output,
|
|
@@ -165,11 +169,29 @@ export const grade = async (output: string, options: GradeOptions = {}): Promise
|
|
|
165
169
|
language: options.language,
|
|
166
170
|
genre: options.genre,
|
|
167
171
|
variant: options.variant,
|
|
172
|
+
task: options.task,
|
|
173
|
+
expect: options.expect,
|
|
168
174
|
};
|
|
169
|
-
const read = readItem(raw, { isLanguage: (language) => packageFor(language) !== undefined, genres: GENRES });
|
|
175
|
+
const read = readItem(raw, { isLanguage: (language) => packageFor(language) !== undefined, genres: GENRES, tasks });
|
|
170
176
|
if ("problem" in read) throw new GradeInputError([TEXT.problem(read.problem)]);
|
|
171
|
-
|
|
172
|
-
|
|
177
|
+
return read.item;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Grades one output (spec §29.6): its findings and rates, its facts against `reference`, its `citations` against
|
|
182
|
+
* `sources`, its `expect` facts, and pass or fail by chaff.yaml's `grade:` (its `task`'s rubric when named) or the
|
|
183
|
+
* default. Reads a file only when `config` is a path, and never writes one. Throws GradeInputError for an input or a
|
|
184
|
+
* chaff.yaml `chaff grade` would refuse with exit 2, an unknown `task` or an `expect` entry that is not one fact among them.
|
|
185
|
+
*/
|
|
186
|
+
export const grade = async (output: string, options: GradeOptions = {}): Promise<GradeResult> => {
|
|
187
|
+
const config = await configOf(options.config);
|
|
188
|
+
const rubric = rubricOf(config);
|
|
189
|
+
const item = itemOf(output, options, taskNamesOf(rubric));
|
|
190
|
+
const languages = item.language === undefined ? [] : [item.language];
|
|
191
|
+
const setup = await loadedSetup(config, rubric, options.experimental ?? config.experimental, languages);
|
|
192
|
+
const problems = await expectProblems(item, 0, setup);
|
|
193
|
+
if (problems.length > 0) throw new GradeInputError(problems.map((problem) => TEXT.problem(problem)));
|
|
194
|
+
return gradeItem(item, setup);
|
|
173
195
|
};
|
|
174
196
|
|
|
175
197
|
/**
|
package/src/heading-numbering.ts
CHANGED
|
@@ -41,16 +41,31 @@ const labelPattern = (label: NumberLabel): RegExp =>
|
|
|
41
41
|
/** 見出しの頭の強調の印と空白。「**1. 概要**」も「1. 概要」と読む。 */
|
|
42
42
|
const LEAD = /^[\s*_]+/u;
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
type NumberMark = { readonly style: string; readonly mark: string };
|
|
45
|
+
|
|
46
|
+
const markOf = (heading: string, labels: readonly NumberLabel[]): NumberMark => {
|
|
46
47
|
const text = heading.replace(LEAD, "");
|
|
47
48
|
const label = labels.find((candidate) => labelPattern(candidate).test(text));
|
|
48
|
-
if (label !== undefined) return `label:${label.word.toLowerCase()}
|
|
49
|
-
|
|
49
|
+
if (label !== undefined) return { style: `label:${label.word.toLowerCase()}`, mark: labelPattern(label).exec(text)?.[0] ?? "" };
|
|
50
|
+
const shape = SHAPES.find(([, pattern]) => pattern.test(text));
|
|
51
|
+
return shape === undefined ? { style: UNNUMBERED, mark: "" } : { style: shape[0], mark: shape[1].exec(text)?.[0] ?? "" };
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** 見出しの言葉から、番号の書き方を読む。番号を持つ語(第1章)を先に、字だけの形(1.)を後に見る。 */
|
|
55
|
+
export const numberingStyleOf = (heading: string, labels: readonly NumberLabel[]): string => markOf(heading, labels).style;
|
|
56
|
+
|
|
57
|
+
/** 番号の中の数の一つ一つ(「7.1」の 7 と 1、「Chapter II」の II)。番号は印の最後に来るので、語の頭の大文字(Chapter の C)は最後にならない。 */
|
|
58
|
+
const NUMBER_TOKEN = /[0-90-9]+|[A-ZⅠ-Ⅻ①-⑳一二三四五六七八九十百]+/gu;
|
|
59
|
+
const FIRST_NUMBERS: ReadonlySet<string> = new Set(["1", "1", "I", "Ⅰ", "A", "①", "一"]);
|
|
60
|
+
|
|
61
|
+
/** 番号が並びの最初(1.、1.1、第1章、A.、①)か。最後の数が最初の数のときで、番号の無い見出しは並びを始めない。 */
|
|
62
|
+
export const opensNumbering = (heading: string, labels: readonly NumberLabel[]): boolean => {
|
|
63
|
+
const last = markOf(heading, labels).mark.match(NUMBER_TOKEN)?.at(-1);
|
|
64
|
+
return last !== undefined && FIRST_NUMBERS.has(last);
|
|
50
65
|
};
|
|
51
66
|
|
|
52
|
-
/** 見出し 1 つ。depth は深さ(#の数)、style は番号の書き方、skipped
|
|
53
|
-
export type NumberedHeading = { readonly depth: number; readonly style: string; readonly skipped: boolean };
|
|
67
|
+
/** 見出し 1 つ。depth は深さ(#の数)、style は番号の書き方、skipped は番号を付けない前後の見出し(目次・参考文献)、opens は番号が 1 から始まるか。 */
|
|
68
|
+
export type NumberedHeading = { readonly depth: number; readonly style: string; readonly skipped: boolean; readonly opens: boolean };
|
|
54
69
|
|
|
55
70
|
/** 親(直前の浅い見出し)と深さが同じ見出しの組。番号は兄弟の見出しのあいだで揃える。位置は渡した並びの添え字。 */
|
|
56
71
|
export const siblingGroups = (headings: readonly NumberedHeading[]): number[][] => {
|
|
@@ -87,13 +102,25 @@ const minorityBy = (members: readonly number[], keyOf: (at: number) => string):
|
|
|
87
102
|
const minoritiesOf = (split: Split | undefined, kind: NumberingMinority["kind"]): NumberingMinority[] =>
|
|
88
103
|
split === undefined ? [] : split.minority.map((at) => ({ at, kind, majority: split.majority, count: split.count, example: split.example }));
|
|
89
104
|
|
|
105
|
+
/** 番号の並びの最初の兄弟が 1 から始まるとき、その前の番号の無い兄弟(文書の題、「Applies to England」)は並びの外。番号付きの兄弟が二つ以上あるときだけ。 */
|
|
106
|
+
const MIN_NUMBERED_RUN = 2;
|
|
107
|
+
|
|
108
|
+
const leadingFrontMatter = (members: readonly number[], headings: readonly NumberedHeading[]): ReadonlySet<number> => {
|
|
109
|
+
const numbered = members.filter((at) => (headings[at]?.style ?? UNNUMBERED) !== UNNUMBERED);
|
|
110
|
+
const first = numbered[0];
|
|
111
|
+
if (first === undefined || numbered.length < MIN_NUMBERED_RUN || headings[first]?.opens !== true) return new Set();
|
|
112
|
+
return new Set(members.filter((at) => at < first));
|
|
113
|
+
};
|
|
114
|
+
|
|
90
115
|
/**
|
|
91
116
|
* 兄弟の見出しのうち、少ないほうの書き方のもの。先に番号の有無を比べ(「1. 概要」「2. 手順」「まとめ」の「まとめ」)、
|
|
92
|
-
* 番号の付いた見出しのあいだで書き方を比べる(「1.」「2.」「3)」の「3
|
|
117
|
+
* 番号の付いた見出しのあいだで書き方を比べる(「1.」「2.」「3)」の「3)」)。同じ数なら言わない(二つだけの兄弟は必ず同じ数か一つの書き方)。前後の見出し(目次・参考文献)と、1 から始まる番号の前の見出しは数えない。
|
|
93
118
|
*/
|
|
94
119
|
export const numberingMinorities = (headings: readonly NumberedHeading[]): NumberingMinority[] =>
|
|
95
120
|
siblingGroups(headings).flatMap((group) => {
|
|
96
|
-
const
|
|
121
|
+
const listed = group.filter((at) => headings[at]?.skipped !== true);
|
|
122
|
+
const front = leadingFrontMatter(listed, headings);
|
|
123
|
+
const members = listed.filter((at) => !front.has(at));
|
|
97
124
|
const styleOf = (at: number): string => headings[at]?.style ?? UNNUMBERED;
|
|
98
125
|
const numbered = members.filter((at) => styleOf(at) !== UNNUMBERED);
|
|
99
126
|
return [
|
package/src/name-end-letter.ts
CHANGED
|
@@ -31,6 +31,15 @@ export type WordUse = { readonly ordinary: ReadonlySet<string>; readonly common:
|
|
|
31
31
|
|
|
32
32
|
export const NO_WORD_USE: WordUse = { ordinary: new Set(), common: new Set() };
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* 終わりの重なった字の一つの抜け(Licensee と License、Bennett と Bennet)で、どちらかを文書が小文字か普通名詞で書く(license)。
|
|
36
|
+
* 語の終わりの抜けと同じく、語の形の違い(License と、その許諾を受ける Licensee)。
|
|
37
|
+
*/
|
|
38
|
+
export const isWordFormOf = (left: string, right: string, use: WordUse): boolean => {
|
|
39
|
+
const [longer, shorter] = left.length > right.length ? [left, right] : [right, left];
|
|
40
|
+
return longer.slice(0, -1) === shorter && (use.common.has(longer) || use.common.has(shorter));
|
|
41
|
+
};
|
|
42
|
+
|
|
34
43
|
/**
|
|
35
44
|
* 名前の語(words、小文字)の at の語を usual と書いた名前があるとき、その違いを書き損じと見るか。違う語が最後の語(姓)なら、
|
|
36
45
|
* 終わりの一字の抜けか足しか、四字以上の一字違い(oneLetterApart)で、前の語がどれも文書の中で普通名詞として書かない語(Rachel、Mr)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Token } from "./plugin.ts";
|
|
2
|
+
|
|
3
|
+
// 姓のすぐ前に書いた名(Jeffrey D. Walker の Jeffrey)。名の違う二人(Jeffrey D. Walker と Christopher J. Waller)は、姓が一字違いでも
|
|
4
|
+
// 別の人で、書き損じではない。
|
|
5
|
+
|
|
6
|
+
const GIVEN_NAME = /^\p{Lu}\p{Ll}+$/u;
|
|
7
|
+
const INITIAL = /^\p{Lu}$/u;
|
|
8
|
+
const NEWLINE = /\n/u;
|
|
9
|
+
|
|
10
|
+
const isInitial = (token: Token | undefined): boolean => INITIAL.test(token?.surface ?? "");
|
|
11
|
+
|
|
12
|
+
/** 名と名前のあいだの頭文字(D.、D)を飛ばした、名の語の添字。 */
|
|
13
|
+
const givenIndexOf = (tokens: readonly Token[], index: number): number => {
|
|
14
|
+
if (tokens[index - 1]?.surface === "." && isInitial(tokens[index - 2])) return index - 3;
|
|
15
|
+
return isInitial(tokens[index - 1]) ? index - 2 : index - 1;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** 名前の書き方ごとの、すぐ前に書いた名のすべて。 */
|
|
19
|
+
export type GivenNames = ReadonlyMap<string, readonly string[]>;
|
|
20
|
+
|
|
21
|
+
export const givenNamesBySurface = (mentions: readonly { readonly surface: string; readonly givenName?: string | undefined }[]): GivenNames =>
|
|
22
|
+
mentions.reduce((names, { surface, givenName }) => {
|
|
23
|
+
const known = names.get(surface) ?? [];
|
|
24
|
+
return givenName === undefined || known.includes(givenName) ? names : names.set(surface, [...known, givenName]);
|
|
25
|
+
}, new Map<string, string[]>());
|
|
26
|
+
|
|
27
|
+
/** どちらの書き方にも名前のすぐ前の名があり、一つも同じでない(Jeffrey D. Walker と Christopher J. Waller)。別の人。 */
|
|
28
|
+
export const areOtherPeople = (left: readonly string[] = [], right: readonly string[] = []): boolean =>
|
|
29
|
+
left.length > 0 && right.length > 0 && !left.some((name) => right.includes(name));
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* tokens[index] で始まる名前のすぐ前の名。解析器が固有名詞と読んだ、大文字一字に小文字の続く語で、あいだに頭文字一つを挟んでよい。
|
|
33
|
+
* 行を越えては読まない(一行に一人ずつ並べた出席者の名簿)。
|
|
34
|
+
*/
|
|
35
|
+
export const givenNameBefore = (tokens: readonly Token[], index: number, source: string): string | undefined => {
|
|
36
|
+
const name = tokens[index];
|
|
37
|
+
const given = tokens[givenIndexOf(tokens, index)];
|
|
38
|
+
if (name === undefined || given === undefined || given.pos !== "PROPN" || !GIVEN_NAME.test(given.surface)) return undefined;
|
|
39
|
+
return NEWLINE.test(source.slice(given.span.end, name.span.start)) ? undefined : given.surface;
|
|
40
|
+
};
|
package/src/name-separator.ts
CHANGED
|
@@ -49,13 +49,64 @@ const QUANTITY_LEAD_POS = new Set(["ADJ", "ADV", "ADP", "AUX", "VERB", "SCONJ",
|
|
|
49
49
|
/** 値の組の最も短い長さ。1 字の語(計 5件)は量の前置き。 */
|
|
50
50
|
const MIN_PART_LENGTH = 2;
|
|
51
51
|
|
|
52
|
+
/** 組を囲む字(括弧・表の升目の線)。組が囲みの中で読点で並ぶこともある(電話 03-1234-5678、内線 3839)。 */
|
|
53
|
+
const LAST_ENCLOSURE_OPEN = /[((||][^((||]*$/u;
|
|
54
|
+
const ENCLOSURE_CLOSE = /[))||]/u;
|
|
55
|
+
|
|
56
|
+
/** 組と組を分ける読点。数の桁の区切り(1,000)のコンマは除く。 */
|
|
57
|
+
const PAIR_COMMA = /[、,]|(?<!\d),|,(?!\d)/gu;
|
|
58
|
+
|
|
59
|
+
/** 行の頭の字下げと箇条書きの印(「- 」「1. 」)。 */
|
|
60
|
+
const PAIR_LEAD = /^[ \t\u3000]*(?:(?:[-*+]|\d{1,3}[..))])[ \t\u3000]+)?/u;
|
|
61
|
+
|
|
62
|
+
/** 項目名: 空白・数字・区切りの字を含まない短い語。値: 数字で始まり、空白も区切りも文の終わりも含まない一続き(桁の区切りのコンマは含む)。 */
|
|
63
|
+
const PAIR_LABEL = /^[^\s\d、,,()()||::。!?]{2,12}$/u;
|
|
64
|
+
const PAIR_VALUE = /^\d(?:[^\s、,,()()||。!?]|(?<=\d),(?=\d))*$/u;
|
|
65
|
+
|
|
66
|
+
/** 項目名に出る品詞。名詞だけで、助詞・副詞(およそ)・接頭の語(約)を含めば、量の前置きか文。 */
|
|
67
|
+
const PAIR_LABEL_POS = new Set(["NOUN", "PROPN"]);
|
|
68
|
+
|
|
69
|
+
const SPACE_RUN = /^[ \t\u3000]*/u;
|
|
70
|
+
|
|
71
|
+
/** at を含む囲み: 括弧か表の升目の線の内側、無ければ行(字下げと箇条書きの印の後ろ)。 */
|
|
72
|
+
const enclosureAround = (text: string, line: Span, at: number): Span => {
|
|
73
|
+
const open = LAST_ENCLOSURE_OPEN.exec(text.slice(line.start, at));
|
|
74
|
+
const start = open === null ? line.start + (PAIR_LEAD.exec(text.slice(line.start, line.end))?.[0].length ?? 0) : line.start + open.index + 1;
|
|
75
|
+
const close = text.slice(at, line.end).search(ENCLOSURE_CLOSE);
|
|
76
|
+
return { start, end: close === -1 ? line.end : at + close };
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** 囲みの中で at を含む組。読点で区切り、前後の空白を除く。 */
|
|
80
|
+
const pairAround = (text: string, enclosure: Span, at: number): Span => {
|
|
81
|
+
const commas = [...text.slice(enclosure.start, enclosure.end).matchAll(PAIR_COMMA)].map((match) => enclosure.start + match.index);
|
|
82
|
+
const rawStart = (commas.filter((comma) => comma < at).at(-1) ?? enclosure.start - 1) + 1;
|
|
83
|
+
const rawEnd = commas.find((comma) => comma > at) ?? enclosure.end;
|
|
84
|
+
const start = rawStart + (SPACE_RUN.exec(text.slice(rawStart, at))?.[0].length ?? 0);
|
|
85
|
+
return { start, end: rawStart + text.slice(rawStart, rawEnd).trimEnd().length };
|
|
86
|
+
};
|
|
87
|
+
|
|
52
88
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
89
|
+
* 「最高速度 40km/h」「(内線 3839~40)」「- 定員 30名」のように、短い項目名と、それが名指す値を空白一つで並べただけの組の、名と値のあいだの空白。
|
|
90
|
+
* 組は行・括弧の中・表の升目にそれだけで書くか、読点で並べる。項目の区切りで、文の中の空け方ではない。
|
|
91
|
+
* 囲みの中に助詞や動詞がある(定員 30名で行います、参加者 30名、来場者50名を見込みます)、値の後ろに語が続く(内線 3839 まで)、
|
|
92
|
+
* 項目名が 1 字(約 30名)か名詞でない語を含む組は、文の中の空白として数える。
|
|
56
93
|
*/
|
|
57
|
-
|
|
58
|
-
|
|
94
|
+
const isLabelledValue = (text: string, boundary: Boundary, tokens: readonly Token[], base: number): boolean => {
|
|
95
|
+
const enclosure = enclosureAround(text, lineAround(text, boundary.offset), boundary.offset);
|
|
96
|
+
const pair = pairAround(text, enclosure, boundary.offset);
|
|
97
|
+
const label = { start: pair.start, end: boundary.offset };
|
|
98
|
+
if (!PAIR_LABEL.test(text.slice(label.start, label.end)) || !PAIR_VALUE.test(text.slice(boundary.offset + 1, pair.end))) return false;
|
|
99
|
+
if (SENTENCE_END.test(text.slice(enclosure.start, enclosure.end))) return false;
|
|
100
|
+
const labelTokens = tokensIn(tokens, label, base);
|
|
101
|
+
return (
|
|
102
|
+
labelTokens.length > 0 &&
|
|
103
|
+
labelTokens.every((token) => PAIR_LABEL_POS.has(token.pos)) &&
|
|
104
|
+
!tokensIn(tokens, enclosure, base).some((token) => CLAUSE_POS.has(token.pos))
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/** 項目名とコロンの後ろに値の組を並べた行の、組と、数で始まる次の組のあいだの空白か。 */
|
|
109
|
+
const isColonFieldPart = (text: string, boundary: Boundary, tokens: readonly Token[], base: number): boolean => {
|
|
59
110
|
const line = lineAround(text, boundary.offset);
|
|
60
111
|
const label = FIELD_LABEL.exec(text.slice(line.start, line.end));
|
|
61
112
|
if (label === null || SENTENCE_END.test(text.slice(line.start, line.end))) return false;
|
|
@@ -66,6 +117,16 @@ export const isFieldSeparator = (text: string, boundary: Boundary, tokens: reado
|
|
|
66
117
|
return boundary.offset - partStart >= MIN_PART_LENGTH && partEnd !== undefined && !QUANTITY_LEAD_POS.has(partEnd.pos);
|
|
67
118
|
};
|
|
68
119
|
|
|
120
|
+
/**
|
|
121
|
+
* 項目と値を分ける空白。「所属:経済学部 経済学科 1年」「振込先:みなと銀行 普通 1234567」のように項目名とコロンの後ろに値の組を並べた行の組と組のあいだか、
|
|
122
|
+
* 「最高速度 40km/h」「(内線 3839)」のように短い項目名と値を並べただけの組の、名と値のあいだ。値の区切りで、日本語と数字のあいだの空け方ではない。
|
|
123
|
+
* boundary は空けた数字の前の境目(text[offset] が空白)。tokens は文書全体の座標で、base は text の先頭の位置。
|
|
124
|
+
*/
|
|
125
|
+
export const isFieldSeparator = (text: string, boundary: Boundary, tokens: readonly Token[] | undefined, base: number): boolean => {
|
|
126
|
+
if (tokens === undefined || boundary.kind !== "before-digit" || !boundary.spaced) return false;
|
|
127
|
+
return isColonFieldPart(text, boundary, tokens, base) || isLabelledValue(text, boundary, tokens, base);
|
|
128
|
+
};
|
|
129
|
+
|
|
69
130
|
/**
|
|
70
131
|
* 「株式会社あおば電子 経営企画部 IR担当」「東京本社 PR室」のように、名前だけを空白で並べた行で、空白の両側がどちらも組織の名前のときの空白。
|
|
71
132
|
* 名前と名前の区切りで、英字の前後の空け方ではない。文の中の空白(この IT システムは)や、組織の名前でない語のあいだ(生成AI 活用事例、Patch リリース)は区切りと読まない。
|
package/src/name-variants.ts
CHANGED
|
@@ -2,7 +2,8 @@ import type { Token } from "./plugin.ts";
|
|
|
2
2
|
import { escapeRegExp } from "./orthography.ts";
|
|
3
3
|
import { nameCueAt, type NameCue, type NameCues } from "./name-cue.ts";
|
|
4
4
|
import { isNearSurname } from "./surname-near.ts";
|
|
5
|
-
import { isEndWordSlip, NO_WORD_USE, type WordUse } from "./name-end-letter.ts";
|
|
5
|
+
import { isEndWordSlip, isWordFormOf, NO_WORD_USE, type WordUse } from "./name-end-letter.ts";
|
|
6
|
+
import { areOtherPeople, givenNamesBySurface, type GivenNames } from "./name-given-before.ts";
|
|
6
7
|
import { foldedKeysOf, NO_SPELLING, type SpellingInput } from "./name-spelling-chars.ts";
|
|
7
8
|
import { NO_CHAR_READINGS, readingOfWords } from "./name-char-reading.ts";
|
|
8
9
|
import { isPersonRun } from "./name-person.ts";
|
|
@@ -17,7 +18,8 @@ import { heavierFirst, outweighs, type NameWeight } from "./name-weight.ts";
|
|
|
17
18
|
* 人名と読むか、敬称が付く)。cue は前後の語から見た名前らしさ(name-cue.ts)。ordinary は、ふつうの語でもある一語の名前で、
|
|
18
19
|
* 文書がその形をどこでも名前に使わないもの(name-ordinary-word.ts)。labelled は、人を書く欄(氏名:、Contractor:)に書いた名前
|
|
19
20
|
* (person-field.ts)。givenNameReading は、空白を挟んだ姓と名の名を、名の中での字の読みで読み直した読み(name-char-reading.ts の
|
|
20
|
-
* givenNameReadingOf)。解析器が名を別の読みに切ったとき(建太 を タテフトシ)のもう一通りの読み。
|
|
21
|
+
* givenNameReadingOf)。解析器が名を別の読みに切ったとき(建太 を タテフトシ)のもう一通りの読み。givenName は、名前のすぐ前に
|
|
22
|
+
* 書いた名(Jeffrey D. Walker の Jeffrey、name-given-before.ts)。
|
|
21
23
|
*/
|
|
22
24
|
export type NameMention = {
|
|
23
25
|
readonly surface: string;
|
|
@@ -29,6 +31,7 @@ export type NameMention = {
|
|
|
29
31
|
readonly ordinary?: boolean;
|
|
30
32
|
readonly labelled?: boolean;
|
|
31
33
|
readonly givenNameReading?: string | undefined;
|
|
34
|
+
readonly givenName?: string | undefined;
|
|
32
35
|
};
|
|
33
36
|
|
|
34
37
|
export type NameVariant = {
|
|
@@ -310,11 +313,13 @@ const differingWords = (left: Tally, right: Tally): readonly [string, string] |
|
|
|
310
313
|
};
|
|
311
314
|
|
|
312
315
|
/** 一語だけが一字違いで、相手のほうが重い(outweighs)。どちらもふつうの語(Green と Greek)なら、別の二つの語。 */
|
|
313
|
-
const isSlipOf =
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
316
|
+
const isSlipOf =
|
|
317
|
+
(use: WordUse): Alike =>
|
|
318
|
+
(tally, other) => {
|
|
319
|
+
if (!outweighs(other, tally) || (tally.first.ordinary === true && other.first.ordinary === true)) return false;
|
|
320
|
+
const words = differingWords(tally, other);
|
|
321
|
+
return words !== undefined && isNearWord(...words) && !isWordFormOf(...words, use);
|
|
322
|
+
};
|
|
318
323
|
|
|
319
324
|
/**
|
|
320
325
|
* 二語以上の名前の一語だけが、終わりの一字の抜けか足し(Rachel Whitmore と Rachel Whitmor)か、最後の語(姓)が四字でも一字違い
|
|
@@ -330,9 +335,14 @@ const isEndSlipOf =
|
|
|
330
335
|
return !use.ordinary.has(word) && !use.ordinary.has(usual) && isEndWordSlip(words, at, usual, use, isOneLetterApart);
|
|
331
336
|
};
|
|
332
337
|
|
|
333
|
-
const nearVariants = (tallies: readonly Tally[], use: WordUse): NameVariant[] => {
|
|
334
|
-
const isEndSlip = isEndSlipOf(use);
|
|
335
|
-
return variantsIn(
|
|
338
|
+
const nearVariants = (tallies: readonly Tally[], use: WordUse, givenNames: GivenNames): NameVariant[] => {
|
|
339
|
+
const [isSlip, isEndSlip] = [isSlipOf(use), isEndSlipOf(use)];
|
|
340
|
+
return variantsIn(
|
|
341
|
+
groupBy(tallies, maskedKeys),
|
|
342
|
+
"near",
|
|
343
|
+
(tally, other) => !areOtherPeople(givenNames.get(tally.surface), givenNames.get(other.surface)) && (isSlip(tally, other) || isEndSlip(tally, other)),
|
|
344
|
+
true,
|
|
345
|
+
);
|
|
336
346
|
};
|
|
337
347
|
|
|
338
348
|
/**
|
|
@@ -419,7 +429,7 @@ export const nameVariants = (
|
|
|
419
429
|
"reading",
|
|
420
430
|
oneWordApart,
|
|
421
431
|
),
|
|
422
|
-
...nearVariants(tallies, use),
|
|
432
|
+
...nearVariants(tallies, use, givenNamesBySurface(mentions)),
|
|
423
433
|
...surnameVariants(tallies),
|
|
424
434
|
...characterVariants(tallies, chars),
|
|
425
435
|
...personReadingVariants(tallies),
|
package/src/name-word-order.ts
CHANGED
|
@@ -41,12 +41,25 @@ const CAPITALISED = "\\p{Lu}[\\p{L}'’-]*";
|
|
|
41
41
|
|
|
42
42
|
const FIRST_WORD = new RegExp(`^${CAPITALISED}[ \\t]+`, "u");
|
|
43
43
|
|
|
44
|
+
/** 小さな語の並び。冠詞(the)は前置詞(of、for)のすぐ後ろにだけ立つ(Bank of the West)。 */
|
|
45
|
+
const joinerPattern = (joiners: readonly string[], articles: readonly string[]): string => {
|
|
46
|
+
if (joiners.length === 0) return "";
|
|
47
|
+
const article = articles.length === 0 ? "" : `(?:(?:${articles.map(escapeRegExp).join("|")})[ \\t]+)?`;
|
|
48
|
+
return `(?:(?:${joiners.map(escapeRegExp).join("|")})[ \\t]+${article})*`;
|
|
49
|
+
};
|
|
50
|
+
|
|
44
51
|
/**
|
|
45
|
-
* 大文字で始まる語が二つ以上並んだ所(Foundations of Statistics)。語のあいだに小さな語(of
|
|
46
|
-
*
|
|
52
|
+
* 大文字で始まる語が二つ以上並んだ所(Foundations of Statistics)。語のあいだに小さな語(of、of the)を挟んでよい。冠詞だけを
|
|
53
|
+
* 挟んだ二つの語(this Agreement the Stockholders)は、句の切れ目をまたぐので一つの名前にしない。文の頭の語は、文の頭だから
|
|
54
|
+
* 大文字のことがあるので、外す(Take Data Lab の Take)。
|
|
47
55
|
*/
|
|
48
|
-
export const titleCaseSpans = (
|
|
49
|
-
|
|
56
|
+
export const titleCaseSpans = (
|
|
57
|
+
text: string,
|
|
58
|
+
joiners: readonly string[],
|
|
59
|
+
isSentenceStart: (offset: number) => boolean = () => false,
|
|
60
|
+
articles: readonly string[] = [],
|
|
61
|
+
): NameSpan[] => {
|
|
62
|
+
const joiner = joinerPattern(joiners, articles);
|
|
50
63
|
const pattern = new RegExp(`(?<![\\p{L}'’-])${CAPITALISED}(?:[ \\t]+${joiner}${CAPITALISED})+`, "gu");
|
|
51
64
|
return [...text.matchAll(pattern)].map((found) => {
|
|
52
65
|
const start = found.index ?? 0;
|
|
@@ -112,16 +125,33 @@ export const orderNamesOf = (text: string, spans: readonly NameSpan[], words: re
|
|
|
112
125
|
.toSorted((left, right) => left.offset - right.offset);
|
|
113
126
|
};
|
|
114
127
|
|
|
115
|
-
type Tally = { readonly first: OrderName; readonly count: number; readonly anchored: boolean };
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
];
|
|
128
|
+
type Tally = { readonly first: OrderName; readonly count: number; readonly anchored: boolean; readonly offsets: readonly number[] };
|
|
129
|
+
|
|
130
|
+
/** 書き方ごとの、現れの位置のすべて。 */
|
|
131
|
+
const offsetsBySurface = (names: readonly OrderName[]): Map<string, number[]> =>
|
|
132
|
+
names.reduce((offsets, name) => {
|
|
133
|
+
const known = offsets.get(name.surface);
|
|
134
|
+
if (known === undefined) offsets.set(name.surface, [name.offset]);
|
|
135
|
+
else known.push(name.offset);
|
|
136
|
+
return offsets;
|
|
137
|
+
}, new Map<string, number[]>());
|
|
138
|
+
|
|
139
|
+
const talliesOf = (names: readonly OrderName[]): Tally[] => {
|
|
140
|
+
const offsets = offsetsBySurface(names);
|
|
141
|
+
return [
|
|
142
|
+
...names
|
|
143
|
+
.reduce<Map<string, Tally>>((tallies, name) => {
|
|
144
|
+
const known = tallies.get(name.surface);
|
|
145
|
+
return tallies.set(name.surface, {
|
|
146
|
+
first: known?.first ?? name,
|
|
147
|
+
count: (known?.count ?? 0) + 1,
|
|
148
|
+
anchored: (known?.anchored ?? false) || name.anchored,
|
|
149
|
+
offsets: offsets.get(name.surface) ?? [],
|
|
150
|
+
});
|
|
151
|
+
}, new Map())
|
|
152
|
+
.values(),
|
|
153
|
+
];
|
|
154
|
+
};
|
|
125
155
|
|
|
126
156
|
/** 多いほうが前。同じ数なら先に書いたほうが前。 */
|
|
127
157
|
const byUse = (left: Tally, right: Tally): number => right.count - left.count || left.first.offset - right.first.offset;
|
|
@@ -135,15 +165,30 @@ export const isReordered = (left: OrderName, right: OrderName): boolean =>
|
|
|
135
165
|
!left.surface.includes(right.surface) &&
|
|
136
166
|
!right.surface.includes(left.surface);
|
|
137
167
|
|
|
168
|
+
/** 文の番号。同じ文の二つの書き方を見分けるため。 */
|
|
169
|
+
export type SentenceOf = (offset: number) => number;
|
|
170
|
+
|
|
171
|
+
/** 二つの書き方を、どこかで同じ文に並べて書いたか(“Request Additional Time” or “Additional Time Requests”)。 */
|
|
172
|
+
const sharesSentence = (left: Tally, right: Tally, sentenceOf: SentenceOf): boolean => {
|
|
173
|
+
const sentences = new Set(left.offsets.map(sentenceOf));
|
|
174
|
+
return right.offsets.some((offset) => sentences.has(sentenceOf(offset)));
|
|
175
|
+
};
|
|
176
|
+
|
|
138
177
|
/**
|
|
139
178
|
* 多く使った書き方と同じ語を違う順に並べた名前。少ないほうを一つずつ指す。二つのどちらかが名前として書いた所(anchored)に
|
|
140
|
-
* あるときだけ。surfaces は表の升まるごとの書き方で、その書き方の名前も anchored
|
|
179
|
+
* あるときだけ。surfaces は表の升まるごとの書き方で、その書き方の名前も anchored と見る。同じ文に並べて書いた二つは、書き手が
|
|
180
|
+
* 言い分けた別の名前(画面の二つの項目、定義する語とその中身)なので比べない。
|
|
141
181
|
*/
|
|
142
|
-
export const wordOrderVariants = (
|
|
182
|
+
export const wordOrderVariants = (
|
|
183
|
+
names: readonly OrderName[],
|
|
184
|
+
surfaces: ReadonlySet<string> = new Set(),
|
|
185
|
+
sentenceOf: SentenceOf = (offset) => offset,
|
|
186
|
+
): OrderVariant[] => {
|
|
143
187
|
const tallies = talliesOf(names).toSorted(byUse);
|
|
144
188
|
const anchored = (tally: Tally): boolean => tally.anchored || surfaces.has(tally.first.surface);
|
|
189
|
+
const apart = (left: Tally, right: Tally): boolean => !sharesSentence(left, right, sentenceOf);
|
|
145
190
|
return tallies.flatMap((tally, index) => {
|
|
146
|
-
const usual = tallies.slice(0, index).find((other) => (anchored(other) || anchored(tally)) && isReordered(other.first, tally.first));
|
|
191
|
+
const usual = tallies.slice(0, index).find((other) => (anchored(other) || anchored(tally)) && isReordered(other.first, tally.first) && apart(other, tally));
|
|
147
192
|
return usual === undefined ? [] : [{ name: tally.first, usual: usual.first.surface }];
|
|
148
193
|
});
|
|
149
194
|
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Token } from "./plugin.ts";
|
|
2
|
+
|
|
3
|
+
/** The last kana of a verb's dictionary form, and the kana its stem form (連用形) ends in instead: 払い込む → 払い込み. */
|
|
4
|
+
const STEM_ENDING: Readonly<Record<string, string>> = { う: "い", く: "き", ぐ: "ぎ", す: "し", つ: "ち", ぬ: "に", ぶ: "び", む: "み", る: "り" };
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Whether a verb is written in its stem form (払い込み from 払い込む, 申出 from 申出る). Other forms that differ from the
|
|
8
|
+
* dictionary form (ください from 下さる, 払い込ん before で) are not: they are never a noun.
|
|
9
|
+
*/
|
|
10
|
+
export const isStemForm = (surface: string, lemma: string): boolean => {
|
|
11
|
+
const last = lemma.slice(-1);
|
|
12
|
+
const stem = lemma.slice(0, -1);
|
|
13
|
+
if (stem === "") return false;
|
|
14
|
+
return surface === stem + (STEM_ENDING[last] ?? "\u0000") || (last === "る" && surface === stem);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** What carries a verb on (払い込み、払い込んで, 払い込みます, 払い込みやすい): after these the stem form is still the verb. */
|
|
18
|
+
const VERB_GOES_ON = new Set(["AUX", "SCONJ", "VERB", "ADJ"]);
|
|
19
|
+
const CLAUSE_COMMA = new Set(["、", ",", ","]);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A free noun after the stem form may begin the next clause (疾病にかかり療養のために), so the stem form stays a verb; a suffix
|
|
23
|
+
* (払込み等), or a noun the caller lists as one (受取り前), may not.
|
|
24
|
+
*/
|
|
25
|
+
const verbGoesOn = (next: Token, suffixes: ReadonlySet<string>): boolean =>
|
|
26
|
+
VERB_GOES_ON.has(next.pos) || CLAUSE_COMMA.has(next.surface) || (next.pos === "NOUN" && next.features?.["Bound"] !== "Yes" && !suffixes.has(next.surface));
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A verb's stem form that the verb does not go on from (払込みの期日, 申出により, 株式の買取り)): a noun the analyser read as the
|
|
30
|
+
* verb, returned as the noun it is, spelled as written. Official Japanese spells the noun and the verb apart (払込み, 払い込む),
|
|
31
|
+
* so the two are not one word in two spellings; the noun is compared with nouns. Any other token is returned as it is.
|
|
32
|
+
*/
|
|
33
|
+
export const asNounWhereNominal = (token: Token, next: Token | undefined, suffixes: ReadonlySet<string> = new Set()): Token => {
|
|
34
|
+
if (token.pos !== "VERB" || token.lemma === undefined || !isStemForm(token.surface, token.lemma)) return token;
|
|
35
|
+
return next !== undefined && verbGoesOn(next, suffixes) ? token : { ...token, pos: "NOUN", lemma: token.surface };
|
|
36
|
+
};
|
package/src/sentence-load.ts
CHANGED
|
@@ -20,18 +20,23 @@ const BRACKETS: Readonly<Record<string, string>> = {
|
|
|
20
20
|
"“": "”",
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
+
/** A closer and the same closer in the other width: a numbering marker written (1) closes the bracket it opened. */
|
|
24
|
+
const OTHER_WIDTH: Readonly<Record<string, string>> = { ")": ")", ")": ")", "]": "]", "]": "]" };
|
|
25
|
+
|
|
23
26
|
/** A formula written in TeX ($d^k = -\nabla f(x^k)$): its brackets are the formula's, not the sentence's. */
|
|
24
27
|
const FORMULA = /\$\$[^$]*\$\$|\$[^$\n]*\$/gu;
|
|
25
28
|
|
|
26
29
|
/**
|
|
27
30
|
* The deepest the brackets of a text nest: 「計画(案)」 is one, 「(注(a)を参照)」 two, plain text none. A closer with no
|
|
28
|
-
* opener (the 1) of a list) is ignored, an opener never closed counts as open until the end,
|
|
31
|
+
* opener (the 1) of a list) is ignored, an opener never closed counts as open until the end, a closer of the other width closes
|
|
32
|
+
* its opener ((1) is one), and formulas in $…$ are skipped.
|
|
29
33
|
*/
|
|
30
34
|
export const bracketDepth = (text: string): number => {
|
|
31
35
|
// The closers still awaited, innermost last. Pushed and popped in place: copying it per character is quadratic on deep nesting.
|
|
32
36
|
const awaited: string[] = [];
|
|
33
37
|
return [...text.replace(FORMULA, " ")].reduce((deepest, char) => {
|
|
34
|
-
|
|
38
|
+
const innermost = awaited.at(-1);
|
|
39
|
+
if (innermost !== undefined && (innermost === char || innermost === OTHER_WIDTH[char])) awaited.pop();
|
|
35
40
|
else if (BRACKETS[char] !== undefined) awaited.push(BRACKETS[char]);
|
|
36
41
|
return Math.max(deepest, awaited.length);
|
|
37
42
|
}, 0);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { StructureNode } from "../plugin.ts";
|
|
2
|
+
|
|
3
|
+
// 他の文書を引き写した節。「##### 地方公務員法」の下に引いた第十六条の「第六十条」は、地方公務員法の条で、この文書の条ではない。
|
|
4
|
+
|
|
5
|
+
/** heading: いちばん近い見出しの節の言葉。inArticle: 条の中か。 */
|
|
6
|
+
type Visit = { readonly node: StructureNode; readonly heading: string | undefined; readonly inArticle: boolean };
|
|
7
|
+
|
|
8
|
+
/** 見出しの言葉。文書の最初の見出しは文書自身の題(「# 就業規則」)なので、他の文書の名前として読まない。 */
|
|
9
|
+
const headingOf = (node: StructureNode, title: StructureNode | undefined): string | undefined =>
|
|
10
|
+
node === title ? undefined : String(node.attrs["heading"] ?? "").trim();
|
|
11
|
+
|
|
12
|
+
const documentsCited = (nodes: readonly StructureNode[]): ReadonlySet<string> =>
|
|
13
|
+
new Set(nodes.flatMap((node) => (node.kind === "reference" && typeof node.attrs["document"] === "string" ? [node.attrs["document"]] : [])));
|
|
14
|
+
|
|
15
|
+
/** 木を上から順に、見出しの言葉と条の中かと一緒に並べる。再帰にしないのは、深い木でスタックを使い切らないため。 */
|
|
16
|
+
const visitsOf = (tree: StructureNode): Visit[] => {
|
|
17
|
+
const title = tree.children.find((child) => child.kind === "section");
|
|
18
|
+
const visits: Visit[] = [];
|
|
19
|
+
const pending: Visit[] = [{ node: tree, heading: undefined, inArticle: false }];
|
|
20
|
+
while (pending.length > 0) {
|
|
21
|
+
const visit = pending.pop();
|
|
22
|
+
if (visit === undefined) break;
|
|
23
|
+
visits.push(visit);
|
|
24
|
+
const { node } = visit;
|
|
25
|
+
const heading = node.kind === "section" ? headingOf(node, title) : visit.heading;
|
|
26
|
+
const inArticle = node.kind === "section" ? false : visit.inArticle || node.kind === "article";
|
|
27
|
+
node.children.toReversed().forEach((child) => pending.push({ node: child, heading, inArticle }));
|
|
28
|
+
}
|
|
29
|
+
return visits;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 見出しが他の文書の名前そのものである節に引き写した条の中の参照。その名前を文書がほかで他の文書として引いている
|
|
34
|
+
* (「地方公務員法第16条」)ときだけ、節はその文書を引き写したものと読む。条の外の地の文の参照は、この文書のもの。Pure.
|
|
35
|
+
*/
|
|
36
|
+
export const referencesInCitedDocuments = (tree: StructureNode): ReadonlySet<StructureNode> => {
|
|
37
|
+
const visits = visitsOf(tree);
|
|
38
|
+
const cited = documentsCited(visits.map((visit) => visit.node));
|
|
39
|
+
if (cited.size === 0) return new Set();
|
|
40
|
+
const quoted = visits.filter((visit) => visit.node.kind === "reference" && visit.inArticle && visit.heading !== undefined && cited.has(visit.heading));
|
|
41
|
+
return new Set(quoted.map((visit) => visit.node));
|
|
42
|
+
};
|