Files
16gagent/research/pr-37/pr-37-domain-intelligence/industry-feature-generator.mjs
T
2026-06-06 10:40:48 +08:00

264 lines
9.0 KiB
JavaScript

/**
* 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;
}