Files
2026-06-06 10:40:48 +08:00

266 lines
9.0 KiB
JavaScript

/**
* PR-37 Part C — Industry Epic Generator
*
* Generates domain-specific epics for a given strategy package.
* Replaces the generic epic generation in SF-02's strategy-to-requirement.mjs
* with domain-aware templates from the registry.
*
* @module pr-37-domain-intelligence/industry-epic-generator
* @since PR-37
*/
import { getDomain, listDomains } from "./domain-registry.mjs";
// ══════════════════════════════════════════════════════════
// Internal: ID counter
// ══════════════════════════════════════════════════════════
let _epicCounter = 0;
/**
* Reset the internal epic ID counter (useful in tests).
*/
export function resetEpicCounter() {
_epicCounter = 0;
}
/**
* Generate the next sequential epic ID.
*
* @returns {string} ID like "ep-001"
*/
function nextEpicId() {
_epicCounter++;
return `ep-${String(_epicCounter).padStart(3, "0")}`;
}
// ══════════════════════════════════════════════════════════
// Generic fallback template
// ══════════════════════════════════════════════════════════
/**
* Default generic epics used when no domain is found or for unknown domains.
*
* @type {object[]}
*/
const GENERIC_EPIC_TEMPLATES = Object.freeze([
{
title: "系统管理",
category: "系统管理",
objective: "基础系统配置与用户管理",
successMetric: "系统可配置率 ≥ 90%",
},
{
title: "数据管理",
category: "数据管理",
objective: "核心数据维护与管理",
successMetric: "数据完整率 ≥ 95%",
},
{
title: "用户管理",
category: "用户管理",
objective: "用户身份与权限管控",
successMetric: "用户管理效率提升 50%",
},
{
title: "流程管理",
category: "流程管理",
objective: "核心业务流程数字化",
successMetric: "流程处理时间缩短 40%",
},
{
title: "报表分析",
category: "报表分析",
objective: "多维度数据分析与报表",
successMetric: "报表生成时间 < 30s",
},
{
title: "安全管理",
category: "安全管理",
objective: "数据安全与合规管控",
successMetric: "安全事件为零",
},
{
title: "接口集成",
category: "接口集成",
objective: "系统间数据集成与同步",
successMetric: "集成成功率 ≥ 99.5%",
},
{
title: "运维管理",
category: "运维管理",
objective: "系统运维与监控",
successMetric: "系统可用性 ≥ 99.9%",
},
]);
// ══════════════════════════════════════════════════════════
// Public API
// ══════════════════════════════════════════════════════════
/**
* Generate industry-specific epics from a strategy package for a given domain.
*
* @param {string} domainName - Target domain (e.g., "ERP", "MES", "CRM")
* @param {object|string} strategyPackage - Strategy context; can be:
* - An object with `productName`, `description`, and/or `goals`
* - A plain string used as product name
* @returns {object[]} Array of epic objects, each with:
* - id (string): "ep-xxx"
* - title (string): Epic title in Chinese
* - description (string): Context-enriched description
* - category (string): Category grouping
* - objective (string): What this epic aims to achieve
* - successMetric (string): Measurable success criterion
*
* @example
* generateIndustryEpics("MES", { productName: "智能车间MES", description: "半导体封测", goals: ["良率提升"] })
* // => [ { id: "ep-001", title: "生产执行", ... }, ... ]
*/
export function generateIndustryEpics(domainName, strategyPackage) {
// Normalize strategyPackage
const strategyName =
typeof strategyPackage === "string"
? strategyPackage
: strategyPackage?.productName || "";
const strategyDesc =
typeof strategyPackage === "string"
? strategyPackage
: strategyPackage?.description || "";
const strategyGoals =
typeof strategyPackage === "object" && !Array.isArray(strategyPackage)
? strategyPackage.goals || []
: [];
const domain = getDomain(domainName);
if (!domain) {
// Fall back to generic template
return generateGenericEpics(domainName, strategyName, strategyDesc, strategyGoals);
}
// Use domain's coreEpics as templates
return domain.coreEpics.map((template) => {
const enriched = enrichEpic(template, domain, strategyName, strategyDesc, strategyGoals);
return {
id: nextEpicId(),
...enriched,
};
});
}
// ══════════════════════════════════════════════════════════
// Internal helpers
// ══════════════════════════════════════════════════════════
/**
* Enrich a template epic with strategy context.
*
* @param {object} template - Core epic template { title, category, objective, successMetric }
* @param {object} domain - Domain definition
* @param {string} strategyName - Product strategy name
* @param {string} strategyDesc - Strategy description
* @param {string[]} strategyGoals - Strategy goals
* @returns {object} Enriched epic
*/
function enrichEpic(template, domain, strategyName, strategyDesc, strategyGoals) {
const title = template.title;
const category = template.category;
// Build a contextual description
const contextParts = [
`基于 ${domain.displayName} (${domain.name}) 领域`,
strategyName ? `为 "${strategyName}" 产品` : "",
`实现 ${template.objective}`,
];
// Add strategy goals context if applicable
if (strategyGoals.length > 0) {
contextParts.push(`目标: ${strategyGoals.join("、")}`);
}
// Add any relevant process context
const relatedProcesses = domain.coreProcesses.filter((p) =>
p.toLowerCase().includes(title.slice(0, 4).toLowerCase())
);
if (relatedProcesses.length > 0) {
contextParts.push(`相关流程: ${relatedProcesses.join("; ")}`);
}
const description = contextParts.filter(Boolean).join("。") + "。";
return {
title,
description,
category,
objective: template.objective,
successMetric: strategyGoals.length > 0 ? enrichMetric(template.successMetric, strategyGoals) : template.successMetric,
};
}
/**
* Optionally enrich a success metric with strategy goals.
*
* @param {string} metric - Original success metric
* @param {string[]} goals - Strategy goals
* @returns {string}
*/
function enrichMetric(metric, goals) {
const extraGoals = goals
.filter((g) => !metric.toLowerCase().includes(g.toLowerCase()))
.slice(0, 2);
if (extraGoals.length === 0) return metric;
return `${metric},同时支撑 ${extraGoals.join("、")}`;
}
/**
* Generate generic epics for unknown domains.
*
* @param {string} domainName - Original domain name (may be null/unknown)
* @param {string} strategyName - Product strategy name
* @param {string} strategyDesc - Strategy description
* @param {string[]} strategyGoals - Strategy goals
* @returns {object[]}
*/
function generateGenericEpics(domainName, strategyName, strategyDesc, strategyGoals) {
const domainLabel = domainName || "通用系统";
const prefix = strategyName ? `${strategyName} ` : "";
return GENERIC_EPIC_TEMPLATES.map((template) => ({
id: nextEpicId(),
title: template.title,
description: `${prefix}${domainLabel}领域 — ${template.objective}${
strategyDesc ? `背景: ${strategyDesc}` : ""
}${strategyGoals.length > 0 ? `目标: ${strategyGoals.join("、")}` : ""}`,
category: template.category,
objective: template.objective,
successMetric: template.successMetric,
}));
}
/**
* Describe a domain briefly (utility function).
*
* @param {string} domainName - Domain name
* @returns {string} Brief domain description
*/
export function describeDomain(domainName) {
const domain = getDomain(domainName);
if (!domain) return `${domainName || "Unknown"}: domain not found in registry`;
return `${domain.displayName} (${domain.name}): ${domain.description}`;
}
/**
* List epic titles available for a given domain.
*
* @param {string} domainName - Domain name
* @returns {string[]} Epic title array, or generic titles if domain unknown
*/
export function listDomainEpics(domainName) {
const domain = getDomain(domainName);
if (!domain) return GENERIC_EPIC_TEMPLATES.map((e) => e.title);
return domain.coreEpics.map((e) => e.title);
}