From e3490e5d2ea26dca33cd77a7478c15cc8496f6a1 Mon Sep 17 00:00:00 2001 From: che Date: Sun, 12 Jul 2026 20:42:21 +0500 Subject: [PATCH] fix --- analize/controllers.go | 137 ++++- analize/routers.go | 2 + analize/service.go | 30 +- control-ui/src/api.ts | 44 +- control-ui/src/api_client.ts | 4 + .../src/components/params/ParamsEditCard.vue | 73 +++ .../params/ParamsQuickBatchCard.vue | 125 +++++ .../src/components/params/ParamsTableCard.vue | 140 +++++ control-ui/src/models.ts | 22 +- control-ui/src/statuses.ts | 19 + control-ui/src/views/AnalizeChartView.vue | 356 +++++++++++-- control-ui/src/views/AnalizeListView.vue | 54 +- control-ui/src/views/ControlCaseChartView.vue | 4 + .../src/views/ControlCaseDetailView.vue | 40 +- control-ui/src/views/ControlCaseListView.vue | 149 ++++-- control-ui/src/views/ParamsListView.vue | 493 +++++------------- control_case/controllers.go | 225 +++++++- control_case/hdf5.go | 75 +++ control_case/models.go | 61 ++- control_case/routers.go | 33 +- control_case/services.go | 12 +- go.mod | 4 +- go.sum | 4 + 23 files changed, 1568 insertions(+), 538 deletions(-) create mode 100644 control-ui/src/components/params/ParamsEditCard.vue create mode 100644 control-ui/src/components/params/ParamsQuickBatchCard.vue create mode 100644 control-ui/src/components/params/ParamsTableCard.vue create mode 100644 control-ui/src/statuses.ts diff --git a/analize/controllers.go b/analize/controllers.go index 3f5080a..201d446 100644 --- a/analize/controllers.go +++ b/analize/controllers.go @@ -1,12 +1,14 @@ package analize import ( + "errors" "fmt" + "math" "net/http" - "path/filepath" "strconv" "strings" + "control/control_case" "github.com/che4web/go4rest" "github.com/gin-gonic/gin" "gorm.io/gorm" @@ -32,10 +34,19 @@ type AnalizeSeriesPoint struct { PsiMax float64 `json:"psi_max"` } +type AnalizeSeriesGroup struct { + GroupValue float64 `json:"group_value"` + GroupLabel string `json:"group_label"` + Points []AnalizeSeriesPoint `json:"points"` +} + type AnalizeSeriesResponse struct { - Parameter string `json:"parameter"` - Label string `json:"label"` - Points []AnalizeSeriesPoint `json:"points"` + Parameter string `json:"parameter"` + Label string `json:"label"` + GroupParameter string `json:"group_parameter,omitempty"` + GroupLabel string `json:"group_label,omitempty"` + Points []AnalizeSeriesPoint `json:"points,omitempty"` + Groups []AnalizeSeriesGroup `json:"groups,omitempty"` } var analizeParameterColumns = map[string]struct { @@ -45,7 +56,8 @@ var analizeParameterColumns = map[string]struct { "Rel": {Column: "rel", Label: "Rel"}, "RelC": {Column: "rel_c", Label: "RelC"}, "Le": {Column: "le", Label: "Le"}, - "Pr": {Column: "pr", Label: "Pr"}, + "Pr": {Column: "sc", Label: "Sc"}, + "Sc": {Column: "sc", Label: "Sc"}, "Pe": {Column: "pe", Label: "Pe"}, "Ma": {Column: "ma", Label: "Ma"}, "Time": {Column: "time", Label: "Time"}, @@ -59,6 +71,13 @@ func (c *AnalizeController) Series(ctx *gin.Context) { return } + groupParam := ctx.Query("group_parameter") + groupSelected, groupOK := analizeParameterColumns[groupParam] + if groupParam != "" && !groupOK { + ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid group parameter"}) + return + } + filterParam := ctx.Query("filter_parameter") filterSelected, filterOK := analizeParameterColumns[filterParam] filterMin := ctx.Query("filter_min") @@ -67,17 +86,19 @@ func (c *AnalizeController) Series(ctx *gin.Context) { type row struct { CaseID uint `gorm:"column:case_id"` CaseName string `gorm:"column:case_name"` + GroupVal float64 `gorm:"column:group_val"` X float64 `gorm:"column:x"` Omega float64 `gorm:"column:omega"` PsiMax float64 `gorm:"column:psi_max"` } + groupExpr := groupExpr(groupParam, groupSelected.Column) query := fmt.Sprintf(` - SELECT a.case_id, a.case_name, p.%s AS x, a.omega, a.psi_max + SELECT a.case_id, a.case_name, %s AS group_val, p.%s AS x, a.omega, a.psi_max FROM analizes a JOIN control_cases c ON c.id = a.case_id JOIN params p ON p.id = c.params_id - `, selected.Column) + `, groupExpr, selected.Column) where := make([]string, 0, 2) args := make([]any, 0, 2) if filterParam != "" { @@ -97,7 +118,11 @@ func (c *AnalizeController) Series(ctx *gin.Context) { if len(where) > 0 { query += " WHERE " + strings.Join(where, " AND ") } - query += fmt.Sprintf(" ORDER BY p.%s, a.id", selected.Column) + if groupParam != "" { + query += fmt.Sprintf(" ORDER BY ROUND(p.%s, 6), p.%s, a.id", groupSelected.Column, selected.Column) + } else { + query += fmt.Sprintf(" ORDER BY p.%s, a.id", selected.Column) + } var rows []row if err := c.db.Raw(query, args...).Scan(&rows).Error; err != nil { @@ -106,23 +131,63 @@ func (c *AnalizeController) Series(ctx *gin.Context) { } points := make([]AnalizeSeriesPoint, 0, len(rows)) - for _, r := range rows { - points = append(points, AnalizeSeriesPoint{ - CaseID: r.CaseID, - CaseName: r.CaseName, - X: r.X, - Omega: r.Omega, - PsiMax: r.PsiMax, - }) + groups := make([]AnalizeSeriesGroup, 0) + if groupParam == "" { + for _, r := range rows { + points = append(points, AnalizeSeriesPoint{ + CaseID: r.CaseID, + CaseName: r.CaseName, + X: r.X, + Omega: r.Omega, + PsiMax: r.PsiMax, + }) + } + } else { + groupIndex := map[string]int{} + for _, r := range rows { + groupValue := round6(r.GroupVal) + key := fmt.Sprintf("%.6f", groupValue) + idx, ok := groupIndex[key] + if !ok { + idx = len(groups) + groupIndex[key] = idx + groups = append(groups, AnalizeSeriesGroup{ + GroupValue: groupValue, + GroupLabel: fmt.Sprintf("%s=%.6f", groupSelected.Label, groupValue), + Points: []AnalizeSeriesPoint{}, + }) + } + groups[idx].Points = append(groups[idx].Points, AnalizeSeriesPoint{ + CaseID: r.CaseID, + CaseName: r.CaseName, + X: r.X, + Omega: r.Omega, + PsiMax: r.PsiMax, + }) + } } ctx.JSON(http.StatusOK, AnalizeSeriesResponse{ - Parameter: param, - Label: selected.Label, - Points: points, + Parameter: param, + Label: selected.Label, + GroupParameter: groupParam, + GroupLabel: groupSelected.Label, + Points: points, + Groups: groups, }) } +func groupExpr(groupParam, groupColumn string) string { + if groupParam == "" { + return "0" + } + return fmt.Sprintf("ROUND(p.%s, 6)", groupColumn) +} + +func round6(v float64) float64 { + return math.Round(v*1e6) / 1e6 +} + func (c *AnalizeController) Recalculate(ctx *gin.Context) { id, err := strconv.ParseUint(ctx.Param("id"), 10, 32) if err != nil { @@ -136,7 +201,17 @@ func (c *AnalizeController) Recalculate(ctx *gin.Context) { return } - csvPath := filepath.Join(".", fmt.Sprintf("%d", item.CaseID), "foo.csv") + var controlCase control_case.ControlCase + if err := c.db.First(&controlCase, item.CaseID).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + ctx.JSON(http.StatusNotFound, gin.H{"error": "control case not found"}) + } else { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + } + return + } + + csvPath := controlCase.FooCSVPath() updated, err := AnalyzeCSV(c.db, item.CaseID, item.CaseName, csvPath) if err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) @@ -145,3 +220,25 @@ func (c *AnalizeController) Recalculate(ctx *gin.Context) { ctx.JSON(http.StatusOK, updated) } + +func (c *AnalizeController) RecalculateControlCaseAnalysis(ctx *gin.Context) { + id, err := strconv.ParseUint(ctx.Param("id"), 10, 32) + if err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid ID"}) + return + } + + var controlCase control_case.ControlCase + if err := c.db.First(&controlCase, id).Error; err != nil { + ctx.JSON(http.StatusNotFound, gin.H{"error": "record not found"}) + return + } + + result, err := AnalyzeCSV(c.db, controlCase.ID, controlCase.Name, controlCase.FooCSVPath()) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + ctx.JSON(http.StatusOK, result) +} diff --git a/analize/routers.go b/analize/routers.go index e251c3f..2911aac 100644 --- a/analize/routers.go +++ b/analize/routers.go @@ -13,4 +13,6 @@ func RegisterApp(r *gin.Engine, db *gorm.DB) { go4rest.RegisterCRUDRoutes(r, "analize", controller) r.GET("/api/analize/series", controller.Series) r.POST("/api/analize/:id/recalculate", controller.Recalculate) + r.POST("/api/control_case/:id/recalculate-analysis", controller.RecalculateControlCaseAnalysis) + go RunAnalyzeWorker(db) } diff --git a/analize/service.go b/analize/service.go index 7234310..caf488a 100644 --- a/analize/service.go +++ b/analize/service.go @@ -8,11 +8,37 @@ import ( "os" "strconv" "strings" + "time" + "control/control_case" "gonum.org/v1/gonum/dsp/fourier" "gorm.io/gorm" ) +func RunAnalyzeWorker(db *gorm.DB) { + for { + var cases []control_case.ControlCase + if err := db.Where("status = ?", "R").Find(&cases).Error; err != nil { + fmt.Printf("failed to load cases for analysis: %v\n", err) + time.Sleep(10 * time.Second) + continue + } + + for _, controlCase := range cases { + if _, err := AnalyzeCSV(db, controlCase.ID, controlCase.Name, controlCase.FooCSVPath()); err != nil { + fmt.Printf("failed to analyze case %d: %v\n", controlCase.ID, err) + continue + } + + if err := db.Model(&control_case.ControlCase{}).Where("id = ?", controlCase.ID).Update("status", "D").Error; err != nil { + fmt.Printf("failed to mark case %d as done: %v\n", controlCase.ID, err) + } + } + + time.Sleep(10 * time.Second) + } +} + func AnalyzeCSV(db *gorm.DB, caseID uint, caseName, csvPath string) (*Analize, error) { file, err := os.Open(csvPath) if err != nil { @@ -136,8 +162,8 @@ func maxFloat(values []float64) float64 { if len(values) == 0 { return 0 } - max := values[0] - for _, v := range values[1:] { + max := values[len(values)-1] + for _, v := range values[len(values):] { if v > max { max = v } diff --git a/control-ui/src/api.ts b/control-ui/src/api.ts index 1fc9f82..c7fb7d1 100644 --- a/control-ui/src/api.ts +++ b/control-ui/src/api.ts @@ -1,16 +1,30 @@ import { createModelApi, apiClient } from "@/api_client.ts"; -import type { Params, ControlCase, Analize } from "@/models.ts"; +import type { Params, ControlCase, Analize, InitialCondition } from "@/models.ts"; export const paramsApi = createModelApi("params"); export const controlCaseApi = createModelApi("control_case"); export const analizeApi = createModelApi("analize"); +export const initialConditionApi = createModelApi("initial_condition"); export interface CsvChartResponse { columns: string[]; rows: Array>; } +export interface PsiSpectrumPoint { + frequency: number; + amplitude: number; +} + +export interface PsiSpectrumResponse { + time_step: number; + points: { + psi_m: PsiSpectrumPoint[]; + psi_l: PsiSpectrumPoint[]; + }; +} + export interface FieldMapResponse { requested_t: number; stage_t: number; @@ -19,6 +33,10 @@ export interface FieldMapResponse { fields: Record; } +export interface InitialConditionResponse { + initial_condition: InitialCondition; +} + export async function getControlCaseChartData(id: number) { const response = await apiClient.get( `/control_case/${id}/chart-data`, @@ -26,6 +44,13 @@ export async function getControlCaseChartData(id: number) { return response.data; } +export async function getControlCasePsiSpectrum(id: number) { + const response = await apiClient.get( + `/control_case/${id}/psi-spectrum`, + ); + return response.data; +} + export async function getControlCaseFieldMap(id: number, time: number) { const response = await apiClient.get( `/control_case/${id}/field-map`, @@ -36,6 +61,13 @@ export async function getControlCaseFieldMap(id: number, time: number) { return response.data; } +export async function createControlCaseInitialCondition(id: number) { + const response = await apiClient.post( + `/control_case/${id}/initial-condition`, + ); + return response.data; +} + export interface LaunchControlCasePayload { params_id: number; name?: string; @@ -69,16 +101,26 @@ export interface AnalizeSeriesPoint { psi_max: number; } +export interface AnalizeSeriesGroup { + group_value: number; + group_label: string; + points: AnalizeSeriesPoint[]; +} + export interface AnalizeSeriesResponse { parameter: string; label: string; + group_parameter?: string; + group_label?: string; points: AnalizeSeriesPoint[]; + groups?: AnalizeSeriesGroup[]; } export interface AnalizeSeriesFilters { filter_parameter?: string; filter_min?: number; filter_max?: number; + group_parameter?: string; } export async function getAnalizeSeries(parameter: string, filters?: AnalizeSeriesFilters) { diff --git a/control-ui/src/api_client.ts b/control-ui/src/api_client.ts index 9740483..524591e 100644 --- a/control-ui/src/api_client.ts +++ b/control-ui/src/api_client.ts @@ -74,6 +74,10 @@ export function formatApiError( error: unknown, fallback = "Не удалось выполнить запрос.", ): string { + if (error instanceof Error && !axios.isAxiosError(error)) { + return error.message || fallback; + } + if (!axios.isAxiosError(error)) return fallback; const payload = error.response?.data; diff --git a/control-ui/src/components/params/ParamsEditCard.vue b/control-ui/src/components/params/ParamsEditCard.vue new file mode 100644 index 0000000..e841ebe --- /dev/null +++ b/control-ui/src/components/params/ParamsEditCard.vue @@ -0,0 +1,73 @@ + + + diff --git a/control-ui/src/components/params/ParamsQuickBatchCard.vue b/control-ui/src/components/params/ParamsQuickBatchCard.vue new file mode 100644 index 0000000..3899479 --- /dev/null +++ b/control-ui/src/components/params/ParamsQuickBatchCard.vue @@ -0,0 +1,125 @@ + + + diff --git a/control-ui/src/components/params/ParamsTableCard.vue b/control-ui/src/components/params/ParamsTableCard.vue new file mode 100644 index 0000000..5b3a451 --- /dev/null +++ b/control-ui/src/components/params/ParamsTableCard.vue @@ -0,0 +1,140 @@ + + + diff --git a/control-ui/src/models.ts b/control-ui/src/models.ts index aa5f560..c3ca5b6 100644 --- a/control-ui/src/models.ts +++ b/control-ui/src/models.ts @@ -11,23 +11,33 @@ export interface Params { rel: number; rel_c: number; le: number; - pr: number; + sc: number; pe: number; ma: number; - initial_condition: string; + initial_condition_id: number | null; + initial_condition: InitialCondition | null; time: number; folder_path: string; } +export interface InitialCondition { + ID: number; + CreatedAt: string; + UpdatedAt: string; + DeletedAt: GormDeletedAt | null; + name: string; + file_path: string; +} + export interface ControlCase { ID: number; CreatedAt: string; UpdatedAt: string; DeletedAt: GormDeletedAt | null; - Name: string; - ParamsID: number; - Params: Params; - Status: string; + name: string; + paramsID: number; + params: Params; + status: string; } export interface Analize { diff --git a/control-ui/src/statuses.ts b/control-ui/src/statuses.ts new file mode 100644 index 0000000..8fd9bed --- /dev/null +++ b/control-ui/src/statuses.ts @@ -0,0 +1,19 @@ +const controlCaseStatuses: Record = { + N: { label: 'В очереди', className: 'text-bg-secondary' }, + R: { label: 'Ожидает анализа', className: 'text-bg-warning' }, + D: { label: 'Готово', className: 'text-bg-success' }, +} + +export function controlCaseStatusMeta(status: string | undefined) { + if (!status) { + return { code: '', label: '—', className: 'text-bg-secondary' } + } + + const normalized = status.toUpperCase() + const meta = controlCaseStatuses[normalized] + if (!meta) { + return { code: normalized, label: normalized, className: 'text-bg-secondary' } + } + + return { code: normalized, ...meta } +} diff --git a/control-ui/src/views/AnalizeChartView.vue b/control-ui/src/views/AnalizeChartView.vue index 26c779d..2d62b0b 100644 --- a/control-ui/src/views/AnalizeChartView.vue +++ b/control-ui/src/views/AnalizeChartView.vue @@ -1,9 +1,10 @@ @@ -102,6 +121,15 @@ watch(id, loadItem) > {{ recalculating ? 'Пересчет...' : 'Пересчитать анализ' }} + @@ -116,9 +144,11 @@ watch(id, loadItem)
Название
-
{{ item.Name || '—' }}
+
{{ item.name || '—' }}
- {{ item.Status || '—' }} + + {{ controlCaseStatusMeta(item.status).label }} +
@@ -126,7 +156,7 @@ watch(id, loadItem)
{{ item.ID }}
Params ID
-
{{ item.ParamsID }}
+
{{ item.paramsID }}
Создан
{{ formatDate(item.CreatedAt) }}
@@ -146,7 +176,7 @@ watch(id, loadItem)
- + diff --git a/control-ui/src/views/ControlCaseListView.vue b/control-ui/src/views/ControlCaseListView.vue index 8ec0c84..688c9ed 100644 --- a/control-ui/src/views/ControlCaseListView.vue +++ b/control-ui/src/views/ControlCaseListView.vue @@ -1,85 +1,95 @@
{{ key }} {{ formatValue(value) }}