@perrylink/dsh-einvoice 0.0.0-stage → 0.1.0-alpha.1

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.
@@ -0,0 +1,212 @@
1
+ /** Storage domain and local original-file store for SKU-04 S1. */
2
+ import { z } from 'zod';
3
+ import type { Context } from '@deepseek-ai/cordis';
4
+ import { type InvoiceLine } from '../core/invoice.js';
5
+ export declare const partySchema: z.ZodObject<{
6
+ name: z.ZodString;
7
+ tax_id: z.ZodString;
8
+ }, z.core.$strip>;
9
+ export declare const lineSchema: z.ZodObject<{
10
+ line_no: z.ZodNumber;
11
+ description: z.ZodString;
12
+ quantity: z.ZodOptional<z.ZodString>;
13
+ unit_price_cents: z.ZodOptional<z.ZodNumber>;
14
+ amount_cents: z.ZodNumber;
15
+ tax_rate: z.ZodOptional<z.ZodString>;
16
+ tax_cents: z.ZodOptional<z.ZodNumber>;
17
+ }, z.core.$strip>;
18
+ export declare const verificationSchema: z.ZodObject<{
19
+ state: z.ZodEnum<{
20
+ verified: "verified";
21
+ pending: "pending";
22
+ failed: "failed";
23
+ manual: "manual";
24
+ }>;
25
+ queued_at: z.ZodOptional<z.ZodString>;
26
+ updated_at: z.ZodOptional<z.ZodString>;
27
+ attempts: z.ZodDefault<z.ZodNumber>;
28
+ last_result: z.ZodOptional<z.ZodString>;
29
+ manual_reason: z.ZodOptional<z.ZodString>;
30
+ }, z.core.$strip>;
31
+ export declare const receiptSchema: z.ZodObject<{
32
+ receipt_id: z.ZodString;
33
+ result_code: z.ZodString;
34
+ result: z.ZodEnum<{
35
+ pass: "pass";
36
+ fail: "fail";
37
+ unknown: "unknown";
38
+ }>;
39
+ verified_at: z.ZodString;
40
+ operator: z.ZodString;
41
+ receipt_sha256: z.ZodString;
42
+ receipt_path: z.ZodString;
43
+ recorded_at: z.ZodString;
44
+ }, z.core.$strip>;
45
+ export declare const invoiceSchema: z.ZodObject<{
46
+ doc_id: z.ZodString;
47
+ entity_id: z.ZodString;
48
+ invoice_no: z.ZodString;
49
+ invoice_code: z.ZodOptional<z.ZodString>;
50
+ issue_date: z.ZodString;
51
+ buyer: z.ZodObject<{
52
+ name: z.ZodString;
53
+ tax_id: z.ZodString;
54
+ }, z.core.$strip>;
55
+ seller: z.ZodObject<{
56
+ name: z.ZodString;
57
+ tax_id: z.ZodString;
58
+ }, z.core.$strip>;
59
+ amount_cents: z.ZodNumber;
60
+ tax_cents: z.ZodNumber;
61
+ total_cents: z.ZodNumber;
62
+ currency: z.ZodLiteral<"CNY">;
63
+ lines: z.ZodArray<z.ZodObject<{
64
+ line_no: z.ZodNumber;
65
+ description: z.ZodString;
66
+ quantity: z.ZodOptional<z.ZodString>;
67
+ unit_price_cents: z.ZodOptional<z.ZodNumber>;
68
+ amount_cents: z.ZodNumber;
69
+ tax_rate: z.ZodOptional<z.ZodString>;
70
+ tax_cents: z.ZodOptional<z.ZodNumber>;
71
+ }, z.core.$strip>>;
72
+ confidence: z.ZodNumber;
73
+ field_evidence: z.ZodRecord<z.ZodString, z.ZodString>;
74
+ unknown_fields: z.ZodRecord<z.ZodString, z.ZodUnknown>;
75
+ parser_version: z.ZodString;
76
+ lifecycle: z.ZodEnum<{
77
+ received: "received";
78
+ parsed: "parsed";
79
+ verified: "verified";
80
+ archived: "archived";
81
+ quarantined: "quarantined";
82
+ }>;
83
+ verification: z.ZodOptional<z.ZodObject<{
84
+ state: z.ZodEnum<{
85
+ verified: "verified";
86
+ pending: "pending";
87
+ failed: "failed";
88
+ manual: "manual";
89
+ }>;
90
+ queued_at: z.ZodOptional<z.ZodString>;
91
+ updated_at: z.ZodOptional<z.ZodString>;
92
+ attempts: z.ZodDefault<z.ZodNumber>;
93
+ last_result: z.ZodOptional<z.ZodString>;
94
+ manual_reason: z.ZodOptional<z.ZodString>;
95
+ }, z.core.$strip>>;
96
+ receipts: z.ZodOptional<z.ZodArray<z.ZodObject<{
97
+ receipt_id: z.ZodString;
98
+ result_code: z.ZodString;
99
+ result: z.ZodEnum<{
100
+ pass: "pass";
101
+ fail: "fail";
102
+ unknown: "unknown";
103
+ }>;
104
+ verified_at: z.ZodString;
105
+ operator: z.ZodString;
106
+ receipt_sha256: z.ZodString;
107
+ receipt_path: z.ZodString;
108
+ recorded_at: z.ZodString;
109
+ }, z.core.$strip>>>;
110
+ source_path: z.ZodString;
111
+ source_sha256: z.ZodString;
112
+ source_size: z.ZodNumber;
113
+ created_at: z.ZodString;
114
+ updated_at: z.ZodString;
115
+ run_id: z.ZodString;
116
+ actor: z.ZodString;
117
+ evidence_sha256: z.ZodString;
118
+ }, z.core.$strip>;
119
+ export type InvoiceRecord = z.infer<typeof invoiceSchema>;
120
+ export type InvoiceTable = {
121
+ get(key: string): InvoiceRecord | undefined;
122
+ entries(): IterableIterator<[string, InvoiceRecord]>;
123
+ keys(): IterableIterator<string>;
124
+ readonly size: number;
125
+ put(key: string, value: InvoiceRecord): Promise<void>;
126
+ delete(key: string): Promise<boolean>;
127
+ update(key: string, fn: (current: InvoiceRecord) => InvoiceRecord): Promise<InvoiceRecord>;
128
+ };
129
+ export declare const finInvoiceSpec: {
130
+ name: string;
131
+ version: number;
132
+ tables: {
133
+ invoices: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<string, {
134
+ doc_id: string;
135
+ entity_id: string;
136
+ invoice_no: string;
137
+ issue_date: string;
138
+ buyer: {
139
+ name: string;
140
+ tax_id: string;
141
+ };
142
+ seller: {
143
+ name: string;
144
+ tax_id: string;
145
+ };
146
+ amount_cents: number;
147
+ tax_cents: number;
148
+ total_cents: number;
149
+ currency: "CNY";
150
+ lines: {
151
+ line_no: number;
152
+ description: string;
153
+ amount_cents: number;
154
+ quantity?: string | undefined;
155
+ unit_price_cents?: number | undefined;
156
+ tax_rate?: string | undefined;
157
+ tax_cents?: number | undefined;
158
+ }[];
159
+ confidence: number;
160
+ field_evidence: Record<string, string>;
161
+ unknown_fields: Record<string, unknown>;
162
+ parser_version: string;
163
+ lifecycle: "received" | "parsed" | "verified" | "archived" | "quarantined";
164
+ source_path: string;
165
+ source_sha256: string;
166
+ source_size: number;
167
+ created_at: string;
168
+ updated_at: string;
169
+ run_id: string;
170
+ actor: string;
171
+ evidence_sha256: string;
172
+ invoice_code?: string | undefined;
173
+ verification?: {
174
+ state: "verified" | "pending" | "failed" | "manual";
175
+ attempts: number;
176
+ queued_at?: string | undefined;
177
+ updated_at?: string | undefined;
178
+ last_result?: string | undefined;
179
+ manual_reason?: string | undefined;
180
+ } | undefined;
181
+ receipts?: {
182
+ receipt_id: string;
183
+ result_code: string;
184
+ result: "pass" | "fail" | "unknown";
185
+ verified_at: string;
186
+ operator: string;
187
+ receipt_sha256: string;
188
+ receipt_path: string;
189
+ recorded_at: string;
190
+ }[] | undefined;
191
+ }>;
192
+ };
193
+ };
194
+ export declare function openInvoiceTable(ctx: Context): Promise<InvoiceTable>;
195
+ /** Generate a ULID-compatible 26-character document id. */
196
+ export declare function newDocId(now?: number): string;
197
+ export declare function sha256Buffer(buffer: Buffer): string;
198
+ export declare function sha256File(filePath: string): Promise<string>;
199
+ export declare function storeOriginal(baseDir: string, sha256: string, extension: string, buffer: Buffer): Promise<string>;
200
+ export declare function findBySha(table: InvoiceTable, sha256: string): InvoiceRecord | undefined;
201
+ export declare function findByInvoiceNo(table: InvoiceTable, entityId: string, invoiceNo: string): InvoiceRecord | undefined;
202
+ export declare function assertUniqueInvoice(table: InvoiceTable, entityId: string, invoiceNo: string, docId: string): void;
203
+ export declare function emptyInvoiceRecord(input: {
204
+ docId: string;
205
+ entityId: string;
206
+ sourcePath: string;
207
+ sha256: string;
208
+ size: number;
209
+ runId: string;
210
+ actor: string;
211
+ }): InvoiceRecord;
212
+ export declare function recordLines(lines: InvoiceLine[]): InvoiceLine[];
@@ -0,0 +1,188 @@
1
+ /** Storage domain and local original-file store for SKU-04 S1. */
2
+ import { createHash, randomBytes } from 'node:crypto';
3
+ import { createReadStream } from 'node:fs';
4
+ import { mkdir, writeFile } from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import { z } from 'zod';
7
+ import { defineDomain, domainTable } from '@deepseek-ai/dsh-storage-domain';
8
+ import { InvoiceError } from '../core/invoice.js';
9
+ export const partySchema = z.object({
10
+ name: z.string(),
11
+ tax_id: z.string(),
12
+ });
13
+ export const lineSchema = z.object({
14
+ line_no: z.number().int().nonnegative(),
15
+ description: z.string(),
16
+ quantity: z.string().optional(),
17
+ unit_price_cents: z.number().int().optional(),
18
+ amount_cents: z.number().int(),
19
+ tax_rate: z.string().optional(),
20
+ tax_cents: z.number().int().optional(),
21
+ });
22
+ export const verificationSchema = z.object({
23
+ state: z.enum(['pending', 'verified', 'failed', 'manual']),
24
+ queued_at: z.string().optional(),
25
+ updated_at: z.string().optional(),
26
+ attempts: z.number().int().nonnegative().default(0),
27
+ last_result: z.string().optional(),
28
+ manual_reason: z.string().optional(),
29
+ });
30
+ export const receiptSchema = z.object({
31
+ receipt_id: z.string(),
32
+ result_code: z.string(),
33
+ result: z.enum(['pass', 'fail', 'unknown']),
34
+ verified_at: z.string(),
35
+ operator: z.string(),
36
+ receipt_sha256: z.string(),
37
+ receipt_path: z.string(),
38
+ recorded_at: z.string(),
39
+ });
40
+ export const invoiceSchema = z.object({
41
+ doc_id: z.string().min(1),
42
+ entity_id: z.string(),
43
+ invoice_no: z.string(),
44
+ invoice_code: z.string().optional(),
45
+ issue_date: z.string(),
46
+ buyer: partySchema,
47
+ seller: partySchema,
48
+ amount_cents: z.number().int(),
49
+ tax_cents: z.number().int(),
50
+ total_cents: z.number().int(),
51
+ currency: z.literal('CNY'),
52
+ lines: z.array(lineSchema),
53
+ confidence: z.number(),
54
+ field_evidence: z.record(z.string(), z.string()),
55
+ unknown_fields: z.record(z.string(), z.unknown()),
56
+ parser_version: z.string(),
57
+ lifecycle: z.enum(['received', 'parsed', 'verified', 'archived', 'quarantined']),
58
+ verification: verificationSchema.optional(),
59
+ receipts: z.array(receiptSchema).optional(),
60
+ source_path: z.string(),
61
+ source_sha256: z.string(),
62
+ source_size: z.number().int().nonnegative(),
63
+ created_at: z.string(),
64
+ updated_at: z.string(),
65
+ run_id: z.string(),
66
+ actor: z.string(),
67
+ evidence_sha256: z.string(),
68
+ });
69
+ export const finInvoiceSpec = defineDomain({
70
+ name: 'fin_invoice',
71
+ version: 1,
72
+ tables: {
73
+ invoices: domainTable(invoiceSchema),
74
+ },
75
+ });
76
+ export async function openInvoiceTable(ctx) {
77
+ const domain = await ctx.storageDomain.open(finInvoiceSpec);
78
+ ctx.effect(() => () => domain.close(), 'dsh-einvoice.domainClose');
79
+ return domain.table('invoices');
80
+ }
81
+ const CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
82
+ /** Generate a ULID-compatible 26-character document id. */
83
+ export function newDocId(now = Date.now()) {
84
+ let time = now;
85
+ let timePart = '';
86
+ for (let index = 0; index < 10; index += 1) {
87
+ timePart = (CROCKFORD[time % 32] ?? '0') + timePart;
88
+ time = Math.floor(time / 32);
89
+ }
90
+ const bytes = randomBytes(16);
91
+ let buffer = 0;
92
+ let bits = 0;
93
+ let randomPart = '';
94
+ for (const byte of bytes) {
95
+ buffer = (buffer << 8) | byte;
96
+ bits += 8;
97
+ while (bits >= 5 && randomPart.length < 16) {
98
+ bits -= 5;
99
+ randomPart += CROCKFORD[(buffer >> bits) & 31] ?? '0';
100
+ }
101
+ }
102
+ return timePart + randomPart;
103
+ }
104
+ export function sha256Buffer(buffer) {
105
+ return createHash('sha256').update(buffer).digest('hex');
106
+ }
107
+ export async function sha256File(filePath) {
108
+ const hash = createHash('sha256');
109
+ await new Promise((resolve, reject) => {
110
+ const stream = createReadStream(filePath);
111
+ stream.on('data', (chunk) => hash.update(chunk));
112
+ stream.on('error', reject);
113
+ stream.on('end', resolve);
114
+ });
115
+ return hash.digest('hex');
116
+ }
117
+ export async function storeOriginal(baseDir, sha256, extension, buffer) {
118
+ const directory = path.join(baseDir, 'originals');
119
+ await mkdir(directory, { recursive: true });
120
+ const safeExtension = extension.replace(/[^a-z0-9.]/gi, '').slice(0, 8);
121
+ const target = path.join(directory, `${sha256}${safeExtension}`);
122
+ try {
123
+ await writeFile(target, buffer, { mode: 0o600, flag: 'wx' });
124
+ }
125
+ catch (error) {
126
+ const code = error.code;
127
+ if (code !== 'EEXIST')
128
+ throw new InvoiceError('E_INV_STORAGE_FAILED', `cannot store original: ${String(error)}`, { retryable: true });
129
+ }
130
+ return target;
131
+ }
132
+ export function findBySha(table, sha256) {
133
+ for (const [, record] of table.entries()) {
134
+ if (record.source_sha256 === sha256)
135
+ return record;
136
+ }
137
+ return undefined;
138
+ }
139
+ export function findByInvoiceNo(table, entityId, invoiceNo) {
140
+ for (const [, record] of table.entries()) {
141
+ if (record.entity_id === entityId && record.invoice_no === invoiceNo)
142
+ return record;
143
+ }
144
+ return undefined;
145
+ }
146
+ export function assertUniqueInvoice(table, entityId, invoiceNo, docId) {
147
+ if (!invoiceNo)
148
+ return;
149
+ const existing = findByInvoiceNo(table, entityId, invoiceNo);
150
+ if (existing && existing.doc_id !== docId) {
151
+ throw new InvoiceError('E_INV_DUP_CONFLICT', `invoice ${invoiceNo} already exists for entity ${entityId}`, {
152
+ needs_review: true,
153
+ details: { existing_doc_id: existing.doc_id },
154
+ });
155
+ }
156
+ }
157
+ export function emptyInvoiceRecord(input) {
158
+ const now = new Date().toISOString();
159
+ return {
160
+ doc_id: input.docId,
161
+ entity_id: input.entityId,
162
+ invoice_no: '',
163
+ issue_date: '',
164
+ buyer: { name: '', tax_id: '' },
165
+ seller: { name: '', tax_id: '' },
166
+ amount_cents: 0,
167
+ tax_cents: 0,
168
+ total_cents: 0,
169
+ currency: 'CNY',
170
+ lines: [],
171
+ confidence: 0,
172
+ field_evidence: {},
173
+ unknown_fields: {},
174
+ parser_version: '',
175
+ lifecycle: 'received',
176
+ source_path: input.sourcePath,
177
+ source_sha256: input.sha256,
178
+ source_size: input.size,
179
+ created_at: now,
180
+ updated_at: now,
181
+ run_id: input.runId,
182
+ actor: input.actor,
183
+ evidence_sha256: '',
184
+ };
185
+ }
186
+ export function recordLines(lines) {
187
+ return lines.map((line) => ({ ...line }));
188
+ }
@@ -0,0 +1,8 @@
1
+ /** Three S1 model-facing tools: ingest, parse, query. */
2
+ import { type InvoiceTable } from './storage.js';
3
+ export interface ToolDeps {
4
+ table: InvoiceTable;
5
+ baseDir: string;
6
+ inputRoots: string[];
7
+ }
8
+ export declare function createTools(deps: ToolDeps): import("@deepseek-ai/dsh-tools").ToolDefinition[];