| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980 |
- /** Shared by the Skill, save service and dashboard. No credentials or runtime dependencies. */
- export const DELIVERY_SCHEMA = 'ksf.workshop-delivery.v1';
- export const CORE_DELIVERABLES = Object.freeze([
- { key: 'market', name: '市场分析', anchor: 'market-analysis', modules: ['D1-01', 'D1-03'] },
- { key: 'social', name: '社媒场景需求', anchor: 'social-needs', modules: [] },
- { key: 'evidence', name: '用户与竞品证言池', anchor: 'evidence-pool', modules: [] },
- { key: 'opportunities', name: '三方机会卡', anchor: 'opportunity-cards', modules: ['D2-04'] },
- { key: 'validation', name: '产品论证机会清单', anchor: 'opportunity-validation', modules: ['D2-05', 'D2-06'] },
- { key: 'value', name: '场景买点方案', anchor: 'buying-points', modules: ['D2-07'] },
- { key: 'proposal', name: '产品概念提案', anchor: 'product-proposal', modules: [] },
- ]);
- export const DELIVERY_KINDS = Object.freeze({
- market: { category: 'category', types: ['category_report'] },
- social: { category: 'social', types: ['voc_report'] },
- evidence: { category: 'evidence', types: ['voc_report'] },
- opportunities: { category: 'opportunities', types: ['opportunity_card'] },
- validation: { category: 'assessment', types: ['assessment_report'] },
- value: { category: 'value', types: ['concept'] },
- proposal: { category: 'concept', types: ['concept'] },
- competitor: { category: 'competitor', types: ['competitor_report'] },
- pain: { category: 'pain', types: ['pain_list'] },
- research: { category: 'voc', types: ['voc_report'] },
- method: { category: 'method', types: ['category_report', 'pain_list', 'assessment_report'] },
- });
- export const FALLBACK_REPORT_TYPES = Object.freeze({
- category_report: 'category', competitor_report: 'competitor', voc_report: 'voc',
- pain_list: 'pain', opportunity_card: 'opportunities', assessment_report: 'assessment', concept: 'concept',
- });
- export const REPORT_ATTACHMENT_TYPES = Object.freeze({
- 'text/html': ['html', 'htm'], 'text/markdown': ['md'], 'text/plain': ['txt'],
- 'application/json': ['json'], 'application/pdf': ['pdf'],
- 'image/png': ['png'], 'image/jpeg': ['jpg', 'jpeg'], 'image/webp': ['webp'],
- });
- export const REPORT_ATTACHMENT_LIMITS = Object.freeze({ count: 8, perFileBytes: 1024 * 1024, totalBytes: 2 * 1024 * 1024 });
- const deliveryText = x => typeof x === 'string' && Boolean(x.trim());
- const deliveryChapters = report => Array.isArray(report?.chapters) ? report.chapters : [];
- const deliveryValue = x => typeof x === 'string' ? deliveryText(x) : typeof x === 'number' ? Number.isFinite(x)
- : Array.isArray(x) ? x.some(deliveryValue) : x && typeof x === 'object' ? Object.values(x).some(deliveryValue) : false;
- export function chapterHasContent(chapter) {
- return Array.isArray(chapter?.sections) && chapter.sections.some(s => {
- if (!s || typeof s !== 'object') return false;
- if (s.kind === 'para' || s.kind === 'longpara') return deliveryText(s.text);
- if (s.kind === 'table' || s.kind === 'matrix') return Array.isArray(s.data?.rows) && s.data.rows.some(deliveryValue);
- return deliveryValue(s.data);
- });
- }
- export function reportBodyStatus(artifact) {
- if (deliveryChapters(artifact?.report).some(chapterHasContent)) return 'report';
- return deliveryText(artifact?.rawText) ? 'text' : 'none';
- }
- export function validateReportDelivery(report, artifactType) {
- const d = report?.delivery;
- if (d === undefined) return null; // Historical clients remain readable and writable.
- const reject = message => { throw Object.assign(new Error(message), { code: 'VALIDATION_FAILED' }); };
- if (!d || typeof d !== 'object' || Array.isArray(d)
- || Object.keys(d).some(k => !['schema', 'moduleId', 'kind', 'contentStatus', 'outputs'].includes(k))) reject('报告交付标记字段无效');
- if (d.schema !== DELIVERY_SCHEMA || !Object.hasOwn(DELIVERY_KINDS, d.kind) || !DELIVERY_KINDS[d.kind].types.includes(artifactType)) reject('报告分类与成果类型不匹配');
- if (!/^(D1-(0[1-4]|N)|D2-0[1-8])$/.test(d.moduleId || '')) reject('报告环节标记无效');
- if (!['draft', 'ready'].includes(d.contentStatus) || !Array.isArray(d.outputs)
- || new Set(d.outputs).size !== d.outputs.length) reject('报告交付状态无效');
- for (const key of d.outputs) {
- const output = CORE_DELIVERABLES.find(o => o.key === key);
- if (!output || !deliveryChapters(report).some(ch => ch?.id === output.anchor && chapterHasContent(ch))) reject('交付物须对应有实质正文的稳定章节:' + key);
- }
- if (d.contentStatus === 'ready' && (!deliveryChapters(report).some(chapterHasContent)
- || CORE_DELIVERABLES.some(o => o.key === d.kind) && !d.outputs.includes(d.kind))) reject('已提交报告缺少对应的交付物正文');
- return d;
- }
- export function reportCategoryOf(artifact) {
- const type = artifact?.type || artifact?.artifactType;
- try {
- const d = validateReportDelivery(artifact?.report, type);
- if (d) return DELIVERY_KINDS[d.kind].category;
- } catch { /* Do not trust malformed legacy metadata; retain its artifact type. */ }
- // Only unambiguous, substantive legacy anchors refine a broad category.
- const keys = CORE_DELIVERABLES.filter(o => deliveryChapters(artifact?.report).some(ch => ch?.id === o.anchor && chapterHasContent(ch)));
- const compatible = keys.filter(o => DELIVERY_KINDS[o.key].types.includes(type));
- if (compatible.length === 1) return DELIVERY_KINDS[compatible[0].key].category;
- return Object.hasOwn(FALLBACK_REPORT_TYPES, type) ? FALLBACK_REPORT_TYPES[type] : '';
- }
|