agreement-design-prototype 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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"