package control_case import ( "errors" "fmt" "net/http" "os" "path/filepath" "strconv" "strings" "control/series" "github.com/che4web/go4rest" "github.com/gin-gonic/gin" "gorm.io/gorm" ) type ControlCaseController struct { *go4rest.ViewSet[ControlCase] db *gorm.DB } func NewControlCaseController(db *gorm.DB) *ControlCaseController { viewSet := go4rest.NewViewSet[ControlCase](db) viewSet.PreloadField = []string{"Params", "Params.InitialCondition"} return &ControlCaseController{ ViewSet: viewSet, db: db, } } type ParamsController struct { *go4rest.ViewSet[Params] db *gorm.DB } type InitialConditionController struct { *go4rest.ViewSet[InitialCondition] db *gorm.DB } func NewParamsController(db *gorm.DB) *ParamsController { viewSet := go4rest.NewViewSet[Params](db) viewSet.PreloadField = []string{"InitialCondition"} return &ParamsController{ ViewSet: viewSet, db: db, } } func NewInitialConditionController(db *gorm.DB) *InitialConditionController { return &InitialConditionController{ ViewSet: go4rest.NewViewSet[InitialCondition](db), db: db, } } type LaunchControlCaseRequest struct { ParamsID uint `json:"params_id" binding:"required"` Name string `json:"name"` } func (c *ControlCaseController) Launch(ctx *gin.Context) { var req LaunchControlCaseRequest if err := ctx.ShouldBindJSON(&req); err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } var params Params if err := c.db.First(¶ms, req.ParamsID).Error; err != nil { ctx.JSON(http.StatusNotFound, gin.H{"error": "params not found"}) return } name := req.Name if strings.TrimSpace(name) == "" { name = fmt.Sprintf("case-%d", params.ID) } controlCase := ControlCase{ Name: name, ParamsID: params.ID, Status: "N", } if err := c.db.Create(&controlCase).Error; err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if err := c.db.Preload("Params").Preload("Params.InitialCondition").First(&controlCase, controlCase.ID).Error; err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } ctx.JSON(http.StatusCreated, controlCase) } type CSVSeriesResponse struct { Columns []string `json:"columns"` Rows [][]interface{} `json:"rows"` } type FFTPoint = series.SpectrumPoint type PSISpectrumResponse struct { TimeStep float64 `json:"time_step"` Points map[string][]FFTPoint `json:"points"` } type InitialConditionResponse struct { InitialCondition InitialCondition `json:"initial_condition"` } func (c *ControlCaseController) ChartData(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 ControlCase if err := c.db.Preload("Params").First(&controlCase, id).Error; err != nil { ctx.JSON(http.StatusNotFound, gin.H{"error": "record not found"}) return } csvPath := controlCase.FooCSVPath() data, ok := readCSVResponse(ctx, csvPath) if !ok { return } if len(data.Columns) == 0 { ctx.JSON(http.StatusOK, CSVSeriesResponse{Columns: []string{}, Rows: [][]interface{}{}}) return } rows := make([][]interface{}, 0, len(data.Rows)) for _, record := range data.Rows { row := make([]interface{}, 0, len(record)) for _, value := range record { trimmed := strings.TrimSpace(value) if f, err := strconv.ParseFloat(trimmed, 64); err == nil { row = append(row, f) } else { row = append(row, trimmed) } } rows = append(rows, row) } ctx.JSON(http.StatusOK, CSVSeriesResponse{Columns: data.Columns, Rows: rows}) } func (c *ControlCaseController) PSISpectrum(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 ControlCase if err := c.db.Preload("Params").First(&controlCase, id).Error; err != nil { ctx.JSON(http.StatusNotFound, gin.H{"error": "record not found"}) return } csvPath := controlCase.FooCSVPath() data, ok := readCSVResponse(ctx, csvPath) if !ok { return } if len(data.Rows) == 0 { ctx.JSON(http.StatusOK, PSISpectrumResponse{Points: map[string][]FFTPoint{}}) return } columns, err := data.NumericColumns("t", "psi_m", "psi_l") if err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } times, psiM, psiL := columns[0], columns[1], columns[2] timeStep := series.AverageDelta(times) if timeStep <= 0 { ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid time step"}) return } ctx.JSON(http.StatusOK, PSISpectrumResponse{ TimeStep: timeStep, Points: map[string][]FFTPoint{ "psi_m": series.SpectrumPoints(psiM, timeStep), "psi_l": series.SpectrumPoints(psiL, timeStep), }, }) } func readCSVResponse(ctx *gin.Context, csvPath string) (*series.CSVData, bool) { data, err := series.ReadCSV(csvPath) if err == nil { return data, true } if errors.Is(err, os.ErrNotExist) { ctx.JSON(http.StatusNotFound, gin.H{"error": "csv file not found"}) } else { ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } return nil, false } func (c *ControlCaseController) FieldMap(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 } timeValue, err := strconv.ParseFloat(ctx.Query("time"), 64) if err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid time"}) return } var controlCase ControlCase if err := c.db.First(&controlCase, id).Error; err != nil { ctx.JSON(http.StatusNotFound, gin.H{"error": "record not found"}) return } result, err := ReadNearestFieldMap(controlCase.StorageH5Path(), timeValue) if err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } ctx.JSON(http.StatusOK, result) } func (c *ControlCaseController) CreateInitialCondition(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 ControlCase if err := c.db.First(&controlCase, id).Error; err != nil { ctx.JSON(http.StatusNotFound, gin.H{"error": "record not found"}) return } outputPath := filepath.Join(BASE_DIR, "initial_conditions", fmt.Sprintf("control_case_%d", controlCase.ID), "storage.h5") if err := controlCase.CreateStorageH5FromLastState(outputPath); err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } initialCondition := InitialCondition{ Name: fmt.Sprintf("control-case-%d", controlCase.ID), FilePath: outputPath, } if err := c.db.Where("file_path = ?", outputPath).Assign(initialCondition).FirstOrCreate(&initialCondition).Error; err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } ctx.JSON(http.StatusOK, InitialConditionResponse{InitialCondition: initialCondition}) }