from pathlib import Path
from datetime import date,timedelta
from collections import Counter,defaultdict
from xlsxwriter.utility import xl_col_to_name
import json,re,ast,math,unicodedata,hashlib
import xlsxwriter,openpyxl
R=Path(__file__).resolve().parent
ROOT=Path('/Users/jack/code/010-cpt/008-zhct/zhctprompt')
SRC=ROOT/'outputs/2026-10-08-team-work-log-analysis/source'
ITEMS=json.loads((SRC/'items.json').read_text())
LOGS=json.loads((SRC/'logs.json').read_text())
PEOPLE=sorted({x['name'] for x in LOGS})
DAYS=[date(2026,8,17)+timedelta(days=i) for i in range(53)]
lsorted=sorted(LOGS,key=lambda x:(x['time'],x['name']))
LID={x['id']:f'L{i:04}' for i,x in enumerate(lsorted,1)}
ns={'re':re};tree=ast.parse((SRC.parent/'analyze_logs.py').read_text());fn=next(n for n in tree.body if isinstance(n,ast.FunctionDef) and n.name=='redact');exec(compile(ast.Module(body=[fn],type_ignores=[]),'redactor','exec'),ns);redact=ns['redact']

# One shared dictionary serves quote contents and work records. Prices are separate.
T=[
('SV01','需求与交付范围确认','虚拟服务项内容','采集客户需求、确认选购模块/功能、使用入口、标准范围与双方交付边界','项目条件表、范围确认记录','售前/产品/销售','需求|范围|蓝图|业务.*确认|客户.*确认|客户.*沟通|方案.*确认|项目.*协调|排期'),
('SV02','现场与部署条件核对','虚拟服务项内容','核对就餐规模、设备点位、现场网络/电源、利旧条件和部署资源需求','现场调研与点位表、资源条件清单','售前/交付/技术','勘察|勘测|实地测量|点位|布线准备|现场.*条件|服务器配置.*梳理|资源.*梳理|利旧'),
('SV03','服务器与基础环境部署','虚拟服务项内容','在已确认资源上安装和配置操作系统、数据库、缓存等基础运行环境','环境部署记录、服务状态检查','技术','服务器.*(?:配置|部署|安装)|(?:系统|数据库|MySQL|Redis|Linux|麒麟|环境).*?(?:配置|部署|安装)|Docker|虚拟机|云服务.*配置'),
('SV04','网络域名与访问配置','虚拟服务项内容','协同确认并配置网络、域名、证书、访问入口和端口开通事项','访问/网络检查记录、客户开通清单','技术/交付＋客户IT','网络|端口|域名|SSL|VPN|vpn|外网|内网|网络配置|堡垒机'),
('SV05','小程序账号与支付开通协同','虚拟服务项内容','协同客户办理或配置小程序、备案、商户、支付平台与相关账号资料','申请/配置资料与开通状态','售前/技术＋客户账号主体','小程序.*(?:申请|备案|认证|注册)|备案材料|商户.*(?:申请|配置)|支付宝.*(?:应用|审核|进件|开通)|支付.*开通|商户号|证书设置'),
('SV06','标准系统安装与业务配置','虚拟服务项内容','安装已选标准软件，配置角色、食堂档口、餐次、消费策略和业务参数','软件安装与配置清单、可访问入口','技术/交付','系统.*(?:部署|安装|配置)|软件.*(?:部署|安装|升级|更新)|标准.*部署|设备.*软件|档口.*(?:创建|建立)|创建.*档口|系统初始化|策略.*配置'),
('SV07','基础资料导入与初始化','虚拟服务项内容','按确认模板准备并导入人员、菜品、供应商、库存和账户等基础资料','导入模板、核对记录、初始化结果','交付/技术＋客户数据负责人','导入|录入|初始化|数据.*(?:核对|整理|修正|清理)|菜品建立|基础信息|人员信息.*收集'),
('SV08','设备安装接入与调试','虚拟服务项内容','安装和接入已确认设备，完成配对、扫码/称重/打印/摄像等基础调试','设备清单、安装配对与调试记录','交付/硬件/技术','设备.*(?:安装|配对|调试|拆箱)|(?:安装|配对|调试).*?(?:设备|消费机|秤|摄像头|打印机|读卡器)|接货|搬运|物资转运|岛台安装'),
('SV09','既有接口与支付联调','虚拟服务项内容','对已经确认并具备的接口与支付流程进行配置、联调和业务核验','接口分工表、联调记录、交易核对','技术＋客户/接口方','接口|联调|一卡通|协议|SDK|SOAP|PSAM|魔点|钉钉|数据同步|支付测试|读卡器'),
('SV10','测试验证与上线','虚拟服务项内容','检查已选业务流程、执行测试、审查版本、发布并回验上线结果','测试记录、发布版本、上线检查表','测试/技术/交付','测试|验证|回归|Review|review|代码审核|代码审查|走查|上线|发布|发版|用例|assertions|tests'),
('SV11','客户培训与操作资料','虚拟服务项内容','培训客户使用所选系统与设备，交付约定的操作说明、手册与视频','培训记录、资料清单、客户操作确认','交付/产品','培训|操作手册|说明书|操作视频|指导.*使用|流程图|操作.*指引|用户手册'),
('SV12','试运行保障与验收交接','虚拟服务项内容','按约定时段跟餐/试运行，收集处理上线使用问题，完成验收与交接','跟餐问题记录、验收清单、交接记录','交付/技术＋客户验收人','跟餐|开餐|试运行|现场值守|验收|交接|收尾|上线保障'),
('WK13','功能研发与定制改造','其他工作标签','新增功能、页面/规则/算法改造、新接口或新协议开发；项目定制与标准产品研发分清','需求/代码/原型与测试记录','产品/研发','开发|新增.*功能|重构|代码|新接口|逆向|复刻|功能改造|功能.*优化'),
('WK14','特殊部署与专项迁移','其他工作标签','信创/达梦等专项适配、高可用架构或历史数据库迁移，范围需单独确认','专项方案、适配/迁移/演练记录','技术','达梦|DM8|金仓|OceanBase|高可用|热备|集群|数据库迁移|数据迁移|历史.*回填'),
('WK15','日常维护与故障处理','其他工作标签','处理线上/现场故障、维修设备或日常远程保障，包内/保修/新增责任需核对','问题记录、原因与处理结果','技术/交付/硬件','排查|修复|故障|维修|检修|闪退|异常|售后|恢复|问题.*解决|解决.*问题'),
('WK16','产品研究设计与标准化','其他工作标签','竞品调研、产品原型、功能规划、选型、产品资料和标准化建设','研究/原型/产品标准资料','产品/硬件','竞品|调研|访谈|原型|产品设计|标准化|选型|规划|卖点|产品.*资料|PPT|海报|易拉宝'),
('WK17','安全认证与资质材料','其他工作标签','漏洞整改、等保/密评/软著和相关证书材料工作，不推定已有资质','整改/认证资料、证据状态','安全/产品/技术','漏洞|等保|密评|软著|网安|安全评估|CMA|CNAS|备案证明|CPA'),
('WK18','商务报价与售前材料','其他工作标签','项目报价估时、投标响应、建设方案和销售/渠道沟通','报价/方案/参数与评审记录','销售/售前/商务','报价|估时|工时|投标|应标|控标|建设方案|售前|定价|渠道'),
('WK19','内部管理与行政协作','其他工作标签','内部会议、预算报销、合同流程、账号/成员协作和行政事务','内部流程与协作记录','各岗位','报销|发票|预算|合同评审|盖章|续签|行政|内部.*会议|周会|月度例会|周报|候选人面试'),
('WK20','AI工具与知识方法沉淀','其他工作标签','可复用的Skill、知识库、工具、Agent与团队方法建设','工具/知识/方法资产','各岗位','Skill|skill|方法论|AI资产|知识库|工作台|Codex|Agent|知识服务'),
('WK21','学习与休假记录','非交付工作标签','学习、入职培训和休假保留记录；休假不作为工作产出','学习/培训或请假记录','各岗位','请假|婚假|休假|入职|新人培训|学习|熟悉'),
('WK99','待确认或非具体表述','待确认标签','只有无、已完成、进度百分比或笼统表述时保留原文，不强行确定具体工作','补充具体动作、项目或依据','填报人','')
]
TD={x[0]:x for x in T}
SVIDS=[x[0] for x in T if x[0].startswith('SV')]

def has(p,t):return bool(re.search(p,t,re.I))
def pick(item):
 t=item['text'];ctx=item['heading'];scope=item.get('scope','');old=item['category']
 if re.fullmatch(r'(?:无|暂无|已完成|完成|\d+[%％]|公司交办的其他工作|配合销售完成相关工作|按事业部规划完成|响应销售同事的问题，协助提供支持。)[。\s]*',t):return 'WK99','明确无具体事项','笼统/模板表述'
 if has(r'请假|婚假|休假|入职培训|新人培训',t):return 'WK21','高','学习或休假关键词'
 if has(r'报销|发票|预算|合同评审|盖章|候选人面试',t):return 'WK19','高','内部事务关键词'
 if has(r'漏洞|密评|软著|网安|安全评估|整改回函|等保认证|等保备案|CMA|CNAS|CPA',t):return 'WK17','高','安全/资质动作'
 if has(TD['WK14'][6],t):return 'WK14','高','专项环境或迁移关键词'
 if has(r'Skill|skill|方法论|AI资产|团队.*知识|方法.*沉淀|搭建.*竞品 Agent',t):return 'WK20','高','可复用工具/方法建设'
 if has(r'报价|估时|投标|应标|控标|建设方案',t):return 'WK18','高','商务/售前材料动作'
 if has(r'竞品|调研|访谈|选型|产品.*规划',t):return 'WK16','高','产品研究/选型动作'
 if has(r'跟餐|开餐|试运行|现场值守|交付验收|收尾工作',t):return 'SV12','高','运行保障/交付验收动作'
 if has(r'排查|修复|故障|维修|检修|闪退|异常|售后|问题.*(?:解决|处理)|解决.*问题',t):return 'WK15','高','问题处理动作；责任与合同待核'
 if has(r'开发|代码|重构|逆向|复刻|功能改造|新接口',t):return 'WK13','高','研发/新增实现动作'
 if has(r'小程序.*(?:申请|备案|认证)|支付宝.*(?:应用|审核|进件)|商户.*(?:申请|配置)|商户号|支付.*开通',t):return 'SV05','高','开通/平台账号动作'
 if has(r'操作手册|说明书|操作视频|用户手册|(?:客户|使用).*培训|培训.*(?:客户|商户|使用)|指导.*使用',t):return 'SV11','高','客户使用培训或操作材料'
 if has(r'网络|端口|域名|SSL|VPN|vpn|外网|堡垒机|防火墙',t):return 'SV04','高','访问/网络动作'
 if has(r'导入|录入|初始化|菜品建立|基础信息.*收集|数据.*(?:核对|清理|整理)|人员.*同步',t):return 'SV07','高','基础数据准备/导入'
 if has(r'服务器.*(?:安装|部署|配置)|(?:操作系统|数据库|Redis|Linux|麒麟|环境).*?(?:安装|部署|配置)|Docker|虚拟机',t):return 'SV03','高','基础环境部署动作'
 if has(r'勘察|勘测|实地测量|点位|现场.*条件|布线准备|利旧',t):return 'SV02','高','现场/部署前置条件'
 if has(r'设备.*(?:安装|配对|调试|拆箱)|(?:安装|配对|调试).*?(?:设备|消费机|秤|摄像头|打印机)|接货|搬运|物资转运',t):return 'SV08','高','设备安装/调试动作'
 if has(r'接口|联调|协议|SDK|SOAP|PSAM|魔点|钉钉|一卡通|读卡器',t) and not has(r'对接(?:客户|甲方|销售|负责人).*?(?:进度|疑问|沟通)',t):return 'SV09','高','具体接口/协议/设备业务联调'
 if has(r'测试|验证|回归|Review|review|代码审核|走查|上线|发布|用例|assertions|tests',t):return 'SV10','高','测试/上线动作'
 if has(r'系统.*(?:部署|安装|配置)|软件.*(?:部署|安装|更新|升级)|档口.*(?:创建|建立)|创建.*档口|策略.*配置',t):return 'SV06','高','应用安装/业务配置'
 if has(r'原型|产品设计|标准化|卖点|PPT|海报|易拉宝|产品.*资料',t) and scope!='客户项目':return 'WK16','高','标准产品设计/资料'
 if has(r'需求|范围|业务.*确认|客户.*确认|客户.*沟通|方案.*确认|项目.*协调|排期|进度|对接甲方|对接客户',t):return 'SV01','中','项目需求/协作；需确认具体范围'
 fallback={'售前方案与报价投标':'WK18','产品设计与需求确认':'SV01' if scope=='客户项目' else 'WK16','系统部署与上线发布':'SV06','第三方接口与设备对接':'SV09','修复问题与运行保障':'WK15','测试验证与代码审查':'SV10','现场安装与项目交付':'SV08','数据整理与系统迁移':'SV07','培训手册与宣传材料':'SV11','AI工具与方法沉淀':'WK20','调研选型与竞品研究':'WK16','开发新功能与改进体验':'WK13','内部行政与费用事务':'WK19','安全整改与认证':'WK17','学习休假与笼统记录':'WK21','项目协调与计划管理':'SV01' if scope=='客户项目' else 'WK19'}
 return fallback.get(old,'WK99'),'待人工核对','沿用原工作性质辅助判定，动作证据不足'

M=[]
for it in ITEMS:
 main,confidence,reason=pick(it)
 aux=[x[0] for x in T if x[0]!=main and x[6] and has(x[6],it['text'])]
 aux=aux[:5]
 fee=('可关联虚拟服务内容；历史合同包含关系待确认' if main.startswith('SV') else ('故障/保修/新增责任待核；不自动另收费' if main=='WK15' else ('范围需专项评估；不据标签自动收费' if main in ['WK13','WK14','WK17'] else '其他/内部/待确认工作；不按标签分摊价格')))
 if it.get('scope')=='标准产品建设' and main.startswith('SV'):fee='标准产品工作对应此动作类型；不视为某客户收费事项'
 if it.get('scope')=='内部支撑' and main.startswith('SV'):fee='内部工作对应此动作类型；项目归属需核对'
 M.append({**it,'main':main,'label':TD[main][1],'aux':aux,'confidence':confidence,'reason':reason,'fee':fee})
ACTIVE=[x for x in M if x['count']]
MAINCOUNT=Counter(x['main'] for x in ACTIVE)
PCOUNT=Counter((x['name'],x['main']) for x in ACTIVE)
DCOUNT=Counter((x['date'],x['main']) for x in ACTIVE)
BYDP=defaultdict(list)
for x in ACTIVE:BYDP[(x['date'],x['name'])].append(x)
LDP=Counter((x['date'],x['name']) for x in LOGS)

QUOTE_NAME='项目部署与交付实施服务（虚拟服务项）'
QUOTE_TEXT='按确认的业务范围提供需求与交付范围确认、现场条件核对、服务器/基础环境部署、访问配置协同、小程序与支付开通协同、标准系统安装配置、基础数据初始化、设备接入调试、既有接口联调、测试上线、客户培训资料、试运行验收交接。按实际适用范围填写，金额按项目确定。'

P=R/'虚拟服务报价项与全员工作标签对齐_20261008_v02.xlsx'
w=xlsxwriter.Workbook(P,{'strings_to_formulas':False,'strings_to_urls':False})
f=w.add_format({'font_name':'微软雅黑','font_size':12,'valign':'top','text_wrap':True});h=w.add_format({'font_name':'微软雅黑','font_size':12,'bold':True,'font_color':'FFFFFF','bg_color':'163B56','valign':'vcenter','text_wrap':True});n=w.add_format({'font_name':'微软雅黑','font_size':12,'num_format':'0','valign':'top'});money=w.add_format({'font_name':'微软雅黑','font_size':12,'num_format':'¥#,##0.00','valign':'top'});warn=w.add_format({'font_name':'微软雅黑','font_size':12,'bg_color':'FFF0CD','text_wrap':True,'valign':'top'})

def table(name,heads,rows,widths={}):
 s=w.add_worksheet(name);s.hide_gridlines(2);s.freeze_panes(1,min(3,len(heads)));s.set_column(0,len(heads)-1,23,f);s.set_row(0,52)
 for c,wd in widths.items():s.set_column(c,c,wd,f)
 s.write_row(0,0,heads,h)
 for i,row in enumerate(rows,1):
  for j,v in enumerate(row):
   if isinstance(v,(int,float)):s.write_number(i,j,v,n)
   else:s.write_string(i,j,redact(v),f)
  lc=max(sum(max(1,math.ceil(sum(2 if unicodedata.east_asian_width(c) in 'WF' else 1 for c in z)/max(12,widths.get(j,23)-2))) for z in str(v or '').split('\n')) for j,v in enumerate(row))
  s.set_row(i,min(150,max(32,18*lc+8)))
 if rows:s.add_table(0,0,len(rows),len(heads)-1,{'columns':[{'header':x} for x in heads],'style':'Table Style Medium 2'})
 s.set_landscape();s.fit_to_pages(1,0);return s

o=w.add_worksheet('先看双目标');o.hide_gridlines(2);o.set_column('A:A',4);o.set_column('B:H',20,f)
for rg,txt,style in [
('B2:H3','一个虚拟服务项，一套统一工作标签',h),
('B5:H7','目的1：报价中把售前确认、服务器部署与交付实施工作包装成服务类虚拟商品。价格按项目填写，不固定5000元；子项列内容，统一报价，避免任意分摊。',f),
('B9:H11','目的2：每条日志事项对应这套标签。原4855个片段均有主标签，关联463份汇报、15人、53天；需要时增加辅助标签。包含非具体、计划、卡点和复述原文。',f),
('B13:H16','SV01–SV12是虚拟服务内容标签；WK13–WK21用于研发、专项环境、维护、产品、资质、商务、内部、AI方法、学习休假；WK99用于待确认或非具体表述。这些补充标签保证大家的工作都能如实落位。',f),
('B18:H21','统计只按主标签与原计入标记累计，辅助标签不再加总。片段数不是独立任务、工时、工作质量或金额；历史标签不能证明该工作已含入某客户合同或可另收费。',warn),
('B23:H26','单价空白就是尚未定价，不能当0元。报价单列一个主项，包内子项写服务内容；具体系统、地点、设备数、服务次数/时段、税口径及包内外关系按项目确认。',warn),
('B28:H31','先看“报价单展示”和“统一标签字典”，再看“全量事项对齐”“人员标签统计”“日期人员标签视图”。历史自动标签中的低置信度记录集中在“待核对事项”。',f)]:o.merge_range(rg,txt,style)
qrows=[['V-SVC-01',QUOTE_NAME,QUOTE_TEXT,'项',1,None,None,'待按项目定价；本行为唯一收费主项']]
for tid in SVIDS:qrows.append([tid,TD[tid][1],TD[tid][3],'包内子项',None,None,None,'服务内容说明，不单独定价；适用范围需按项目确认'])
q=table('报价单展示',['报价/标签编码','报价项/子服务','可展示在报价单的内容','单位','数量','单价（元，填写）','金额（元）','说明'],qrows,{1:50,2:110,7:85})
q.write_blank(1,5,None,money);q.write_formula(1,6,'=IF(OR(E2="",F2=""),"",E2*F2)',money,'')
q.write_comment(1,5,'最新用户修正：服务类虚拟报价项，价格不固定为5000元。按项目填写。')
q.write_comment(1,4,'示例数量1项；计费范围由项目确认。')
q.write_comment(1,6,'价格未填写时保持空白，不按0元展示。')

tr=[]
for code,name,kind,desc,result,role,pat in T:
 tr.append([code,name,kind,desc,result,role,'V-SVC-01的内容标签' if code.startswith('SV') else '用于完整记录工作，不自动认定另收费',pat or '信息不足需补充','项目、产品和原文是独立维度；标签不是金额分摊'])
table('统一标签字典',['标签编码','统一标签名称','标签类型','报价/工作内容解释','应有产出或证据','常见执行角色','与虚拟报价项的关系','可复算关键词提示','使用边界'],tr,{1:40,3:95,4:65,5:45,6:65,7:70,8:65})
mr=[[x['key'],LID[x['log']],x['date'],x['name'],x['projects'],x['products'],x['main'],x['label'],'；'.join(x['aux']),'；'.join(TD[z][1] for z in x['aux']),x['mode'],x['state'],x['count'],x['confidence'],x['fee'],x['scope'],x['reason'],x['text'],x['category'],x['flags'],x['duplicate'],x['workdate'],x['source'],x['cell']] for x in M]
table('全量事项对齐',['事项编号','原日志编号','提交日期','人员','项目/工作归属','产品/能力','主标签编码','主标签名称','辅助标签编码','辅助标签名称','内容属性','原文状态','计入统计(1/0)','映射置信度','报价归属判断边界','工作范围','判定依据','原文片段（敏感值隐藏）','原工作分类','待核验提示','复述说明','原文明示工作日期','来源文件','源单元格'],mr,{4:55,5:60,7:38,9:65,14:80,16:65,17:110,19:70,22:55})
end=len(M)+1
cr=f"'全量事项对齐'!$M$2:$M${end}";lr=f"'全量事项对齐'!$G$2:$G${end}";pr=f"'全量事项对齐'!$D$2:$D${end}";dr=f"'全量事项对齐'!$C$2:$C${end}"
rows=[]
for name in PEOPLE:rows.append([name,LDP.total() if False else sum(x['name']==name for x in LOGS)]+[PCOUNT[(name,t[0])] for t in T]+[sum(x['name']==name for x in ACTIVE)])
ps=table('人员标签统计',['人员','汇报份数']+[t[0]+' '+t[1] for t in T]+['主标签片段合计'],rows,{c:27 for c in range(2,len(T)+3)})
for i,row in enumerate(rows,1):
 for j,t in enumerate(T,2):ps.write_formula(i,j,f'=SUMIFS({cr},{pr},$A{i+1},{lr},"{t[0]}")',n,row[j])
 ps.write_formula(i,len(T)+2,f'=SUM(C{i+1}:{xl_col_to_name(len(T)+1)}{i+1})',n,row[-1])
ps.conditional_format(1,2,len(PEOPLE),len(T)+1,{'type':'3_color_scale','min_color':'FFFFFF','mid_color':'DAEFF0','max_color':'258D9B'})
rows=[]
for d in DAYS:
 z=d.isoformat();rows.append([z,sum(x['date']==z for x in LOGS)]+[DCOUNT[(z,t[0])] for t in T]+[sum(x['date']==z for x in ACTIVE)])
ds=table('每日标签统计',['提交日期','汇报份数']+[t[0]+' '+t[1] for t in T]+['主标签片段合计'],rows,{c:27 for c in range(2,len(T)+3)})
for i,row in enumerate(rows,1):
 for j,t in enumerate(T,2):ds.write_formula(i,j,f'=SUMIFS({cr},{dr},$A{i+1},{lr},"{t[0]}")',n,row[j])
 ds.write_formula(i,len(T)+2,f'=SUM(C{i+1}:{xl_col_to_name(len(T)+1)}{i+1})',n,row[-1])
ds.conditional_format(1,2,len(DAYS),len(T)+1,{'type':'3_color_scale','min_color':'FFFFFF','mid_color':'DAEFF0','max_color':'258D9B'})
rows=[];matrix=[]
for d in DAYS:
 z=d.isoformat();mat=[z]
 for name in PEOPLE:
  a=BYDP[(z,name)];cc=Counter(x['main'] for x in a);num=LDP[(z,name)]
  labels='；'.join(f'{tid} {TD[tid][1]}({n})' for tid,n in cc.most_common())
  ex='\n'.join(f"{x['main']}｜{x['text']}" for x in a[:5])
  rows.append([z,name,num,len(a),labels,'；'.join(sorted({x['projects'] for x in a})),ex,'有提交' if num else '本次导出无记录，不判定缺勤'])
  mat.append(' / '.join(TD[t][1] for t,_ in cc.most_common(3)) if a else ('有日志但无计入片段' if num else '无导出记录'))
 matrix.append(mat)
table('日期人员标签视图',['提交日期','人员','汇报份数','计入片段数','主要标签（数量）','涉及项目','当日事项举例（最多5条）','记录状态'],rows,{4:90,5:70,6:110,7:65})
table('每日人员标签矩阵',['提交日期']+PEOPLE,matrix,{j:42 for j in range(1,16)})

projects=sorted({p for x in ACTIVE for p in x['projects'].split('；')})
rows=[]
for p in projects:
 a=[x for x in ACTIVE if p in x['projects'].split('；')];ct=Counter(x['main'] for x in a)
 rows.append([p,len({x['name'] for x in a}),'；'.join(sorted({x['name'] for x in a})),'；'.join(f'{tid} {TD[tid][1]}({n})' for tid,n in ct.most_common()),'\n'.join(f"{x['date']} {x['name']} {x['main']}｜{x['text']}" for x in a[:3]),'一段涉及多项目可重复关联；项目行不能加总为工作总量'])
table('项目标签汇总',['项目/工作归属','涉及人数','人员','项目工作标签分布','日志举例','统计边界'],rows,{0:45,2:80,3:115,4:120,5:80})
qa=[x for x in M if x['confidence']=='待人工核对' or x['main']=='WK99' or x['projects']=='待确认/未说明']
table('待核对事项',['事项编号','日期','人员','当前主标签','置信度','项目/归属','需要核对什么','原文片段'],[[x['key'],x['date'],x['name'],x['main']+' '+x['label'],x['confidence'],x['projects'],'补动作/项目或人工确认主标签；不为凑标签覆盖而推定',x['text']] for x in qa],{3:48,5:55,6:85,7:110})
raw=[]
for x in lsorted:
 text='\n\n'.join(f"[{a['metric']}]\n{a['text']}" for a in x['work'])
 raw.append([LID[x['id']],x['date'],x['name'],x['time'],text,x['other'],x['tomorrow'],x['learning'],x['source'],x['source_row']])
table('原日志汇总',['原日志编号','提交日期','人员','汇报时间','重点工作原文','其他重点原文','明日目标','学习反思','来源文件','源首行'],raw,{4:100,5:100,6:80,7:70,8:55})
future=[['待填写','待填写','待填写','待填写','待选择','待填写','待填写','待填写','待填写','待填写','待填写','待填写'] for _ in range(12)]
s=table('后续工作记录模板',['日期','人员','项目编号/名称','产品/业务线','主标签编码','辅助标签编码','做了什么（具体动作）','产出/结果','进度与卡点','对应报价/合同范围ID','合同包含关系（待核）','原文/证据位置'],future,{2:55,6:90,7:70,8:75,9:60,10:70,11:65})
s.data_validation(1,4,len(future),4,{'validate':'list','source':[x[0] for x in T]})
notes=[['报价类型','服务类虚拟商品；命名建议为“项目部署与交付实施服务”','用户明确双目标及最新“不是固定5000”修正'],['价格','金额留空，按具体项目填写；未定价不显示0元','不把初始示例金额作为已确定价格'],['服务内容','12个SV标签用于逐项展示适用服务；是否包含及服务量按项目确认','本版是结构与映射，不代客户签合同'],['工作完整性',f'{len(ITEMS)}个源片段全部映射；463份汇报/15人/53天可追溯','主标签唯一；信息不足归WK99'],['统计',f'沿用原计入标记，计入{len(ACTIVE)}个行动片段；计划、卡点和同份复述另标','不等于工时、任务数量或绩效'],['映射置信度','关键词直接动作、上下文/原类别辅助；弱判定入待核对事项','历史自动标签属于分析判断'],['价格与工作','标签不分摊金额，不计算人员收益或每件事的收费','人员、项目与标签统计只描述内容'],['日期','按汇报提交日期；原文明示工作日另列；凌晨补报不自动移日','保留原日期解释'],['补充标签','研发、专项环境、维护、产品、资质、商务、内部、AI、学习休假','不强行归入虚拟服务包；不自动判另收费']]
table('口径与决策',['事项','约定/说明','依据或边界'],notes,{0:30,1:115,2:100})
w.close()
(R/'source/mapped-items.json').write_text(json.dumps(M,ensure_ascii=False,indent=2))
metrics={'source_items':len(ITEMS),'mapped_items':len(M),'reports':len(LOGS),'people':len(PEOPLE),'days':len(DAYS),'counted_fragments':len(ACTIVE),'labels':len(T),'service_labels':len(SVIDS),'main_label_counts':dict(MAINCOUNT),'needs_manual_check':sum(x['confidence']=='待人工核对' for x in M),'wk99_count':sum(x['main']=='WK99' for x in M),'sheets':len(w.worksheets()),'price_fixed':False,'unit_price':None,'file':str(P)}
(R/'source/metrics.json').write_text(json.dumps(metrics,ensure_ascii=False,indent=2))
md='''# 虚拟服务报价项与全员工作标签对齐 v0.2

用户确认的两个目的：报价单体现这些售前、部署服务器、交付实施等工作，包装为服务类虚拟报价项；同时将人员每件工作对应到同一套标签。最新修正明确金额不固定为5000元，因此本版只设置一个虚拟服务主项，单价留空按项目确认；子项列服务内容，不任意分摊价格。

## 报价单可直接使用的展示

建议名称：**项目部署与交付实施服务（虚拟服务项）**。单位：项；数量按项目填写；单价/金额：待项目确定。说明：按所选营养结算、食安、采购及确认的服务范围提供下列适用服务。服务器硬件、软件许可、施工、新功能开发、第三方费用及长期运维的包含关系须另按范围明确，不推定无限服务。

|标签|报价单服务子项|具体内容|
|---|---|---|
'''
for code in SVIDS:md+=f'|{code}|{TD[code][1]}|{TD[code][3]}|\n'
md+='''
## 人员工作使用同一套标签

每条事项保留日期、人员、项目/产品、主标签、辅助标签、具体动作、产出、状态和原文证据。一个主标签用于统计，辅助标签仅描述多事项关联、不再重复累计。同一次“部署并联调上线”可按主要动作选择主标签，并附联调和上线标签。历史映射不代表该工作已计入某个客户合同或可另收费。

研发、专项部署、维护、产品研究、资质、商务、内部、AI方法和学习休假另有补充标签，避免强行塞进服务报价项。无法确认的用WK99；需要人工确认的保留置信度和依据。

## 交付范围

本版映射原4855片段、463份汇报、15人、53天，沿用2974个原计入行动片段。完整原文和标签明细均在Excel；未计入片段仍保留。价格列留空，金额公式在未定价时留空，不显示0元。人员和每日汇总只统计主标签，不能当工时、绩效、金额或已完成客户服务证明。

## 下一步使用

先在报价单选择适用的SV服务内容；人员填日报时选择同码标签。售前表仍登记项目条件，报价/合同范围ID作为关联键。每个项目的数量、服务地点/次数/时段、税口径和包内外确认后，再填单价与合同范围。当前模板是可评审结果，没有修改原报价、在线表、人员日报或客户合同。

## UC-SERVICE-WORK-002

角色：管理者、售前/商务、工作填报者；触发：需要一个虚拟服务商品与工作对齐。主流程：定义共享标签→列报价服务内容→逐条映射原日志→按人员/日期/项目聚合→回读证据。异常：未知动作归WK99，弱推断待人工确认；空价格不计算收费。验收：4855片段一个不丢、每行主标签合法、463汇报/15人/53天保留、主标签合计等于2974、价格未固定、公式复算与敏感模式检查通过。软件开发、远端写入、生产数据操作：[不适用：本轮交付本地管理资料]。正式价格、税和服务量：待项目确认；不阻断结构与工作映射交付。
'''
(R/'proposal.md').write_text(md)
print(json.dumps(metrics,ensure_ascii=False,indent=2))
