🎉 init: 小龙的工作空间
This commit is contained in:
@@ -0,0 +1,263 @@
|
||||
/**
|
||||
* PR-37 Part D — Industry Feature Generator
|
||||
*
|
||||
* Generates domain-specific Feature objects for each epic, using the
|
||||
* domain registry's `coreFeatures` as a template. Falls back to generic
|
||||
* feature generation for unknown domains or epics without registered features.
|
||||
*
|
||||
* @module pr-37-domain-intelligence/industry-feature-generator
|
||||
* @since PR-37
|
||||
*/
|
||||
|
||||
import { getDomain } from "./domain-registry.mjs";
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// Internal: ID counter
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
let _featureCounter = 0;
|
||||
|
||||
/**
|
||||
* Reset the internal feature ID counter (useful in tests).
|
||||
*/
|
||||
export function resetFeatureCounter() {
|
||||
_featureCounter = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the next sequential feature ID.
|
||||
*
|
||||
* @returns {string} ID like "ft-001"
|
||||
*/
|
||||
function nextFeatureId() {
|
||||
_featureCounter++;
|
||||
return `ft-${String(_featureCounter).padStart(3, "0")}`;
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// Feature type auto-detection
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Feature type constants.
|
||||
*
|
||||
* @readonly
|
||||
* @enum {string}
|
||||
*/
|
||||
export const FEATURE_TYPE = Object.freeze({
|
||||
CRUD: "CRUD",
|
||||
WORKFLOW: "workflow",
|
||||
ANALYTICS: "analytics",
|
||||
CONFIGURATION: "configuration",
|
||||
INTEGRATION: "integration",
|
||||
SEARCH: "search",
|
||||
NOTIFICATION: "notification",
|
||||
});
|
||||
|
||||
/**
|
||||
* Keywords that hint at each feature type.
|
||||
*
|
||||
* @type {object<string, RegExp[]>}
|
||||
*/
|
||||
const TYPE_PATTERNS = Object.freeze({
|
||||
[FEATURE_TYPE.ANALYTICS]: [/分析/i, /报表/i, /看板/i, /统计/i, /趋势/i, /洞察/i, /仪表盘/i, /report/i, /dashboard/i, /analysis/i],
|
||||
[FEATURE_TYPE.CONFIGURATION]: [/配置/i, /参数/i, /设置/i, /规则/i, /策略/i, /模板/i, /setting/i, /config/i],
|
||||
[FEATURE_TYPE.INTEGRATION]: [/集成/i, /接口/i, /对接/i, /同步/i, /API/i, /导入/i, /导出/i, /import/i, /export/i, /webhook/i],
|
||||
[FEATURE_TYPE.SEARCH]: [/搜索/i, /检索/i, /查询/i, /查找/i, /search/i, /query/i],
|
||||
[FEATURE_TYPE.NOTIFICATION]: [/通知/i, /消息/i, /告警/i, /提醒/i, /预警/i, /notification/i, /alert/i, /remind/i],
|
||||
[FEATURE_TYPE.WORKFLOW]: [/流程/i, /审批/i, /审核/i, /流转/i, /工单/i, /workflow/i, /approval/i, /process/i],
|
||||
[FEATURE_TYPE.CRUD]: [/管理/i, /维护/i, /档案/i, /台账/i, /登记/i, /录入/i, /编辑/i, /创建/i],
|
||||
});
|
||||
|
||||
/**
|
||||
* Auto-detect the feature type based on the feature title/description.
|
||||
*
|
||||
* @param {string} title - Feature title
|
||||
* @param {string} [description=""] - Feature description
|
||||
* @returns {string} Detected feature type
|
||||
*/
|
||||
export function detectFeatureType(title, description = "") {
|
||||
const text = `${title} ${description}`;
|
||||
|
||||
// Check patterns in priority order (CRUD last as fallback)
|
||||
const ordered = [
|
||||
FEATURE_TYPE.ANALYTICS,
|
||||
FEATURE_TYPE.CONFIGURATION,
|
||||
FEATURE_TYPE.INTEGRATION,
|
||||
FEATURE_TYPE.SEARCH,
|
||||
FEATURE_TYPE.NOTIFICATION,
|
||||
FEATURE_TYPE.WORKFLOW,
|
||||
FEATURE_TYPE.CRUD,
|
||||
];
|
||||
|
||||
for (const type of ordered) {
|
||||
const patterns = TYPE_PATTERNS[type];
|
||||
for (const pattern of patterns) {
|
||||
if (pattern.test(text)) return type;
|
||||
}
|
||||
}
|
||||
|
||||
// Default
|
||||
return FEATURE_TYPE.CRUD;
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// Public API
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Generate features for a set of epics within a given domain.
|
||||
*
|
||||
* @param {string} domainName - Target domain (e.g., "ERP", "MES", "CRM")
|
||||
* @param {object[]} epics - Array of epic objects (each must have at least `title`)
|
||||
* @returns {object[]} Array of feature objects, each with:
|
||||
* - id (string): "ft-xxx"
|
||||
* - title (string): Feature title
|
||||
* - description (string): Feature description
|
||||
* - epicId (string): ID of the parent epic
|
||||
* - featureType (string): Auto-detected feature type
|
||||
*
|
||||
* @example
|
||||
* const epics = generateIndustryEpics("WMS", "智能仓储");
|
||||
* const features = generateIndustryFeatures("WMS", epics);
|
||||
* // => [ { id: "ft-001", title: "ASN收货", description: "...", epicId: "ep-001", featureType: "workflow" }, ... ]
|
||||
*/
|
||||
export function generateIndustryFeatures(domainName, epics) {
|
||||
if (!domainName || !epics || !Array.isArray(epics) || epics.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const domain = getDomain(domainName);
|
||||
const domainFeatures = domain?.coreFeatures || {};
|
||||
const allFeatures = [];
|
||||
|
||||
for (const epic of epics) {
|
||||
const { id: epicId, title: epicTitle } = epic;
|
||||
|
||||
// Look up domain-specific features for this epic title
|
||||
const featureNames = domainFeatures[epicTitle];
|
||||
|
||||
if (featureNames && Array.isArray(featureNames) && featureNames.length > 0) {
|
||||
// Domain-specific features
|
||||
for (const name of featureNames) {
|
||||
allFeatures.push({
|
||||
id: nextFeatureId(),
|
||||
title: name,
|
||||
description: buildFeatureDescription(name, epicTitle, domain),
|
||||
epicId,
|
||||
featureType: detectFeatureType(name, epicTitle),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Fallback: generate generic features for this epic
|
||||
const generic = generateGenericFeaturesForEpic(epicTitle, epicId, domain);
|
||||
allFeatures.push(...generic);
|
||||
}
|
||||
}
|
||||
|
||||
return allFeatures;
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// Internal helpers
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Build a meaningful description for a domain-specific feature.
|
||||
*
|
||||
* @param {string} featureName - Feature name
|
||||
* @param {string} epicTitle - Parent epic title
|
||||
* @param {object|null} domain - Domain definition (may be null)
|
||||
* @returns {string}
|
||||
*/
|
||||
function buildFeatureDescription(featureName, epicTitle, domain) {
|
||||
const domainLabel = domain
|
||||
? `${domain.displayName} (${domain.name})`
|
||||
: "系统";
|
||||
return `${domainLabel} — ${epicTitle} 模块下的「${featureName}」功能,支持相关业务操作与数据管理。`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate generic features for an epic when no domain-specific features exist.
|
||||
*
|
||||
* @param {string} epicTitle - Epic title
|
||||
* @param {string} epicId - Epic ID
|
||||
* @param {object|null} domain - Domain definition (may be null)
|
||||
* @returns {object[]}
|
||||
*/
|
||||
function generateGenericFeaturesForEpic(epicTitle, epicId, domain) {
|
||||
const domainLabel = domain
|
||||
? `${domain.displayName} (${domain.name})`
|
||||
: "系统";
|
||||
|
||||
// Build generic CRUD + basic features
|
||||
const genericNames = detectGenericFeatures(epicTitle);
|
||||
|
||||
return genericNames.map((name) => {
|
||||
const type = detectFeatureType(name, epicTitle);
|
||||
return {
|
||||
id: nextFeatureId(),
|
||||
title: name,
|
||||
description: `${domainLabel} — ${epicTitle} 模块提供「${name}」功能,支持相关业务数据的维护与管理。`,
|
||||
epicId,
|
||||
featureType: type,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect sensible generic feature names for an epic based on its title.
|
||||
*
|
||||
* @param {string} epicTitle - Epic title
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function detectGenericFeatures(epicTitle) {
|
||||
const titleLower = epicTitle.toLowerCase();
|
||||
|
||||
const featureMap = {
|
||||
// Match based on common patterns
|
||||
default: [
|
||||
`${epicTitle}数据维护`,
|
||||
`${epicTitle}配置管理`,
|
||||
`${epicTitle}报表`,
|
||||
],
|
||||
};
|
||||
|
||||
// Add analytics for most epics
|
||||
const hasAnalytics = /分析|报表|统计|看板|dashboard|report/i.test(titleLower);
|
||||
if (hasAnalytics) {
|
||||
return [
|
||||
`${epicTitle}配置`,
|
||||
`${epicTitle}监控`,
|
||||
`${epicTitle}趋势分析`,
|
||||
`${epicTitle}导出`,
|
||||
];
|
||||
}
|
||||
|
||||
return featureMap.default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add domain-specific features for an epic by keyword matching.
|
||||
* This is a fallback enrichment when coreFeatures doesn't have a direct entry
|
||||
* but the epic title matches domain terminology.
|
||||
*
|
||||
* @param {string} title - Epic title
|
||||
* @param {object} domain - Domain definition
|
||||
* @returns {string[]|null} Matched feature names or null
|
||||
*/
|
||||
export function matchDomainFeaturesByKeyword(title, domain) {
|
||||
if (!domain || !domain.coreFeatures) return null;
|
||||
|
||||
// Try partial title matching against registered epic titles
|
||||
const matchedEpicKey = Object.keys(domain.coreFeatures).find((key) =>
|
||||
title.toLowerCase().includes(key.toLowerCase()) ||
|
||||
key.toLowerCase().includes(title.toLowerCase())
|
||||
);
|
||||
|
||||
if (matchedEpicKey) {
|
||||
return domain.coreFeatures[matchedEpicKey];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user