فهرست منبع

Further specify Powerplant object in swagger for easier validation.

Aurelien 5 سال پیش
والد
کامیت
f5aa61144e
5فایلهای تغییر یافته به همراه296 افزوده شده و 34 حذف شده
  1. 46 2
      models/payload.go
  2. 139 0
      models/powerplant.go
  3. 62 2
      restapi/embedded_spec.go
  4. 26 29
      spoptim/production_planner.go
  5. 23 1
      swagger.yml

+ 46 - 2
models/payload.go

@@ -7,6 +7,7 @@ package models
 
 import (
 	"context"
+	"strconv"
 
 	"github.com/go-openapi/errors"
 	"github.com/go-openapi/strfmt"
@@ -29,7 +30,7 @@ type Payload struct {
 
 	// powerplants
 	// Required: true
-	Powerplants []interface{} `json:"powerplants"`
+	Powerplants []*Powerplant `json:"powerplants"`
 }
 
 // Validate validates this payload
@@ -78,11 +79,54 @@ func (m *Payload) validatePowerplants(formats strfmt.Registry) error {
 		return err
 	}
 
+	for i := 0; i < len(m.Powerplants); i++ {
+		if swag.IsZero(m.Powerplants[i]) { // not required
+			continue
+		}
+
+		if m.Powerplants[i] != nil {
+			if err := m.Powerplants[i].Validate(formats); err != nil {
+				if ve, ok := err.(*errors.Validation); ok {
+					return ve.ValidateName("powerplants" + "." + strconv.Itoa(i))
+				}
+				return err
+			}
+		}
+
+	}
+
 	return nil
 }
 
-// ContextValidate validates this payload based on context it is used
+// ContextValidate validate this payload based on the context it is used
 func (m *Payload) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.contextValidatePowerplants(ctx, formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *Payload) contextValidatePowerplants(ctx context.Context, formats strfmt.Registry) error {
+
+	for i := 0; i < len(m.Powerplants); i++ {
+
+		if m.Powerplants[i] != nil {
+			if err := m.Powerplants[i].ContextValidate(ctx, formats); err != nil {
+				if ve, ok := err.(*errors.Validation); ok {
+					return ve.ValidateName("powerplants" + "." + strconv.Itoa(i))
+				}
+				return err
+			}
+		}
+
+	}
+
 	return nil
 }
 

+ 139 - 0
models/powerplant.go

@@ -0,0 +1,139 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package models
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+	"github.com/go-openapi/validate"
+)
+
+// Powerplant powerplant
+//
+// swagger:model powerplant
+type Powerplant struct {
+
+	// efficiency
+	// Required: true
+	Efficiency *float64 `json:"efficiency"`
+
+	// name
+	// Required: true
+	Name *string `json:"name"`
+
+	// pmax
+	// Required: true
+	Pmax *float64 `json:"pmax"`
+
+	// pmin
+	// Required: true
+	Pmin *float64 `json:"pmin"`
+
+	// type
+	// Required: true
+	Type *string `json:"type"`
+}
+
+// Validate validates this powerplant
+func (m *Powerplant) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateEfficiency(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateName(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validatePmax(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validatePmin(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateType(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *Powerplant) validateEfficiency(formats strfmt.Registry) error {
+
+	if err := validate.Required("efficiency", "body", m.Efficiency); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *Powerplant) validateName(formats strfmt.Registry) error {
+
+	if err := validate.Required("name", "body", m.Name); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *Powerplant) validatePmax(formats strfmt.Registry) error {
+
+	if err := validate.Required("pmax", "body", m.Pmax); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *Powerplant) validatePmin(formats strfmt.Registry) error {
+
+	if err := validate.Required("pmin", "body", m.Pmin); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *Powerplant) validateType(formats strfmt.Registry) error {
+
+	if err := validate.Required("type", "body", m.Type); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ContextValidate validates this powerplant based on context it is used
+func (m *Powerplant) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *Powerplant) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *Powerplant) UnmarshalBinary(b []byte) error {
+	var res Powerplant
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}

+ 62 - 2
restapi/embedded_spec.go

@@ -108,10 +108,40 @@ func init() {
         "powerplants": {
           "type": "array",
           "items": {
-            "type": "object"
+            "$ref": "#/definitions/powerplant"
           }
         }
       }
+    },
+    "powerplant": {
+      "type": "object",
+      "required": [
+        "name",
+        "type",
+        "efficiency",
+        "pmin",
+        "pmax"
+      ],
+      "properties": {
+        "efficiency": {
+          "type": "number",
+          "format": "float64"
+        },
+        "name": {
+          "type": "string"
+        },
+        "pmax": {
+          "type": "number",
+          "format": "float64"
+        },
+        "pmin": {
+          "type": "number",
+          "format": "float64"
+        },
+        "type": {
+          "type": "string"
+        }
+      }
     }
   }
 }`))
@@ -206,10 +236,40 @@ func init() {
         "powerplants": {
           "type": "array",
           "items": {
-            "type": "object"
+            "$ref": "#/definitions/powerplant"
           }
         }
       }
+    },
+    "powerplant": {
+      "type": "object",
+      "required": [
+        "name",
+        "type",
+        "efficiency",
+        "pmin",
+        "pmax"
+      ],
+      "properties": {
+        "efficiency": {
+          "type": "number",
+          "format": "float64"
+        },
+        "name": {
+          "type": "string"
+        },
+        "pmax": {
+          "type": "number",
+          "format": "float64"
+        },
+        "pmin": {
+          "type": "number",
+          "format": "float64"
+        },
+        "type": {
+          "type": "string"
+        }
+      }
     }
   }
 }`))

+ 26 - 29
spoptim/production_planner.go

@@ -1,10 +1,8 @@
 package spoptim
 
 import (
-	"fmt"
 	"math"
 	"sort"
-	"strconv"
 	"encoding/json"
 
 	"gem-spaas-coding-challenge/models"
@@ -26,27 +24,30 @@ func ProductionPlanner(payload *models.Payload) []interface{} {
 		return make([]interface{}, 0)
 	}
 
+	// cast the fuels to map and validate
+	fuels, _ := payload.Fuels.(map[string]interface{})
+	for k := range powerplantFuel {
+		if _, ok := fuels[powerplantFuel[k]]; !ok {
+			res := make([]interface{}, 0)
+			res = append(res, powerplantFuel[k] + " missing in Fuels") 
+			return res
+		}
+	}
+
 	// format wind plants for consistency with other types of plants
-	wind, _ := payload.Fuels.(map[string]interface{})["wind(%)"].(json.Number).Float64()
+	wind, _ := fuels["wind(%)"].(json.Number).Float64()
 	for _, plant := range payload.Powerplants {
-		plant_typed := plant.(map[string]interface{})
-		if plant_typed["type"] == "windturbine" {
-			pmax, _ := plant_typed["pmax"].(json.Number).Float64()
-			plant_typed["pmax"] = json.Number(strconv.FormatFloat((pmax * wind / 100), 'f', 20, 64))
+		if *plant.Type == "windturbine" {
+			*plant.Pmax = *plant.Pmax * wind / 100
 		}
 	}
-	payload.Fuels.(map[string]interface{})["wind(%)"] = json.Number(0)
+	fuels["wind(%)"] = json.Number(0)
 
 	// order plants by merit
 	sort.SliceStable(payload.Powerplants, func(i, j int) bool {
-        planti, _ := payload.Powerplants[i].(map[string]interface{})
-        plantj, _ := payload.Powerplants[j].(map[string]interface{})
-        fuels, _ := payload.Fuels.(map[string]interface{})
-        fueli, _ := fuels[powerplantFuel[planti["type"].(string)]].(json.Number).Float64()
-        fuelj, _ := fuels[powerplantFuel[plantj["type"].(string)]].(json.Number).Float64()
-        effi, _ := planti["efficiency"].(json.Number).Float64()
-        effj, _ := plantj["efficiency"].(json.Number).Float64()
-        return fueli / effi < fuelj / effj
+        fueli, _ := fuels[powerplantFuel[*payload.Powerplants[i].Type]].(json.Number).Float64()
+        fuelj, _ := fuels[powerplantFuel[*payload.Powerplants[j].Type]].(json.Number).Float64()
+        return fueli / *payload.Powerplants[i].Efficiency < fuelj / *payload.Powerplants[j].Efficiency
     })
 
 	// Naively assign production by merit-order (with one check
@@ -57,21 +58,17 @@ func ProductionPlanner(payload *models.Payload) []interface{} {
 	costToCompare := make([]interface{}, 0)
 	resToCompare := make([][]interface{}, 0)
 	for _, plant := range payload.Powerplants {
-		plant_typed := plant.(map[string]interface{})
 		remainingLoad := *payload.Load - load
 		if remainingLoad == 0 {
 			break
 		}
-		pmin, _ := plant_typed["pmin"].(json.Number).Float64()
-		pmax, _ := plant_typed["pmax"].(json.Number).Float64()
-		eff, _ := plant_typed["efficiency"].(json.Number).Float64()
-		fuel, _ := payload.Fuels.(map[string]interface{})[powerplantFuel[plant_typed["type"].(string)]].(json.Number).Float64()
-		if pmin < remainingLoad {
-			usedP := math.Min(remainingLoad, pmax)
+		fuel, _ := fuels[powerplantFuel[*plant.Type]].(json.Number).Float64()
+		if *plant.Pmin < remainingLoad {
+			usedP := math.Min(remainingLoad, *plant.Pmax)
 			load += usedP
-			cost = append(cost, usedP / eff * fuel)
+			cost = append(cost, usedP / *plant.Efficiency * fuel)
 			res = append(res, map[string]interface{}{
-					"name": plant_typed["name"].(string),
+					"name": plant.Name,
 					"p": usedP,
 			})
 		} else {
@@ -79,7 +76,7 @@ func ProductionPlanner(payload *models.Payload) []interface{} {
 			altCost := make([]float64, 0)
 			copy(altRes, res)
 			copy(altCost, cost)
-			loadToRemove := pmin - remainingLoad
+			loadToRemove := *plant.Pmin - remainingLoad
 			for loadToRemove > 0 {
 				if altRes[len(altRes)-1].(map[string]interface{})["p"].(float64) > loadToRemove {
 					altCost[len(altCost)-1] = altCost[len(altCost)-1] * (1 - loadToRemove / altRes[len(altRes)-1].(map[string]interface{})["p"].(float64))
@@ -92,10 +89,10 @@ func ProductionPlanner(payload *models.Payload) []interface{} {
 				}
 			}
 			altRes = append(altRes, map[string]interface{}{
-					"name": plant_typed["name"].(string),
-					"p": pmax,
+					"name": *plant.Name,
+					"p": *plant.Pmax,
 			})
-			altCost = append(altCost, pmax / eff * fuel)
+			altCost = append(altCost, *plant.Pmax / *plant.Efficiency * fuel)
 			resToCompare = append(resToCompare, altRes)
 			costToCompare = append(costToCompare, altCost)
 		}

+ 23 - 1
swagger.yml

@@ -48,7 +48,29 @@ definitions:
       powerplants:
         type: array
         items: 
-          type: object
+          $ref: "#/definitions/powerplant"
+  powerplant:
+    type: object
+    required:
+      - name
+      - type
+      - efficiency
+      - pmin
+      - pmax
+    properties:
+      name:
+        type: string
+      type:
+        type: string
+      efficiency:
+        type: number
+        format: float64
+      pmin:
+        type: number
+        format: float64
+      pmax:
+        type: number
+        format: float64
   error:
     type: object
     required: