🎉 init: 小龙的工作空间
This commit is contained in:
@@ -0,0 +1,265 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
Reference in New Issue
Block a user