Skip to content

Commit 3cd5eff

Browse files
muchinyclaude
andcommitted
Ajouter export complet XLSX et corriger chargement config Tauri
- Nouvel export unifie (13 feuilles XLSX) avec toutes les donnees du pipeline (metadonnees, scenarios, exigences, entites, cas limites, criteres de succes, clarifications, validation, tests, couverture, tracabilite, conformite) - Corriger resolution config.yaml depuis src-tauri/ (fallback ../config.yaml) - Augmenter max_tokens/context_size a 32768 et timeout a 3600s - Ajouter bouton "Exporter tout" sur le dashboard et dans le dropdown export Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f1d733a commit 3cd5eff

9 files changed

Lines changed: 405 additions & 655 deletions

File tree

config.yaml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@ llm:
88
provider: "ollama"
99
model_name: "qwen3:8b"
1010
api_base_url: "http://localhost:11434"
11-
max_tokens: 16384
11+
max_tokens: 32768
1212
temperature: 0.1
13-
timeout_secs: 300
13+
timeout_secs: 3600
1414
# Taille du contexte (num_ctx) envoye a Ollama.
15-
# Reduire economise de la VRAM (KV cache). 8192 suffit pour spec-forge.
16-
# Qwen3:8B defaut=32768 → ~4 GB de KV cache ; 8192 → ~1 GB.
17-
context_size: 8192
15+
# 32768 = defaut qwen3:8b, utilise ~4 GB KV cache (necessite 12+ GB VRAM).
16+
context_size: 32768
1817

1918
templates:
2019
directory: "templates"

output/specs/spec-specification-9-user-stories.md

Lines changed: 0 additions & 606 deletions
This file was deleted.

src-tauri/src/lib.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,21 @@ pub fn run() {
2727
.plugin(tauri_plugin_shell::init())
2828
.plugin(tauri_plugin_dialog::init())
2929
.setup(move |app| {
30-
let config = Config::load().unwrap_or_else(|_| {
30+
// Chercher config.yaml : d'abord dans le CWD, sinon dans le parent (cargo tauri dev)
31+
let config = if std::path::Path::new("config.yaml").exists() {
32+
Config::load().unwrap_or_default()
33+
} else if std::path::Path::new("../config.yaml").exists() {
34+
Config::load_from_file("../config").unwrap_or_default()
35+
} else {
3136
tracing::warn!("config.yaml non trouve, utilisation des valeurs par defaut");
3237
Config::default()
33-
});
38+
};
39+
tracing::info!(
40+
max_tokens = config.llm.max_tokens,
41+
context_size = config.llm.context_size,
42+
timeout_secs = config.llm.timeout_secs,
43+
"Configuration LLM chargee"
44+
);
3445

3546
// Resoudre le chemin des templates (relatif au repertoire de l'executable)
3647
let template_dir = if config.templates.directory.is_relative() {

ui/src/features/export/hooks/use-export.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { exportSpecification } from "../lib/export-spec";
22
import { exportTestSuite } from "../lib/export-tests";
33
import { exportTraceability } from "../lib/export-traceability";
4+
import { exportAll } from "../lib/export-all";
45
import type { Specification } from "@/shared/types/specification";
56
import type { TestSuite } from "@/shared/types/test-suite";
67
import type { TraceabilityMatrix } from "@/shared/types/traceability";
@@ -31,5 +32,14 @@ export function useExport() {
3132
const blob = exportTraceability(matrix, format);
3233
downloadBlob(blob, `traceability.${format}`);
3334
},
35+
exportAllData: (
36+
spec: Specification,
37+
suite: TestSuite,
38+
traceability: TraceabilityMatrix,
39+
) => {
40+
const blob = exportAll(spec, suite, traceability);
41+
const date = new Date().toISOString().slice(0, 10);
42+
downloadBlob(blob, `spec-forge-export-${date}.xlsx`);
43+
},
3444
};
3545
}
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
import * as XLSX from "xlsx";
2+
import type { Specification } from "@/shared/types/specification";
3+
import type { TestSuite } from "@/shared/types/test-suite";
4+
import type { TraceabilityMatrix } from "@/shared/types/traceability";
5+
6+
function formatComplianceProfile(
7+
profile: Specification["compliance_profile"],
8+
): string {
9+
if (!profile) return "";
10+
if (typeof profile === "string") return profile;
11+
const [key, value] = Object.entries(profile)[0] ?? [];
12+
return key ? `${key} (${value})` : "";
13+
}
14+
15+
/** Construit un workbook XLSX complet avec toutes les donnees du pipeline */
16+
export function buildFullExportWorkbook(
17+
spec: Specification,
18+
suite: TestSuite,
19+
traceability: TraceabilityMatrix,
20+
): XLSX.WorkBook {
21+
const workbook = XLSX.utils.book_new();
22+
23+
// 1. Metadonnees
24+
const metaRows = [
25+
{
26+
Champ: "ID",
27+
Valeur: spec.id,
28+
},
29+
{ Champ: "Titre", Valeur: spec.title },
30+
{ Champ: "Version", Valeur: spec.version },
31+
{ Champ: "Statut", Valeur: spec.status },
32+
{ Champ: "Auteur", Valeur: spec.author ?? "" },
33+
{ Champ: "Date de creation", Valeur: spec.created_at },
34+
{ Champ: "Baseline", Valeur: spec.baseline ?? "" },
35+
{ Champ: "Version outil", Valeur: spec.tool_version },
36+
{
37+
Champ: "Profil conformite",
38+
Valeur: formatComplianceProfile(spec.compliance_profile),
39+
},
40+
{
41+
Champ: "Stories source",
42+
Valeur: spec.source_stories.join(", "),
43+
},
44+
];
45+
XLSX.utils.book_append_sheet(
46+
workbook,
47+
XLSX.utils.json_to_sheet(metaRows),
48+
"Metadonnees",
49+
);
50+
51+
// 2. Scenarios utilisateur
52+
const scenarioRows = spec.user_scenarios.map((us) => ({
53+
ID: us.id,
54+
Titre: us.title,
55+
Priorite: us.priority,
56+
Description: us.description,
57+
"Justification priorite": us.why_priority,
58+
"Test independant": us.independent_test,
59+
"Story source": us.source_story_id,
60+
"Scenarios acceptation": us.acceptance_scenarios
61+
.map((a) => `Etant donne: ${a.given} | Quand: ${a.when} | Alors: ${a.then}`)
62+
.join("\n"),
63+
}));
64+
XLSX.utils.book_append_sheet(
65+
workbook,
66+
XLSX.utils.json_to_sheet(scenarioRows),
67+
"Scenarios",
68+
);
69+
70+
// 3. Exigences fonctionnelles
71+
const reqRows = spec.functional_requirements.map((fr) => ({
72+
ID: fr.id,
73+
Enonce: fr.statement,
74+
Priorite: fr.priority,
75+
Categorie: fr.category,
76+
Testable: fr.testable ? "Oui" : "Non",
77+
"Methode verification": fr.verification_method,
78+
Risque: fr.risk_level ?? "",
79+
Justification: fr.rationale ?? "",
80+
Source: fr.source ?? "",
81+
"Exigence parente": fr.parent_requirement ?? "",
82+
"Alloue a": fr.allocated_to.join(", "),
83+
"Caracteristique qualite": fr.quality_characteristic ?? "",
84+
}));
85+
XLSX.utils.book_append_sheet(
86+
workbook,
87+
XLSX.utils.json_to_sheet(reqRows),
88+
"Exigences",
89+
);
90+
91+
// 4. Entites
92+
const entityRows = spec.key_entities.map((e) => ({
93+
Nom: e.name,
94+
Description: e.description,
95+
Attributs: e.attributes.join(", "),
96+
Relations: e.relationships.join(", "),
97+
}));
98+
XLSX.utils.book_append_sheet(
99+
workbook,
100+
XLSX.utils.json_to_sheet(entityRows),
101+
"Entites",
102+
);
103+
104+
// 5. Cas limites
105+
const edgeRows = spec.edge_cases.map((ec) => ({
106+
Description: ec.description,
107+
"Scenario lie": ec.related_scenario ?? "",
108+
Severite: ec.severity,
109+
}));
110+
XLSX.utils.book_append_sheet(
111+
workbook,
112+
XLSX.utils.json_to_sheet(edgeRows),
113+
"Cas limites",
114+
);
115+
116+
// 6. Criteres de succes
117+
const criteriaRows = spec.success_criteria.map((sc) => ({
118+
ID: sc.id,
119+
Description: sc.description,
120+
"Metrique mesurable": sc.measurable_metric,
121+
}));
122+
XLSX.utils.book_append_sheet(
123+
workbook,
124+
XLSX.utils.json_to_sheet(criteriaRows),
125+
"Criteres succes",
126+
);
127+
128+
// 7. Clarifications
129+
const clarRows = spec.clarifications_needed.map((c) => ({
130+
Question: c.question,
131+
Contexte: c.context,
132+
"Options suggerees": c.suggested_options.join(", "),
133+
Impact: c.impact,
134+
Resolue: c.resolved ? "Oui" : "Non",
135+
Reponse: c.answer ?? "",
136+
}));
137+
XLSX.utils.book_append_sheet(
138+
workbook,
139+
XLSX.utils.json_to_sheet(clarRows),
140+
"Clarifications",
141+
);
142+
143+
// 8. Validation
144+
const validationRows: Record<string, string | number>[] = [];
145+
if (spec.validation) {
146+
validationRows.push({
147+
Critere: "Score completude",
148+
Valeur: spec.validation.completeness_score,
149+
});
150+
validationRows.push({
151+
Critere: "Score clarte",
152+
Valeur: spec.validation.clarity_score,
153+
});
154+
validationRows.push({
155+
Critere: "Score testabilite",
156+
Valeur: spec.validation.testability_score,
157+
});
158+
for (const item of spec.validation.checklist_items) {
159+
validationRows.push({
160+
Critere: `[${item.category}] ${item.description}`,
161+
Valeur: item.passed ? "OK" : "NOK",
162+
});
163+
}
164+
}
165+
XLSX.utils.book_append_sheet(
166+
workbook,
167+
XLSX.utils.json_to_sheet(
168+
validationRows.length > 0
169+
? validationRows
170+
: [{ Critere: "Aucune validation", Valeur: "" }],
171+
),
172+
"Validation",
173+
);
174+
175+
// 9. Features & Scenarios de test
176+
const testRows = suite.features.flatMap((f) =>
177+
f.scenarios.map((s) => ({
178+
Feature: f.name,
179+
"Description feature": f.description,
180+
"Niveau test": f.test_level,
181+
"Tags feature": f.tags.join(", "),
182+
"Exigences couvertes": f.covered_requirements.join(", "),
183+
"Scenarios source": f.source_scenario_ids.join(", "),
184+
Scenario: s.name,
185+
Type: s.scenario_type,
186+
"Tags scenario": s.tags.join(", "),
187+
"Technique couverture": s.coverage_technique ?? "",
188+
"Exigences verifiees": s.verification_of.join(", "),
189+
Etapes: s.steps
190+
.map((st) => {
191+
let line = `${st.keyword} ${st.text}`;
192+
if (st.doc_string) line += `\n """\n ${st.doc_string}\n """`;
193+
if (st.data_table)
194+
line += `\n | ${st.data_table.map((r) => r.join(" | ")).join(" |\n | ")} |`;
195+
return line;
196+
})
197+
.join("\n"),
198+
Exemples: s.examples
199+
? `| ${s.examples.headers.join(" | ")} |\n${s.examples.rows.map((r) => `| ${r.join(" | ")} |`).join("\n")}`
200+
: "",
201+
"Suggestions donnees test": s.test_data_suggestions.join(", "),
202+
})),
203+
);
204+
XLSX.utils.book_append_sheet(
205+
workbook,
206+
XLSX.utils.json_to_sheet(testRows),
207+
"Tests",
208+
);
209+
210+
// 10. Couverture des tests (resume)
211+
const coverageRows = [
212+
{
213+
"Exigences couvertes": suite.coverage.requirements_covered.join(", "),
214+
"Total exigences": suite.coverage.requirements_total,
215+
"Couverture (%)": suite.coverage.coverage_percentage,
216+
"Happy path": suite.coverage.scenarios_by_type.happy_path,
217+
"Cas limites": suite.coverage.scenarios_by_type.edge_case,
218+
"Scenarios erreur": suite.coverage.scenarios_by_type.error_scenario,
219+
"Conditions limites": suite.coverage.scenarios_by_type.boundary,
220+
},
221+
];
222+
XLSX.utils.book_append_sheet(
223+
workbook,
224+
XLSX.utils.json_to_sheet(coverageRows),
225+
"Couverture tests",
226+
);
227+
228+
// 11. Tracabilite
229+
const traceRows = traceability.entries.map((e) => ({
230+
"ID Exigence": e.requirement_id,
231+
Enonce: e.statement,
232+
Priorite: e.priority,
233+
Risque: e.risk_level ?? "",
234+
"Stories source": e.source_stories.join(", "),
235+
Verification: e.verification_method,
236+
Features: e.covering_features.join(", "),
237+
Scenarios: e.covering_scenarios.join(", "),
238+
Techniques: e.coverage_techniques.join(", "),
239+
Statut: e.status,
240+
}));
241+
XLSX.utils.book_append_sheet(
242+
workbook,
243+
XLSX.utils.json_to_sheet(traceRows),
244+
"Tracabilite",
245+
);
246+
247+
// 12. Resume tracabilite
248+
const summaryRows = [
249+
{
250+
"Total exigences": traceability.summary.total_requirements,
251+
Couvertes: traceability.summary.covered,
252+
"Partiellement couvertes": traceability.summary.partially_covered,
253+
"Non couvertes": traceability.summary.not_covered,
254+
"Verifiees autrement": traceability.summary.verified_other,
255+
"Couverture (%)": traceability.summary.forward_coverage_pct,
256+
"Tests orphelins": traceability.summary.orphan_tests.join(", "),
257+
},
258+
];
259+
XLSX.utils.book_append_sheet(
260+
workbook,
261+
XLSX.utils.json_to_sheet(summaryRows),
262+
"Resume tracabilite",
263+
);
264+
265+
// 13. Notes de conformite
266+
const complianceRows =
267+
traceability.compliance_notes.length > 0
268+
? traceability.compliance_notes.map((n) => ({
269+
Standard: n.standard,
270+
Section: n.section,
271+
Statut: n.status,
272+
Details: n.details,
273+
}))
274+
: [{ Standard: "Aucune note", Section: "", Statut: "", Details: "" }];
275+
XLSX.utils.book_append_sheet(
276+
workbook,
277+
XLSX.utils.json_to_sheet(complianceRows),
278+
"Conformite",
279+
);
280+
281+
return workbook;
282+
}
283+
284+
/** Exporte toutes les donnees du pipeline en un seul fichier XLSX */
285+
export function exportAll(
286+
spec: Specification,
287+
suite: TestSuite,
288+
traceability: TraceabilityMatrix,
289+
): Blob {
290+
const workbook = buildFullExportWorkbook(spec, suite, traceability);
291+
const buffer = XLSX.write(workbook, { type: "array", bookType: "xlsx" });
292+
return new Blob([buffer], {
293+
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
294+
});
295+
}

0 commit comments

Comments
 (0)