diff --git a/apps/batch-sim/index.js b/apps/batch-sim/index.js index 3aa975010502986f4fe776f30d98ec711900765d..714f3a283f815c8dd1fb57c5bf1f67ec07819bec 100644 --- a/apps/batch-sim/index.js +++ b/apps/batch-sim/index.js @@ -1,5 +1,6 @@ // COPYRIGHT: FUNDACIÓN TECNALIA RESEARCH & INNOVATION, 2022. +const faker = require("@faker-js/faker") const { Option } = require("commander") const { createCLIApp, HypercogApiClient } = require("@hypercog/utils") @@ -38,22 +39,22 @@ const simulateCycle = async (arg1, cmd) => { await signInAs(apiClient, otherArgs.sidenorUser, password) let batchId = await createAssetAndSendToCone(apiClient, "simulated asset 1") - batchId = await createAssetAndSendToCone(apiClient, "simulated asset 2") - batchId = await createAssetAndSendToCone(apiClient, "simulated asset 3") + //batchId = await createAssetAndSendToCone(apiClient, "simulated asset 2") + //batchId = await createAssetAndSendToCone(apiClient, "simulated asset 3") const selectedStock = await sendToStock(apiClient, { assetId: batchId }) await signInAs(apiClient, otherArgs.cement1User, password) await bidAsset(apiClient, { assetId: selectedStock.id, - price: 2 + price: faker.datatype.number({ min: 50, max: 150 }) }) await signInAs(apiClient, otherArgs.cement2User, password) await bidAsset(apiClient, { assetId: selectedStock.id, quantity: selectedStock.quantity - 500, - price: 3 + price: faker.datatype.number({ min: 25, max: 200 }) }) await signInAs(apiClient, otherArgs.sidenorUser, password) diff --git a/apps/batch-sim/package-lock.json b/apps/batch-sim/package-lock.json index 860683f2e1dfb02d38d8dfd7131fde7b7eef18e8..1c2e6800244c1d9ba8aa1492dba18caa3a1ec211 100644 --- a/apps/batch-sim/package-lock.json +++ b/apps/batch-sim/package-lock.json @@ -6,6 +6,7 @@ "": { "name": "@hypercog/batch-sim", "dependencies": { + "@faker-js/faker": "^5.5.3", "commander": "^9.0.0" } }, @@ -38,6 +39,11 @@ "inquirer": "^8.2.0" } }, + "node_modules/@faker-js/faker": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-5.5.3.tgz", + "integrity": "sha512-R11tGE6yIFwqpaIqcfkcg7AICXzFg14+5h5v0TfF/9+RMDL6jhzCy/pxHVOfbALGdtVYdt6JdR21tuxEgl34dw==" + }, "node_modules/commander": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.0.0.tgz", @@ -48,6 +54,11 @@ } }, "dependencies": { + "@faker-js/faker": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-5.5.3.tgz", + "integrity": "sha512-R11tGE6yIFwqpaIqcfkcg7AICXzFg14+5h5v0TfF/9+RMDL6jhzCy/pxHVOfbALGdtVYdt6JdR21tuxEgl34dw==" + }, "commander": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.0.0.tgz", diff --git a/apps/batch-sim/package.json b/apps/batch-sim/package.json index d547ac0d7c26fab53bff897ce6473e839fa12bc2..98dd94580d9805d3fabfedf93318a035d2392ee8 100644 --- a/apps/batch-sim/package.json +++ b/apps/batch-sim/package.json @@ -4,6 +4,7 @@ "@hypercog/utils": "^0.0.1", "@hypercog/sidenor-cli": "^0.0.1", "@hypercog/cement-cli": "^0.0.1", + "@faker-js/faker": "^5.5.3", "commander": "^9.0.0" } } diff --git a/apps/cement-cli/lib.js b/apps/cement-cli/lib.js index 6f6dfce8a711d519374a274570ac8c1824564bb3..fdd44dc4c76d19d0a5417aefe043526ff88f116c 100644 --- a/apps/cement-cli/lib.js +++ b/apps/cement-cli/lib.js @@ -19,7 +19,8 @@ const bidAsset = async ( { type: "number", name: "inputQuantity", - message: `Choose an ammount of slag (max: ${assetToBid.quantity} ${assetToBid.units}):` + message: `Choose an ammount of slag (max: ${assetToBid.quantity} ${assetToBid.units}):`, + default: Math.floor(assetToBid.quantity * 0.1) // Default: 10% } ]) chosenQuantity = inputQuantity diff --git a/apps/pubadmin-cli/index.js b/apps/pubadmin-cli/index.js index 922c5a33a7b633b9df53ea77f5db5d9d50c4c414..86ddc4bea9011ed2b2b6162e693e8d069164736e 100644 --- a/apps/pubadmin-cli/index.js +++ b/apps/pubadmin-cli/index.js @@ -39,35 +39,45 @@ const humanizePercentage = value => { if (value.percentage === undefined) { return value.total } - return `${value.total.toFixed(3)} [%${(value.percentage * 100).toFixed(2)}]` + return `${value.total.toFixed(3)} [${(value.percentage * 100).toFixed(2)} %]` } const humanizePrice = value => `${value.avg.toFixed(2)} [${value.min.toFixed(2)}, ${value.max.toFixed(2)}]` -const toLine = stat => [ +const toSteelLine = stat => [ stat.total.toFixed(3), humanizePercentage(stat.reused), humanizePercentage(stat.discarded), humanizePrice(stat.price) ] +const toCementLine = stat => [stat.total.toFixed(3), humanizePrice(stat.price)] + const getStats = async apiClient => { const stats = await apiClient.getStats() - const tableValues = [ + let tableValues = [ [ "", "Total slag\nt", "Reused\nt [%]", "Discarded\nt [%]", - "Price (€)\n Avg [Min, Max]" + "Price (€/t)\n Avg [Min, Max]" ], ...Object.entries(stats.perSteelCompany || []).map(([org, val]) => [ org, - ...toLine(val) + ...toSteelLine(val) ]) ] + console.log(table(tableValues, TABLE_CONFIG)) + tableValues = [ + ["", "Total slag\nt", "Price (€/t)\n Avg [Min, Max]"], + ...Object.entries(stats.perCementCompany || []).map(([org, val]) => [ + org, + ...toCementLine(val) + ]) + ] console.log(table(tableValues, TABLE_CONFIG)) } diff --git a/apps/sidenor-cli/lib.js b/apps/sidenor-cli/lib.js index 5039deb25f60ea9f3f1044afa76d9316966859b5..08404177fc643daea019bc0a25c0ac1e2699247c 100644 --- a/apps/sidenor-cli/lib.js +++ b/apps/sidenor-cli/lib.js @@ -199,7 +199,7 @@ const bidResponse = name: "inputBidder", message: `Select the bidder whose bid will be ${action}ed`, choices: assetSelected.fields.bids.map(b => ({ - name: `${b.bidder.id} (quantity: ${b.quantity} ${assetSelected.units}, price: ${b.price})`, + name: `${b.bidder.id} (quantity: ${b.quantity} ${assetSelected.units}, price: ${b.price} €)`, value: b.bidder })) } diff --git a/chaincode/controller/bid/bid_controller.go b/chaincode/controller/bid/bid_controller.go index 16503d78fbdbd7ecf5754d8c312b1810ba6d1919..77056a52dc2e0cabf066a6b8ae78b165ab1f4a73 100644 --- a/chaincode/controller/bid/bid_controller.go +++ b/chaincode/controller/bid/bid_controller.go @@ -1,7 +1,8 @@ -// controller.go -// COPYRIGHT: FUNDACIÓN TECNALIA RESEARCH & INNOVATION, 2018. -// controller.go is part of the Computer file TRACEBLOCK. Copyrighted under U.S. Copyright Office Registration Number TXu002105160, registered on 2018-06-25 by FUNDACIÓN TECNALIA RESEARCH & INNOVATION. -// This license is effective worldwide. +/** + * bid_input.go + * + * COPYRIGHT: FUNDACIÓN TECNALIA RESEARCH & INNOVATION, 2022. + */ package bid @@ -15,6 +16,8 @@ import ( "github.com/mitchellh/mapstructure" "git.code.tecnalia.com/blockchain/hypercog/controller/stats" + hmodel "git.code.tecnalia.com/blockchain/hypercog/model" + errs "git.code.tecnalia.com/traceblock/sdk/constants" "git.code.tecnalia.com/traceblock/sdk/controller/base" "git.code.tecnalia.com/traceblock/sdk/controller/split" @@ -30,28 +33,6 @@ type BidDecisionController struct { BidController } -type BidStatus string - -const ( - Proposed = "proposed" - Accepted = "accepted" - Rejected = "rejected" -) - -type Bidder struct { - Id string `json:"id"` - Role string `json:"role"` - Org string `json:"org"` -} - -type Bid struct { - // Not renaming any of these fields in json to make mapstructure.decode work - Quantity uint32 `json:"quantity"` - Price float32 `json:"price"` - Bidder Bidder `json:"bidder"` - Status BidStatus `json:"status"` -} - var ( errInvalidInputData = errors.New("failed to read model") errParamsMissing = errors.New("missing split params. assetId and splitParams config must be send in split request") @@ -101,8 +82,8 @@ func (c BidController) readAsset(stub shared.LedgerBuildrStubInterface, requestA return c.readTraceableAsset(stub, requestAsset.GetID()) } -func (c BidController) getBids(biddableAsset *model.TraceableAsset) ([]Bid, error) { - bidArr := []Bid{} +func (c BidController) getBids(biddableAsset *model.TraceableAsset) ([]hmodel.Bid, error) { + bidArr := []hmodel.Bid{} if _, ok := biddableAsset.ArbitraryDataFields["bids"]; ok { // to avoid interface conversion errors ([]interface {} is not []bid.Bid) err := mapstructure.Decode(biddableAsset.ArbitraryDataFields["bids"], &bidArr) @@ -147,15 +128,15 @@ func (c BidController) BidAsset(stub shared.LedgerBuildrStubInterface, params sh return api.NewApiResponsePtr(fnName, err, nil).SendResponse() } - newBid := Bid{ + newBid := hmodel.Bid{ Quantity: bidParams.Bid.Quantity, Price: bidParams.Bid.Price, - Bidder: Bidder{ + Bidder: hmodel.Bidder{ Id: stub.GetUniqueUserId(), Org: org, Role: role, }, - Status: Proposed, + Status: hmodel.Proposed, } alreadyExists := false @@ -177,7 +158,7 @@ func (c BidController) BidAsset(stub shared.LedgerBuildrStubInterface, params sh return c.SaveAbstractAsset(stub, biddableAsset) } -func (c BidDecisionController) bidResponse(stub shared.LedgerBuildrStubInterface, params shared.LedgerBuildrAsset, acceptBid bool) (*model.TraceableAsset, *Bid, error) { +func (c BidDecisionController) bidResponse(stub shared.LedgerBuildrStubInterface, params shared.LedgerBuildrAsset, acceptBid bool) (*model.TraceableAsset, *hmodel.Bid, error) { bidDecisionParams, ok := params.(*BidDecisionParams) if bidDecisionParams == nil || !ok { //failed when casting/fetching input data @@ -194,14 +175,14 @@ func (c BidDecisionController) bidResponse(stub shared.LedgerBuildrStubInterface return nil, nil, err } - var selectedBid *Bid = nil + var selectedBid *hmodel.Bid = nil for i, bid := range auxArr { if bid.Bidder.Id == bidDecisionParams.Bidder { selectedBid = &bid if acceptBid { - auxArr[i].Status = Accepted + auxArr[i].Status = hmodel.Accepted } else { - auxArr[i].Status = Rejected + auxArr[i].Status = hmodel.Rejected } break } @@ -284,8 +265,8 @@ func (c BidDecisionController) _acceptBid(stub shared.LedgerBuildrStubInterface transferParams.Location = soldStock.Location transferredAsset := transfer.TransferAsset(stub, soldStock, transferParams) - - err = stats.RegisterSale(stub, soldStock.Quantity, bid.Price, soldStock.Units) + + err = stats.RegisterSale(stub, bid, soldStock.Units) if err != nil { return nil, err } diff --git a/chaincode/controller/stats/stats_controller.go b/chaincode/controller/stats/stats_controller.go index f8bf0c1eae82648fd03552d72a9a3f448705675a..4a00f7b175d1b868ba6b7ec8bf8f3dec2ba29b0b 100644 --- a/chaincode/controller/stats/stats_controller.go +++ b/chaincode/controller/stats/stats_controller.go @@ -7,7 +7,7 @@ package stats import ( - "git.code.tecnalia.com/blockchain/hypercog/model" + "git.code.tecnalia.com/blockchain/hypercog/model/stats" "git.code.tecnalia.com/ledgerbuilder/sdk/core/api" "git.code.tecnalia.com/ledgerbuilder/sdk/core/fabric/protos" "git.code.tecnalia.com/ledgerbuilder/sdk/shared" @@ -15,7 +15,8 @@ import ( type StatsResult struct { - PerSteelCompany map[string]model.Stats `json:"perSteelCompany,omitempty"` + PerSteelCompany map[string]stats.SteelStats `json:"perSteelCompany,omitempty"` + PerCementCompany map[string]stats.CementStats `json:"perCementCompany,omitempty"` } func GetStatsOperation(stub shared.LedgerBuildrStubInterface, request shared.LedgerBuildrAsset) protos.Response { @@ -23,23 +24,41 @@ func GetStatsOperation(stub shared.LedgerBuildrStubInterface, request shared.Led ret := new(StatsResult) - orgs, err := getOrgList(stub) + orgs, err := getSteelOrgList(stub) if err != nil { return api.NewApiResponsePtr(fnName, err, nil).SendResponse() } if len(*orgs) > 0 { - ret.PerSteelCompany = make(map[string]model.Stats) + ret.PerSteelCompany = make(map[string]stats.SteelStats) } for _, org := range *orgs { - orgStat, err := getStats(stub, org) + orgStat, err := getSteelStats(stub, org) if err != nil { return api.NewApiResponsePtr(fnName, err, nil).SendResponse() } ret.PerSteelCompany[org] = *orgStat.RefineStats() } + + orgs, err = getCementOrgList(stub) + if err != nil { + return api.NewApiResponsePtr(fnName, err, nil).SendResponse() + } + + if len(*orgs) > 0 { + ret.PerCementCompany = make(map[string]stats.CementStats) + } + + for _, org := range *orgs { + orgStat, err := getCementStats(stub, org) + if err != nil { + return api.NewApiResponsePtr(fnName, err, nil).SendResponse() + } + ret.PerCementCompany[org] = *orgStat.RefineStats() + } + return api.NewAPIGenericResponsePtr(fnName, nil, ret).SendResponse() } diff --git a/chaincode/controller/stats/stub.go b/chaincode/controller/stats/stub.go index 590c281d972d35125926758476c390ef2127bfbe..26359f5f61993d04129e6b97e6160bb1c660dfd6 100644 --- a/chaincode/controller/stats/stub.go +++ b/chaincode/controller/stats/stub.go @@ -11,21 +11,41 @@ import ( "fmt" "math" - "git.code.tecnalia.com/blockchain/hypercog/model" "git.code.tecnalia.com/ledgerbuilder/sdk/core/util/logging" "git.code.tecnalia.com/ledgerbuilder/sdk/shared" + + "git.code.tecnalia.com/blockchain/hypercog/model" + "git.code.tecnalia.com/blockchain/hypercog/model/stats" ) const STEEL_ORG_LIST_KEY = "steel_orgs" +const CEMENT_ORG_LIST_KEY = "cement_orgs" type OrgList []string var log = logging.NewGoLogger("stats") -func getStats(stub shared.LedgerBuildrStubInterface, key string) (st *model.RawStats, err error) { +func getSteelStats(stub shared.LedgerBuildrStubInterface, key string) (st *stats.RawSteelOrgStats, err error) { + value, err := stub.GetState(key) + + if err != nil || len(value) == 0 { + st = new(stats.RawSteelOrgStats) + st.MinPrice = math.MaxFloat64 + } else { + err = json.Unmarshal(value, &st) + if err != nil { + return nil, err + } + } + + return st, nil +} + +func getCementStats(stub shared.LedgerBuildrStubInterface, key string) (st *stats.RawCementOrgStats, err error) { value, err := stub.GetState(key) + if err != nil || len(value) == 0 { - st = new(model.RawStats) + st = new(stats.RawCementOrgStats) st.MinPrice = math.MaxFloat64 } else { err = json.Unmarshal(value, &st) @@ -37,7 +57,7 @@ func getStats(stub shared.LedgerBuildrStubInterface, key string) (st *model.RawS return st, nil } -func putStats(stub shared.LedgerBuildrStubInterface, key string, stats *model.RawStats) (error) { +func putJson(stub shared.LedgerBuildrStubInterface, key string, stats interface{}) (error) { serialized, err := json.Marshal(stats) if err != nil { return err @@ -51,8 +71,8 @@ func putStats(stub shared.LedgerBuildrStubInterface, key string, stats *model.Ra return nil } -func getOrgList(stub shared.LedgerBuildrStubInterface) (st *OrgList, err error) { - value, err := stub.GetState(STEEL_ORG_LIST_KEY) +func _getOrgList(stub shared.LedgerBuildrStubInterface, key string) (st *OrgList, err error) { + value, err := stub.GetState(key) if err != nil || len(value) == 0 { st = new(OrgList) } else { @@ -65,8 +85,26 @@ func getOrgList(stub shared.LedgerBuildrStubInterface) (st *OrgList, err error) return st, nil } +func getSteelOrgList(stub shared.LedgerBuildrStubInterface) (st *OrgList, err error) { + return _getOrgList(stub, STEEL_ORG_LIST_KEY) +} + +func getCementOrgList(stub shared.LedgerBuildrStubInterface) (st *OrgList, err error) { + return _getOrgList(stub, CEMENT_ORG_LIST_KEY) +} + func resetStats(stub shared.LedgerBuildrStubInterface) (error) { - orgs, err := getOrgList(stub) + orgs, err := getSteelOrgList(stub) + if len(*orgs) > 0 { + for _, org := range *orgs { + err = stub.DelState(org) + if err != nil { + return err + } + } + } + + orgs, err = getCementOrgList(stub) if len(*orgs) > 0 { for _, org := range *orgs { err = stub.DelState(org) @@ -77,6 +115,8 @@ func resetStats(stub shared.LedgerBuildrStubInterface) (error) { } err = stub.DelState(STEEL_ORG_LIST_KEY) + err = stub.DelState(CEMENT_ORG_LIST_KEY) + return err } @@ -89,8 +129,8 @@ func (orgs OrgList) contains(org string) (bool) { return false } -func addOrgIfNotExist(stub shared.LedgerBuildrStubInterface, org string) (err error) { - orgs, err := getOrgList(stub) +func addOrgIfNotExist(stub shared.LedgerBuildrStubInterface, key string, org string) (err error) { + orgs, err := _getOrgList(stub, key) if err != nil { return err } @@ -103,7 +143,7 @@ func addOrgIfNotExist(stub shared.LedgerBuildrStubInterface, org string) (err er return err } - err = stub.PutState(STEEL_ORG_LIST_KEY, serialized) + err = stub.PutState(key, serialized) if err != nil { return err } @@ -115,13 +155,13 @@ func addOrgIfNotExist(stub shared.LedgerBuildrStubInterface, org string) (err er } -func retrieveExistingStats(stub shared.LedgerBuildrStubInterface) (*model.RawStats, error) { +func retrieveExistingStats(stub shared.LedgerBuildrStubInterface) (*stats.RawSteelOrgStats, error) { org, err := stub.GetOrganization() if err != nil { return nil, err } - customStats, err := getStats(stub, org) + customStats, err := getSteelStats(stub, org) if err != nil { return nil, err } @@ -129,18 +169,18 @@ func retrieveExistingStats(stub shared.LedgerBuildrStubInterface) (*model.RawSta return customStats, nil } -func upgradeStats(stub shared.LedgerBuildrStubInterface, customStats *model.RawStats) (error) { +func upgradeSteelStats(stub shared.LedgerBuildrStubInterface, customStats *stats.RawSteelOrgStats) (error) { org, err := stub.GetOrganization() if err != nil { return err } - err = putStats(stub, org, customStats) + err = putJson(stub, org, customStats) if err != nil { return err } - err = addOrgIfNotExist(stub, org) + err = addOrgIfNotExist(stub, STEEL_ORG_LIST_KEY, org) if err != nil { return err } @@ -149,6 +189,20 @@ func upgradeStats(stub shared.LedgerBuildrStubInterface, customStats *model.RawS return nil } +func upgradeCementStats(stub shared.LedgerBuildrStubInterface, customStats *stats.RawCementOrgStats, org string) (error) { + err := putJson(stub, org, customStats) + if err != nil { + return err + } + + err = addOrgIfNotExist(stub, CEMENT_ORG_LIST_KEY, org) + if err != nil { + return err + } + + log.Debug("Upgrading stats") + return nil +} func RegisterSlag(stub shared.LedgerBuildrStubInterface, quantity uint32, units string) (error) { orgStats, err := retrieveExistingStats(stub) @@ -158,7 +212,7 @@ func RegisterSlag(stub shared.LedgerBuildrStubInterface, quantity uint32, units orgStats.RegisterSlag(quantity, units) - err = upgradeStats(stub, orgStats) + err = upgradeSteelStats(stub, orgStats) if err != nil { return err } @@ -166,20 +220,33 @@ func RegisterSlag(stub shared.LedgerBuildrStubInterface, quantity uint32, units return nil } -func RegisterSale(stub shared.LedgerBuildrStubInterface, quantity uint32, price float32, units string) (error) { - orgStats, err := retrieveExistingStats(stub) +func RegisterSale(stub shared.LedgerBuildrStubInterface, acceptedBid *model.Bid, units string) (error) { + senderOrgStats, err := retrieveExistingStats(stub) + if err != nil { + return err + } + + senderOrgStats.UpgradeSale(acceptedBid.Quantity, acceptedBid.Price, units) + + err = upgradeSteelStats(stub, senderOrgStats) if err != nil { return err } - - orgStats.UpgradeSale(quantity, price, units) - log.Debug(fmt.Sprintf("Sale registered in stats (quantity: %d %s, price: %.2f €)", quantity, units, price)) - err = upgradeStats(stub, orgStats) + receiverOrgStats, err := getCementStats(stub, acceptedBid.Bidder.Org) if err != nil { return err } + receiverOrgStats.UpgradeSale(acceptedBid.Quantity, acceptedBid.Price, units) + + err = upgradeCementStats(stub, receiverOrgStats, acceptedBid.Bidder.Org) + if err != nil { + return err + } + + + log.Debug(fmt.Sprintf("Sale registered in stats (quantity: %d %s, price: %.2f €)", acceptedBid.Quantity, units, acceptedBid.Price)) return nil } \ No newline at end of file diff --git a/chaincode/model/bid.go b/chaincode/model/bid.go new file mode 100644 index 0000000000000000000000000000000000000000..c24537aa897835f3e57033477d40c61e38fb805e --- /dev/null +++ b/chaincode/model/bid.go @@ -0,0 +1,29 @@ +/** + * bid.go + * + * COPYRIGHT: FUNDACIÓN TECNALIA RESEARCH & INNOVATION, 2022. + */ + +package model + +type BidStatus string + +const ( + Proposed = "proposed" + Accepted = "accepted" + Rejected = "rejected" +) + +type Bidder struct { + Id string `json:"id"` + Role string `json:"role"` + Org string `json:"org"` +} + +type Bid struct { + // Not renaming any of these fields in json to make mapstructure.decode work + Quantity uint32 `json:"quantity"` + Price float32 `json:"price"` + Bidder Bidder `json:"bidder"` + Status BidStatus `json:"status"` +} diff --git a/chaincode/model/stats.go b/chaincode/model/stats.go deleted file mode 100644 index b63e8e60be9020001188ceb78f3d60445928df59..0000000000000000000000000000000000000000 --- a/chaincode/model/stats.go +++ /dev/null @@ -1,89 +0,0 @@ -/** - * stats.go - * - * COPYRIGHT: FUNDACIÓN TECNALIA RESEARCH & INNOVATION, 2022. - */ - -package model - -type PercentageMetric struct { - Total float64 `json:"total"` // in tons - Percentage float64 `json:"percentage,omitempty"` // in % -} - -type PriceMetric struct { - Minimum float64 `json:"min"` - Maximum float64 `json:"max"` - Average float64 `json:"avg"` -} - -// Total or per company -// Refined stats which will be returned -type Stats struct { - TotalSlag float64 `json:"total"` // in tons - Reused PercentageMetric `json:"reused"` - Discarded PercentageMetric `json:"discarded"` - Price PriceMetric `json:"price"` -} - -// Total or per company -// Raw stored stats -type RawStats struct { - TotalSlag float64 `json:"total"` // in tons - SlagReused float64 `json:"reused"` // in tons - SlagDiscarded float64 `json:"discarded"` // in tons - TotalPrice float64 `json:"totalPrice"` // in € - BidAmount uint32 `json:"bidAmount"` // in units - MinPrice float64 `json:"minPrice"` // in € - MaxPrice float64 `json:"maxPrice"` // in € -} - -func toTons(quantity uint32, units string) (float64) { - amount := float64(quantity) - if units == "kg" { - amount /= 1000.0 - } - return amount -} - -func (raw *RawStats) RegisterSlag(quantity uint32, units string) { - raw.TotalSlag += toTons(quantity, units) -} - -func (raw *RawStats) UpgradeSale(quantity uint32, price float32, units string) { - raw.SlagReused += toTons(quantity, units) - raw.TotalPrice += float64(price) - raw.BidAmount += 1 - - convertedPrice := float64(price) - if convertedPrice < raw.MinPrice { - raw.MinPrice = convertedPrice - } - - if raw.MaxPrice < convertedPrice { - raw.MaxPrice = convertedPrice - } -} - -func (raw *RawStats) RefineStats() (*Stats) { - ret := new(Stats) - ret.TotalSlag = raw.TotalSlag - - ret.Reused.Total = raw.SlagReused - if ret.TotalSlag > 0 && ret.Reused.Total > 0 { - ret.Reused.Percentage = ret.Reused.Total / ret.TotalSlag - } - - ret.Discarded.Total = raw.SlagDiscarded - if ret.TotalSlag > 0 && ret.Discarded.Total > 0 { - ret.Discarded.Percentage = ret.Discarded.Total / ret.TotalSlag - } - - ret.Price.Minimum = raw.MinPrice - ret.Price.Maximum = raw.MaxPrice - if raw.BidAmount > 0 { - ret.Price.Average = raw.TotalPrice / float64(raw.BidAmount) - } - - return ret -} diff --git a/chaincode/model/stats/cement.go b/chaincode/model/stats/cement.go new file mode 100644 index 0000000000000000000000000000000000000000..b2e01ba867426f27cd0aad7f23139f7203c6f451 --- /dev/null +++ b/chaincode/model/stats/cement.go @@ -0,0 +1,50 @@ +/** + * cement.go + * + * COPYRIGHT: FUNDACIÓN TECNALIA RESEARCH & INNOVATION, 2022. + */ + +package stats + +// Refined stats which will be returned +type CementStats struct { + TotalSlag float64 `json:"total"` // in tons + SoldBatch float64 `json:"sold"` // in t/sold batch + Price SummaryMetric `json:"price"` // in t/€ +} + +// Raw stored stats (per cement company) +type RawCementOrgStats struct { + RawStats +} + +func (raw *RawCementOrgStats) UpgradeSale(quantity uint32, price float32, units string) { + soldBatch := toTons(quantity, units) + raw.TotalSlag += soldBatch + raw.TotalPrice += float64(price) + raw.SoldBatches += 1 + + convertedPrice := float64(price) / soldBatch + if convertedPrice < raw.MinPrice { + raw.MinPrice = convertedPrice + } + + if raw.MaxPrice < convertedPrice { + raw.MaxPrice = convertedPrice + } +} + +func (raw *RawCementOrgStats) RefineStats() (*CementStats) { + ret := new(CementStats) + ret.TotalSlag = raw.TotalSlag + + ret.SoldBatch = ret.TotalSlag / float64(raw.SoldBatches) + + ret.Price.Minimum = raw.MinPrice + ret.Price.Maximum = raw.MaxPrice + if raw.TotalSlag > 0 { + ret.Price.Average = raw.TotalPrice / raw.TotalSlag + } + + return ret +} diff --git a/chaincode/model/stats/common.go b/chaincode/model/stats/common.go new file mode 100644 index 0000000000000000000000000000000000000000..df52ba77e168e7f8c92c6982dd5e0f20086323dd --- /dev/null +++ b/chaincode/model/stats/common.go @@ -0,0 +1,35 @@ +/** + * common.go + * + * COPYRIGHT: FUNDACIÓN TECNALIA RESEARCH & INNOVATION, 2022. + */ + +package stats + +type PercentageMetric struct { + Total float64 `json:"total"` // in tons + Percentage float64 `json:"percentage,omitempty"` // in % +} + +type SummaryMetric struct { + Minimum float64 `json:"min"` + Maximum float64 `json:"max"` + Average float64 `json:"avg"` +} + +// Generic raw stats +type RawStats struct { + TotalSlag float64 `json:"total"` // in tons + TotalPrice float64 `json:"totalPrice"` // in € + SoldBatches uint32 `json:"soldBatches"` // in units + MinPrice float64 `json:"minPrice"` // in € + MaxPrice float64 `json:"maxPrice"` // in € +} + +func toTons(quantity uint32, units string) (float64) { + amount := float64(quantity) + if units == "kg" { + amount /= 1000.0 + } + return amount +} diff --git a/chaincode/model/stats/steel.go b/chaincode/model/stats/steel.go new file mode 100644 index 0000000000000000000000000000000000000000..14020c08d2860ce5ad2a1a844b4959b0e54a816a --- /dev/null +++ b/chaincode/model/stats/steel.go @@ -0,0 +1,70 @@ +/** + * steel.go + * + * COPYRIGHT: FUNDACIÓN TECNALIA RESEARCH & INNOVATION, 2022. + */ + +package stats + +// Refined stats which will be returned +type SteelStats struct { + TotalSlag float64 `json:"total"` // in tons + Reused PercentageMetric `json:"reused"` + Discarded PercentageMetric `json:"discarded"` + SoldBatch float64 `json:"sold"` // in t/sold batch + Price SummaryMetric `json:"price"` // in t/€ +} + +// Raw stored stats (per steel company) +type RawSteelOrgStats struct { + RawStats + + SlagReused float64 `json:"reused"` // in tons + SlagDiscarded float64 `json:"discarded"` // in tons +} + + +func (raw *RawSteelOrgStats) RegisterSlag(quantity uint32, units string) { + raw.TotalSlag += toTons(quantity, units) +} + +func (raw *RawSteelOrgStats) UpgradeSale(quantity uint32, price float32, units string) { + soldBatch := toTons(quantity, units) + raw.SlagReused += soldBatch + raw.TotalPrice += float64(price) + raw.SoldBatches += 1 + + convertedPrice := float64(price) / soldBatch + if convertedPrice < raw.MinPrice { + raw.MinPrice = convertedPrice + } + + if raw.MaxPrice < convertedPrice { + raw.MaxPrice = convertedPrice + } +} + +func (raw *RawSteelOrgStats) RefineStats() (*SteelStats) { + ret := new(SteelStats) + ret.TotalSlag = raw.TotalSlag + + ret.Reused.Total = raw.SlagReused + if ret.TotalSlag > 0 && ret.Reused.Total > 0 { + ret.Reused.Percentage = ret.Reused.Total / ret.TotalSlag + } + + ret.Discarded.Total = raw.SlagDiscarded + if ret.TotalSlag > 0 && ret.Discarded.Total > 0 { + ret.Discarded.Percentage = ret.Discarded.Total / ret.TotalSlag + } + + ret.SoldBatch = ret.TotalSlag / float64(raw.SoldBatches) + + ret.Price.Minimum = raw.MinPrice + ret.Price.Maximum = raw.MaxPrice + if raw.TotalSlag > 0 { + ret.Price.Average = raw.TotalPrice / raw.TotalSlag + } + + return ret +} \ No newline at end of file