Skip to content
Snippets Groups Projects
Commit 8cda676c authored by Gomez Goiri, Aitor's avatar Gomez Goiri, Aitor
Browse files

Creating new command to simulate a whole slag lifecycle example

parent 9ff6e2c1
No related branches found
No related tags found
No related merge requests found
......@@ -6,6 +6,10 @@ This project has three command-line applications:
- [sidenor-cli](./sidenor-cli)
- [pubadmin-cli](./pubadmin-cli)
There is also a simulator to generate a bunch of transactions and affect stats.
- [@hypercog/batch-sim](./batch-sim)
Finally, there is a common library that they use:
- [@hypercog/utils](./utils)
......@@ -98,4 +102,8 @@ Bid from cement-company1-com:eDUwOTo6Q049dXNlcixPVT1jbGllbnQrT1U9b3JnMStPVT1kZXB
$ cd ../pubadmin-cli
$ node index.js stats
# Or to generate/simulate a complete cycle:
$ cd ../batch-sim
$ node index.js simulate
```
// COPYRIGHT: FUNDACIÓN TECNALIA RESEARCH & INNOVATION, 2022.
const { Option } = require("commander")
const { createCLIApp, HypercogApiClient } = require("@hypercog/utils")
const {
createAsset,
editComposition,
sendToCone,
sendToStock,
acceptBid
} = require("@hypercog/sidenor-cli")
const { bidAsset } = require("@hypercog/cement-cli")
const signInAs = async (apiClient, userMail, password) => {
const [username, organization] = userMail.split("@")
const okLogin = await apiClient.signIn(username, password, organization)
if (!okLogin) {
console.error("Unauthorized login")
return
}
}
const createAssetAndSendToCone = async (apiClient, name) => {
const assetId = await createAsset(apiClient, name)
await editComposition(apiClient, { assetId })
return sendToCone(apiClient, { assetId })
}
const simulateCycle = async (arg1, cmd) => {
const { api, network, channel, chaincode, password, ...otherArgs } =
cmd.parent.opts()
const apiClient = new HypercogApiClient(api, network, channel, chaincode)
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")
const stockId = await sendToStock(apiClient, { assetId: batchId })
console.log("Stock Id", stockId)
//await createAsset(apiClient, "simulated asset 2")
//await createAsset(apiClient, "simulated asset 3")
await signInAs(apiClient, otherArgs.cement1User, password)
await bidAsset(apiClient, { assetId: stockId, quantity: 1000, price: 2 })
await signInAs(apiClient, otherArgs.cement2User, password)
await bidAsset(apiClient, { assetId: stockId, quantity: 500, price: 3 })
await signInAs(apiClient, otherArgs.sidenorUser, password)
await acceptBid(apiClient, { assetId: stockId })
}
const program = createCLIApp()
.addOption(
new Option(
"-su --sidenorUser <value>",
"User who will operate with blockchain as a steel manufacturer"
).env("SIDENOR_USER_ID")
)
.addOption(
new Option(
"-cu1 --cement1User <value>",
"User who will operate with blockchain as a cement company 1"
).env("CEMENT1_USER_ID")
)
.addOption(
new Option(
"-cu2 --cement2User <value>",
"User who will operate with blockchain as a cement company 2"
).env("CEMENT2_USER_ID")
)
program.command("simulate").description("Simulate usage").action(simulateCycle)
program.parse(process.argv)
{
"name": "@hypercog/batch-sim",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@hypercog/batch-sim",
"dependencies": {
"commander": "^9.0.0"
}
},
"../cement-cli": {
"name": "@hypercog/cement-cli",
"version": "0.0.1",
"extraneous": true,
"dependencies": {
"@hypercog/utils": "^0.0.1"
}
},
"../sidenor-cli": {
"name": "@hypercog/sidenor-cli",
"version": "0.0.1",
"extraneous": true,
"dependencies": {
"@faker-js/faker": "^5.5.3",
"@hypercog/utils": "^0.0.1"
}
},
"../utils": {
"name": "@hypercog/utils",
"version": "0.0.1",
"extraneous": true,
"dependencies": {
"@faker-js/faker": "^5.5.3",
"@traceblock/api-client": "^0.2.5",
"commander": "^9.0.0",
"dotenv": "^16.0.0",
"inquirer": "^8.2.0"
}
},
"node_modules/commander": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-9.0.0.tgz",
"integrity": "sha512-JJfP2saEKbQqvW+FI93OYUB4ByV5cizMpFMiiJI8xDbBvQvSkIk0VvQdn1CZ8mqAO8Loq2h0gYTYtDFUZUeERw==",
"engines": {
"node": "^12.20.0 || >=14"
}
}
},
"dependencies": {
"commander": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-9.0.0.tgz",
"integrity": "sha512-JJfP2saEKbQqvW+FI93OYUB4ByV5cizMpFMiiJI8xDbBvQvSkIk0VvQdn1CZ8mqAO8Loq2h0gYTYtDFUZUeERw=="
}
}
}
{
"name": "@hypercog/batch-sim",
"dependencies": {
"@hypercog/utils": "^0.0.1",
"@hypercog/sidenor-cli": "^0.0.1",
"@hypercog/cement-cli": "^0.0.1",
"commander": "^9.0.0"
}
}
......@@ -2,47 +2,9 @@
const inquirer = require("inquirer")
const {
createCLIApp,
loginAndCallAfterwards,
promptAssetSelection
} = require("@hypercog/utils")
const { createCLIApp, loginAndCallAfterwards } = require("@hypercog/utils")
const bidAsset = async (
apiClient,
{ assetId: paramAssetId, quantity, price }
) => {
const biddableAssets = await apiClient.getBiddableAssets()
const assetId = paramAssetId || (await promptAssetSelection(biddableAssets))
let chosenQuantity = quantity
if (!quantity) {
const { inputQuantity } = await inquirer.prompt([
{
type: "number",
name: "inputQuantity",
message: "Choose an ammount of slag"
}
])
chosenQuantity = inputQuantity
}
let chosenPrice = price
if (!quantity) {
const { inputPrice } = await inquirer.prompt([
{
type: "number",
name: "inputPrice",
message: "Provide the offered price"
}
])
chosenPrice = inputPrice
}
await apiClient.bidForAsset(assetId, chosenQuantity, chosenPrice)
console.log("Bid for slag stock", assetId)
}
const { bidAsset } = require("./lib")
const program = createCLIApp()
......@@ -55,3 +17,6 @@ program
.action(loginAndCallAfterwards(bidAsset))
program.parse(process.argv)
// To be reused as a library in @hypercog/batch-sim
module.exports = { bidAsset }
// COPYRIGHT: FUNDACIÓN TECNALIA RESEARCH & INNOVATION, 2022.
const inquirer = require("inquirer")
const { promptAssetSelection } = require("@hypercog/utils")
const bidAsset = async (
apiClient,
{ assetId: paramAssetId, quantity, price }
) => {
const biddableAssets = await apiClient.getBiddableAssets()
const assetId = paramAssetId || (await promptAssetSelection(biddableAssets))
let chosenQuantity = quantity
if (!quantity) {
const { inputQuantity } = await inquirer.prompt([
{
type: "number",
name: "inputQuantity",
message: "Choose an ammount of slag"
}
])
chosenQuantity = inputQuantity
}
let chosenPrice = price
if (!quantity) {
const { inputPrice } = await inquirer.prompt([
{
type: "number",
name: "inputPrice",
message: "Provide the offered price"
}
])
chosenPrice = inputPrice
}
await apiClient.bidForAsset(assetId, chosenQuantity, chosenPrice)
console.log("Bid for slag stock", assetId)
}
// To be reused as a library in @hypercog/batch-sim
module.exports = { bidAsset }
{
"name": "@hypercog/cement-cli",
"version": "0.0.1",
"main": "index.js",
"main": "lib.js",
"dependencies": {
"@hypercog/utils": "^0.0.1"
}
......
{
"packages": ["utils", "sidenor-cli", "cement-cli", "pubadmin-cli"],
"packages": [
"utils",
"sidenor-cli",
"cement-cli",
"pubadmin-cli",
"batch-sim"
],
"version": "0.0.1"
}
// COPYRIGHT: FUNDACIÓN TECNALIA RESEARCH & INNOVATION, 2021.
const faker = require("@faker-js/faker")
const inquirer = require("inquirer")
const { createCLIApp, loginAndCallAfterwards } = require("@hypercog/utils")
const {
createCLIApp,
loginAndCallAfterwards,
createRandomLocation,
promptAssetSelection,
decodeUserId,
TYPES,
STATUSES
} = require("@hypercog/utils")
const createAsset = async (apiClient, { assetName }) => {
const newAsset = await apiClient.createAsset({
id: `asst_${faker.datatype.uuid()}`,
type: TYPES.RESIDUO_PROCESOS_TERMICOS,
units: "kg",
quantity: faker.datatype.number({ max: 5000 }),
fields: {
description: "Escoria",
name: assetName,
status: STATUSES.SLAG,
castingId: 1
},
location: createRandomLocation()
})
console.log("Registered asset", newAsset.id)
}
const editComposition = async (apiClient, { assetId: paramAssetId }) => {
const assetId =
paramAssetId || (await promptAssetSelection(await apiClient.richQuery()))
const previousVersion = await apiClient.getAsset(assetId)
await apiClient.modifyAsset({
id: assetId,
fields: {
...previousVersion.fields,
composition: "comp1"
}
})
console.log("New composition measure for", assetId)
}
const sendToCone = async (apiClient, { assetId: paramAssetId }) => {
const assetId =
paramAssetId ||
(await promptAssetSelection(
await apiClient.richQuery({
fields: {
status: STATUSES.SLAG
}
})
))
const assetToBeSent = await apiClient.getAsset(assetId)
const assetsInCone = await apiClient.richQuery({
fields: {
status: STATUSES.SLAG_BATCH
}
})
const [firstAsset] = assetsInCone
const newConeQuantity =
assetToBeSent.quantity + (firstAsset ? firstAsset.quantity : 0)
const coneAsset = await apiClient.joinAsset(
//`cone_${faker.datatype.uuid()}`,
firstAsset ? [firstAsset.id, assetId] : [assetId],
{ parentsShouldExist: false, bidirectional: true },
{
type: TYPES.RESIDUO_HIERRO_ACERO,
location:
assetsInCone.length === 0
? assetToBeSent.location
: assetsInCone[0].location,
units: "kg",
quantity: newConeQuantity,
fields: {
status: STATUSES.SLAG_BATCH
}
}
)
// Archive individual slag and previous cone
await apiClient.deleteAsset(assetId)
if (assetsInCone.length > 0) await apiClient.deleteAsset(assetsInCone[0].id)
console.log("Moved to cone", coneAsset.id)
}
const sendToStock = async (apiClient, { assetId: paramAssetId }) => {
const assetId =
paramAssetId ||
(await promptAssetSelection(
await apiClient.richQuery({
fields: {
status: STATUSES.SLAG_BATCH
}
})
))
const assetToBeSent = await apiClient.getAsset(assetId)
if (assetToBeSent.fields.status !== STATUSES.SLAG_BATCH) {
console.error("You can only combine a cone with a stock")
return
}
const selectedStockId = await promptAssetSelection(
[
...(await apiClient.richQuery({
fields: {
status: STATUSES.STOCK
}
})),
{
id: "newStock",
type: TYPES.RESIDUO_HIERRO_ACERO,
fields: { name: "New stock", status: STATUSES.STOCK }
}
],
"Select the stock where the cone will be merged"
)
if (!selectedStockId) {
console.error("You must select a stock")
return
}
let previousStock = false
if (selectedStockId !== "newStock") {
previousStock = await apiClient.getAsset(selectedStockId)
}
const newStockQuantity =
assetToBeSent.quantity + (previousStock ? previousStock.quantity : 0)
const newStockAsset = await apiClient.joinAsset(
selectedStockId === "newStock" ? [assetId] : [selectedStockId, assetId],
{ parentsShouldExist: true, bidirectional: true },
{
type: TYPES.RESIDUO_HIERRO_ACERO,
location:
selectedStockId === "newStock"
? assetToBeSent.location
: (
await apiClient.getAsset(selectedStockId)
).location,
units: "kg",
quantity: newStockQuantity,
fields: {
status: STATUSES.STOCK,
name: "Stock"
}
}
)
// Archive individual slag and previous cone
await apiClient.deleteAsset(assetId)
if (selectedStockId !== "newStock")
await apiClient.deleteAsset(selectedStockId)
console.log("Moved to stock", newStockAsset.id)
}
const bidResponse =
action =>
async (apiClient, { assetId: paramAssetId, bidder: paramBidder }) => {
const assetId =
paramAssetId ||
(await promptAssetSelection(
await apiClient.richQuery({
fields: {
status: STATUSES.STOCK
}
})
))
const assetSelected = await apiClient.getAsset(assetId)
if (!assetSelected.fields.bids || assetSelected.fields.bids.length === 0) {
console.error(`There are no active bids to be ${action}ed`)
return
}
let bidder = paramBidder
if (!bidder) {
const { inputBidder } = await inquirer.prompt([
{
type: "list",
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}, price: ${b.price})`,
value: b.bidder
}))
}
])
bidder = inputBidder
}
await apiClient[`${action}Bid`](assetId, bidder.id)
console.log(`Bid from ${bidder.id} ${action}ed for asset ${assetId}`)
}
createAsset,
editComposition,
sendToCone,
sendToStock,
acceptBid,
rejectBid
} = require("./lib")
const program = createCLIApp()
......@@ -247,7 +51,7 @@ program
"--bidder [value]",
"Identifier of the bidder for the provided slag stock"
)
.action(loginAndCallAfterwards(bidResponse("accept")))
.action(loginAndCallAfterwards(acceptBid))
program
.command("bid-reject")
......@@ -257,7 +61,7 @@ program
"--bidder [value]",
"Identifier of the bidder for the provided slag stock"
)
.action(loginAndCallAfterwards(bidResponse("reject")))
.action(loginAndCallAfterwards(rejectBid))
/*program
.command("discard-stock")
......
// COPYRIGHT: FUNDACIÓN TECNALIA RESEARCH & INNOVATION, 2022.
const faker = require("@faker-js/faker")
const inquirer = require("inquirer")
const {
createRandomLocation,
promptAssetSelection,
TYPES,
STATUSES
} = require("@hypercog/utils")
const createAsset = async (apiClient, { assetName }) => {
const newAsset = await apiClient.createAsset({
id: `asst_${faker.datatype.uuid()}`,
type: TYPES.RESIDUO_PROCESOS_TERMICOS,
units: "kg",
quantity: faker.datatype.number({ max: 5000 }),
fields: {
description: "Escoria",
name: assetName,
status: STATUSES.SLAG,
castingId: 1
},
location: createRandomLocation()
})
console.log("Registered asset", newAsset.id)
return newAsset.id
}
const editComposition = async (apiClient, { assetId: paramAssetId }) => {
const assetId =
paramAssetId || (await promptAssetSelection(await apiClient.richQuery()))
const previousVersion = await apiClient.getAsset(assetId)
await apiClient.modifyAsset({
...previousVersion,
id: assetId,
fields: {
...previousVersion.fields,
composition: "comp1"
}
})
console.log("New composition measure for", assetId)
}
const sendToCone = async (apiClient, { assetId: paramAssetId }) => {
const assetId =
paramAssetId ||
(await promptAssetSelection(
await apiClient.richQuery({
fields: {
status: STATUSES.SLAG
}
})
))
const assetToBeSent = await apiClient.getAsset(assetId)
const assetsInCone = await apiClient.richQuery({
fields: {
status: STATUSES.SLAG_BATCH
}
})
const [firstAsset] = assetsInCone
const newConeQuantity =
assetToBeSent.quantity + (firstAsset ? firstAsset.quantity : 0)
const coneAsset = await apiClient.joinAsset(
//`cone_${faker.datatype.uuid()}`,
firstAsset ? [firstAsset.id, assetId] : [assetId],
{ parentsShouldExist: false, bidirectional: true },
{
type: TYPES.RESIDUO_HIERRO_ACERO,
location:
assetsInCone.length === 0
? assetToBeSent.location
: assetsInCone[0].location,
units: "kg",
quantity: newConeQuantity,
fields: {
status: STATUSES.SLAG_BATCH
}
}
)
// Archive individual slag and previous cone
await apiClient.deleteAsset(assetId)
if (assetsInCone.length > 0) await apiClient.deleteAsset(assetsInCone[0].id)
console.log("Moved to cone", coneAsset.id)
return coneAsset.id
}
const sendToStock = async (apiClient, { assetId: paramAssetId }) => {
const assetId =
paramAssetId ||
(await promptAssetSelection(
await apiClient.richQuery({
fields: {
status: STATUSES.SLAG_BATCH
}
})
))
const assetToBeSent = await apiClient.getAsset(assetId)
if (assetToBeSent.fields.status !== STATUSES.SLAG_BATCH) {
console.error("You can only combine a cone with a stock")
return
}
const selectedStockId = await promptAssetSelection(
[
...(await apiClient.richQuery({
fields: {
status: STATUSES.STOCK
}
})),
{
id: "newStock",
type: TYPES.RESIDUO_HIERRO_ACERO,
fields: { name: "New stock", status: STATUSES.STOCK }
}
],
"Select the stock where the cone will be merged"
)
if (!selectedStockId) {
console.error("You must select a stock")
return
}
let previousStock = false
if (selectedStockId !== "newStock") {
previousStock = await apiClient.getAsset(selectedStockId)
}
const newStockQuantity =
assetToBeSent.quantity + (previousStock ? previousStock.quantity : 0)
const newStockAsset = await apiClient.joinAsset(
selectedStockId === "newStock" ? [assetId] : [selectedStockId, assetId],
{ parentsShouldExist: true, bidirectional: true },
{
type: TYPES.RESIDUO_HIERRO_ACERO,
location:
selectedStockId === "newStock"
? assetToBeSent.location
: (
await apiClient.getAsset(selectedStockId)
).location,
units: "kg",
quantity: newStockQuantity,
fields: {
status: STATUSES.STOCK,
name: "Stock"
}
}
)
// Archive individual slag and previous cone
await apiClient.deleteAsset(assetId)
if (selectedStockId !== "newStock")
await apiClient.deleteAsset(selectedStockId)
console.log("Moved to stock", newStockAsset.id)
return newStockAsset.id
}
const bidResponse =
action =>
async (apiClient, { assetId: paramAssetId, bidder: paramBidder }) => {
const assetId =
paramAssetId ||
(await promptAssetSelection(
await apiClient.richQuery({
fields: {
status: STATUSES.STOCK
}
})
))
const assetSelected = await apiClient.getAsset(assetId)
if (!assetSelected.fields.bids || assetSelected.fields.bids.length === 0) {
console.error(`There are no active bids to be ${action}ed`)
return
}
let bidder = paramBidder
if (!bidder) {
const { inputBidder } = await inquirer.prompt([
{
type: "list",
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}, price: ${b.price})`,
value: b.bidder
}))
}
])
bidder = inputBidder
}
await apiClient[`${action}Bid`](assetId, bidder.id)
console.log(`Bid from ${bidder.id} ${action}ed for asset ${assetId}`)
}
const acceptBid = bidResponse("accept")
const rejectBid = bidResponse("reject")
// To be reused as a library in @hypercog/batch-sim
module.exports = {
createAsset,
editComposition,
sendToCone,
sendToStock,
acceptBid,
rejectBid
}
{
"name": "@hypercog/sidenor-cli",
"version": "0.0.1",
"main": "index.js",
"main": "lib.js",
"scripts": {
"sidenor": "func() { node index.js $@; }; func",
"sidenor2": "node index.js --"
......
......@@ -150,6 +150,7 @@ const decodeUserId = userId => {
}
module.exports = {
HypercogApiClient,
createCLIApp,
loginAndCallAfterwards,
createRandomLocation,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment