Sistema de códigos jerárquicos 2-niveles + asignación determinista post-IA
- hierarchy.ts: Spike (S) agregado, buildHierarchyPath genera [E01-F04] (2 niveles)
Legacy [E05-F04-U01] preservado (regex opcional 3er segmento)
- hierarchy-generator.ts (nuevo): analyzeExisting() computa contadores por épica+tipo
assignEpicCodes() asigna E{max+1} secuencial
assignItemCodes() asigna {epic}-{tipo}{n+1} a cada HU dentro de su épica
- project-analyzer.ts: post-procesa épicas y HUs con generador de códigos
saveEpicDrafts usa epicCode en metadata y título con [E01]
- prompts-db.ts: prompt FASE 2 instruye a la IA a no generar códigos
- workitems.ts: EnrichedEpic._epicCode, EnrichedUserStory._epicCode/_itemCode
- DashboardView: muestra códigos en drafts y tabla de épicas
This commit is contained in:
@@ -2,7 +2,8 @@ import { callAI } from '@/services/ai'
|
||||
import { getPrompt } from '@/services/prompts-db'
|
||||
import { getSessionsByProject, getSessionSummary, getProjectState } from '@/services/transcriptions-db'
|
||||
import { saveDraft, createDraftId } from '@/services/hu-drafts-db'
|
||||
import type { EnrichedUserStory } from '@/stores/workitems'
|
||||
import { analyzeExisting, assignEpicCodes, assignItemCodes } from '@/services/hierarchy-generator'
|
||||
import type { EnrichedEpic, EnrichedUserStory } from '@/stores/workitems'
|
||||
|
||||
export interface AnalysisHU {
|
||||
title: string
|
||||
@@ -70,8 +71,9 @@ export async function analyzeProjectEpics(
|
||||
projectId: number,
|
||||
projectName: string,
|
||||
existingHUs: EnrichedUserStory[],
|
||||
existingEpics: string[], // épicas que ya están en KAPPA
|
||||
existingEpics: string[], // nombres de épicas que ya están en KAPPA
|
||||
signal?: AbortSignal,
|
||||
existingEpicItems?: EnrichedEpic[], // épicas enriquecidas para contar códigos
|
||||
): Promise<AnalysisEpicsResult> {
|
||||
const context = await buildProjectContext(projectId, projectName, existingHUs)
|
||||
|
||||
@@ -101,8 +103,14 @@ IMPORTANTE: Respondé SOLO con épicas en esta fase. NO generes HUs aún.`
|
||||
try {
|
||||
const jsonStr = extractJSON(content)
|
||||
const parsed = JSON.parse(jsonStr)
|
||||
const rawEpics: AnalysisEpic[] = parsed.epics || parsed.epic || []
|
||||
|
||||
// Asignar códigos jerárquicos (E06, E07...) a las épicas propuestas
|
||||
const counters = analyzeExisting(existingEpicItems || [], existingHUs)
|
||||
const epicsWithCodes = assignEpicCodes(rawEpics, counters)
|
||||
|
||||
const result: AnalysisEpicsResult = {
|
||||
epics: parsed.epics || parsed.epic || [],
|
||||
epics: epicsWithCodes,
|
||||
summary: parsed.summary || '',
|
||||
rationale: parsed.rationale || parsed.summary || '',
|
||||
}
|
||||
@@ -153,11 +161,21 @@ IMPORTANTE: Respondé SOLO con HUs en esta fase. NO generes épicas nuevas.`
|
||||
try {
|
||||
const jsonStr = extractJSON(content)
|
||||
const parsed = JSON.parse(jsonStr)
|
||||
const rawHUs: AnalysisHU[] = parsed.hus || []
|
||||
|
||||
// Asignar códigos jerárquicos (E01-F04, E01-T01...) a las HUs propuestas
|
||||
const counters = analyzeExisting([], existingHUs)
|
||||
const epicsWithCodes = confirmedEpics.map(e => ({
|
||||
name: e.name,
|
||||
epicCode: (e as any).epicCode || '',
|
||||
}))
|
||||
const husWithCodes = assignItemCodes(rawHUs, epicsWithCodes, counters)
|
||||
|
||||
const result: AnalysisHUsResult = {
|
||||
hus: parsed.hus || [],
|
||||
hus: husWithCodes,
|
||||
summary: parsed.summary || '',
|
||||
}
|
||||
console.log(`[Alpha] Phase 2 complete: ${result.hus.length} HUs generadas`)
|
||||
console.log(`[Alpha] Phase 2 complete: ${result.hus.length} HUs generadas con códigos`)
|
||||
return result
|
||||
} catch (e) {
|
||||
console.error('[Alpha] Failed to parse HUs analysis. Raw:', content)
|
||||
@@ -176,18 +194,23 @@ export async function saveEpicDrafts(
|
||||
let skipped = 0
|
||||
|
||||
for (const epic of epics) {
|
||||
const epicWithCode = epic as any
|
||||
const epicCode = epicWithCode.epicCode || ''
|
||||
const fullTitle = epicCode ? `[${epicCode}] ${epic.name}` : epic.name
|
||||
|
||||
const normalizedName = epic.name.toLowerCase().trim()
|
||||
const isDuplicate = existingHUs.some(ex => (ex._cleanTitle || ex.title).toLowerCase().trim() === normalizedName)
|
||||
if (isDuplicate) { skipped++; continue }
|
||||
|
||||
await saveDraft({
|
||||
id: createDraftId(), projectId, title: epic.name,
|
||||
id: createDraftId(), projectId, title: fullTitle,
|
||||
description: epic.description, acceptanceCriteria: '',
|
||||
priority: 'Media', type: 'E',
|
||||
metadata: JSON.stringify({
|
||||
linkedHuTitles: epic.linkedHuTitles,
|
||||
estimatedStart: epic.estimatedStart,
|
||||
estimatedEnd: epic.estimatedEnd,
|
||||
epicCode,
|
||||
}),
|
||||
syncStatus: 'draft', createdAt: new Date().toISOString(),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user