feat(history): enable 'Analysis Report' button in simulation history

- ProjectManager._to_dict now scans simulation state files to find
  last_simulation_id and last_report_id for each project
- HistoryDatabase: report button enabled when last_report_id or
  last_simulation_id available; goToReport looks up report by
  simulation ID if no direct report_id; report status icon reflects
  availability
- Add getReportBySimulation() to frontend report API

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ubuntu 2026-05-04 23:27:10 +00:00
parent f0f8a797ef
commit e7641e9831
3 changed files with 69 additions and 4 deletions

View File

@ -242,8 +242,43 @@ class ProjectManager:
@classmethod
def _to_dict(cls, proj: "ProjectModel") -> Dict[str, Any]:
import os, json as _json
ontology = cls.get_ontology(proj.id)
graph_external_id = cls.get_latest_graph_external_id(proj.id)
# Find the latest simulation for this project by scanning state.json files
last_simulation_id = None
last_report_id = None
sim_base = Config.OASIS_SIMULATION_DATA_DIR
if os.path.isdir(sim_base):
candidates = []
for entry in os.scandir(sim_base):
if not entry.is_dir():
continue
state_path = os.path.join(entry.path, "state.json")
if not os.path.exists(state_path):
continue
try:
with open(state_path, encoding="utf-8") as f:
state = _json.load(f)
if state.get("project_id") == proj.id:
candidates.append((state.get("updated_at", ""), state.get("simulation_id")))
except Exception:
pass
if candidates:
candidates.sort(reverse=True)
last_simulation_id = candidates[0][1]
# Find latest report for that simulation
if last_simulation_id:
from ..services.report_agent import ReportManager
try:
report = ReportManager.get_report_by_simulation(last_simulation_id)
if report:
last_report_id = report.report_id
except Exception:
pass
return {
"id": proj.id,
"project_id": proj.id,
@ -262,4 +297,6 @@ class ProjectManager:
"graph_id": graph_external_id,
"graph_build_task_id": None,
"error": None,
"last_simulation_id": last_simulation_id,
"last_report_id": last_report_id,
}

View File

@ -50,3 +50,11 @@ export const chatWithReport = (data) => {
return requestWithRetry(() => service.post('/api/report/chat', data), 3, 1000)
}
/**
* Get report by simulation ID
* @param {string} simulationId
*/
export const getReportBySimulation = (simulationId) => {
return service.get(`/api/report/by-simulation/${simulationId}`)
}

View File

@ -44,7 +44,8 @@
:title="$t('history.envSetup')"
></span>
<span
class="status-icon unavailable"
class="status-icon"
:class="{ available: project.last_report_id || project.last_simulation_id, unavailable: !project.last_report_id && !project.last_simulation_id }"
:title="$t('history.analysisReport')"
></span>
</div>
@ -173,7 +174,7 @@
<button
class="modal-btn btn-report"
@click="goToReport"
:disabled="true"
:disabled="!selectedProject.last_report_id && !selectedProject.last_simulation_id"
>
<span class="btn-step">Step4</span>
<span class="btn-icon"></span>
@ -196,6 +197,7 @@ import { ref, computed, onMounted, onUnmounted, onActivated, watch, nextTick } f
import { useRouter, useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { listProjects } from '../api/graph'
import { getReportBySimulation } from '../api/report'
const router = useRouter()
const route = useRoute()
@ -427,8 +429,26 @@ const goToSimulation = () => {
}
// Report
const goToReport = () => {
// Report not yet implemented in F1-1
const goToReport = async () => {
if (!selectedProject.value) return
// Use known report_id if available
let reportId = selectedProject.value.last_report_id
// Otherwise look it up by simulation_id
if (!reportId && selectedProject.value.last_simulation_id) {
try {
const res = await getReportBySimulation(selectedProject.value.last_simulation_id)
reportId = res?.data?.report_id
} catch {
// no report found
}
}
if (reportId) {
closeModal()
router.push({ name: 'Interaction', params: { reportId } })
}
}
//