agreement-design-prototype 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9b8b0ee3c86692db54196238eb53be579e4b3f8b2bc24dee7a6dc67ca89d7068
4
+ data.tar.gz: 511c0e41fd44633b68f48efc18cb00a29b7692db569eb3ef112adcb6b7392e87
5
+ SHA512:
6
+ metadata.gz: 7b167ee34fc849c9d4e4470a93ef8b21283f50c8e2059a5e862e1b1239255122cf605eb397e59da8b308b14138237f632a591330b8f3c72cca570e6afa2de3f8
7
+ data.tar.gz: feeca74a1f808b7663062910a89719db1c551d9a57b100a4f9088219e17b3141bb6eb88a0c6952bb958c9d75a5371c36279a7edc0223596b16c6bfe37bcc33f9
data/README.md ADDED
@@ -0,0 +1,17 @@
1
+
2
+ # Agreement Design
3
+
4
+ this is a prototype for an agreement design system. In line with
5
+ [ADR 0010 - use shared drfinition of cmp agreement](https://github.com/Crown-Commercial-Service/CCS-Architecture-Decision-Records/blob/master/doc/adr/0010-use-shared-definition-of-cmp-agreement-when-building-all-cmp-services.md) we want to define the metamodel
6
+ for Commertial Agreements and supporting data in a common way, so all
7
+ services, interfaces and data types are consistent.
8
+
9
+ This prototype
10
+
11
+ - defines a common metamodel
12
+ - models a number of agreement categories (as alpha prototypes, not as definitive representations)
13
+ - produces API definitions such as Swagger
14
+ - possibly produces domain objects (though these should probably be created from API specs)
15
+ - possibly produces data domain descriptions, e.g. Ruby-on-Rails schema records
16
+
17
+
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ task default: %w[test build]
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << "test"
7
+ t.test_files = FileList['test/*.rb']
8
+ t.verbose = true
9
+ end
10
+
11
+ task :build do
12
+ ruby "build/build_models.rb"
13
+ end
14
+
15
+ task :deploy do
16
+ `gem build agreement-design.gemspec`
17
+ `gem push *.gem`
18
+ end
19
+
20
+ require 'rake/clean'
21
+
22
+ CLEAN.include FileList['out', 'gen', '*.gem']
@@ -0,0 +1,18 @@
1
+ # agreement-design.gemspec
2
+ Gem::Specification.new do |spec|
3
+ spec.name = "agreement-design-prototype"
4
+ spec.version = "0.0.3"
5
+ spec.authors = ["CCS"]
6
+ spec.homepage = 'https://github.com/Crown-Commercial-Service/cmp-design-prototype/agreement-design'
7
+ spec.email = ["rubygems@humphries.tech"]
8
+ spec.description = %q{A prototype of techniques to manage agreement specifications.}
9
+ spec.summary = %q{Agreement prototype .}
10
+ spec.license = "MIT"
11
+
12
+ spec.files = Dir["./**/*"]
13
+ spec.executables = Dir["./bin/*"]
14
+ spec.test_files = Dir["./test/**/*"]
15
+ spec.require_paths = ["src", "model"]
16
+
17
+ spec.add_development_dependency "rake", "12.3.0"
18
+ end
@@ -0,0 +1,16 @@
1
+ require_relative "../src/diagram"
2
+ require_relative "../model/agreement"
3
+
4
+ path= File.join(File.dirname(__FILE__), '../', "gen")
5
+
6
+ # TODO: escape the path somehow so it works when called direct. Works ok from Rake
7
+ diagram= Diagram.new( path, "data_model")
8
+ diagram.describe Category, Parties
9
+
10
+ doc= Doc.new( path, "metamodel")
11
+ doc.document_metamodel Category, Parties
12
+
13
+ doc= Doc.new( path, "frameworks")
14
+ require_relative '../model/fm'
15
+ doc.document Category::FM
16
+
@@ -0,0 +1,34 @@
1
+ strict digraph {
2
+ subgraph cluster_Category {
3
+ node [shape=plaintext margin=0];
4
+ label=Category;
5
+ "ItemParameter" [label=<<table BORDER="1" CELLBORDER="0" CELLSPACING="0"><TH><TD>ItemParameter</TD></TH><TR><TD ALIGN="LEFT">-id </TD></TR><TR><TD ALIGN="LEFT">-detail </TD></TR><TR><TD ALIGN="LEFT">-keyword [*]</TD></TR><TR><TD ALIGN="LEFT">-valueMin </TD></TR><TR><TD ALIGN="LEFT">-valueMax </TD></TR><TR><TD ALIGN="LEFT">-type </TD></TR><TR><TD ALIGN="LEFT">-standard </TD></TR><TR><TD ALIGN="LEFT">-reference </TD></TR></table>>];
6
+ "Agreement" [label=<<table BORDER="1" CELLBORDER="0" CELLSPACING="0"><TH><TD>Agreement</TD></TH><TR><TD ALIGN="LEFT">-id </TD></TR><TR><TD ALIGN="LEFT">-item_params [*]</TD></TR><TR><TD ALIGN="LEFT">-version </TD></TR><TR><TD ALIGN="LEFT">-start_date </TD></TR><TR><TD ALIGN="LEFT">-end_date </TD></TR></table>>];
7
+ "Framework" [label=<<table BORDER="1" CELLBORDER="0" CELLSPACING="0"><TH><TD>Framework</TD></TH><TR><TD ALIGN="LEFT">-fwk_id </TD></TR></table>>];
8
+ "Lot" [label=<<table BORDER="1" CELLBORDER="0" CELLSPACING="0"><TH><TD>Lot</TD></TH><TR><TD ALIGN="LEFT">-fwk_id </TD></TR></table>>];
9
+ "Item" [label=<<table BORDER="1" CELLBORDER="0" CELLSPACING="0"><TH><TD>Item</TD></TH><TR><TD ALIGN="LEFT">-id </TD></TR><TR><TD ALIGN="LEFT">-params </TD></TR><TR><TD ALIGN="LEFT">-description </TD></TR><TR><TD ALIGN="LEFT">-value </TD></TR></table>>];
10
+ "Catalogue" [label=<<table BORDER="1" CELLBORDER="0" CELLSPACING="0"><TH><TD>Catalogue</TD></TH><TR><TD ALIGN="LEFT">-id </TD></TR><TR><TD ALIGN="LEFT">-items [*]</TD></TR><TR><TD ALIGN="LEFT">-agreement_id </TD></TR></table>>];
11
+ "Offer" [label=<<table BORDER="1" CELLBORDER="0" CELLSPACING="0"><TH><TD>Offer</TD></TH><TR><TD ALIGN="LEFT">-id </TD></TR><TR><TD ALIGN="LEFT">-item_id </TD></TR><TR><TD ALIGN="LEFT">-catalogue_id </TD></TR><TR><TD ALIGN="LEFT">-supplier_id </TD></TR><TR><TD ALIGN="LEFT">-description </TD></TR></table>>];
12
+ "Award" [label=<<table BORDER="1" CELLBORDER="0" CELLSPACING="0"><TH><TD>Award</TD></TH><TR><TD ALIGN="LEFT">-buyer_id </TD></TR></table>>];
13
+ }
14
+ subgraph cluster_Parties {
15
+ node [shape=plaintext margin=0];
16
+ label=Parties;
17
+ "Party" [label=<<table BORDER="1" CELLBORDER="0" CELLSPACING="0"><TH><TD>Party</TD></TH><TR><TD ALIGN="LEFT">-id </TD></TR></table>>];
18
+ "Supplier" [label=<<table BORDER="1" CELLBORDER="0" CELLSPACING="0"><TH><TD>Supplier</TD></TH></table>>];
19
+ "Buyer" [label=<<table BORDER="1" CELLBORDER="0" CELLSPACING="0"><TH><TD>Buyer</TD></TH></table>>];
20
+ }
21
+ "Agreement" -> "ItemParameter" [label="{contains} item_params" arrowhead = "none" arrowtail = "diamond" ];
22
+ "Framework" -> "Agreement" [label="extends" arrowhead = "none" arrowtail = "normal" ];
23
+ "Lot" -> "Framework" [label="fwk_id" arrowhead = "open" arrowtail = "none" ];
24
+ "Lot" -> "Agreement" [label="extends" arrowhead = "none" arrowtail = "normal" ];
25
+ "Item" -> "ItemParameter" [label="params" arrowhead = "open" arrowtail = "none" ];
26
+ "Catalogue" -> "Item" [label="{contains} items" arrowhead = "none" arrowtail = "diamond" ];
27
+ "Catalogue" -> "Agreement" [label="agreement_id" arrowhead = "open" arrowtail = "none" ];
28
+ "Offer" -> "Item" [label="item_id" arrowhead = "open" arrowtail = "none" ];
29
+ "Offer" -> "Catalogue" [label="catalogue_id" arrowhead = "open" arrowtail = "none" ];
30
+ "Offer" -> "Supplier" [label="supplier_id" arrowhead = "open" arrowtail = "none" ];
31
+ "Award" -> "Buyer" [label="buyer_id" arrowhead = "open" arrowtail = "none" ];
32
+ "Supplier" -> "Party" [label="extends" arrowhead = "none" arrowtail = "normal" ];
33
+ "Buyer" -> "Party" [label="extends" arrowhead = "none" arrowtail = "normal" ];
34
+ }
@@ -0,0 +1,19 @@
1
+ # Category: FM
2
+ ## framework
3
+ ### framework RM123
4
+ - id RM123
5
+ - fwk_id RM123
6
+ - version 1.0.0
7
+ ## lot
8
+ ### lot 1
9
+ - id 1
10
+ - fwk_id RM123
11
+ #### item_params 1
12
+ - id 1
13
+ - valueMin 0
14
+ - valueMax 100
15
+ - keyword.1 thing
16
+ - keyword.2 thing2
17
+ ### lot 2
18
+ - id 2
19
+ - fwk_id RM123
@@ -0,0 +1,98 @@
1
+ # Category
2
+ ## ItemParameter
3
+ Defines the meaning of Items in Catalogues
4
+
5
+ |attribute|type|multiplicity|description|
6
+ |---------|----|------------|-----------|
7
+ |id|String|1||
8
+ |detail|String|1||
9
+ |keyword|String|*||
10
+ |valueMin|String|1||
11
+ |valueMax|String|1||
12
+ |type|String|1|One of: String, Currency, Location, Amount|
13
+ |standard|String|1|which standard defines the item type, such as UBL2.1|
14
+ |reference|String|1|reference within standard, such as UBL2.1|
15
+ ## Agreement
16
+ General definition of Commercial Agreements
17
+
18
+ |attribute|type|multiplicity|description|
19
+ |---------|----|------------|-----------|
20
+ |id|String|1||
21
+ |item_params|Category::ItemParameter|*||
22
+ |version|String|1|semantic version id of the form X.Y.Z|
23
+ |start_date|Date|1||
24
+ |end_date|Date|1||
25
+ ## Framework extends Category::Agreement
26
+ A kind of Framework used for calloffs, composed of Lots
27
+
28
+ |attribute|type|multiplicity|description|
29
+ |---------|----|------------|-----------|
30
+ |id|String|1||
31
+ |item_params|Category::ItemParameter|*||
32
+ |version|String|1|semantic version id of the form X.Y.Z|
33
+ |start_date|Date|1||
34
+ |end_date|Date|1||
35
+ |fwk_id|String|1|Such as the RM number|
36
+ ## Lot extends Category::Agreement
37
+
38
+
39
+ |attribute|type|multiplicity|description|
40
+ |---------|----|------------|-----------|
41
+ |id|String|1||
42
+ |item_params|Category::ItemParameter|*||
43
+ |version|String|1|semantic version id of the form X.Y.Z|
44
+ |start_date|Date|1||
45
+ |end_date|Date|1||
46
+ |fwk_id|String|1|Part of framework|
47
+ ## Item
48
+ Something offered to a buyer as part of a contract.Items are defined in Catalogues.
49
+
50
+ |attribute|type|multiplicity|description|
51
+ |---------|----|------------|-----------|
52
+ |id|String|1|Item id, possibly a concatentation of the standard (in params) and the catalogue and an incrementatl id?|
53
+ |params|String|1||
54
+ |description|String|1||
55
+ |value|String|1||
56
+ ## Catalogue
57
+ A collection of items that can be bought via an Agreement.
58
+
59
+ |attribute|type|multiplicity|description|
60
+ |---------|----|------------|-----------|
61
+ |id|String|1||
62
+ |items|Category::Item|*||
63
+ |agreement_id|String|1||
64
+ ## Offer
65
+
66
+
67
+ |attribute|type|multiplicity|description|
68
+ |---------|----|------------|-----------|
69
+ |id|String|1|Offer id, probably a UUID|
70
+ |item_id|String|1||
71
+ |catalogue_id|String|1||
72
+ |supplier_id|String|1||
73
+ |description|String|1|Description of the offer|
74
+ ## Award
75
+
76
+
77
+ |attribute|type|multiplicity|description|
78
+ |---------|----|------------|-----------|
79
+ |buyer_id|String|1||
80
+ # Parties
81
+ ## Party
82
+
83
+
84
+ |attribute|type|multiplicity|description|
85
+ |---------|----|------------|-----------|
86
+ |id|String|1|UUID or Salesforce ID?|
87
+ ## Supplier extends Parties::Party
88
+
89
+
90
+ |attribute|type|multiplicity|description|
91
+ |---------|----|------------|-----------|
92
+ |id|String|1|UUID or Salesforce ID?|
93
+ ## Buyer extends Parties::Party
94
+
95
+
96
+ |attribute|type|multiplicity|description|
97
+ |---------|----|------------|-----------|
98
+ |id|String|1|UUID or Salesforce ID?|
Binary file
@@ -0,0 +1,69 @@
1
+ require_relative '../src/data_model'
2
+ require_relative 'party'
3
+ require 'date'
4
+ include DataModel
5
+
6
+ domain :Category do
7
+
8
+ datatype(:ItemParameter,
9
+ description: "Defines the meaning of Items in Catalogues") {
10
+ attribute :id, String
11
+ attribute :detail, String
12
+ attribute :keyword, String, ZERO_TO_MANY
13
+ attribute :valueMin, String
14
+ attribute :valueMax, String
15
+ attribute :type, String, "One of: String, Currency, Location, Amount"
16
+ attribute :standard, String, "which standard defines the item type, such as UBL2.1"
17
+ attribute :reference, String, "reference within standard, such as UBL2.1"
18
+ }
19
+
20
+ datatype(:Agreement,
21
+ description: "General definition of Commercial Agreements") {
22
+ attribute :id, String
23
+ attribute :item_params, Category::ItemParameter, ZERO_TO_MANY, links: Category::ItemParameter
24
+ attribute :version, String, "semantic version id of the form X.Y.Z"
25
+ attribute :start_date, Date
26
+ attribute :end_date, Date
27
+ }
28
+
29
+ datatype(:Framework, extends: Category::Agreement,
30
+ description: "A kind of Framework used for calloffs, composed of Lots") {
31
+ attribute :fwk_id, String, "Such as the RM number"
32
+ }
33
+
34
+ datatype(:Lot, extends: Category::Agreement) {
35
+ attribute :fwk_id, String, "Part of framework", links: Category::Framework
36
+ }
37
+
38
+ datatype(:Item,
39
+ description: "Something offered to a buyer as part of a contract."\
40
+ "Items are defined in Catalogues.") {
41
+ attribute :id, String, "Item id, possibly a concatentation of the standard (in params) and the catalogue and an incrementatl id?"
42
+ attribute :params, String, links: Category::ItemParameter
43
+ attribute :description, String
44
+ attribute :value, String
45
+ }
46
+
47
+ datatype(:Catalogue,
48
+ description: "A collection of items that can be bought via an Agreement.") {
49
+ attribute :id, String
50
+ attribute :items, Category::Item, ZERO_TO_MANY, links: Category::Item
51
+ attribute :agreement_id, String, links: Category::Agreement
52
+ }
53
+
54
+ datatype(:Offer,
55
+ description: "") {
56
+ attribute :id, String, "Offer id, probably a UUID"
57
+ attribute :item_id, String, links: Category::Item
58
+ attribute :catalogue_id, String, links: Category::Catalogue
59
+ attribute :supplier_id, String, links: Parties::Supplier
60
+ attribute :description, String, "Description of the offer"
61
+ }
62
+
63
+ datatype(:Award,
64
+ description: "") {
65
+ attribute :buyer_id, String, links: Parties::Buyer
66
+ }
67
+
68
+ end
69
+
data/model/fm.rb ADDED
@@ -0,0 +1,33 @@
1
+ require_relative 'agreement'
2
+
3
+ Category.new :FM do
4
+
5
+ FM_ID = "RM123"
6
+ framework do
7
+ id FM_ID
8
+ fwk_id FM_ID
9
+ version "1.0.0"
10
+ end
11
+
12
+ lot do
13
+ id "1"; fwk_id FM_ID
14
+ item_params do
15
+ id 1; valueMin 0; valueMax 100
16
+ keyword :thing
17
+ keyword :thing2
18
+ end
19
+ item_params do
20
+ id 2; valueMin 22; valueMax 33
21
+ end
22
+ end
23
+
24
+ lot do
25
+ id "2"
26
+ fwk_id FM_ID
27
+ end
28
+
29
+ end
30
+
31
+ puts Category::FM.contents[:lot]
32
+
33
+
data/model/party.rb ADDED
@@ -0,0 +1,17 @@
1
+ require_relative '../src/data_model'
2
+ include DataModel
3
+
4
+ domain :Parties do
5
+
6
+ datatype :Party do
7
+ attribute :id, String, "UUID or Salesforce ID?"
8
+ end
9
+
10
+ datatype :Supplier, extends: Parties::Party do
11
+
12
+ end
13
+
14
+ datatype :Buyer, extends: Parties::Party do
15
+
16
+ end
17
+ end
@@ -0,0 +1,34 @@
1
+ strict digraph {
2
+ subgraph cluster_Category {
3
+ node [shape=plaintext margin=0];
4
+ label=Category;
5
+ "ItemParameter" [label=<<table BORDER="1" CELLBORDER="0" CELLSPACING="0"><TH><TD>ItemParameter</TD></TH><TR><TD ALIGN="LEFT">-id </TD></TR><TR><TD ALIGN="LEFT">-detail </TD></TR><TR><TD ALIGN="LEFT">-keyword [*]</TD></TR><TR><TD ALIGN="LEFT">-valueMin </TD></TR><TR><TD ALIGN="LEFT">-valueMax </TD></TR><TR><TD ALIGN="LEFT">-type </TD></TR><TR><TD ALIGN="LEFT">-standard </TD></TR><TR><TD ALIGN="LEFT">-reference </TD></TR></table>>];
6
+ "Agreement" [label=<<table BORDER="1" CELLBORDER="0" CELLSPACING="0"><TH><TD>Agreement</TD></TH><TR><TD ALIGN="LEFT">-id </TD></TR><TR><TD ALIGN="LEFT">-item_params [*]</TD></TR><TR><TD ALIGN="LEFT">-version </TD></TR><TR><TD ALIGN="LEFT">-start_date </TD></TR><TR><TD ALIGN="LEFT">-end_date </TD></TR></table>>];
7
+ "Framework" [label=<<table BORDER="1" CELLBORDER="0" CELLSPACING="0"><TH><TD>Framework</TD></TH><TR><TD ALIGN="LEFT">-fwk_id </TD></TR></table>>];
8
+ "Lot" [label=<<table BORDER="1" CELLBORDER="0" CELLSPACING="0"><TH><TD>Lot</TD></TH><TR><TD ALIGN="LEFT">-fwk_id </TD></TR></table>>];
9
+ "Item" [label=<<table BORDER="1" CELLBORDER="0" CELLSPACING="0"><TH><TD>Item</TD></TH><TR><TD ALIGN="LEFT">-id </TD></TR><TR><TD ALIGN="LEFT">-params </TD></TR><TR><TD ALIGN="LEFT">-description </TD></TR><TR><TD ALIGN="LEFT">-value </TD></TR></table>>];
10
+ "Catalogue" [label=<<table BORDER="1" CELLBORDER="0" CELLSPACING="0"><TH><TD>Catalogue</TD></TH><TR><TD ALIGN="LEFT">-id </TD></TR><TR><TD ALIGN="LEFT">-items [*]</TD></TR><TR><TD ALIGN="LEFT">-agreement_id </TD></TR></table>>];
11
+ "Offer" [label=<<table BORDER="1" CELLBORDER="0" CELLSPACING="0"><TH><TD>Offer</TD></TH><TR><TD ALIGN="LEFT">-id </TD></TR><TR><TD ALIGN="LEFT">-item_id </TD></TR><TR><TD ALIGN="LEFT">-catalogue_id </TD></TR><TR><TD ALIGN="LEFT">-supplier_id </TD></TR><TR><TD ALIGN="LEFT">-description </TD></TR></table>>];
12
+ "Award" [label=<<table BORDER="1" CELLBORDER="0" CELLSPACING="0"><TH><TD>Award</TD></TH><TR><TD ALIGN="LEFT">-buyer_id </TD></TR></table>>];
13
+ }
14
+ subgraph cluster_Parties {
15
+ node [shape=plaintext margin=0];
16
+ label=Parties;
17
+ "Party" [label=<<table BORDER="1" CELLBORDER="0" CELLSPACING="0"><TH><TD>Party</TD></TH><TR><TD ALIGN="LEFT">-id </TD></TR></table>>];
18
+ "Supplier" [label=<<table BORDER="1" CELLBORDER="0" CELLSPACING="0"><TH><TD>Supplier</TD></TH></table>>];
19
+ "Buyer" [label=<<table BORDER="1" CELLBORDER="0" CELLSPACING="0"><TH><TD>Buyer</TD></TH></table>>];
20
+ }
21
+ "Agreement" -> "ItemParameter" [label="{contains} item_params" arrowhead = "none" arrowtail = "diamond" ];
22
+ "Framework" -> "Agreement" [label="extends" arrowhead = "none" arrowtail = "normal" ];
23
+ "Lot" -> "Framework" [label="fwk_id" arrowhead = "open" arrowtail = "none" ];
24
+ "Lot" -> "Agreement" [label="extends" arrowhead = "none" arrowtail = "normal" ];
25
+ "Item" -> "ItemParameter" [label="params" arrowhead = "open" arrowtail = "none" ];
26
+ "Catalogue" -> "Item" [label="{contains} items" arrowhead = "none" arrowtail = "diamond" ];
27
+ "Catalogue" -> "Agreement" [label="agreement_id" arrowhead = "open" arrowtail = "none" ];
28
+ "Offer" -> "Item" [label="item_id" arrowhead = "open" arrowtail = "none" ];
29
+ "Offer" -> "Catalogue" [label="catalogue_id" arrowhead = "open" arrowtail = "none" ];
30
+ "Offer" -> "Supplier" [label="supplier_id" arrowhead = "open" arrowtail = "none" ];
31
+ "Award" -> "Buyer" [label="buyer_id" arrowhead = "open" arrowtail = "none" ];
32
+ "Supplier" -> "Party" [label="extends" arrowhead = "none" arrowtail = "normal" ];
33
+ "Buyer" -> "Party" [label="extends" arrowhead = "none" arrowtail = "normal" ];
34
+ }
@@ -0,0 +1,19 @@
1
+ # Category: FM
2
+ ## framework
3
+ ### framework RM123
4
+ - id RM123
5
+ - fwk_id RM123
6
+ - version 1.0.0
7
+ ## lot
8
+ ### lot 1
9
+ - id 1
10
+ - fwk_id RM123
11
+ #### item_params 1
12
+ - id 1
13
+ - valueMin 0
14
+ - valueMax 100
15
+ - keyword.1 thing
16
+ - keyword.2 thing2
17
+ ### lot 2
18
+ - id 2
19
+ - fwk_id RM123
Binary file
data/src/data_model.rb ADDED
@@ -0,0 +1,165 @@
1
+ module DataModel
2
+
3
+ SINGLE = 1..1
4
+ ONE_TO_MANY = 1..-1
5
+ ZERO_TO_MANY = 0..-1
6
+
7
+ class DataType
8
+
9
+ class << self
10
+
11
+ def init(name, domain, extends, description)
12
+ @attributes = {}
13
+ @typename = name
14
+ @domain = domain
15
+ @extends = extends
16
+ @description = description
17
+
18
+ self.define_singleton_method :domain do
19
+ @domain
20
+ end
21
+ self.define_singleton_method :description do
22
+ @description
23
+ end
24
+
25
+ self.define_singleton_method(:attributes) do |inherited=true|
26
+ if inherited && self.superclass.respond_to?(:attributes)
27
+ self.superclass.attributes.merge @attributes
28
+ else
29
+ @attributes
30
+ end
31
+ end
32
+
33
+ end
34
+
35
+ def typename
36
+ @typename
37
+ end
38
+
39
+ def extends
40
+ @extends < DataType ? @extends.name : nil
41
+ end
42
+
43
+ def attribute(name, type, *args, multiplicity: SINGLE, description: "", links: nil)
44
+ options = {:multiplicity => multiplicity, :description => description, :name => name, :type => type}
45
+ if links
46
+ options[:links]= links
47
+ end
48
+
49
+ for opt in args
50
+ if opt.is_a? Range
51
+ options[:multiplicity] = opt
52
+ elsif opt.is_a? String
53
+ options[:description] = opt
54
+ else
55
+ raise "optional arguments should be string (description), or range\n " << opt.to_s
56
+ end
57
+ end
58
+ @attributes[name] = options
59
+ end
60
+
61
+ end
62
+
63
+ attr_reader :name, :attributes
64
+
65
+ def initialize(name)
66
+ @name = name
67
+ @attributes = {}
68
+ end
69
+
70
+
71
+ def method_missing(sym, *args, &block)
72
+ if (args.length==0) && @attributes[sym]
73
+ return @attributes[sym]
74
+ end
75
+ if self.class.attributes[sym]
76
+ if self.class.attributes[sym][:multiplicity] != SINGLE
77
+ @attributes[sym] = [] unless @attributes[sym]
78
+ @attributes[sym] << valueof(sym, *args, &block)
79
+ else
80
+ @attributes[sym] = valueof(sym, *args, &block)
81
+ end
82
+ return @attributes[sym]
83
+ end
84
+ puts "Warning: unknown attribute #{sym}?"
85
+ end
86
+
87
+ def to_s
88
+ "#{self.class.typename} #{self.name} #{@attributes}"
89
+ end
90
+
91
+ private
92
+
93
+ def valueof(sym, *args, &block)
94
+ if self.class.attributes[sym][:type] < DataType
95
+ at = self.class.attributes[sym][:type].new(self.class.attributes[sym][:name])
96
+ if nil == block
97
+ raise "Need a block for a nested type #{sym}"
98
+ end
99
+ at.instance_exec &block
100
+ return at
101
+ else
102
+ args[0]
103
+ end
104
+ end
105
+
106
+ end
107
+
108
+ class Domain
109
+
110
+ class << self
111
+ def datatype(name, extends: DataType, description: "", &block)
112
+ @types = {} unless instance_variable_defined? :@types
113
+ if extends.class != Class
114
+ extends = @types.fetch(extends, DataType)
115
+ end
116
+ type = self.const_set name, Class.new(extends)
117
+ # puts "defined #{type} from #{name} on #{self }"
118
+ @types[name] = type
119
+ self.define_singleton_method(:types) {@types}
120
+ dom = self
121
+ type.instance_exec do
122
+ init name, dom, extends, description
123
+ end
124
+ type.instance_exec &block
125
+ type
126
+ end
127
+
128
+ end
129
+
130
+ attr_reader :contents, :name
131
+
132
+ def initialize name, &block
133
+ @contents = {}
134
+ @name = name
135
+ self.class.const_set name, self
136
+ self.instance_exec &block
137
+ end
138
+
139
+ def method_missing(sym, &block)
140
+ # create a new datatype for each matching type name, and run its block
141
+ for t in self.class.types.values
142
+ if t.typename.downcase == sym
143
+ decl = t.new(sym)
144
+ @contents[sym] = [] unless @contents[sym]
145
+ @contents[sym] << decl
146
+ decl.instance_exec &block
147
+ return decl
148
+ end
149
+ end
150
+ puts "Warning unknown type #{sym} ignored"
151
+ end
152
+
153
+ end
154
+
155
+
156
+ module_function
157
+
158
+ def domain(name, &block)
159
+ dom = Object.const_set name, Class.new(Domain)
160
+ dom.instance_exec &block
161
+ return dom
162
+ end
163
+
164
+
165
+ end # DataModel
data/src/diagram.rb ADDED
@@ -0,0 +1,146 @@
1
+ require 'fileutils'
2
+ require_relative 'doc'
3
+
4
+
5
+ class TableElement < Element
6
+ def initialize file, node
7
+ super(file)
8
+ @node = node
9
+ pput %Q("#{@node}" [label=<<table BORDER="1" CELLBORDER="0" CELLSPACING="0"><TH><TD>#{@node}</TD></TH>)
10
+ @items = []
11
+ end
12
+
13
+ def item i
14
+ @items << i
15
+ self.pput %Q(<TR><TD ALIGN="LEFT">-#{i}</TD></TR>)
16
+ end
17
+
18
+ def finish
19
+ # self.pput " " << @items.join( ",")
20
+ self.pput "</table>"
21
+ self.pput ">];\n"
22
+ end
23
+
24
+
25
+ end
26
+
27
+ class Graph < Element
28
+ def initialize file
29
+ super(file)
30
+ self.pput "strict digraph {\n"
31
+ end
32
+
33
+ def finish
34
+ self.pput "}\n"
35
+ end
36
+
37
+ end
38
+
39
+ class SubGraph < Element
40
+ def initialize file, model
41
+ super(file)
42
+ self.pput "subgraph cluster_#{model} {\n"
43
+ self.pput "node [shape=plaintext margin=0];\n"
44
+ self.pput "label=#{model};\n"
45
+ end
46
+
47
+ def finish
48
+ self.pput "}\n"
49
+ end
50
+
51
+ end
52
+
53
+ class Links < Element
54
+
55
+ def link el1, el2, label: el, arrowhead: nil, arrowtail: nil
56
+ ah = arrowhead ? %Q!arrowhead = "#{arrowhead}"! : ""
57
+ at = arrowtail ? %Q!arrowtail = "#{arrowtail}"! : ""
58
+ pput %Q!"#{el1}" -> "#{el2}" [label="#{label}" #{ah} #{at} ];\n!
59
+ end
60
+
61
+ end
62
+
63
+ class Diagram < Doc
64
+
65
+ def dotfile
66
+ File.join(diagram_path, "#{self.name}.dot")
67
+ end
68
+
69
+ def jpgfile
70
+ File.join(image_path, "#{self.name}.jpg")
71
+ end
72
+
73
+ def describe *models
74
+
75
+ FileUtils.mkpath diagram_path
76
+ FileUtils.mkpath image_path
77
+ File.open(self.dotfile, "w") do |file|
78
+ graph = Graph.new(file)
79
+ for model in models
80
+ subgraph = SubGraph.new(file, modelname(model))
81
+ for type in model.types.values
82
+ table = TableElement.new(file, typename(type))
83
+ for att in type.attributes(false).values
84
+ table.item att_detail(att)
85
+ end
86
+ table.finish
87
+ end
88
+ subgraph.finish
89
+ end
90
+ for model in models
91
+ links = Links.new(file)
92
+ for type in model.types.values
93
+ for att in type.attributes(false).keys
94
+ if type.attributes[att][:links]
95
+ contains = (type.attributes[att][:type] < DataType)
96
+ links.link typename(type),
97
+ typename(type.attributes[att][:links]),
98
+ label: %Q!#{contains ? "{contains} " : ""}#{type.attributes[att][:name]}!,
99
+ arrowhead: contains ? "none": "open",
100
+ arrowtail: contains ? "diamond": "none"
101
+ end
102
+ end
103
+ if type.superclass < DataType
104
+ links.link typename(type),
105
+ typename(type.superclass),
106
+ label: "extends",
107
+ arrowhead: "none",
108
+ arrowtail: "normal"
109
+ end
110
+ end
111
+ links.finish
112
+ end
113
+ graph.finish
114
+ end
115
+
116
+ unless system("dot -Tjpg #{self.dotfile} > #{self.jpgfile}")
117
+ puts "Error: couldn't create jpeg: #{self.jpgfile}"
118
+ end
119
+
120
+ end
121
+
122
+ def att_detail(att)
123
+ mult = att[:multiplicity]
124
+ mstring = (mult == ZERO_TO_MANY ? "[*]" : mult == ONE_TO_MANY ? "[1..*]" : mult == SINGLE ? "" : "[#{mult.to_s}]")
125
+ "#{att[:name]} #{mstring}"
126
+ end
127
+
128
+ def modelname m
129
+ m.name.gsub(/^#{DataModel.name}::/, "").gsub /::/, "-"
130
+ end
131
+
132
+ def typename t
133
+ t.name.gsub(/^#{t.domain}::/, "").gsub /::/, "-"
134
+ end
135
+
136
+ private
137
+
138
+ def image_path
139
+ File.join(self.path, "images")
140
+ end
141
+
142
+ def diagram_path
143
+ File.join(self.path, "diagrams")
144
+ end
145
+
146
+ end
data/src/doc.rb ADDED
@@ -0,0 +1,181 @@
1
+ class Element
2
+ def initialize file
3
+ @file = file
4
+ end
5
+
6
+ def finish
7
+ self
8
+ end
9
+
10
+ protected
11
+
12
+ def pput(string)
13
+ @file.print string
14
+ self
15
+ end
16
+
17
+ end
18
+
19
+ def cond_call(depth, id, value, lam, lambdas)
20
+ if lambdas[lam]
21
+ return lambdas[lam].(depth, id, value)
22
+ end
23
+ return 0
24
+ end
25
+
26
+ # each lambda takes the depth, id, value
27
+ # Lambdas are:
28
+ # :before_type_lambda,
29
+ # :after_type_lambda,
30
+ # :before_array_lambda,
31
+ # :after_array_lambda,
32
+ # :attribute_lambda
33
+ # which takes a type and returns an object (the id)
34
+ # depth is the number counting from zero, incremented with array elements and nested types.
35
+ # ID is a nested int of the type a for types and a.b for array types,
36
+ # and is the name of the attribute for attributes
37
+ # decl is the type or attribute or array
38
+
39
+ def transform_type depth, id, decl, lambdas
40
+
41
+ if decl.class <= Array
42
+ cond_call(depth, id, decl, :before_array_lambda, lambdas)
43
+ j = 1
44
+ for aa in decl
45
+ transform_type(depth , "#{id}.#{j}", aa, lambdas)
46
+ j = j + 1
47
+ end
48
+ cond_call(depth, id, decl, :after_array_lambda, lambdas)
49
+ elsif decl.class <= DataType
50
+ cond_call(depth, id, decl, :before_type_lambda, lambdas)
51
+ for ak in decl.attributes.keys
52
+ av = decl.attributes[ak]
53
+ transform_type(depth + 1, ak, av, lambdas)
54
+ end
55
+ cond_call(depth, id, decl, :after_type_lambda, lambdas)
56
+ else
57
+ cond_call(depth, id, decl, :attribute_lambda, lambdas)
58
+ end
59
+ self
60
+ end
61
+
62
+ class ClassNameElement < Element
63
+
64
+ def write cat
65
+ pput %Q!\# #{cat.class}: #{cat.name}\n!
66
+ if (cat.respond_to?(:description))
67
+ pput %Q! #{cat.description}\n!
68
+ end
69
+ end
70
+
71
+ end
72
+
73
+ class GroupElement < Element
74
+
75
+ def write grpname, level: 2
76
+ pput %Q! #{'#' * level} #{grpname}\n!
77
+ end
78
+
79
+ end
80
+
81
+ class TypeDeclElement < Element
82
+
83
+ def write indent, id, decl
84
+ transform_type(indent, id, decl,
85
+ {
86
+ :before_type_lambda => lambda do |indent, id, decl|
87
+ pput %Q!####{'#' * indent} #{decl.name} #{decl.attributes[:id] || id} \n!
88
+ end,
89
+ :attribute_lambda => lambda do |indent, id, decl|
90
+ pput %Q!#{" " * indent} - #{id} #{decl}\n!
91
+ end
92
+ })
93
+ self
94
+ end
95
+
96
+ end
97
+
98
+ class MetaTypeElement < Element
99
+
100
+ def write type
101
+ pput "## #{type.typename}"
102
+ if type.extends
103
+ pput " extends #{type.extends}"
104
+ end
105
+ pput "\n #{type.description}\n\n"
106
+ pput "|attribute|type|multiplicity|description|\n"
107
+ pput "|---------|----|------------|-----------|\n"
108
+ for a in type.attributes.values
109
+ pput "|#{a[:name]}|#{a[:type]}|#{self.multiplicity(a)}|#{a[:description]}|\n"
110
+ end
111
+ self
112
+ end
113
+
114
+ def multiplicity m
115
+ m = m[:multiplicity]
116
+ if m.end == -1
117
+ if m.begin == 0
118
+ return "*"
119
+ else
120
+ return "#{m.begin}..*"
121
+ end
122
+ end
123
+ if m.end == m.begin
124
+ return m.end.to_s
125
+ end
126
+ return m.to_s
127
+ end
128
+ end
129
+
130
+ class Doc
131
+ attr_accessor :path, :name
132
+
133
+ def initialize path, name
134
+ self.path = path
135
+ self.name = name
136
+ end
137
+
138
+ def document *models
139
+ FileUtils.mkpath doc_path
140
+ File.open(self.docfile, "w") do |file|
141
+ for model in models
142
+ dom = ClassNameElement.new(file)
143
+ dom.write model
144
+ for typename in model.contents.keys
145
+ grp = GroupElement.new(file)
146
+ grp.write typename
147
+ i = 1
148
+ for decl in model.contents[typename]
149
+ TypeDeclElement.new(file).write(0, i, decl).finish
150
+ i = i + 1
151
+ end
152
+ grp.finish
153
+ end
154
+ dom.finish
155
+ end
156
+ end
157
+ end
158
+
159
+ def document_metamodel *models
160
+ FileUtils.mkpath doc_path
161
+ File.open(self.docfile, "w") do |file|
162
+ for model in models
163
+ dom = GroupElement.new(file)
164
+ dom.write model.name, level: 1
165
+ for type in model.types.values
166
+ MetaTypeElement.new(file).write(type).finish
167
+ end
168
+ dom.finish
169
+ end
170
+ end
171
+ end
172
+
173
+ def docfile
174
+ File.join(doc_path, "#{self.name}.md")
175
+ end
176
+
177
+ def doc_path
178
+ File.join(self.path, "doc")
179
+ end
180
+
181
+ end
@@ -0,0 +1,91 @@
1
+ require 'test/unit'
2
+ require_relative '../src/data_model'
3
+ include DataModel
4
+
5
+ DataModel::domain :TestMetamodel do
6
+
7
+ datatype :BasicType do
8
+ attribute :id, String
9
+ end
10
+
11
+ datatype :Table, description: "Test type" do
12
+ MULT = [0..10]
13
+ DESC = "table of values"
14
+ attribute :vals, Integer,
15
+ multiplicity: MULT,
16
+ :description => DESC
17
+ attribute :morevals, String, 2..5, "array of strings"
18
+ end
19
+
20
+ datatype :ReferencingType do
21
+ attribute :id, String
22
+ attribute :mate, TestMetamodel::BasicType
23
+ end
24
+
25
+ datatype :DerivedType, extends: TestMetamodel::ReferencingType do
26
+ attribute :more, String
27
+ end
28
+
29
+ datatype :DerivedTypeNamingClass, extends: TestMetamodel::ReferencingType do
30
+ attribute :othermore, String
31
+ end
32
+
33
+ datatype :Empty do
34
+
35
+ end
36
+
37
+ end
38
+
39
+ class DataModelTest < Test::Unit::TestCase
40
+
41
+
42
+ def test_metamodel
43
+ assert(contains(TestMetamodel::BasicType, :id), 'has id attribute')
44
+ id_t = TestMetamodel::BasicType.attributes[:id]
45
+ assert_equal(1..1, TestMetamodel::BasicType.attributes[:id][:multiplicity], "has default multiplicity")
46
+ assert(id_t[:type] == String)
47
+
48
+ assert_equal(MULT, TestMetamodel::Table.attributes[:vals][:multiplicity]);
49
+ assert_equal(2..5, TestMetamodel::Table.attributes[:morevals][:multiplicity]);
50
+ assert_equal("Test type", TestMetamodel::Table.description);
51
+
52
+ assert_equal(MULT, TestMetamodel::Table.attributes[:vals][:multiplicity], "has multiplicity")
53
+ assert_equal(DESC, TestMetamodel::Table.attributes[:vals][:description], "has description")
54
+
55
+ mate_t = TestMetamodel::ReferencingType.attributes[:mate]
56
+ assert(mate_t[:type] == TestMetamodel::BasicType)
57
+
58
+ assert(contains(TestMetamodel::DerivedType, :more), "has derived attribute")
59
+ assert(contains(TestMetamodel::DerivedType, :mate), "has derived attribute")
60
+ assert(contains(TestMetamodel::DerivedType, :id), "has derived attribute")
61
+ assert(contains(TestMetamodel::DerivedTypeNamingClass, :othermore), "has derived attribute")
62
+ assert(contains(TestMetamodel::DerivedTypeNamingClass, :id), "has derived attribute")
63
+ end
64
+
65
+ TestMetamodel.new :Test_Model do
66
+ table do
67
+ vals 1
68
+ vals 2
69
+ end
70
+ referencingtype do
71
+ id :owner
72
+ mate do
73
+ id :content
74
+ end
75
+ end
76
+ end
77
+
78
+ def test_model
79
+ assert_equal(1, TestMetamodel::Test_Model.contents[:table][0].attributes[:vals][0], "has vals")
80
+ assert_equal(2, TestMetamodel::Test_Model.contents[:table][0].attributes[:vals][1], "has vals")
81
+ assert_equal(:content, TestMetamodel::Test_Model.contents[:referencingtype][0].attributes[:mate].attributes[:id], "has vals")
82
+ assert_equal(:content, TestMetamodel::Test_Model.contents[:referencingtype][0].mate.id, "has vals")
83
+ end
84
+
85
+ private
86
+
87
+ def contains(type, attr)
88
+ type.attributes.keys.one? {|k| k == attr}
89
+ end
90
+
91
+ end
@@ -0,0 +1,34 @@
1
+ require 'test/unit'
2
+ require_relative '../src/diagram'
3
+ require_relative '../model/agreement'
4
+ include DataModel
5
+
6
+
7
+ class DiagramTest < Test::Unit::TestCase
8
+
9
+ # Called before every test method runs. Can be used
10
+ # to set up fixture information.
11
+ PATH = "out/test/"
12
+ NAME = "d"
13
+
14
+ def setup
15
+ @d = Diagram.new(PATH, NAME)
16
+ if File.file?(@d.dotfile)
17
+ File.delete(@d.dotfile)
18
+ end
19
+ if File.file?(@d.jpgfile)
20
+ File.delete(@d.jpgfile)
21
+ end
22
+ end
23
+
24
+ def test_dot
25
+ assert_equal("out/test/diagrams/d.dot", @d.dotfile, "file format")
26
+ assert_equal("out/test/images/d.jpg", @d.jpgfile, "file format")
27
+ assert(!File.file?(@d.dotfile), "no dotfile")
28
+ @d.describe( Category)
29
+ assert(File.file?(@d.dotfile), "file created")
30
+ assert(File.file?(@d.jpgfile), "file created")
31
+ # TODO test the features in the diagram
32
+ @d.describe( Category, Parties)
33
+ end
34
+ end
data/test/doc_test.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'test/unit'
2
+ require_relative '../src/doc'
3
+ require_relative '../model/fm'
4
+ include DataModel
5
+
6
+ class DocTest < Test::Unit::TestCase
7
+
8
+ # Called before every test method runs. Can be used
9
+ # to set up fixture information.
10
+ PATH = "out/test/"
11
+ NAME = "doctest"
12
+
13
+ def setup
14
+ @d = Doc.new(PATH, NAME)
15
+ if File.file?(@d.docfile)
16
+ File.delete(@d.docfile)
17
+ end
18
+ end
19
+
20
+
21
+ def test_dot
22
+ assert_equal("#{PATH}doc/#{NAME}.md", @d.docfile, "file name")
23
+ assert(!File.file?(@d.docfile), "no dotfile")
24
+ @d.document( Category::FM)
25
+ assert(File.file?(@d.docfile), "file created")
26
+ end
27
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: agreement-design-prototype
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - CCS
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-08-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 12.3.0
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 12.3.0
27
+ description: A prototype of techniques to manage agreement specifications.
28
+ email:
29
+ - rubygems@humphries.tech
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - "./README.md"
35
+ - "./Rakefile"
36
+ - "./agreement-design.gemspec"
37
+ - "./build/build_models.rb"
38
+ - "./gen/diagrams/data_model.dot"
39
+ - "./gen/doc/frameworks.md"
40
+ - "./gen/doc/metamodel.md"
41
+ - "./gen/images/data_model.jpg"
42
+ - "./model/agreement.rb"
43
+ - "./model/fm.rb"
44
+ - "./model/party.rb"
45
+ - "./out/test/diagrams/d.dot"
46
+ - "./out/test/doc/doctest.md"
47
+ - "./out/test/images/d.jpg"
48
+ - "./src/data_model.rb"
49
+ - "./src/diagram.rb"
50
+ - "./src/doc.rb"
51
+ - "./test/data_model_test.rb"
52
+ - "./test/diagram_test.rb"
53
+ - "./test/doc_test.rb"
54
+ homepage: https://github.com/Crown-Commercial-Service/cmp-design-prototype/agreement-design
55
+ licenses:
56
+ - MIT
57
+ metadata: {}
58
+ post_install_message:
59
+ rdoc_options: []
60
+ require_paths:
61
+ - src
62
+ - model
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubyforge_project:
75
+ rubygems_version: 2.7.6
76
+ signing_key:
77
+ specification_version: 4
78
+ summary: Agreement prototype .
79
+ test_files:
80
+ - "./test/diagram_test.rb"
81
+ - "./test/data_model_test.rb"
82
+ - "./test/doc_test.rb"