@things-factory/ai-assistant 10.1.87 → 10.1.90

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 (67) hide show
  1. package/client/components/assistant-chat.ts +19 -5
  2. package/client/components/assistant-dock.ts +36 -5
  3. package/client/components/assistant-session-manager.ts +4 -2
  4. package/client/components/entry-review.ts +246 -0
  5. package/client/utils/assistant-proposals.ts +11 -1
  6. package/client/utils/entry-proposal.ts +103 -0
  7. package/dist-client/components/assistant-chat.d.ts +1 -0
  8. package/dist-client/components/assistant-chat.js +19 -5
  9. package/dist-client/components/assistant-chat.js.map +1 -1
  10. package/dist-client/components/assistant-dock.js +31 -5
  11. package/dist-client/components/assistant-dock.js.map +1 -1
  12. package/dist-client/components/assistant-session-manager.js +4 -2
  13. package/dist-client/components/assistant-session-manager.js.map +1 -1
  14. package/dist-client/components/entry-review.d.ts +9 -0
  15. package/dist-client/components/entry-review.js +247 -0
  16. package/dist-client/components/entry-review.js.map +1 -0
  17. package/dist-client/tsconfig.tsbuildinfo +1 -1
  18. package/dist-client/utils/assistant-proposals.d.ts +1 -0
  19. package/dist-client/utils/assistant-proposals.js +9 -1
  20. package/dist-client/utils/assistant-proposals.js.map +1 -1
  21. package/dist-client/utils/entry-proposal.d.ts +55 -0
  22. package/dist-client/utils/entry-proposal.js +52 -0
  23. package/dist-client/utils/entry-proposal.js.map +1 -0
  24. package/dist-server/service/assistant-mode/registry.js +13 -0
  25. package/dist-server/service/assistant-mode/registry.js.map +1 -1
  26. package/dist-server/service/assistant-mode/resolve.d.ts +11 -0
  27. package/dist-server/service/assistant-mode/resolve.js +13 -3
  28. package/dist-server/service/assistant-mode/resolve.js.map +1 -1
  29. package/dist-server/service/assistant-mode/types.d.ts +7 -1
  30. package/dist-server/service/assistant-mode/types.js.map +1 -1
  31. package/dist-server/service/data-entry/entry-apply.d.ts +46 -0
  32. package/dist-server/service/data-entry/entry-apply.js +102 -0
  33. package/dist-server/service/data-entry/entry-apply.js.map +1 -0
  34. package/dist-server/service/data-entry/entry-resolver.d.ts +4 -0
  35. package/dist-server/service/data-entry/entry-resolver.js +80 -0
  36. package/dist-server/service/data-entry/entry-resolver.js.map +1 -0
  37. package/dist-server/service/data-entry/entry-sources.d.ts +1 -0
  38. package/dist-server/service/data-entry/entry-sources.js +41 -0
  39. package/dist-server/service/data-entry/entry-sources.js.map +1 -0
  40. package/dist-server/service/data-entry/entry-target.d.ts +157 -0
  41. package/dist-server/service/data-entry/entry-target.js +343 -0
  42. package/dist-server/service/data-entry/entry-target.js.map +1 -0
  43. package/dist-server/service/data-entry/entry-tools.d.ts +43 -0
  44. package/dist-server/service/data-entry/entry-tools.js +185 -0
  45. package/dist-server/service/data-entry/entry-tools.js.map +1 -0
  46. package/dist-server/service/index.d.ts +4 -1
  47. package/dist-server/service/index.js +10 -2
  48. package/dist-server/service/index.js.map +1 -1
  49. package/dist-server/tsconfig.tsbuildinfo +1 -1
  50. package/package.json +2 -2
  51. package/server/service/assistant-mode/registry.ts +13 -0
  52. package/server/service/assistant-mode/resolve.ts +13 -3
  53. package/server/service/assistant-mode/types.ts +7 -1
  54. package/server/service/data-entry/data-entry.test.ts +311 -0
  55. package/server/service/data-entry/entry-apply.ts +134 -0
  56. package/server/service/data-entry/entry-resolver.ts +74 -0
  57. package/server/service/data-entry/entry-sources.ts +35 -0
  58. package/server/service/data-entry/entry-target.ts +442 -0
  59. package/server/service/data-entry/entry-tools.ts +174 -0
  60. package/server/service/index.ts +7 -1
  61. package/test/entry-proposal.test.ts +67 -0
  62. package/test/open-doors.test.ts +2 -1
  63. package/translations/en.json +36 -1
  64. package/translations/ja.json +36 -1
  65. package/translations/ko.json +36 -1
  66. package/translations/ms.json +36 -1
  67. package/translations/zh.json +36 -1
@@ -0,0 +1,67 @@
1
+ /*
2
+ * The data entry card on the screen (design/ai/data-entry.md §5) - what it counts, which card is current,
3
+ * and what it leaves in the conversation. The chat's wiring is read as text: importing it pulls the shell.
4
+ */
5
+ import { readFileSync } from 'fs'
6
+ import { join } from 'path'
7
+
8
+ import { runProposal, setEntryProposalHandler } from '../client/utils/assistant-proposals'
9
+ import {
10
+ entryOutcomeNote,
11
+ entryProposalHandler,
12
+ entryReadyCount,
13
+ latestEntryKeys,
14
+ type EntryApplyAnswer
15
+ } from '../client/utils/entry-proposal'
16
+
17
+ const t = (key: string, options: any = {}) =>
18
+ String(options.defaultValue ?? key).replace(/\{(\w+)\}/g, (_, name) => String(options[name] ?? ''))
19
+
20
+ const card = (key: string, target = 'office.expense-line', rows: any[] = []) => ({ key, kind: 'entry', target, rows })
21
+
22
+ describe('the entry card', () => {
23
+ it('★ counts only the rows that will go in', () => {
24
+ const p = card('k', undefined, [
25
+ { index: 0, values: {}, sources: [], issues: [] },
26
+ { index: 1, values: {}, sources: [], issues: [{ code: 'required', field: 'amount', blocking: true }] },
27
+ { index: 2, values: {}, sources: [], issues: [{ code: 'no-source', blocking: false }] }
28
+ ])
29
+ expect(entryReadyCount(p)).toBe(2)
30
+ })
31
+
32
+ it('★ the newest card per record is the current one - an older one is replaced', () => {
33
+ const latest = latestEntryKeys([[card('k1')], [{ key: 'x', tool: 'proposeAction' }], [card('k2'), card('k3', 'office.leave')]])
34
+ expect(latest.get('office.expense-line')).toBe('k2')
35
+ expect(latest.get('office.leave')).toBe('k3')
36
+ })
37
+
38
+ it('leaves in the conversation what went in and each row that did not, by its place', () => {
39
+ const answer: EntryApplyAnswer = {
40
+ verdict: 'accepted',
41
+ applied: 2,
42
+ missed: 1,
43
+ rows: [{ index: 0, ok: true }, { index: 1, ok: false, error: 'closed period' }, { index: 2, ok: true }]
44
+ }
45
+ expect(entryOutcomeNote(answer, t)).toBe('entered 2 rows.\nrow 2: closed period')
46
+ })
47
+
48
+ it('★ every entry card runs the one framework handler, whatever record it enters - no app registers one', async () => {
49
+ const asked: string[] = []
50
+ setEntryProposalHandler(
51
+ entryProposalHandler(async (sessionId, key) => {
52
+ asked.push(`${sessionId}/${key}`)
53
+ return { verdict: 'accepted', applied: 1, missed: 0, rows: [{ index: 0, ok: true }] }
54
+ }, t)
55
+ )
56
+ const outcome = await runProposal({ ...card('k9'), tool: 'proposeOfficeExpenseLineEntries' }, { sessionId: 's1', confirm: async () => true, notify: () => {} })
57
+ expect(asked).toEqual(['s1/k9'])
58
+ expect(outcome).toMatchObject({ verdict: 'accepted', applied: 1, missed: 0 })
59
+ })
60
+
61
+ it('the chat draws the review and names the button by the rows it enters', () => {
62
+ const chat = readFileSync(join(__dirname, '..', 'client', 'components', 'assistant-chat.ts'), 'utf8')
63
+ expect(chat).toContain('<ox-assistant-entry-review .proposal=${p}>')
64
+ expect(chat).toContain("'ai-assistant.entry.enter'")
65
+ expect(chat).toContain("'ai-assistant.entry.replaced'")
66
+ })
67
+ })
@@ -142,11 +142,12 @@ describe('what the walk itself has to be true for', () => {
142
142
  * 2026-09-24: 27 root fields, 25 declared, 2 open — both streams.
143
143
  * 2026-09-25: 28 — attachToConversation (a conversation's files, shared dock §9), declared.
144
144
  * 2026-09-28: 29 — requestProposal (a proposal is requested once, ADR-0097 Q-D4), declared.
145
+ * 2026-09-30: 30 — applyEntryProposal (enter the rows the person pressed, design/ai/data-entry.md), declared.
145
146
  */
146
147
  const all = fieldsOf('Query').length + fieldsOf('Mutation').length + fieldsOf('Subscription').length
147
148
  const open = openFields('Query').length + openFields('Mutation').length + openFields('Subscription').length
148
149
 
149
- expect(all).toBe(29)
150
+ expect(all).toBe(30)
150
151
  expect(open).toBe(2)
151
152
  })
152
153
  })
@@ -197,5 +197,40 @@
197
197
  "ai-assistant.session.filter-private": "Private",
198
198
  "ai-assistant.session.filter-unread": "Unread",
199
199
  "ai-assistant.dock.ask-needs-conversation": "the question was not sent.",
200
- "ai-assistant.dock.audience-open-band": "Public conversation - everyone in this domain can read and write here."
200
+ "ai-assistant.dock.audience-open-band": "Public conversation - everyone in this domain can read and write here.",
201
+ "ai-assistant.entry.rows": "{count} rows",
202
+ "ai-assistant.entry.ready": "{count} ready",
203
+ "ai-assistant.entry.blocked": "{count} will not be entered",
204
+ "ai-assistant.entry.cut": "{count} left out over the limit",
205
+ "ai-assistant.entry.source": "from",
206
+ "ai-assistant.entry.missing": "missing",
207
+ "ai-assistant.entry.more-rows": "{count} more rows are not shown - they are entered too.",
208
+ "ai-assistant.entry.check-failed": "the application could not check these rows; they are checked again when entered.",
209
+ "ai-assistant.entry.replaced": "replaced by a newer card",
210
+ "ai-assistant.entry.enter": "enter {count} rows",
211
+ "ai-assistant.entry.nothing-to-enter": "nothing to enter",
212
+ "ai-assistant.entry.entered": "entered {count} rows.",
213
+ "ai-assistant.entry.entered-none": "no row was entered.",
214
+ "ai-assistant.entry.row-refused": "row {n}: {reason}",
215
+ "ai-assistant.entry.more-refused": "{count} more rows were not entered.",
216
+ "ai-assistant.entry.issue.required": "no value",
217
+ "ai-assistant.entry.issue.not-a-number": "not a number",
218
+ "ai-assistant.entry.issue.not-an-integer": "not a whole number",
219
+ "ai-assistant.entry.issue.not-a-date": "not a date",
220
+ "ai-assistant.entry.issue.not-a-boolean": "not yes or no",
221
+ "ai-assistant.entry.issue.not-in-list": "not one of {values}",
222
+ "ai-assistant.entry.issue.unknown-field": "not a field of this record - left out",
223
+ "ai-assistant.entry.issue.no-source": "no source given",
224
+ "ai-assistant.entry.verified": "{count} found in their file",
225
+ "ai-assistant.entry.unmatched": "{count} with a value their file does not have",
226
+ "ai-assistant.entry.evidence": "found on line {line} of the file",
227
+ "ai-assistant.entry.total": "{field} total {sum}",
228
+ "ai-assistant.entry.total-ready": "entered {sum}",
229
+ "ai-assistant.entry.total-declared": "the material says {sum}",
230
+ "ai-assistant.entry.total-not-found": "not found in the file",
231
+ "ai-assistant.entry.total-differs": "differs by {sum}",
232
+ "ai-assistant.entry.total-matches": "matches",
233
+ "ai-assistant.entry.issue.not-in-source": "the file does not have this value",
234
+ "ai-assistant.entry.issue.source-scattered": "the values are not on one line of the file",
235
+ "ai-assistant.entry.issue.source-not-checked": "a photo cannot be checked here"
201
236
  }
@@ -197,5 +197,40 @@
197
197
  "ai-assistant.session.filter-private": "非公開",
198
198
  "ai-assistant.session.filter-unread": "未読",
199
199
  "ai-assistant.dock.ask-needs-conversation": "質問は送信されませんでした。",
200
- "ai-assistant.dock.audience-open-band": "公開会話 — この事業所の誰でも読み書きできます。"
200
+ "ai-assistant.dock.audience-open-band": "公開会話 — この事業所の誰でも読み書きできます。",
201
+ "ai-assistant.entry.rows": "{count}行",
202
+ "ai-assistant.entry.ready": "入力可能 {count}",
203
+ "ai-assistant.entry.blocked": "{count}行は入力しません",
204
+ "ai-assistant.entry.cut": "上限を超えた{count}行は除外されました",
205
+ "ai-assistant.entry.source": "出典",
206
+ "ai-assistant.entry.missing": "なし",
207
+ "ai-assistant.entry.more-rows": "残り{count}行は表示されませんが、一緒に入力します。",
208
+ "ai-assistant.entry.check-failed": "アプリケーションがこれらの行を検査できませんでした。入力時に再検査します。",
209
+ "ai-assistant.entry.replaced": "新しいカードに置き換え",
210
+ "ai-assistant.entry.enter": "{count}行を入力",
211
+ "ai-assistant.entry.nothing-to-enter": "入力できる行なし",
212
+ "ai-assistant.entry.entered": "{count}行を入力しました。",
213
+ "ai-assistant.entry.entered-none": "入力された行はありません。",
214
+ "ai-assistant.entry.row-refused": "{n}行目: {reason}",
215
+ "ai-assistant.entry.more-refused": "さらに{count}行が入力されませんでした。",
216
+ "ai-assistant.entry.issue.required": "値がありません",
217
+ "ai-assistant.entry.issue.not-a-number": "数値ではありません",
218
+ "ai-assistant.entry.issue.not-an-integer": "整数ではありません",
219
+ "ai-assistant.entry.issue.not-a-date": "日付ではありません",
220
+ "ai-assistant.entry.issue.not-a-boolean": "はい/いいえではありません",
221
+ "ai-assistant.entry.issue.not-in-list": "許可された値ではありません ({values})",
222
+ "ai-assistant.entry.issue.unknown-field": "この記録にない項目のため除外しました",
223
+ "ai-assistant.entry.issue.no-source": "出典がありません",
224
+ "ai-assistant.entry.verified": "{count}行を資料と照合済み",
225
+ "ai-assistant.entry.unmatched": "{count}行に資料にない値",
226
+ "ai-assistant.entry.evidence": "資料の{line}行目で確認",
227
+ "ai-assistant.entry.total": "{field} 合計 {sum}",
228
+ "ai-assistant.entry.total-ready": "入力分 {sum}",
229
+ "ai-assistant.entry.total-declared": "資料の合計 {sum}",
230
+ "ai-assistant.entry.total-not-found": "資料で見つかりません",
231
+ "ai-assistant.entry.total-differs": "差 {sum}",
232
+ "ai-assistant.entry.total-matches": "一致",
233
+ "ai-assistant.entry.issue.not-in-source": "資料にない値です",
234
+ "ai-assistant.entry.issue.source-scattered": "値が資料の同じ行にありません",
235
+ "ai-assistant.entry.issue.source-not-checked": "写真の資料は照合できませんでした"
201
236
  }
@@ -197,5 +197,40 @@
197
197
  "ai-assistant.session.filter-private": "비공개",
198
198
  "ai-assistant.session.filter-unread": "안 읽음",
199
199
  "ai-assistant.dock.ask-needs-conversation": "질문을 보내지 않았습니다.",
200
- "ai-assistant.dock.audience-open-band": "공개 대화 — 이 사업장의 누구나 읽고 쓸 수 있습니다."
200
+ "ai-assistant.dock.audience-open-band": "공개 대화 — 이 사업장의 누구나 읽고 쓸 수 있습니다.",
201
+ "ai-assistant.entry.rows": "{count}행",
202
+ "ai-assistant.entry.ready": "입력 가능 {count}",
203
+ "ai-assistant.entry.blocked": "{count}행은 입력하지 않습니다",
204
+ "ai-assistant.entry.cut": "한도를 넘어 {count}행이 빠졌습니다",
205
+ "ai-assistant.entry.source": "출처",
206
+ "ai-assistant.entry.missing": "없음",
207
+ "ai-assistant.entry.more-rows": "나머지 {count}행은 표에 보이지 않지만 함께 입력합니다.",
208
+ "ai-assistant.entry.check-failed": "애플리케이션이 이 행들을 검사하지 못했습니다. 입력할 때 다시 검사합니다.",
209
+ "ai-assistant.entry.replaced": "새 카드로 바뀜",
210
+ "ai-assistant.entry.enter": "{count}행 입력",
211
+ "ai-assistant.entry.nothing-to-enter": "입력할 행 없음",
212
+ "ai-assistant.entry.entered": "{count}행을 입력했습니다.",
213
+ "ai-assistant.entry.entered-none": "입력한 행이 없습니다.",
214
+ "ai-assistant.entry.row-refused": "{n}행: {reason}",
215
+ "ai-assistant.entry.more-refused": "{count}행이 더 입력되지 않았습니다.",
216
+ "ai-assistant.entry.issue.required": "값이 없습니다",
217
+ "ai-assistant.entry.issue.not-a-number": "숫자가 아닙니다",
218
+ "ai-assistant.entry.issue.not-an-integer": "정수가 아닙니다",
219
+ "ai-assistant.entry.issue.not-a-date": "날짜가 아닙니다",
220
+ "ai-assistant.entry.issue.not-a-boolean": "예/아니오가 아닙니다",
221
+ "ai-assistant.entry.issue.not-in-list": "허용된 값이 아닙니다 ({values})",
222
+ "ai-assistant.entry.issue.unknown-field": "이 기록에 없는 칸이라 뺐습니다",
223
+ "ai-assistant.entry.issue.no-source": "출처가 없습니다",
224
+ "ai-assistant.entry.verified": "{count}행 자료와 대조됨",
225
+ "ai-assistant.entry.unmatched": "{count}행에 자료에 없는 값",
226
+ "ai-assistant.entry.evidence": "자료 {line}번째 줄에서 확인",
227
+ "ai-assistant.entry.total": "{field} 합계 {sum}",
228
+ "ai-assistant.entry.total-ready": "입력분 {sum}",
229
+ "ai-assistant.entry.total-declared": "자료의 합계 {sum}",
230
+ "ai-assistant.entry.total-not-found": "자료에서 찾지 못함",
231
+ "ai-assistant.entry.total-differs": "차이 {sum}",
232
+ "ai-assistant.entry.total-matches": "일치",
233
+ "ai-assistant.entry.issue.not-in-source": "자료에 없는 값입니다",
234
+ "ai-assistant.entry.issue.source-scattered": "값이 자료의 한 줄에 함께 있지 않습니다",
235
+ "ai-assistant.entry.issue.source-not-checked": "사진 자료는 대조하지 못했습니다"
201
236
  }
@@ -197,5 +197,40 @@
197
197
  "ai-assistant.session.filter-private": "Peribadi",
198
198
  "ai-assistant.session.filter-unread": "Belum dibaca",
199
199
  "ai-assistant.dock.ask-needs-conversation": "soalan tidak dihantar.",
200
- "ai-assistant.dock.audience-open-band": "Perbualan awam - sesiapa dalam domain ini boleh membaca dan menulis di sini."
200
+ "ai-assistant.dock.audience-open-band": "Perbualan awam - sesiapa dalam domain ini boleh membaca dan menulis di sini.",
201
+ "ai-assistant.entry.rows": "{count} baris",
202
+ "ai-assistant.entry.ready": "{count} sedia",
203
+ "ai-assistant.entry.blocked": "{count} tidak akan dimasukkan",
204
+ "ai-assistant.entry.cut": "{count} ditinggalkan kerana melebihi had",
205
+ "ai-assistant.entry.source": "sumber",
206
+ "ai-assistant.entry.missing": "tiada",
207
+ "ai-assistant.entry.more-rows": "{count} baris lagi tidak dipaparkan - ia turut dimasukkan.",
208
+ "ai-assistant.entry.check-failed": "aplikasi tidak dapat menyemak baris ini; ia disemak semula semasa dimasukkan.",
209
+ "ai-assistant.entry.replaced": "diganti kad yang lebih baharu",
210
+ "ai-assistant.entry.enter": "masukkan {count} baris",
211
+ "ai-assistant.entry.nothing-to-enter": "tiada untuk dimasukkan",
212
+ "ai-assistant.entry.entered": "{count} baris dimasukkan.",
213
+ "ai-assistant.entry.entered-none": "tiada baris dimasukkan.",
214
+ "ai-assistant.entry.row-refused": "baris {n}: {reason}",
215
+ "ai-assistant.entry.more-refused": "{count} baris lagi tidak dimasukkan.",
216
+ "ai-assistant.entry.issue.required": "tiada nilai",
217
+ "ai-assistant.entry.issue.not-a-number": "bukan nombor",
218
+ "ai-assistant.entry.issue.not-an-integer": "bukan nombor bulat",
219
+ "ai-assistant.entry.issue.not-a-date": "bukan tarikh",
220
+ "ai-assistant.entry.issue.not-a-boolean": "bukan ya atau tidak",
221
+ "ai-assistant.entry.issue.not-in-list": "bukan salah satu daripada {values}",
222
+ "ai-assistant.entry.issue.unknown-field": "bukan medan rekod ini - ditinggalkan",
223
+ "ai-assistant.entry.issue.no-source": "tiada sumber",
224
+ "ai-assistant.entry.verified": "{count} ditemui dalam fail",
225
+ "ai-assistant.entry.unmatched": "{count} dengan nilai yang tiada dalam fail",
226
+ "ai-assistant.entry.evidence": "ditemui pada baris {line} fail",
227
+ "ai-assistant.entry.total": "jumlah {field} {sum}",
228
+ "ai-assistant.entry.total-ready": "dimasukkan {sum}",
229
+ "ai-assistant.entry.total-declared": "bahan menyatakan {sum}",
230
+ "ai-assistant.entry.total-not-found": "tidak ditemui dalam fail",
231
+ "ai-assistant.entry.total-differs": "berbeza {sum}",
232
+ "ai-assistant.entry.total-matches": "sepadan",
233
+ "ai-assistant.entry.issue.not-in-source": "fail tiada nilai ini",
234
+ "ai-assistant.entry.issue.source-scattered": "nilai tidak berada pada satu baris fail",
235
+ "ai-assistant.entry.issue.source-not-checked": "foto tidak dapat disemak di sini"
201
236
  }
@@ -197,5 +197,40 @@
197
197
  "ai-assistant.session.filter-private": "私密",
198
198
  "ai-assistant.session.filter-unread": "未读",
199
199
  "ai-assistant.dock.ask-needs-conversation": "问题未发送。",
200
- "ai-assistant.dock.audience-open-band": "公开对话 — 本工作区的任何人都可以阅读和发言。"
200
+ "ai-assistant.dock.audience-open-band": "公开对话 — 本工作区的任何人都可以阅读和发言。",
201
+ "ai-assistant.entry.rows": "{count}行",
202
+ "ai-assistant.entry.ready": "可录入 {count}",
203
+ "ai-assistant.entry.blocked": "{count}行不会录入",
204
+ "ai-assistant.entry.cut": "超出上限,{count}行未包含",
205
+ "ai-assistant.entry.source": "来源",
206
+ "ai-assistant.entry.missing": "缺少",
207
+ "ai-assistant.entry.more-rows": "其余{count}行未显示,但会一并录入。",
208
+ "ai-assistant.entry.check-failed": "应用无法检查这些行;录入时会再次检查。",
209
+ "ai-assistant.entry.replaced": "已被新卡片取代",
210
+ "ai-assistant.entry.enter": "录入{count}行",
211
+ "ai-assistant.entry.nothing-to-enter": "没有可录入的行",
212
+ "ai-assistant.entry.entered": "已录入{count}行。",
213
+ "ai-assistant.entry.entered-none": "未录入任何行。",
214
+ "ai-assistant.entry.row-refused": "第{n}行:{reason}",
215
+ "ai-assistant.entry.more-refused": "另有{count}行未录入。",
216
+ "ai-assistant.entry.issue.required": "缺少值",
217
+ "ai-assistant.entry.issue.not-a-number": "不是数字",
218
+ "ai-assistant.entry.issue.not-an-integer": "不是整数",
219
+ "ai-assistant.entry.issue.not-a-date": "不是日期",
220
+ "ai-assistant.entry.issue.not-a-boolean": "不是是/否",
221
+ "ai-assistant.entry.issue.not-in-list": "不是允许的值({values})",
222
+ "ai-assistant.entry.issue.unknown-field": "不是此记录的字段,已忽略",
223
+ "ai-assistant.entry.issue.no-source": "未注明来源",
224
+ "ai-assistant.entry.verified": "{count}行已与资料核对",
225
+ "ai-assistant.entry.unmatched": "{count}行含资料中没有的值",
226
+ "ai-assistant.entry.evidence": "在资料第{line}行找到",
227
+ "ai-assistant.entry.total": "{field}合计 {sum}",
228
+ "ai-assistant.entry.total-ready": "录入部分 {sum}",
229
+ "ai-assistant.entry.total-declared": "资料合计 {sum}",
230
+ "ai-assistant.entry.total-not-found": "资料中未找到",
231
+ "ai-assistant.entry.total-differs": "相差 {sum}",
232
+ "ai-assistant.entry.total-matches": "一致",
233
+ "ai-assistant.entry.issue.not-in-source": "资料中没有此值",
234
+ "ai-assistant.entry.issue.source-scattered": "这些值不在资料的同一行",
235
+ "ai-assistant.entry.issue.source-not-checked": "照片资料无法核对"
201
236
  }