PMFlow/frontend/src/components/sstc/charterModules.ts

656 lines
19 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

export type CharterModuleKey =
| "module_1"
| "module_2"
| "module_3"
| "module_4"
| "module_5"
| "module_6";
export interface CharterModuleFieldSchema {
key: string;
label: string;
required?: boolean;
editable?: boolean;
multiline?: boolean;
rows?: number;
placeholder?: string;
}
export interface CharterModuleSchema {
key: CharterModuleKey;
title: string;
shortTitle: string;
fields: CharterModuleFieldSchema[];
}
export interface CharterModuleDraft {
metadata: {
status: string;
confidence_score: string;
last_validated_at: string;
evidence_refs: string;
escalation_reason: string;
};
modules: Record<CharterModuleKey, Record<string, string>>;
}
export interface ModuleProgress {
requiredTotal: number;
requiredDone: number;
percent: number;
done: boolean;
missing: string[];
}
export interface CharterProgress {
requiredTotal: number;
requiredDone: number;
requiredPercent: number;
completedModules: number;
moduleProgress: Record<CharterModuleKey, ModuleProgress>;
}
export const CHARTER_MODULE_SCHEMAS: CharterModuleSchema[] = [
{
key: "module_1",
title: "模块1:触发与目标",
shortTitle: "模块1 触发目标",
fields: [
{
key: "problem_statement",
label: "问题陈述(必填)",
required: true,
multiline: true,
rows: 3,
placeholder: "一句话问题定义,避免模糊表达",
},
{
key: "goal_statement",
label: "目标陈述(必填)",
required: true,
multiline: true,
rows: 3,
placeholder: "包含当前基线、目标值、目标日期",
},
{
key: "kpis",
label: "KPI 定义(必填)",
required: true,
multiline: true,
rows: 4,
placeholder: "每行一个指标,建议包含口径/负责人/数据源",
},
{
key: "ai_validations",
label: "AI 校验规则",
editable: false,
multiline: true,
rows: 3,
},
{
key: "escalation_trigger",
label: "升级触发条件",
editable: false,
multiline: true,
rows: 2,
},
],
},
{
key: "module_2",
title: "模块2:范围与对齐检查",
shortTitle: "模块2 范围对齐",
fields: [
{
key: "in_scope",
label: "范围内(必填)",
required: true,
multiline: true,
rows: 4,
placeholder: "每行一条功能 + 验收条件",
},
{
key: "out_of_scope",
label: "范围外(必填)",
required: true,
multiline: true,
rows: 4,
placeholder: "每行一条排除项 + 边界示例",
},
{
key: "alignment_qa",
label: "5个固定对齐问答(必填)",
required: true,
multiline: true,
rows: 5,
},
{
key: "ai_validations",
label: "AI 校验规则",
editable: false,
multiline: true,
rows: 3,
},
{
key: "escalation_trigger",
label: "升级触发条件",
editable: false,
multiline: true,
rows: 2,
},
],
},
{
key: "module_3",
title: "模块3:模型与技术依赖",
shortTitle: "模块3 模型依赖",
fields: [
{
key: "primary_model",
label: "主模型(必填)",
required: true,
placeholder: "例如:主推理模型",
},
{
key: "fallback_model",
label: "备用模型(必填)",
required: true,
placeholder: "无则写“无”",
},
{
key: "capability_table",
label: "能力边界/成本表(必填)",
required: true,
multiline: true,
rows: 4,
},
{
key: "dependency_map",
label: "技术依赖图(必填)",
required: true,
multiline: true,
rows: 4,
},
{
key: "business_view",
label: "业务视图",
multiline: true,
rows: 3,
},
{
key: "engineering_view",
label: "工程视图",
multiline: true,
rows: 3,
},
{
key: "ai_validations",
label: "AI 校验规则",
editable: false,
multiline: true,
rows: 3,
},
{
key: "escalation_trigger",
label: "升级触发条件",
editable: false,
multiline: true,
rows: 2,
},
],
},
{
key: "module_4",
title: "模块4:时间线与资源规划",
shortTitle: "模块4 计划资源",
fields: [
{
key: "milestones",
label: "里程碑计划(必填)",
required: true,
multiline: true,
rows: 4,
placeholder: "每行一个里程碑 + 开始/结束/依赖",
},
{
key: "role_effort_estimates",
label: "角色工时估算(必填)",
required: true,
multiline: true,
rows: 4,
},
{
key: "capability_owner_assignment",
label: "能力图谱分配(必填)",
required: true,
multiline: true,
rows: 3,
},
{
key: "buffer_policy",
label: "缓冲策略",
multiline: true,
rows: 2,
},
{
key: "ai_validations",
label: "AI 校验规则",
editable: false,
multiline: true,
rows: 3,
},
{
key: "escalation_trigger",
label: "升级触发条件",
editable: false,
multiline: true,
rows: 2,
},
],
},
{
key: "module_5",
title: "模块5:风险与应对",
shortTitle: "模块5 风险应对",
fields: [
{
key: "risk_register",
label: "风险台账(必填)",
required: true,
multiline: true,
rows: 4,
placeholder: "每行一个风险:类别/概率/影响/负责人",
},
{
key: "trigger_rules",
label: "触发规则(必填)",
required: true,
multiline: true,
rows: 3,
},
{
key: "execution_rule",
label: "触发后执行规则(必填)",
required: true,
multiline: true,
rows: 3,
},
{
key: "ai_validations",
label: "AI 校验规则",
editable: false,
multiline: true,
rows: 3,
},
{
key: "escalation_trigger",
label: "升级触发条件",
editable: false,
multiline: true,
rows: 2,
},
],
},
{
key: "module_6",
title: "模块6:附件与治理",
shortTitle: "模块6 附件治理",
fields: [
{
key: "attachment_metadata",
label: "附件元数据(必填)",
required: true,
multiline: true,
rows: 4,
},
{
key: "attachment_summary",
label: "附件摘要/决策提取(必填)",
required: true,
multiline: true,
rows: 3,
},
{
key: "search_tags",
label: "检索标签(必填)",
required: true,
multiline: true,
rows: 2,
},
{
key: "ai_validations",
label: "AI 校验规则",
editable: false,
multiline: true,
rows: 3,
},
{
key: "governance_rule",
label: "治理规则",
multiline: true,
rows: 2,
},
],
},
];
function toRecord(value: unknown): Record<string, unknown> {
if (!value || typeof value !== "object" || Array.isArray(value)) {
return {};
}
return value as Record<string, unknown>;
}
const CHARTER_ZH_DICTIONARY: Array<[RegExp, string]> = [
[
/Develop an AI-powered automated short video generation platform with script-to-video automation and launch an API for programmatic video content generation\./gi,
"建设 AI 驱动的短视频自动化生成平台,实现脚本转视频并开放程序化视频生成 API。",
],
[
/Develop a script-to-video automation feature for Pixelle-Video\./gi,
"为 Pixelle-Video 开发脚本转视频自动化能力。",
],
[
/Launch an API for programmatic video content generation\./gi,
"上线程序化视频内容生成 API。",
],
[
/Scale the AI video infrastructure to support increased demand\./gi,
"扩容 AI 视频基础设施以支撑增长需求。",
],
[
/Successful deployment of the script-to-video automation feature\./gi,
"脚本转视频自动化能力成功上线。",
],
[
/API launch with a minimum of 100 active users within the first three months\./gi,
"API 上线后前三个月活跃用户不少于 100。",
],
[
/50% increase in AI video infrastructure capacity within six months of the funding round\./gi,
"融资后六个月内 AI 视频基础设施容量提升 50%。",
],
[
/Design and implement the script-to-video automation feature\./gi,
"设计并实现脚本转视频自动化能力。",
],
[
/Develop and deploy the API for programmatic video content generation\./gi,
"开发并部署程序化视频内容生成 API。",
],
[
/Expand the AI video infrastructure to handle increased load\./gi,
"扩展 AI 视频基础设施以承载增长负载。",
],
[
/Develop additional features not directly related to the core script-to-video automation and API functionalities\./gi,
"不开发与核心脚本转视频和 API 功能无直接关系的附加功能。",
],
[
/Manage marketing and customer acquisition activities\./gi,
"不纳入市场营销与获客运营活动。",
],
[
/Availability of skilled AI and video development resources\./gi,
"依赖具备 AI 与视频开发能力的人才资源供给。",
],
[
/Successful integration of the API with third-party platforms\./gi,
"依赖 API 与第三方平台的顺利集成。",
],
[
/Sufficient funding to support the scaling of the AI video infrastructure\./gi,
"依赖足够资金支撑 AI 视频基础设施扩容。",
],
[
/Technical challenges in implementing the script-to-video automation feature\./gi,
"脚本转视频自动化实现存在技术挑战风险。",
],
[
/Limited adoption of the API due to technical or market issues\./gi,
"受技术或市场因素影响,API 采纳率可能不足。",
],
[
/Inadequate scaling of the AI video infrastructure leading to performance issues\./gi,
"基础设施扩容不足可能引发性能问题。",
],
[/script-to-video/gi, "脚本转视频"],
[/programmatic video content generation/gi, "程序化视频内容生成"],
[/AI video infrastructure/gi, "AI 视频基础设施"],
[/funding round/gi, "融资轮次"],
[/within the first three months/gi, "前三个月内"],
[/within six months/gi, "六个月内"],
[/successful deployment/gi, "成功上线"],
];
function applyCharterZhDictionary(text: string): string {
let output = String(text || "");
if (!output.trim()) return "";
for (const [pattern, replacement] of CHARTER_ZH_DICTIONARY) {
output = output.replace(pattern, replacement);
}
return output;
}
function toText(value: unknown): string {
if (value === null || value === undefined) return "";
if (typeof value === "string") return applyCharterZhDictionary(value);
if (typeof value === "number" || typeof value === "boolean") return String(value);
if (Array.isArray(value)) {
return value
.map((item) => {
if (item === null || item === undefined) return "";
if (typeof item === "string") return applyCharterZhDictionary(item);
if (typeof item === "number" || typeof item === "boolean") return String(item);
return applyCharterZhDictionary(JSON.stringify(item));
})
.filter((line) => line.trim().length > 0)
.join("\n");
}
try {
return applyCharterZhDictionary(JSON.stringify(value, null, 2));
} catch {
return applyCharterZhDictionary(String(value));
}
}
function splitLines(input: string): string[] {
return input
.split(/\r?\n/)
.map((line) => line.trim())
.filter((line) => line.length > 0);
}
function ensureModuleMap(): Record<CharterModuleKey, Record<string, string>> {
return {
module_1: {},
module_2: {},
module_3: {},
module_4: {},
module_5: {},
module_6: {},
};
}
function ensureModuleProgressMap(): Record<CharterModuleKey, ModuleProgress> {
return {
module_1: { requiredTotal: 0, requiredDone: 0, percent: 0, done: false, missing: [] },
module_2: { requiredTotal: 0, requiredDone: 0, percent: 0, done: false, missing: [] },
module_3: { requiredTotal: 0, requiredDone: 0, percent: 0, done: false, missing: [] },
module_4: { requiredTotal: 0, requiredDone: 0, percent: 0, done: false, missing: [] },
module_5: { requiredTotal: 0, requiredDone: 0, percent: 0, done: false, missing: [] },
module_6: { requiredTotal: 0, requiredDone: 0, percent: 0, done: false, missing: [] },
};
}
function setIfEmpty(target: Record<string, string>, key: string, value: unknown): void {
if ((target[key] || "").trim().length > 0) return;
const text = toText(value).trim();
if (text.length > 0) {
target[key] = text;
}
}
function applyLegacyMapping(
root: Record<string, unknown>,
modules: Record<CharterModuleKey, Record<string, string>>
): void {
setIfEmpty(modules.module_1, "problem_statement", root.problem_statement ?? root.rationale);
setIfEmpty(modules.module_1, "goal_statement", root.goal_statement ?? root.decision);
setIfEmpty(modules.module_1, "kpis", root.success_metrics ?? root.core_metrics);
setIfEmpty(modules.module_2, "in_scope", root.in_scope ?? root.scope_in);
setIfEmpty(modules.module_2, "out_of_scope", root.out_of_scope ?? root.scope_out);
setIfEmpty(modules.module_2, "alignment_qa", root.alignment_checklist ?? root.alignment_questions);
setIfEmpty(modules.module_3, "primary_model", root.primary_model ?? root.model_name ?? root.model);
setIfEmpty(modules.module_3, "fallback_model", root.fallback_model);
setIfEmpty(modules.module_3, "capability_table", root.capability_table ?? root.model_capabilities);
setIfEmpty(modules.module_3, "dependency_map", root.dependency_map ?? root.dependencies ?? root.tech_dependencies);
setIfEmpty(modules.module_4, "milestones", root.milestones);
setIfEmpty(modules.module_4, "role_effort_estimates", root.role_effort_estimates ?? root.resource_plan);
setIfEmpty(modules.module_4, "capability_owner_assignment", root.capability_owner_assignment ?? root.assignment);
setIfEmpty(modules.module_5, "risk_register", root.risk_register ?? root.risks);
setIfEmpty(modules.module_5, "trigger_rules", root.trigger_rules);
setIfEmpty(modules.module_5, "execution_rule", root.execution_rule ?? root.fallback_plan);
setIfEmpty(modules.module_6, "attachment_metadata", root.attachment_metadata ?? root.attachments);
setIfEmpty(modules.module_6, "attachment_summary", root.attachment_summary ?? root.notes ?? root.misc_notes);
setIfEmpty(modules.module_6, "search_tags", root.search_tags ?? root.tags);
}
export function createEmptyCharterModuleDraft(): CharterModuleDraft {
const modules = ensureModuleMap();
for (const schema of CHARTER_MODULE_SCHEMAS) {
for (const field of schema.fields) {
modules[schema.key][field.key] = "";
}
}
return {
metadata: {
status: "draft",
confidence_score: "",
last_validated_at: "",
evidence_refs: "",
escalation_reason: "",
},
modules,
};
}
export function parseCharterModuleDraft(content: Record<string, unknown> | null | undefined): CharterModuleDraft {
const root = toRecord(content);
const metadata = toRecord(root.metadata);
const modulesRoot = toRecord(root.modules);
const draft = createEmptyCharterModuleDraft();
draft.metadata.status = toText(metadata.status ?? root.status) || "draft";
draft.metadata.confidence_score = toText(metadata.confidence_score ?? root.confidence_score);
draft.metadata.last_validated_at = toText(metadata.last_validated_at ?? root.last_validated_at);
draft.metadata.evidence_refs = toText(metadata.evidence_refs ?? root.evidence_refs);
draft.metadata.escalation_reason = toText(metadata.escalation_reason ?? root.escalation_reason);
for (const schema of CHARTER_MODULE_SCHEMAS) {
const moduleRaw = toRecord(modulesRoot[schema.key]);
const directModuleRaw = toRecord(root[schema.key]);
for (const field of schema.fields) {
draft.modules[schema.key][field.key] = toText(moduleRaw[field.key] ?? directModuleRaw[field.key]);
}
}
applyLegacyMapping(root, draft.modules);
return draft;
}
export function buildCharterContentPayload(
draft: CharterModuleDraft,
originalContent: Record<string, unknown> | null | undefined
): Record<string, unknown> {
const root = toRecord(originalContent);
const payload: Record<string, unknown> = {
...root,
schema_version: "charter_ai_first_v1",
};
const confidence = Number(draft.metadata.confidence_score);
payload.metadata = {
status: draft.metadata.status.trim() || "draft",
confidence_score: Number.isFinite(confidence)
? Math.max(0, Math.min(1, confidence))
: null,
last_validated_at: draft.metadata.last_validated_at.trim() || null,
evidence_refs: splitLines(draft.metadata.evidence_refs),
escalation_reason: draft.metadata.escalation_reason.trim() || null,
};
const modules: Record<string, Record<string, string>> = {};
for (const schema of CHARTER_MODULE_SCHEMAS) {
modules[schema.key] = {};
for (const field of schema.fields) {
modules[schema.key][field.key] = draft.modules[schema.key][field.key] || "";
}
}
payload.modules = modules;
const rawSignalIds = Array.isArray(root.signal_ids) ? root.signal_ids : [];
payload.signal_ids = rawSignalIds.filter((sid) => Number.isFinite(Number(sid))).map((sid) => Number(sid));
const metrics = splitLines(draft.modules.module_1.kpis);
payload.success_metrics = metrics;
payload.core_metrics = metrics;
payload.rationale = draft.modules.module_1.problem_statement || "";
payload.decision = draft.modules.module_1.goal_statement || "";
payload.in_scope = splitLines(draft.modules.module_2.in_scope);
payload.out_of_scope = splitLines(draft.modules.module_2.out_of_scope);
payload.risks = splitLines(draft.modules.module_5.risk_register);
return payload;
}
export function calculateCharterProgress(draft: CharterModuleDraft): CharterProgress {
const moduleProgress = ensureModuleProgressMap();
let requiredTotal = 0;
let requiredDone = 0;
let completedModules = 0;
for (const schema of CHARTER_MODULE_SCHEMAS) {
const requiredFields = schema.fields.filter((field) => !!field.required);
const missing: string[] = [];
let done = 0;
for (const field of requiredFields) {
const value = draft.modules[schema.key][field.key] || "";
if (value.trim().length > 0) {
done += 1;
} else {
missing.push(field.label.replace("(必填)", ""));
}
}
const total = requiredFields.length;
const percent = total > 0 ? Math.round((done / total) * 100) : 100;
const isDone = total === done;
moduleProgress[schema.key] = {
requiredTotal: total,
requiredDone: done,
percent,
done: isDone,
missing,
};
requiredTotal += total;
requiredDone += done;
if (isDone) completedModules += 1;
}
return {
requiredTotal,
requiredDone,
requiredPercent: requiredTotal > 0 ? Math.round((requiredDone / requiredTotal) * 100) : 100,
completedModules,
moduleProgress,
};
}