266 lines
9.0 KiB
JavaScript
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);
|
|
}
|