dsh-form-fill-agent 0.2.17 → 0.2.18
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/README.md +3 -3
- package/lib/http.js +2 -1
- package/lib/mapping-recommendations.js +37 -0
- package/lib/ui.html +6 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
AI填表智能体:支持自动填表、表格填充、表格补全、Excel填表与 Excel回填;使用企查查 MCP 填写 XLSX 空白字段,预览确认后导出新副本。
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
dsh plugin --profile web add dsh-form-fill-agent@0.2.
|
|
8
|
+
dsh plugin --profile web add dsh-form-fill-agent@0.2.18
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
请先满足下文的 DSH、连接器及侧边栏依赖要求;安装后完整停止并重启对应 Profile。
|
|
@@ -64,7 +64,7 @@ dsh plugin --profile web add dsh-form-fill-agent@latest
|
|
|
64
64
|
|
|
65
65
|
## 使用边界与数据保存
|
|
66
66
|
|
|
67
|
-
补充要求为模型可读文本;字段或工作表范围请通过向导/工作台修改,不能仅改文字后假定执行范围已同步。任务 schema 5 可读取 schema 1–5;回退旧版本请使用升级前目录副本或独立任务目录。主包为 0.2.
|
|
67
|
+
补充要求为模型可读文本;字段或工作表范围请通过向导/工作台修改,不能仅改文字后假定执行范围已同步。任务 schema 5 可读取 schema 1–5;回退旧版本请使用升级前目录副本或独立任务目录。主包为 0.2.18,共享内核为 0.2.10,Provider 为 0.2.1,共享字段契约为 0.1.0。
|
|
68
68
|
|
|
69
69
|
支持企业完整登记名称或 18 位信用代码;简称、主体不一致或多候选不会自动猜选。仅有信用代码的表格也可作为查询锚点。连接候选检索工具时可检索简称,仍需人工确认;缺少工具时降级人工输入。字段映射采用确定性规则及人工确认,不让模型编造事实。支持普通内部公式、固定区域下拉、基础条件格式和普通表格对象;复杂 Excel 结构和 Word 暂不支持,保真边界见下方安全说明。
|
|
70
70
|
|
|
@@ -72,7 +72,7 @@ dsh plugin --profile web add dsh-form-fill-agent@latest
|
|
|
72
72
|
|
|
73
73
|
显式设置 DSH_HOME 时,任务保存至其 form-fill-tasks 目录,默认 24 小时过期;未设置时使用内存。任务保存输入表格和归一化预览,不持久化原始 MCP 响应,可主动删除任务。模型及 MCP 凭据由宿主管理。
|
|
74
74
|
|
|
75
|
-
0.2.
|
|
75
|
+
0.2.18 保存任务 schema 5,兼容读取旧 schema 1–4;回退旧版本须使用升级前任务目录副本或新目录,防止丢失已确认的字段/主体设置。
|
|
76
76
|
|
|
77
77
|
## 文档与反馈
|
|
78
78
|
|
package/lib/http.js
CHANGED
|
@@ -7,6 +7,7 @@ import { createTaskStore } from './task-store.js';
|
|
|
7
7
|
import { allCandidates, selectCandidates } from './task-model.js';
|
|
8
8
|
import { gridPage } from './grid.js';
|
|
9
9
|
import { diagnostics } from './diagnostics.js';
|
|
10
|
+
import { mappingRecommendations } from './mapping-recommendations.js';
|
|
10
11
|
const XLSX_TYPE = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
|
11
12
|
const FIXTURES = ['客户台账', '供应商准入表', '合同主体信息表'];
|
|
12
13
|
export function createFormFillHandler({ basePath = '', getPort, now = Date.now, ttlMs = 15 * 60 * 1000, maxTasks = 10, taskDirectory, getQccStatus = () => false } = {}) {
|
|
@@ -29,7 +30,7 @@ export function createFormFillHandler({ basePath = '', getPort, now = Date.now,
|
|
|
29
30
|
catalogGroups: [...QCC_FIELD_CATALOG,ACTUAL_CONTROLLER_GROUP].map(g=>({id:g.id,label:g.label,fields:g.fields.flatMap(item=>FIELD_CATALOG.filter(f=>f.key===item.id||f.aliases?.includes(item.id)).map(f=>f.key))})),
|
|
30
31
|
structure: parseWorkbook(task.bytes).sheets.filter(s => !s.hidden).map(s => ({
|
|
31
32
|
name: s.name,
|
|
32
|
-
rows: s.rows.filter(r => !r.hidden).slice(0,30).map(r => ({ number: r.number, cells: Object.values(s.cells).filter(c => c.row === r.number && !c.hidden && c.value.trim()).map(c => ({ column: c.column, label: c.value })) })),
|
|
33
|
+
rows: s.rows.filter(r => !r.hidden).slice(0,30).map(r => ({ number: r.number, cells: Object.values(s.cells).filter(c => c.row === r.number && !c.hidden && c.value.trim()).map(c => ({ column: c.column, label: c.value, recommendations: mappingRecommendations(c.value,FIELD_CATALOG) })) })),
|
|
33
34
|
})),
|
|
34
35
|
});
|
|
35
36
|
const sweep = () => { for (const [id, task] of tasks) if (now() - task.created >= ttlMs) {controllers.get(id)?.abort();tasks.delete(id);} };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Display recommendations only: adopting a candidate remains an explicit UI action.
|
|
2
|
+
// Cleaning-side keys are adapted to the form-fill catalog, not its output schema.
|
|
3
|
+
const aliases = {
|
|
4
|
+
company_name:['name','company','公司名称','单位名称','企业全称','公司全称','主体标识','原文件导入名称','单位','名称'],
|
|
5
|
+
credit_no:['CreditCode','unified_credit_code','unifiedSocialCreditCode','信用代码','社会信用代码','统一信用代码','USCC'],
|
|
6
|
+
reg_no:['registration_no','工商注册号'],
|
|
7
|
+
legal_person:['法定代表','法人代表','legalRepresentative','legal_rep'],
|
|
8
|
+
reg_capital:['注册资金'],established_date:['成立时间','企业成立日期','establishmentDate','establish_date'],
|
|
9
|
+
business_status:['工商登记状态','企业状态','reg_status'],
|
|
10
|
+
registered_address:['企业注册地址','公司注册地址'],
|
|
11
|
+
contact_preferred_phone:['mobile','tel','telephone','电话','手机号码','手机号','phone'],
|
|
12
|
+
contact_official_website:['官网','企业官网','官方网站地址','网址','website'],
|
|
13
|
+
contact_preferred_email:['首选电子邮箱','邮箱','email'],
|
|
14
|
+
actual_controller_name:['实控人','实际控制人','企业实控人名称','企业实际控制人','企业实控人名称(自然人请填写姓名)'],
|
|
15
|
+
actual_controller_direct_ratio:['实际控制人直接持股比例'],
|
|
16
|
+
actual_controller_total_ratio:['实际控制人总持股比例'],
|
|
17
|
+
actual_controller_voting_ratio:['实际控制人表决权比例'],
|
|
18
|
+
};
|
|
19
|
+
const ambiguous = {
|
|
20
|
+
地址:['registered_address','mailing_address','invoice_address'],
|
|
21
|
+
address:['registered_address','mailing_address'],企业地址:['registered_address','mailing_address'],公司地址:['registered_address','mailing_address'],
|
|
22
|
+
开业时间:['established_date'],开业日期:['established_date'],
|
|
23
|
+
所属行业:['industry_category','qcc_industry'],行业:['industry_category','qcc_industry'],法人:['legal_person'],
|
|
24
|
+
};
|
|
25
|
+
const normalize=value=>String(value??'').normalize('NFKC').toLowerCase().replace(/[\s_\-·]/g,'');
|
|
26
|
+
export function mappingRecommendations(header,catalog){
|
|
27
|
+
const label=normalize(String(header??'').normalize('NFKC').replace(/\s*\(?\s*YYYY[-/]MM[-/]DD\s*\)?\s*$/i,''));
|
|
28
|
+
if(!label)return [];
|
|
29
|
+
const available=new Set(catalog.map(f=>f.key));
|
|
30
|
+
const hinted=Object.entries(ambiguous).find(([name])=>normalize(name)===label)?.[1];
|
|
31
|
+
if(hinted)return hinted.filter(key=>available.has(key));
|
|
32
|
+
const exact=catalog.filter(f=>[f.key,f.label,...(f.aliases||[]),...(aliases[f.key]||[])].some(name=>normalize(name)===label));
|
|
33
|
+
if(exact.length)return exact.map(f=>f.key);
|
|
34
|
+
// Conservative partial recommendations only. Do not discard units or semantic
|
|
35
|
+
// qualifiers (industry definitions, staff populations, financial periods).
|
|
36
|
+
return catalog.filter(f=>[f.key,f.label,...(f.aliases||[])].some(name=>label.length>=2&&normalize(name).includes(label))).slice(0,4).map(f=>f.key);
|
|
37
|
+
}
|
package/lib/ui.html
CHANGED
|
@@ -229,8 +229,12 @@ function renderSettings(data){
|
|
|
229
229
|
select.onkeydown=event=>{if(event.key==='Enter'){event.preventDefault();finish()}};
|
|
230
230
|
search.oninput=options;dimension.onchange=options;
|
|
231
231
|
if(!chosen&&!manual){
|
|
232
|
-
|
|
233
|
-
|
|
232
|
+
// Strip only a trailing date-format annotation for recommendations. Keep
|
|
233
|
+
// the original header and require confirmation; semantic qualifiers remain.
|
|
234
|
+
const candidateLabel=String(cell.label).normalize('NFKC').replace(/\s*\(?\s*YYYY[-/]MM[-/]DD\s*\)?\s*$/i,'').trim();
|
|
235
|
+
const hintMap={'原文件导入名称':['company_name'],'单位':['company_name'],'名称':['company_name'],'法定代表人':['legal_person'],'登记状态':['business_status'],'企业状态':['business_status'],'creditcode':['credit_no'],'unifiedsocialcreditcode':['credit_no'],'地址':['registered_address','mailing_address','invoice_address'],'网址':['contact_official_website']};
|
|
236
|
+
const hints=hintMap[normalize(candidateLabel)]||[];
|
|
237
|
+
const recommended=Array.isArray(cell.recommendations)?cell.recommendations.map(key=>data.catalog.find(f=>f.key===key)).filter(Boolean):candidateLabel?[...hints.map(key=>data.catalog.find(f=>f.key===key)).filter(Boolean),...(data.catalog||[]).filter(f=>!hints.includes(f.key)&&matches(f,candidateLabel))].slice(0,4):[];
|
|
234
238
|
if(recommended.length){const hint=document.createElement('span');hint.textContent='推荐(确认后采用):';suggestions.append(hint)}
|
|
235
239
|
for(const field of recommended){const button=document.createElement('button');button.type='button';button.textContent='推荐:'+field.label;button.title=data.catalogGroups?.find(g=>g.fields.includes(field.key))?.label||'字段目录';button.onclick=()=>{chosen=field.key;manual=true;suggestions.replaceChildren();options();$('mapping-next').hidden=true;picker.open=false;trigger.focus()};suggestions.append(button)}
|
|
236
240
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-form-fill-agent",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.18",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "AI form filling and spreadsheet autofill for XLSX in DeepSeek Harness, using authorized Qichacha MCP facts with cell previews, user confirmation and export to a new copy.",
|
|
6
6
|
"repository": {
|