PK >-K-Kix֟ README.md# Perishable Goods Network > Example business network that shows Sender_Hospitals, receiver_hospitals and couriers defining contracts for the price of perishable goods, based on temperature readings received for shipping containers. The business network defines a contract between Sender_Hospitals and couriers. The contract stipulates that: On receipt of the shipment the courier pays the sender_hospital the unit price x the number of units in the shipment. Shipments that arrive late are free. Shipments that have breached the low temperate threshold have a penalty applied proportional to the magnitude of the breach x a penalty factor. Shipments that have breached the high temperate threshold have a penalty applied proportional to the magnitude of the breach x a penalty factor. This business network defines: **Participants** `Sender_Hospital` `Courier` `Receiver_Hospital` **Assets** `Contract` `Shipment` **Transactions** `TemperatureReading` `ShipmentReceived` `SetupDemo` To test this Business Network Definition in the **Test** tab: Submit a `SetupDemo` transaction: ``` { "$class": "org.acme.SetupDemo" } ``` This transaction populates the Participant Registries with a `Sender_Hospital`, an `Courier` and a `Receiver_Hospital`. The Asset Registries will have a `Contract` asset and a `Shipment` asset. Submit a `TemperatureReading` transaction: ``` { "$class": "org.acme.TemperatureReading", "centigrade": 8, "shipment": "resource:org.acme.Shipment#SHIP_01" } ``` If the temperature reading falls outside the min/max range of the contract, the price received by the sender_hospital will be reduced. You may submit several readings if you wish. Each reading will be aggregated within `SHIP_01` Shipment Asset Registry. Submit a `ShipmentReceived` transaction for `SHIP_01` to trigger the payout to the sender_hospital, based on the parameters of the `CON_01` contract: ``` { "$class": "org.acme.ShipmentReceived", "shipment": "resource:org.acme.Shipment#SHIP_01" } ``` If the date-time of the `ShipmentReceived` transaction is after the `arrivalDateTime` on `CON_01` then the sender_hospital will no receive any payment for the shipment. Congratulations! PK >-Kò5permissions.acl/** * Sample access control list. */ rule Default { description: "Allow all participants access to all resources" participant: "ANY" operation: ALL resource: "org.acme.*" action: ALLOW } rule SystemACL { description: "System ACL to permit all access" participant: "org.hyperledger.composer.system.Participant" operation: ALL resource: "org.hyperledger.composer.system.**" action: ALLOW }PK >-Kmodels/PK >-K[mω< < models/perishable.cto/** * A business network for shipping perishable goods * The cargo is temperature controlled and contracts * can be negociated based on the temperature * readings received for the cargo */ namespace org.acme /** * The type of perishable product being shipped */ enum ProductType { o ORGAN o TISSUE o CELLS o PLASMA o OTHER } /** * The status of a shipment */ enum ShipmentStatus { o CREATED o IN_TRANSIT o ARRIVED } /** * An abstract transaction that is related to a Shipment */ abstract transaction ShipmentTransaction { --> Shipment shipment } /** * An temperature reading for a shipment. E.g. received from a * device within a temperature controlled shipping container */ transaction TemperatureReading extends ShipmentTransaction { o Double centigrade } /** * A notification that a shipment has been received by the * courier and that funds should be transferred from the courier * to the sender_hospital to pay for the shipment. */ transaction ShipmentReceived extends ShipmentTransaction { } /** * A shipment being tracked as an asset on the ledger */ asset Shipment identified by shipmentId { o String shipmentId o ProductType type o ShipmentStatus status o Long itemKind o TemperatureReading[] temperatureReadings optional --> Contract contract } /** * Defines a contract between a Sender_Hospital and an Courier to ship using * a Receiver_Hospital, paying a set unit price. The unit price is multiplied by * a penality factor proportional to the deviation from the min and max * negociated temperatures for the shipment. */ asset Contract identified by contractId { o String contractId --> Sender_Hospital sender_hospital --> Receiver_Hospital receiver_hospital --> Courier courier o DateTime arrivalDateTime o Double constantFactor o Double minTemperature o Double maxTemperature o Double minPenaltyFactor o Double maxPenaltyFactor } /** * A concept for a simple street address */ concept Address { o String city optional o String country o String street optional o String zip optional } /** * An abstract participant type in this business network */ abstract participant Business identified by email { o String email o Address address o Double accountBalance } /** * A Sender_Hospital is a type of participant in the network */ participant Sender_Hospital extends Business { } /** * A Receiver_Hospital is a type of participant in the network */ participant Receiver_Hospital extends Business { } /** * An Courier is a type of participant in the network */ participant Courier extends Business { } /** * JUST FOR INITIALIZING A DEMO */ transaction SetupDemo { } PK >-Klib/PK >-Kׅ8% % lib/logic.js/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * A shipment has been received by an courier * @param {org.acme.ShipmentReceived} shipmentReceived - the ShipmentReceived transaction * @transaction */ function trustiness(shipmentReceived) { var contract = shipmentReceived.shipment.contract; var shipment = shipmentReceived.shipment; var trustiness = contract.constantFactor * shipment.itemKind; sender_hospital.accountBalance = 0; courier.accountBalance = 0; receiver_hospital.accountBalance = 0; console.log('Received at: ' + shipmentReceived.timestamp); console.log('Contract arrivalDateTime: ' + contract.arrivalDateTime); // set the status of the shipment shipment.status = 'ARRIVED'; // if the shipment did not arrive on time the payout is zero if (shipmentReceived.timestamp > contract.arrivalDateTime) { trustiness = 0; console.log('Late shipment'); } else { // find the lowest temperature reading if (shipment.temperatureReadings) { // sort the temperatureReadings by centigrade shipment.temperatureReadings.sort(function (a, b) { return (a.centigrade - b.centigrade); }); var lowestReading = shipment.temperatureReadings[0]; var highestReading = shipment.temperatureReadings[shipment.temperatureReadings.length - 1]; var penalty = 0; console.log('Lowest temp reading: ' + lowestReading.centigrade); console.log('Highest temp reading: ' + highestReading.centigrade); // does the lowest temperature violate the contract? if (lowestReading.centigrade < contract.minTemperature) { penalty += (contract.minTemperature - lowestReading.centigrade) * contract.minPenaltyFactor; console.log('Min temp penalty: ' + penalty); } // does the highest temperature violate the contract? if (highestReading.centigrade > contract.maxTemperature) { penalty += (highestReading.centigrade - contract.maxTemperature) * contract.maxPenaltyFactor; console.log('Max temp penalty: ' + penalty); } // apply any penalities trustiness -= (penalty * shipment.itemKind); if (trustiness < 0) { trustiness = 0; } } } console.log('Payout: ' + trustiness); contract.sender_hospital.accountBalance += trustiness; contract.courier.accountBalance -= trustiness; console.log('Sender_Hospital: ' + contract.sender_hospital.$identifier + ' new balance: ' + contract.sender_hospital.accountBalance); console.log('Courier: ' + contract.courier.$identifier + ' new balance: ' + contract.courier.accountBalance); return getParticipantRegistry('org.acme.Sender_Hospital') .then(function (Sender_HospitalRegistry) { // update the sender_hospital's balance return Sender_HospitalRegistry.update(contract.sender_hospital); }) .then(function () { return getParticipantRegistry('org.acme.Courier'); }) .then(function (courierRegistry) { // update the courier's balance return courierRegistry.update(contract.courier); }) .then(function () { return getAssetRegistry('org.acme.Shipment'); }) .then(function (shipmentRegistry) { // update the state of the shipment return shipmentRegistry.update(shipment); }); } /** * A temperature reading has been received for a shipment * @param {org.acme.TemperatureReading} temperatureReading - the TemperatureReading transaction * @transaction */ function temperatureReading(temperatureReading) { var shipment = temperatureReading.shipment; console.log('Adding temperature ' + temperatureReading.centigrade + ' to shipment ' + shipment.$identifier); if (shipment.temperatureReadings) { shipment.temperatureReadings.push(temperatureReading); } else { shipment.temperatureReadings = [temperatureReading]; } return getAssetRegistry('org.acme.Shipment') .then(function (shipmentRegistry) { // add the temp reading to the shipment return shipmentRegistry.update(shipment); }); } /** * Initialize some test assets and participants useful for running a demo. * @param {org.acme.SetupDemo} setupDemo - the SetupDemo transaction * @transaction */ function setupDemo(setupDemo) { var factory = getFactory(); var NS = 'org.acme'; // create the sender_hospital var sender_hospital = factory.newResource(NS, 'Sender_Hospital', 'SENDER'); var Sender_HospitalAddress = factory.newConcept(NS, 'Address'); Sender_HospitalAddress.country = 'USA'; sender_hospital.address = Sender_HospitalAddress; sender_hospital.accountBalance = 0; // create the courier var courier = factory.newResource(NS, 'Courier', 'COURIER'); var courierAddress = factory.newConcept(NS, 'Address'); courierAddress.country = 'UK'; courier.address = courierAddress; courier.accountBalance = 0; // create the receiver_hospital var receiver_hospital = factory.newResource(NS, 'Receiver_Hospital', 'RECEIVER'); var receiver_hospitalAddress = factory.newConcept(NS, 'Address'); receiver_hospitalAddress.country = 'Panama'; receiver_hospital.address = receiver_hospitalAddress; receiver_hospital.accountBalance = 0; // create the contract var contract = factory.newResource(NS, 'Contract', 'CON_01 '); contract.sender_hospital = factory.newRelationship(NS, 'Sender_Hospital', 'SENDER'); contract.courier = factory.newRelationship(NS, 'Courier', 'COURIER'); contract.receiver_hospital = factory.newRelationship(NS, 'Receiver_Hospital', 'RECEIVER'); var tomorrow = setupDemo.timestamp; tomorrow.setDate(tomorrow.getDate() + 1); contract.arrivalDateTime = tomorrow; // the shipment has to arrive tomorrow contract.constantFactor = 10; // pay 50 cents per unit contract.minTemperature = 5; // min temperature for the cargo contract.maxTemperature = 15; // max temperature for the cargo contract.minPenaltyFactor = 0.2; // we reduce the price by 20 cents for every degree below the min temp contract.maxPenaltyFactor = 0.1; // we reduce the price by 10 cents for every degree above the max temp // create the shipment var shipment = factory.newResource(NS, 'Shipment', 'SHIP_01'); shipment.type = 'ORGAN'; shipment.status = 'IN_TRANSIT'; shipment.itemKind = 9; shipment.contract = factory.newRelationship(NS, 'Contract', 'CON_01'); return getParticipantRegistry(NS + '.Sender_Hospital') .then(function (Sender_HospitalRegistry) { // add the Sender_Hospitals return Sender_HospitalRegistry.addAll([sender_hospital]); }) .then(function() { return getParticipantRegistry(NS + '.Courier'); }) .then(function(courierRegistry) { // add the couriers return courierRegistry.addAll([courier]); }) .then(function() { return getParticipantRegistry(NS + '.Receiver_Hospital'); }) .then(function(receiver_hospitalRegistry) { // add the receiver_hospitals return receiver_hospitalRegistry.addAll([receiver_hospital]); }) .then(function() { return getAssetRegistry(NS + '.Contract'); }) .then(function(contractRegistry) { // add the contracts return contractRegistry.addAll([contract]); }) .then(function() { return getAssetRegistry(NS + '.Shipment'); }) .then(function(shipmentRegistry) { // add the shipments return shipmentRegistry.addAll([shipment]); }); // create another shipment var shipment = factory.newResource(NS, 'Shipment', 'SHIP_02'); shipment.type = 'TISSUE'; shipment.status = 'IN_TRANSIT'; shipment.itemKind = 10; shipment.contract = factory.newRelationship(NS, 'Contract', 'CON_01'); return getParticipantRegistry(NS + '.Sender_Hospital') .then(function (Sender_HospitalRegistry) { // add the Sender_Hospitals return Sender_HospitalRegistry.addAll([sender_hospital]); }) .then(function() { return getParticipantRegistry(NS + '.Courier'); }) .then(function(courierRegistry) { // add the couriers return courierRegistry.addAll([courier]); }) .then(function() { return getParticipantRegistry(NS + '.Receiver_Hospital'); }) .then(function(receiver_hospitalRegistry) { // add the receiver_hospitals return receiver_hospitalRegistry.addAll([receiver_hospital]); }) .then(function() { return getAssetRegistry(NS + '.Contract'); }) .then(function(contractRegistry) { // add the contracts return contractRegistry.addAll([contract]); }) .then(function() { return getAssetRegistry(NS + '.Shipment'); }) .then(function(shipmentRegistry) { // add the shipments return shipmentRegistry.addAll([shipment]); }); } PK >-K-Kix֟ README.mdPK >-Kò5permissions.aclPK >-Kumodels/PK >-K[mω< < models/perishable.ctoPK >-K lib/PK >-Kׅ8% % +lib/logic.jsPKz;