chaffjs 0.27.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +2 -1
  2. package/dist/cli-text.d.ts.map +1 -1
  3. package/dist/cli-text.js +6 -4
  4. package/dist/cli-text.js.map +1 -1
  5. package/dist/derived/number-word-counts.d.ts +10 -0
  6. package/dist/derived/number-word-counts.d.ts.map +1 -0
  7. package/dist/derived/number-word-counts.js +21 -0
  8. package/dist/derived/number-word-counts.js.map +1 -0
  9. package/dist/detectors/amount-scale.d.ts +3 -0
  10. package/dist/detectors/amount-scale.d.ts.map +1 -0
  11. package/dist/detectors/amount-scale.js +50 -0
  12. package/dist/detectors/amount-scale.js.map +1 -0
  13. package/dist/detectors/change-rate.d.ts.map +1 -1
  14. package/dist/detectors/change-rate.js +42 -8
  15. package/dist/detectors/change-rate.js.map +1 -1
  16. package/dist/detectors/currency-notation.d.ts +3 -1
  17. package/dist/detectors/currency-notation.d.ts.map +1 -1
  18. package/dist/detectors/currency-notation.js +2 -2
  19. package/dist/detectors/currency-notation.js.map +1 -1
  20. package/dist/detectors/derived-numbers.d.ts.map +1 -1
  21. package/dist/detectors/derived-numbers.js +9 -1
  22. package/dist/detectors/derived-numbers.js.map +1 -1
  23. package/dist/detectors/fact-consistency.d.ts.map +1 -1
  24. package/dist/detectors/fact-consistency.js +14 -1
  25. package/dist/detectors/fact-consistency.js.map +1 -1
  26. package/dist/detectors/name-variant.d.ts.map +1 -1
  27. package/dist/detectors/name-variant.js +18 -10
  28. package/dist/detectors/name-variant.js.map +1 -1
  29. package/dist/detectors/registry/amount-scale.d.ts +3 -0
  30. package/dist/detectors/registry/amount-scale.d.ts.map +1 -0
  31. package/dist/detectors/registry/amount-scale.js +3 -0
  32. package/dist/detectors/registry/amount-scale.js.map +1 -0
  33. package/dist/facts/counted-facts.d.ts +16 -0
  34. package/dist/facts/counted-facts.d.ts.map +1 -0
  35. package/dist/facts/counted-facts.js +45 -0
  36. package/dist/facts/counted-facts.js.map +1 -0
  37. package/dist/name-variants.d.ts +19 -5
  38. package/dist/name-variants.d.ts.map +1 -1
  39. package/dist/name-variants.js +60 -6
  40. package/dist/name-variants.js.map +1 -1
  41. package/dist/structure/amount-scale.d.ts +29 -0
  42. package/dist/structure/amount-scale.d.ts.map +1 -0
  43. package/dist/structure/amount-scale.js +70 -0
  44. package/dist/structure/amount-scale.js.map +1 -0
  45. package/dist/structure/change-rate.d.ts +19 -1
  46. package/dist/structure/change-rate.d.ts.map +1 -1
  47. package/dist/structure/change-rate.js +115 -15
  48. package/dist/structure/change-rate.js.map +1 -1
  49. package/dist/structure/subject-change.d.ts +8 -0
  50. package/dist/structure/subject-change.d.ts.map +1 -0
  51. package/dist/structure/subject-change.js +31 -0
  52. package/dist/structure/subject-change.js.map +1 -0
  53. package/package.json +3 -3
  54. package/rules/amount-scale-consistency.yaml +74 -0
  55. package/rules/change-rate-mismatch.yaml +7 -6
  56. package/rules/elapsed-years-mismatch.yaml +2 -2
  57. package/rules/name-variant.yaml +5 -2
  58. package/rules/summary-fact-mismatch.yaml +2 -2
  59. package/src/cli-text.ts +6 -4
  60. package/src/derived/number-word-counts.ts +28 -0
  61. package/src/detectors/amount-scale.ts +63 -0
  62. package/src/detectors/change-rate.ts +55 -11
  63. package/src/detectors/currency-notation.ts +2 -2
  64. package/src/detectors/derived-numbers.ts +9 -2
  65. package/src/detectors/fact-consistency.ts +17 -1
  66. package/src/detectors/name-variant.ts +14 -4
  67. package/src/detectors/registry/amount-scale.ts +4 -0
  68. package/src/facts/counted-facts.ts +59 -0
  69. package/src/name-variants.ts +106 -11
  70. package/src/structure/amount-scale.ts +97 -0
  71. package/src/structure/change-rate.ts +136 -16
  72. package/src/structure/subject-change.ts +35 -0
@@ -7,11 +7,24 @@ const MARK_GAP = 3;
7
7
  * away, the rate is of something else in the sentence (「100店から120店に増え、売上は15%増えた」).
8
8
  */
9
9
  const BASE_REACH = 12;
10
+ /**
11
+ * The two values written together, the earlier then the later or the other way round (「昨年の100万円から今年は150万円」
12
+ * "from $1.0 million last year to $1.5 million"): how far apart they may stand, and how far after them the rate may
13
+ * (「150万円になり、前年比20%」", an increase of 20%"). Measured with a Latin word counting as one character.
14
+ */
15
+ const PAIR_GAP = 16;
16
+ const PAIR_REACH = 16;
17
+ /** How far a year may stand from the value it dates (「2025年の100万円」"$1.0 million in 2025"), a word counting as one. */
18
+ const PERIOD_GAP = 8;
19
+ const LATIN_WORD = /[A-Za-z]+/gu;
10
20
  const PERCENT = 100;
11
21
  const HALF = 0.5;
12
22
  const DECIMAL_BASE = 10;
13
23
  const within = (outer, inner) => inner.start >= outer.start && inner.end <= outer.end;
14
24
  const distance = (left, right) => Math.max(left.start - right.end, right.start - left.end, 0);
25
+ const gapBetween = (left, right) => ({ start: left.end, end: right.start });
26
+ /** The length of a gap, a Latin word counting as one character: "an increase of" is as short as 「前年比」. */
27
+ export const gapLength = (source, gap) => source.slice(gap.start, gap.end).replaceAll(LATIN_WORD, "w").length;
15
28
  const markOf = (figure, marks) => marks.find((mark) => {
16
29
  const gap = mark.position === "after" ? mark.start - figure.end : figure.start - mark.end;
17
30
  return gap >= 0 && gap <= MARK_GAP;
@@ -43,29 +56,116 @@ const rateRange = (base, current) => {
43
56
  return undefined;
44
57
  return [(currentLow / baseHigh - 1) * PERCENT, (currentHigh / baseLow - 1) * PERCENT];
45
58
  };
46
- const issueIn = (text, sentence) => {
47
- const rates = text.rates.filter((rate) => within(sentence, rate));
48
- const figures = text.figures.filter((figure) => within(sentence, figure));
49
- const [rate] = rates;
50
- if (rates.length !== 1 || rate === undefined)
51
- return [];
52
- const sign = signNear(rate, text.directions.filter((direction) => within(sentence, direction)));
53
- const bases = figures.filter((figure) => isBaseFor(figure, rate, text.marks));
59
+ /** A word between the two that starts another subject or clause (「100万円から150万円に増え、利益は20%」). */
60
+ const brokenBetween = (text, one, other) => {
61
+ const gap = one.end <= other.start ? gapBetween(one, other) : gapBetween(other, one);
62
+ return text.breaks.some((word) => within(gap, word));
63
+ };
64
+ /** The earlier value marked beside the rate, and the one value in its unit before the rate or marked as reached. */
65
+ const besideRate = (text) => {
66
+ const bases = text.figures.filter((figure) => isBaseFor(figure, text.rate, text.marks) && !brokenBetween(text, figure, text.rate));
54
67
  const [base] = bases;
55
- if (sign === undefined || bases.length !== 1 || base === undefined)
56
- return [];
57
- const currents = figures.filter((figure) => figure !== base && figure.unit === base.unit && isCurrentFor(figure, rate, text.targets));
68
+ if (bases.length !== 1 || base === undefined)
69
+ return undefined;
70
+ const currents = text.figures.filter((figure) => figure !== base && figure.unit === base.unit && isCurrentFor(figure, text.rate, text.targets));
58
71
  const [current] = currents;
59
- const range = currents.length === 1 && current !== undefined ? rateRange(base, current) : undefined;
60
- if (range === undefined || current === undefined)
72
+ return currents.length === 1 && current !== undefined ? { base, current } : undefined;
73
+ };
74
+ const yearsBeside = (text, figure, side, bound) => text.periods.filter((period) => {
75
+ const [left, right] = side === "before" ? [period, figure] : [figure, period];
76
+ return left.end <= right.start && within(bound, period) && gapLength(text.source, gapBetween(left, right)) <= PERIOD_GAP;
77
+ });
78
+ /**
79
+ * Of two values each dated by a year, the earlier year's. The years stand on the same side of both values: before them
80
+ * (「2025年は100万円、2026年は150万円」) or after them ("$1.0 million in 2025 and $1.5 million in 2026").
81
+ */
82
+ const datedEarlier = (text, first, second) => {
83
+ const middle = gapBetween(first, second);
84
+ const sides = [
85
+ [yearsBeside(text, first, "before", { start: 0, end: first.start }), yearsBeside(text, second, "before", middle)],
86
+ [yearsBeside(text, first, "after", middle), yearsBeside(text, second, "after", { start: second.end, end: text.source.length })],
87
+ ].filter(([early, late]) => early?.length === 1 && late?.length === 1);
88
+ const [[early, late] = []] = sides;
89
+ const [firstYear, secondYear] = [early?.[0]?.year, late?.[0]?.year];
90
+ if (sides.length !== 1 || firstYear === undefined || secondYear === undefined || firstYear === secondYear)
91
+ return undefined;
92
+ return firstYear < secondYear ? first : second;
93
+ };
94
+ /** Which of two values written together is the earlier: the one marked (「から」, "from", 「前年の」), else the one dated earlier. */
95
+ const pairOf = (text, first, second) => {
96
+ const marked = [first, second].filter((figure) => markOf(figure, text.marks) !== undefined);
97
+ if (marked.length > 1)
98
+ return undefined;
99
+ const base = marked[0] ?? datedEarlier(text, first, second);
100
+ if (base === undefined)
101
+ return undefined;
102
+ return { base, current: base === first ? second : first };
103
+ };
104
+ /** Nothing between the later value and the rate that makes the rate another one's: a value, another subject, another base. */
105
+ const leadsTo = (text, second) => {
106
+ const gap = gapBetween(second, text.rate);
107
+ if (gap.start > gap.end || gapLength(text.source, gap) > PAIR_REACH)
108
+ return false;
109
+ return ![...text.figures, ...text.breaks, ...text.marks].some((span) => within(gap, span));
110
+ };
111
+ /** Two values of one subject written close, with no rate between them (「売上は100万円、費用は150万円」 is two subjects). */
112
+ const standTogether = (text, first, second) => {
113
+ const gap = gapBetween(first, second);
114
+ const inGap = (span) => within(gap, span);
115
+ if (gapLength(text.source, gap) > PAIR_GAP || text.rates.some(inGap))
116
+ return false;
117
+ return !text.breaks.some((word) => !word.beforeRateOnly && inGap(word));
118
+ };
119
+ /** The two values written together right before the rate (「昨年の100万円から今年は150万円になり、前年比20%の増加」). */
120
+ const beforeRate = (text) => {
121
+ const pairs = text.figures.flatMap((first, index) => {
122
+ const second = text.figures.slice(index + 1).find((figure) => figure.unit === first.unit);
123
+ const pair = second !== undefined && standTogether(text, first, second) && leadsTo(text, second) ? pairOf(text, first, second) : undefined;
124
+ return pair === undefined ? [] : [pair];
125
+ });
126
+ const [pair] = pairs;
127
+ return pairs.length === 1 ? pair : undefined;
128
+ };
129
+ /**
130
+ * The sentence's one rate with a word of direction beside it, a word counting for the rate it stands nearest. A percentage
131
+ * without one (「20%の増加、利益率は10%」) is about something else.
132
+ */
133
+ const readingIn = (text, sentence) => {
134
+ const inSentence = (span) => within(sentence, span);
135
+ const directions = text.directions.filter(inSentence);
136
+ const rates = text.rates.filter(inSentence);
137
+ const nearestTo = (rate, direction) => rates.every((other) => distance(direction, rate) <= distance(direction, other));
138
+ const signed = rates.flatMap((rate) => {
139
+ const sign = signNear(rate, directions.filter((direction) => nearestTo(rate, direction)));
140
+ return sign === undefined ? [] : [{ rate, sign }];
141
+ });
142
+ const [only] = signed;
143
+ if (signed.length !== 1 || only === undefined)
144
+ return undefined;
145
+ const parts = {
146
+ figures: text.figures.filter(inSentence).toSorted((left, right) => left.start - right.start),
147
+ periods: text.periods.filter(inSentence),
148
+ breaks: text.breaks.filter(inSentence),
149
+ };
150
+ return { ...text, ...parts, ...only, rates };
151
+ };
152
+ const issueIn = (text, sentence) => {
153
+ const reading = readingIn(text, sentence);
154
+ const pair = reading === undefined ? undefined : (besideRate(reading) ?? beforeRate(reading));
155
+ const range = pair === undefined ? undefined : rateRange(pair.base, pair.current);
156
+ if (reading === undefined || pair === undefined || range === undefined)
61
157
  return [];
158
+ const { rate, sign } = reading;
62
159
  const written = sign * rate.value;
63
160
  const slack = HALF * DECIMAL_BASE ** -rate.decimals;
64
161
  if (written >= range[0] - slack && written <= range[1] + slack)
65
162
  return [];
66
- const computed = Math.abs((current.value / base.value - 1) * PERCENT).toFixed(rate.decimals);
163
+ const computed = Math.abs((pair.current.value / pair.base.value - 1) * PERCENT).toFixed(rate.decimals);
67
164
  return [{ offset: rate.start, values: { rate: String(rate.value), computed } }];
68
165
  };
69
- /** Each sentence with one rate of change, one direction, one marked earlier value and one other value in its unit, that disagree. */
166
+ /**
167
+ * Each sentence with one rate of change and its direction, and the two values it is computed from, that disagree: the
168
+ * earlier value marked beside the rate, or the two values written together right before it.
169
+ */
70
170
  export const changeRateMismatches = (text) => text.sentences.flatMap((sentence) => issueIn(text, sentence));
71
171
  //# sourceMappingURL=change-rate.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"change-rate.js","sourceRoot":"","sources":["../../src/structure/change-rate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"change-rate.js","sourceRoot":"","sources":["../../src/structure/change-rate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { Token } from "../plugin.ts";
2
+ /**
3
+ * The joining word at index is followed by a noun the sentence has not used before it: "and costs were", "and costs $1,500"
4
+ * after "Revenue was $1,000", where "and revenue was" after "ACME's revenue was $1,000" names the same thing again. A
5
+ * sentence with no noun before the joining word gives nothing to compare with.
6
+ */
7
+ export declare const startsOtherSubject: (tokens: readonly Token[], index: number) => boolean;
8
+ //# sourceMappingURL=subject-change.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subject-change.d.ts","sourceRoot":"","sources":["../../src/structure/subject-change.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAsB1C;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,SAAS,KAAK,EAAE,EAAE,OAAO,MAAM,KAAG,OAO5E,CAAC"}
@@ -0,0 +1,31 @@
1
+ // Whether a joining word ("and") starts a clause about another subject: "Revenue was $1,000 in 2025 and costs were $1,500
2
+ // in 2026" joins two subjects, "… in 2025 and $1,500 in 2026" or "… and revenue was $1,500" one. Two words for one thing
3
+ // ("sales" and "revenue") read as two subjects, so the pair is left alone rather than read wrongly. Pure: reads the tagger's
4
+ // parts of speech and lemmas.
5
+ const NOUN = new Set(["NOUN", "PROPN"]);
6
+ /** Words that may stand before the noun that starts the clause ("and the costs", "and our operating costs"). */
7
+ const BEFORE_NOUN = new Set(["DET", "ADJ", "PRON"]);
8
+ const wordOf = (token) => (token.lemma ?? token.surface).toLowerCase();
9
+ /** The last noun of the run of nouns at the start of tokens, past determiners and adjectives ("the operating costs" → cost). */
10
+ const leadingNoun = (tokens) => {
11
+ const start = tokens.findIndex((token) => !BEFORE_NOUN.has(token.pos));
12
+ if (start < 0)
13
+ return undefined;
14
+ const end = tokens.slice(start).findIndex((token) => !NOUN.has(token.pos));
15
+ const nouns = tokens.slice(start, end < 0 ? tokens.length : start + end);
16
+ return nouns.at(-1);
17
+ };
18
+ /**
19
+ * The joining word at index is followed by a noun the sentence has not used before it: "and costs were", "and costs $1,500"
20
+ * after "Revenue was $1,000", where "and revenue was" after "ACME's revenue was $1,000" names the same thing again. A
21
+ * sentence with no noun before the joining word gives nothing to compare with.
22
+ */
23
+ export const startsOtherSubject = (tokens, index) => {
24
+ const next = leadingNoun(tokens.slice(index + 1));
25
+ const before = tokens
26
+ .slice(0, index)
27
+ .filter((token) => NOUN.has(token.pos))
28
+ .map(wordOf);
29
+ return next !== undefined && before.length > 0 && !before.includes(wordOf(next));
30
+ };
31
+ //# sourceMappingURL=subject-change.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subject-change.js","sourceRoot":"","sources":["../../src/structure/subject-change.ts"],"names":[],"mappings":"AAEA,0HAA0H;AAC1H,yHAAyH;AACzH,6HAA6H;AAC7H,8BAA8B;AAE9B,MAAM,IAAI,GAAwB,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAC7D,gHAAgH;AAChH,MAAM,WAAW,GAAwB,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;AAEzE,MAAM,MAAM,GAAG,CAAC,KAAY,EAAU,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;AAEtF,gIAAgI;AAChI,MAAM,WAAW,GAAG,CAAC,MAAwB,EAAqB,EAAE;IAClE,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IACvE,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IAChC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3E,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC;IACzE,OAAO,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AACtB,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,MAAwB,EAAE,KAAa,EAAW,EAAE;IACrF,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,MAAM;SAClB,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC;SACf,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;SACtC,GAAG,CAAC,MAAM,CAAC,CAAC;IACf,OAAO,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;AACnF,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chaffjs",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
4
4
  "description": "Prose validation harness. Finds what is hard to read, and never rewrites it for you.",
5
5
  "keywords": [
6
6
  "chaff",
@@ -87,8 +87,8 @@
87
87
  },
88
88
  "dependencies": {
89
89
  "@anthropic-ai/sdk": "^0.125.0",
90
- "@chaffjs/lang-en": "0.24.0",
91
- "@chaffjs/lang-ja": "0.26.0",
90
+ "@chaffjs/lang-en": "0.25.0",
91
+ "@chaffjs/lang-ja": "0.27.0",
92
92
  "mdast-util-from-markdown": "^2.0.3",
93
93
  "mdast-util-frontmatter": "^2.0.1",
94
94
  "mdast-util-gfm-table": "^2.0.0",
@@ -0,0 +1,74 @@
1
+ id: amount-scale-consistency
2
+ layer: L2
3
+ status: stable
4
+ how_to_find: amount-scale
5
+ severity: info
6
+
7
+ name:
8
+ ja: 同じ桁の金額を二通りに書いている
9
+ en: Amounts of one size written two ways
10
+
11
+ why:
12
+ ja: 「設計費30万円、制作費250,000円」のように、同じくらいの金額を「万」と数字だけの二通りで書くと、読み手は桁を数え直し、別の扱いの金額かと迷います。別々の見積もりやメールを寄せ集めたときに起きます。小さな金額を数字で、大きな金額を「万」「億」で書くのはふつうの書き方なので、同じ桁(十万の位なら十万の位どうし)の金額だけを比べ、その桁で少ないほうを指します。
13
+ en: When amounts of one size are written two ways ("$1.5 million for design, $2,500,000 for building"), the reader counts the zeros again and wonders whether the two are different kinds of amount. It happens when quotes and emails are pasted together. Small amounts in digits and large ones with "million" is a normal way of writing, so only amounts of the same order of magnitude are compared, and the way that order uses less is pointed at.
14
+
15
+ message:
16
+ ja: 「{written}」と書いています(この文書は同じ桁の金額を「{usual}」のように書きます)
17
+ en: '"{written}" here, where the document writes amounts of this size like "{usual}"'
18
+
19
+ how_to_fix:
20
+ ja: 同じ桁の金額は、文書の多いほうの書き方にそろえてください。
21
+ en: Write amounts of the same size the way the rest of the document does.
22
+
23
+ levels: { strict: warning, normal: info, relaxed: info }
24
+
25
+ use_for: [business, technical, blog, legal, docs, academic, literature, speech]
26
+
27
+ group: consistency
28
+ summary:
29
+ ja: 「30万円」と「250,000円」のように、同じ桁の金額を「万」と数字だけの二通りで書いている所
30
+ en: Amounts of one order of magnitude written both with and without a word of scale, such as "$1.5 million" and "$2,500,000"
31
+ example:
32
+ ja:
33
+ before: |
34
+ お見積もりの総額は55万円です。内訳は、設計費30万円、制作費250,000円です。
35
+ after: |
36
+ お見積もりの総額は55万円です。内訳は、設計費30万円、制作費25万円です。
37
+ en:
38
+ before: |
39
+ The total is $4 million: $1.5 million for design and $2,500,000 for building.
40
+ after: |
41
+ The total is $4 million: $1.5 million for design and $2.5 million for building.
42
+
43
+ not_flagged:
44
+ ja: 桁の違う金額(「480円」と「30万円」)は比べません。小さな金額は数字で、大きな金額は「万」で書くのはふつうの書き方です。端数のある金額(「1,234,567円」)は「万」で書けないので比べません。通貨ごとに比べ、コードの中は読みません。
45
+ en: Amounts of different sizes ("$480" and "$3 million") are not compared; digits for small amounts and "million" for large ones is a normal way of writing. A precise amount ("$1,234,567") cannot be written with "million" and is not compared. Each currency is compared with itself, and code is not read.
46
+ level_meaning:
47
+ ja: 同じ桁の金額を二通りに書いたとき、少ないほうを指摘
48
+ en: reported when amounts of one size are written two ways, at the less common way
49
+
50
+ rewrite:
51
+ ja:
52
+ direction: 同じ桁の金額を、文書で多いほうの書き方にそろえてください。金額そのものは変えません。
53
+ pairs:
54
+ - before: 設計費30万円、制作費250,000円
55
+ after: 設計費30万円、制作費25万円
56
+ - before: 上限は5,000,000円とする。前項の上限額500万円には
57
+ after: 上限は5,000,000円とする。前項の上限額5,000,000円には
58
+ keep:
59
+ - 金額
60
+ - どの通貨か
61
+ avoid:
62
+ - 桁の違う金額まで同じ書き方にそろえる
63
+ en:
64
+ direction: Write amounts of the same size the way the document mostly does. Keep the amounts as they are.
65
+ pairs:
66
+ - before: $1.5 million for design and $2,500,000 for building
67
+ after: $1.5 million for design and $2.5 million for building
68
+ - before: The cap is $5,000,000. The cap of $5 million includes legal fees.
69
+ after: The cap is $5,000,000. The cap of $5,000,000 includes legal fees.
70
+ keep:
71
+ - the amounts
72
+ - which currency each amount is in
73
+ avoid:
74
+ - rewriting amounts of a different size to match
@@ -3,6 +3,7 @@ layer: L3
3
3
  status: stable
4
4
  how_to_find: change-rate
5
5
  requires: [structure]
6
+ uses: [pos]
6
7
  severity: warning
7
8
 
8
9
  name:
@@ -10,8 +11,8 @@ name:
10
11
  en: A rate of change the two values do not give
11
12
 
12
13
  why:
13
- ja: 「前年同月の1,000社から20%増えて1,200社」のように、もとの値・今の値・増減率を一つの文に書くと、読み手は率を確かめずに信じます。数字を一つだけ更新したとき、率だけを去年の文書から写したときに食い違います。一つの文に、もとの値(「から」「に比べて」の付いた値)、同じ単位の今の値、向きの語(増加、減少)の付いた百分率が一つずつあるとき、二つの値から率を計算して比べます。値は書いた桁までの丸めを認めます(12億円は11.5億円から12.5億円のどれか)。
14
- en: When one sentence gives the earlier value, the current one and the rate of change ("1,200 companies, up 20% from 1,000"), readers trust the rate without checking it. It goes wrong when one figure is updated and the others are not, or when the rate is copied from last year's text. When a sentence has one earlier value (marked by "from" or "compared with"), one current value in the same unit, and one percentage with a word of direction (up, decline), chaff computes the rate from the two values and compares. Each value may be rounded to its last written digit ($12 million is anything from $11.5 million to $12.5 million).
14
+ ja: 「前年同月の1,000社から20%増えて1,200社」のように、もとの値・今の値・増減率を一つの文に書くと、読み手は率を確かめずに信じます。数字を一つだけ更新したとき、率だけを去年の文書から写したときに食い違います。一つの文に、もとの値(「から」「に比べて」「前年の」の付いた値)、同じ単位の今の値、向きの語(増加、減少)の付いた百分率が一つずつあるとき、二つの値から率を計算して比べます。率のそばにもとの値が無くても、「昨年の100万円から今年は150万円になり、前年比50%の増加」「2025年度は100万円、2026年度は150万円で」のように二つの値が並んですぐ後に率が続くなら読みます(年が書いてあれば早い年の値がもとの値)。値は書いた桁までの丸めを認めます(12億円は11.5億円から12.5億円のどれか)。
15
+ en: When one sentence gives the earlier value, the current one and the rate of change ("1,200 companies, up 20% from 1,000"), readers trust the rate without checking it. It goes wrong when one figure is updated and the others are not, or when the rate is copied from last year's text. When a sentence has one earlier value (marked by "from" or "compared with"), one current value in the same unit, and one percentage with a word of direction (up, decline), chaff computes the rate from the two values and compares. The two values may also stand together right before the rate ("rose from $1.0 million last year to $1.5 million, an increase of 50%", "$1.0 million in 2025 and $1.5 million in 2026, up 50%"; with years, the earlier year's value is the earlier one). Each value may be rounded to its last written digit ($12 million is anything from $11.5 million to $12.5 million).
15
16
 
16
17
  message:
17
18
  ja: 「{rate}%」と書いていますが、二つの値からは {computed}% です
@@ -36,9 +37,9 @@ example:
36
37
  before: 利用企業は1,300社となり、前年の1,000社から20%増えました。
37
38
  after: 利用企業は1,300社となり、前年の1,000社から30%増えました。
38
39
  en:
39
- before: 1,300 companies used the app, up 20% from 1,000 companies a year earlier.
40
- after: 1,300 companies used the app, up 30% from 1,000 companies a year earlier.
40
+ before: Revenue was $1.0 million in 2025 and $1.5 million in 2026, up 20%.
41
+ after: Revenue was $1.0 million in 2025 and $1.5 million in 2026, up 50%.
41
42
 
42
43
  not_flagged:
43
- ja: 率が二つある文、もとの値が「から」「に比べて」で名指されていない文、同じ単位の今の値が一つでない文、向きの語が率のそばに無い文は比べません。「前年比20%増」のように値が一つしか無いもの、ポイントの差、倍は見ません。丸めで届く範囲の率は言いません。
44
- en: 'A sentence with two rates, with no value marked as the earlier one, with no single current value in the same unit, or with no word of direction beside the rate is not compared. A change given with one value ("up 20% year on year"), percentage points and "times" are not read. A rate the rounding of the values can reach is not reported.'
44
+ ja: 向きの語の付いた率が二つある文、もとの値が「から」「に比べて」や年で名指されていない文、二つの値と率のあいだで主語が変わる文(「…に増え、利益は20%増えた」)、二つの値のあいだで主語が変わる文("$1,000 in 2025 and costs were $1,500 in 2026")、同じ単位の今の値が一つでない文、向きの語が率のそばに無い文は比べません。「前年比20%増」のように値が一つしか無いもの、ポイントの差、倍は見ません。丸めで届く範囲の率は言いません。
45
+ en: 'A sentence with two rates that each have a word of direction, with no value marked as the earlier one (by a word or a year), with another subject between the values and the rate ("…, and costs rose 20%") or between the two values ("$1,000 in 2025 and costs were $1,500 in 2026"), with no single current value in the same unit, or with no word of direction beside the rate is not compared. A change given with one value ("up 20% year on year"), percentage points and "times" are not read. A rate the rounding of the values can reach is not reported.'
@@ -10,8 +10,8 @@ name:
10
10
  en: A count of years that does not match its starting year
11
11
 
12
12
  why:
13
- ja: 「2015年創業」と書いた文書の「創業5年」や、「1980年生まれ(30歳)」は、どちらかが古いままです。去年の文書を写して日付だけ直したときによく起きます。文書の日付(日付だけの段落や「更新日:」の段落)の年から起点の年を引き、書いた年数と比べます。誕生日の前後や「10年目」の数え方があるので、一年までの違いは指しません。
14
- en: 'A document that says "founded in 2015" and "5 years since its founding", or "born in 1980 (aged 30)", has kept one figure from an older version. It happens when last year''s document is copied and only the date is changed. The starting year is taken from the document''s own date (a paragraph holding only a date, or "Updated:") and compared with the count of years. A difference of one year is not pointed out, because of birthdays and of counting the tenth year as 10.'
13
+ ja: 「2015年創業」と書いた文書の「創業5年」や、「1980年生まれ(30歳)」は、どちらかが古いままです。去年の文書を写して日付だけ直したときによく起きます。文書の日付(日付だけの段落や「更新日:」の段落)の年から起点の年を引き、書いた年数と比べます。誕生日の前後や「10年目」の数え方があるので、一年までの違いは指しません。英語の文書で語で書いた年数(ten years since its founding)も読みます。
14
+ en: 'A document that says "founded in 2015" and "5 years since its founding", or "born in 1980 (aged 30)", has kept one figure from an older version. It happens when last year''s document is copied and only the date is changed. The starting year is taken from the document''s own date (a paragraph holding only a date, or "Updated:") and compared with the count of years. A difference of one year is not pointed out, because of birthdays and of counting the tenth year as 10. A count written as a word ("ten years since its founding") is read as well.'
15
15
 
16
16
  message:
17
17
  ja: "{origin}から{reference}年までは{expected}年ですが、「{written}」と書かれています"
@@ -24,6 +24,9 @@ messages:
24
24
  reading:
25
25
  ja: 「{name}」は、ほかの所では同じ読みの「{usual}」と書いています
26
26
  en: '"{name}" is written "{usual}", read the same, elsewhere in the document'
27
+ character:
28
+ ja: 「{name}」は、ほかの所では字体の違う同じ字で「{usual}」と書いています
29
+ en: '"{name}" is written "{usual}", with another form of the same character, elsewhere in the document'
27
30
  near:
28
31
  ja: 「{name}」は、ほかの所で何度も書いた「{usual}」と一字違いです
29
32
  en: '"{name}" is one letter away from "{usual}", which the document uses more than once'
@@ -61,8 +64,8 @@ example:
61
64
  The code lives on GitHub. Reviews happen on GitHub, and releases are tagged on GitHub.
62
65
 
63
66
  not_flagged:
64
- ja: 品詞解析が固有名詞と読んだ語だけを見ます。大文字だけの名前(ACME INC、NASA)は見ません。一字違いの英字の名前は、6 字以上で、多いほうが二度以上、少ないほうが一度だけのときに限ります(Iran と Iraq は別の名前)。
65
- en: Only words the tagger reads as proper nouns are checked. Names in capitals only (ACME INC, NASA) are left out. Names one letter apart are reported only when six letters or longer, with the usual form used at least twice and the other once (Iran and Iraq are different names).
67
+ ja: 品詞解析が固有名詞と読んだ語だけを見ます。大文字だけの名前(ACME INC、NASA)は見ません。一字違いの英字の名前は、6 字以上で、多いほうが二度以上、少ないほうが一度だけのときに限ります(Iran と Iraq は別の名前)。人の名前どうしは、字体の違う同じ字(斎藤 と 斉藤、渡辺 と 渡邊)か、読みが同じで一字だけ違い、多いほうが二度以上、少ないほうが一度だけのとき(松本 と 松元)に限ります(伊藤 と 伊東 は別の人のことがある)。人の名前とは、品詞解析が人名と読んだ語か、「様」「さん」「氏」の付いた名前です(解析器が名前と読めない字体の字を含む名前は、敬称の前の 2〜4 字の漢字を名前と読みます)。
68
+ en: Only words the tagger reads as proper nouns are checked. Names in capitals only (ACME INC, NASA) are left out. Names one letter apart are reported only when six letters or longer, with the usual form used at least twice and the other once (Iran and Iraq are different names). Two Japanese person names are compared only when written with another form of the same character (斎藤 and 斉藤), or read the same with one character different, the usual form used at least twice and the other once (松本 and 松元); 伊藤 and 伊東 can be two people. A person's name is one the tagger reads as such, or one followed by 様, さん or 氏 (when a name holds a character form the tagger cannot read, the two to four kanji before the honorific are taken as the name).
66
69
  rewrite:
67
70
  depth: light
68
71
  ja:
@@ -11,8 +11,8 @@ name:
11
11
  en: A summary value that differs from the body
12
12
 
13
13
  why:
14
- ja: 冒頭や「概要」の節は、本文を読まない人も読みます。そこに書いた値が本文と違うと、要約だけを読んだ人が間違った値を持ち帰ります。本文を直したあとに、要約を直し忘れると起きます。最初の見出しより前の文と、概要・要約・まとめの節の「項目:値」「項目は値です」を、本文の同じ項目の値と比べます。本文の値が一通りに揃っていて、要約の値がそれと違うときだけ指します。
15
- en: 'The opening and the summary are read by people who never read the body. A value there that differs from the body sends them away with the wrong one. It happens when the body is corrected and the summary is not. Values written as "Item: value" or "The item is value" before the first heading, or in a section headed Summary, Abstract or Overview, are compared with the body''s values for the same item; it is pointed out only when the body gives one value throughout and the summary gives another.'
14
+ ja: 冒頭や「概要」の節は、本文を読まない人も読みます。そこに書いた値が本文と違うと、要約だけを読んだ人が間違った値を持ち帰ります。本文を直したあとに、要約を直し忘れると起きます。最初の見出しより前の文と、概要・要約・まとめの節の「項目:値」「項目は値です」を、本文の同じ項目の値と比べます。英語の文書では、名前の無い数も、数える動詞と数えるもの(We gained 52 new customers)が同じ本文の数と比べます。本文の値が一通りに揃っていて、要約の値がそれと違うときだけ指します。
15
+ en: 'The opening and the summary are read by people who never read the body. A value there that differs from the body sends them away with the wrong one. It happens when the body is corrected and the summary is not. Values written as "Item: value" or "The item is value" before the first heading, or in a section headed Summary, Abstract or Overview, are compared with the body''s values for the same item, and so is a count with no label, by the verb and the words of what it counts ("We gained 52 new customers"); it is pointed out only when the body gives one value throughout and the summary gives another.'
16
16
 
17
17
  message:
18
18
  ja: 冒頭・要約の「{label}」 {value} が、本文の {other} と違います
package/src/cli-text.ts CHANGED
@@ -43,9 +43,10 @@ const USAGE_JA = `chaff — 文章の読みにくいところを見つけます
43
43
  chaff facts <file> compare が照合する事実を、書き直す前の控えとして一覧にする
44
44
  chaff outline <file> [<後>] 見出しの構成と形(見出しの数・節の平均の長さ・箇条書きの割合・太字)を測る。2 つなら前と後を並べる
45
45
  chaff ai-score <file>... [--format json] AI らしさの簡易判定(低・中・高)。生成文に多い目印を、同じジャンルの人の文書と比べる(書いたのが AI かは判定しない)
46
- chaff grade <items.jsonl> [--out <results.jsonl>] [--json]
46
+ chaff grade <items.jsonl> [--out <results.jsonl>] [--baseline <前の results.jsonl>] [--compact] [--json]
47
47
  AI の出力を JSONL でまとめて採点する(指摘の率・事実・引用・合否。何も送らない)
48
- chaff fix-plan <file> [--json] 指摘をルールごとにまとめ、直す方向と確かめのコマンドを、書き直す人や AI 向けの計画にする(何も送らない)
48
+ chaff fix-plan <file> [--depth light|structure|register] [--json]
49
+ 指摘をルールごとにまとめ、直す方向と確かめのコマンドを、書き直す人や AI 向けの計画にする(何も送らない)
49
50
  chaff rules ルールの一覧を、グループごとに表で出す(いまの段階つき)
50
51
  chaff rules --json いまの設定とルールの説明を JSON で出す(AI に渡す用)
51
52
  chaff baseline <dir> いまある指摘を棚上げする(既存の repo に入れるとき)
@@ -90,9 +91,10 @@ const USAGE_EN = `chaff — finds what makes writing hard to read. It never rewr
90
91
  chaff facts <file> list the facts compare checks, as an inventory to keep before a rewrite
91
92
  chaff outline <file> [<after>] measure the outline and its shape (headings, average section length, text in lists, bold); two files side by side
92
93
  chaff ai-score <file>... [--format json] quick AI-likeness score (low, medium, high): signs common in generated text against human documents of the genre (not a verdict on who wrote it)
93
- chaff grade <items.jsonl> [--out <results.jsonl>] [--json]
94
+ chaff grade <items.jsonl> [--out <results.jsonl>] [--baseline <earlier results.jsonl>] [--compact] [--json]
94
95
  grade a JSONL file of model outputs (finding rates, facts, quotations, pass or fail; sends nothing)
95
- chaff fix-plan <file> [--json] a plan for whoever rewrites the file, a person or an AI: findings by rule, how to rewrite each, the checks to run after (sends nothing)
96
+ chaff fix-plan <file> [--depth light|structure|register] [--json]
97
+ a plan for whoever rewrites the file, a person or an AI: findings by rule, how to rewrite each, the checks to run after (sends nothing)
96
98
  chaff rules the rules as a table, by group, with the level each runs at now
97
99
  chaff rules --json the current settings and what each rule is, as JSON (to give to an AI)
98
100
  chaff baseline <dir> shelve today's findings (when adding chaff to an existing repository)
@@ -0,0 +1,28 @@
1
+ import type { Span } from "../plugin.ts";
2
+ import { escapeRegExp } from "../orthography.ts";
3
+
4
+ // A count written as a word before its unit ("twelve years", "Ten years"), which the tree does not read as a quantity.
5
+ // Pure: the number words come in order from one (the lexicon count-number, the position is the number).
6
+
7
+ export type WordCount = Span & { readonly amount: number };
8
+
9
+ const alternation = (words: readonly string[]): string =>
10
+ words
11
+ .filter((word) => word !== "")
12
+ .map(escapeRegExp)
13
+ .join("|");
14
+
15
+ /**
16
+ * Each number word standing as a word of its own, followed by one of the units after a space. A word joined to letters,
17
+ * digits or a dash on either side is the tail of a larger number (二十年's 十, "twenty-one years", "fortyten").
18
+ */
19
+ export const numberWordCounts = (source: string, numberWords: readonly string[], units: readonly string[]): WordCount[] => {
20
+ const [words, unitWords] = [alternation(numberWords), alternation(units)];
21
+ if (words === "" || unitWords === "") return [];
22
+ const pattern = new RegExp(`(?<![\\p{L}\\p{N}\\p{Pd}])(${words})\\s+(?:${unitWords})(?![\\p{L}\\p{N}])`, "giu");
23
+ const lowered = numberWords.map((word) => word.toLowerCase());
24
+ return [...source.matchAll(pattern)].flatMap((match) => {
25
+ const position = lowered.indexOf((match[1] ?? "").toLowerCase());
26
+ return position < 0 ? [] : [{ start: match.index, end: match.index + match[0].length, amount: position + 1 }];
27
+ });
28
+ };
@@ -0,0 +1,63 @@
1
+ // amount-scale-consistency: the reading half. The amounts are currency-notation's (the lexicon currency-notation says
2
+ // how a currency is written); the words of scale and their values are the lexicon amount-multiplier (weight).
3
+ import type { Detector, Finding, ProseDocument } from "../plugin.ts";
4
+ import { escapeRegExp } from "../orthography.ts";
5
+ import { amountValue, scaleMixes, type ScaleWord, type ScaledAmount } from "../structure/amount-scale.ts";
6
+ import { AMOUNT, amountsIn, formsOf, type WrittenAmount } from "./currency-notation.ts";
7
+ import { quoteAround } from "./quote-around.ts";
8
+
9
+ /** How far back a larger part of one amount may start (1億2,000万円 is read from 2,000万円). */
10
+ const LEADING_REACH = 40;
11
+
12
+ const scaleWordsOf = (doc: ProseDocument): ScaleWord[] =>
13
+ (doc.lexicons["amount-multiplier"] ?? []).flatMap((entry) => (entry.weight === undefined ? [] : [{ word: entry.pattern, value: entry.weight }]));
14
+
15
+ /** The larger parts written right before an amount the currency notation read from its last part (1億 before 2,000万円). */
16
+ const leadingParts = (text: string, offset: number, words: readonly ScaleWord[]): string => {
17
+ if (words.length === 0) return "";
18
+ const scales = words.map((word) => escapeRegExp(word.word)).join("|");
19
+ const before = text.slice(Math.max(0, offset - LEADING_REACH), offset);
20
+ return new RegExp(`(?<![0-90-9.,,])(?:${AMOUNT}(?:${scales}))+$`, "u").exec(before)?.[0] ?? "";
21
+ };
22
+
23
+ /** The written amount without its currency mark, which is on the side its form says. */
24
+ const numberPart = (amount: WrittenAmount): string => {
25
+ const [position = "", ...mark] = amount.form.split(":");
26
+ const pattern = mark.join(":");
27
+ return position === "before" ? amount.written.trimStart().slice(pattern.length) : amount.written.trimEnd().slice(0, -pattern.length);
28
+ };
29
+
30
+ /** The largest word of scale written in the number (万 in 1億2,000万 is smaller than 億), undefined when there is none. */
31
+ const unitOf = (number: string, words: readonly ScaleWord[]): number | undefined => {
32
+ const lower = number.toLowerCase();
33
+ const values = words.filter((word) => lower.includes(word.word.toLowerCase())).map((word) => word.value);
34
+ return values.length === 0 ? undefined : Math.max(...values);
35
+ };
36
+
37
+ const scaledAmountsOf = (text: string, amounts: readonly WrittenAmount[], words: readonly ScaleWord[]): ScaledAmount[] =>
38
+ amounts.flatMap((amount) => {
39
+ const leading = amount.form.startsWith("after:") ? leadingParts(text, amount.offset, words) : "";
40
+ const written = `${leading}${amount.written}`;
41
+ const number = `${leading}${numberPart(amount)}`;
42
+ const value = amountValue(number, words);
43
+ if (value === undefined) return [];
44
+ return [{ offset: amount.offset - leading.length, written, currency: amount.currency, value, unit: unitOf(number, words) }];
45
+ });
46
+
47
+ export const amountScale: Detector = (doc): Finding[] => {
48
+ const text = doc.prose ?? doc.source;
49
+ const words = scaleWordsOf(doc);
50
+ const amounts = amountsIn(
51
+ text,
52
+ formsOf(doc),
53
+ words.map((word) => word.word),
54
+ );
55
+ return scaleMixes(scaledAmountsOf(text, amounts, words)).map(({ odd, usual }) => ({
56
+ rule: "amount-scale-consistency",
57
+ severity: "info",
58
+ line: 0,
59
+ column: 0,
60
+ quote: quoteAround(text, odd.offset, odd.offset + odd.written.length),
61
+ values: { written: odd.written.trim(), usual: usual.written.trim(), offset: odd.offset },
62
+ }));
63
+ };