gsn-model-parser 0.16.10__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Vanderbilt University, ICONS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,5 @@
1
+ prune test
2
+ prune examples
3
+ prune venv
4
+ global-exclude __pycache__
5
+ global-exclude *.py[cod]
@@ -0,0 +1,155 @@
1
+ Metadata-Version: 2.4
2
+ Name: gsn-model-parser
3
+ Version: 0.16.10
4
+ Summary: Parse Goal Structuring Notation (.gsn) models and propagate subjective-logic opinions
5
+ Author-email: Patrik Meijer <patrik.meijer@vanderbilt.edu>, Mark Wutka <mark.wutka@vanderbilt.edu>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://git-dains-vu.isis.vanderbilt.edu/dains/tools/caid/gsn-domain
8
+ Project-URL: Issues, https://git-dains-vu.isis.vanderbilt.edu/dains/tools/caid/gsn-domain/-/issues
9
+ Keywords: gsn,goal-structuring-notation,assurance,subjective-logic
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Scientific/Engineering
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: textX==4.4.0
23
+ Dynamic: license-file
24
+
25
+ # gsn-model-parser
26
+
27
+ Python parser for Goal Structuring Notation (`.gsn`) models. It loads one or more `.gsn` files from a directory, resolves references, and returns `GSNNode` objects with attributes (including opinions) and content hashes.
28
+
29
+ Requires **Python 3.10+**.
30
+
31
+ ## Virtual environment
32
+
33
+ From this directory (`python-model-parser`):
34
+
35
+ ```shell
36
+ python3 -m venv venv
37
+ source venv/bin/activate
38
+ pip install -U pip
39
+ ```
40
+
41
+ ## Install
42
+
43
+ From PyPI:
44
+
45
+ ```shell
46
+ pip install gsn-model-parser
47
+ ```
48
+
49
+ Editable install from a clone (used for development and tests):
50
+
51
+ ```shell
52
+ pip install -e .
53
+ ```
54
+
55
+ A release is also published to this project's GitLab PyPI registry. Authenticate with a [personal access token](https://git-dains-vu.isis.vanderbilt.edu/-/user_settings/personal_access_tokens) that has `read_api` and `read_package_registry` (do not commit the token):
56
+
57
+ ```shell
58
+ export GSN_DOMAIN_PYPI="https://__token__:${TOKEN}@git-dains-vu.isis.vanderbilt.edu/api/v4/projects/dains%2Ftools%2Fcaid%2Fgsn-domain/packages/pypi/simple"
59
+ pip install gsn-model-parser --index-url "${GSN_DOMAIN_PYPI}" --extra-index-url https://pypi.org/simple
60
+ ```
61
+
62
+ ## Build a wheel
63
+
64
+ ```shell
65
+ pip install build
66
+ python -m build
67
+ ```
68
+
69
+ Artifacts land in `dist/` (`gsn_model_parser-*.whl` and `*.tar.gz`). Check the metadata, then upload.
70
+
71
+ Public PyPI (create an API token at <https://pypi.org/manage/account/token/>):
72
+
73
+ ```shell
74
+ pip install twine
75
+ python -m twine check dist/*
76
+ python -m twine upload dist/*
77
+ ```
78
+
79
+ GitLab CI does this on a version tag (`v0.16.9`). The tag job rewrites `version` in `pyproject.toml` from the tag, builds the artifacts, uploads them to the GitLab PyPI registry, and uploads the same artifacts to public PyPI when the masked CI variable `PYPI_API_TOKEN` is set. Make that variable available to tag pipelines.
80
+
81
+ To upload the same build to the GitLab registry by hand:
82
+
83
+ ```shell
84
+ python -m twine upload --repository-url https://git-dains-vu.isis.vanderbilt.edu/api/v4/projects/<project-id>/packages/pypi dist/*
85
+ ```
86
+
87
+ ## Usage
88
+
89
+ Parse a directory of `.gsn` files (all files in the folder are one model).
90
+ `get_solved_bys` and `get_in_context_ofs` return the direct children, in source order.
91
+ `ref_goal`, `ref_strategy`, `ref_solution`, `ref_context`, `ref_assumption`, and
92
+ `ref_justification` resolve to the target node, so the same calls continue into a
93
+ node that lives in another namespace. Recurse to walk the whole argument.
94
+ `examples/traverse_model.py` prints this outline for a model directory.
95
+
96
+ ```python
97
+ from gsn_model_parser import load_model
98
+
99
+ def walk(node, indent=0, seen=None):
100
+ if seen is None:
101
+ seen = set()
102
+ print(f"{' ' * indent}{node.node_type} {node.name}")
103
+ if node.node_id in seen:
104
+ return
105
+ seen.add(node.node_id)
106
+ for context in node.get_in_context_ofs():
107
+ walk(context, indent + 1, seen)
108
+ for child in node.get_solved_bys():
109
+ walk(child, indent + 1, seen)
110
+
111
+ model = load_model("/path/to/sample-models/control-system")
112
+ for root in model.get_roots():
113
+ walk(root)
114
+ ```
115
+
116
+ #### Compute Propagated Opinions
117
+ `get_propagated_opinion` derives the subjective-logic opinion of a node from its `solvedBy` and `inContextOf` links. On a top-level node that walks the whole argument underneath it. Solutions and assumptions still use their stored opinion; a goal or strategy is computed.
118
+
119
+ ```python
120
+ from gsn_model_parser import load_model
121
+
122
+ model = load_model("/path/to/sample-models/simple")
123
+ for root in model.get_roots():
124
+ print(root.name, root.get_propagated_opinion())
125
+ ```
126
+
127
+ #### Updating Opinions
128
+ ``get_gsn_nodes`` still returns the flat node map and a ``persist`` callback:
129
+
130
+ ```python
131
+ from gsn_model_parser import get_gsn_nodes
132
+
133
+ nodes, persist = get_gsn_nodes("/path/to/sample-models/simple", as_dict=True)
134
+
135
+ for node in nodes.values():
136
+ print(node.name, node.node_type, node.opinion, node.get_content_hash(nodes))
137
+ if node.node_type == 'SolutionNode':
138
+ node.opinion = {'b': 1.0, 'd': 0.0, 'u': 0.0, 'a': 0.5}
139
+
140
+ persist("/path/to/out.gsn") # one combined file
141
+ persist("/path/to/out-dir") # original filenames, one file per source .gsn
142
+ ```
143
+
144
+ ## Tests
145
+
146
+ Unit tests parse the sample models under [`../sample-models/`](../sample-models/). From this directory, with the venv activated:
147
+
148
+ ```shell
149
+ pip install -e .
150
+ python -m unittest discover -s test -p '*test*.py'
151
+ ```
152
+
153
+ ## Grammar
154
+
155
+ The textX grammar [`src/gsn_model_parser/gsn.tx`](src/gsn_model_parser/gsn.tx) tracks [`Assurance.xtext`](../gsn-xtext/edu.vanderbilt.isis.caid.assurancedsl/src/main/java/edu/vanderbilt/isis/caid/assurancedsl/Assurance.xtext). When the `.gsn` language changes, update both.
@@ -0,0 +1,131 @@
1
+ # gsn-model-parser
2
+
3
+ Python parser for Goal Structuring Notation (`.gsn`) models. It loads one or more `.gsn` files from a directory, resolves references, and returns `GSNNode` objects with attributes (including opinions) and content hashes.
4
+
5
+ Requires **Python 3.10+**.
6
+
7
+ ## Virtual environment
8
+
9
+ From this directory (`python-model-parser`):
10
+
11
+ ```shell
12
+ python3 -m venv venv
13
+ source venv/bin/activate
14
+ pip install -U pip
15
+ ```
16
+
17
+ ## Install
18
+
19
+ From PyPI:
20
+
21
+ ```shell
22
+ pip install gsn-model-parser
23
+ ```
24
+
25
+ Editable install from a clone (used for development and tests):
26
+
27
+ ```shell
28
+ pip install -e .
29
+ ```
30
+
31
+ A release is also published to this project's GitLab PyPI registry. Authenticate with a [personal access token](https://git-dains-vu.isis.vanderbilt.edu/-/user_settings/personal_access_tokens) that has `read_api` and `read_package_registry` (do not commit the token):
32
+
33
+ ```shell
34
+ export GSN_DOMAIN_PYPI="https://__token__:${TOKEN}@git-dains-vu.isis.vanderbilt.edu/api/v4/projects/dains%2Ftools%2Fcaid%2Fgsn-domain/packages/pypi/simple"
35
+ pip install gsn-model-parser --index-url "${GSN_DOMAIN_PYPI}" --extra-index-url https://pypi.org/simple
36
+ ```
37
+
38
+ ## Build a wheel
39
+
40
+ ```shell
41
+ pip install build
42
+ python -m build
43
+ ```
44
+
45
+ Artifacts land in `dist/` (`gsn_model_parser-*.whl` and `*.tar.gz`). Check the metadata, then upload.
46
+
47
+ Public PyPI (create an API token at <https://pypi.org/manage/account/token/>):
48
+
49
+ ```shell
50
+ pip install twine
51
+ python -m twine check dist/*
52
+ python -m twine upload dist/*
53
+ ```
54
+
55
+ GitLab CI does this on a version tag (`v0.16.9`). The tag job rewrites `version` in `pyproject.toml` from the tag, builds the artifacts, uploads them to the GitLab PyPI registry, and uploads the same artifacts to public PyPI when the masked CI variable `PYPI_API_TOKEN` is set. Make that variable available to tag pipelines.
56
+
57
+ To upload the same build to the GitLab registry by hand:
58
+
59
+ ```shell
60
+ python -m twine upload --repository-url https://git-dains-vu.isis.vanderbilt.edu/api/v4/projects/<project-id>/packages/pypi dist/*
61
+ ```
62
+
63
+ ## Usage
64
+
65
+ Parse a directory of `.gsn` files (all files in the folder are one model).
66
+ `get_solved_bys` and `get_in_context_ofs` return the direct children, in source order.
67
+ `ref_goal`, `ref_strategy`, `ref_solution`, `ref_context`, `ref_assumption`, and
68
+ `ref_justification` resolve to the target node, so the same calls continue into a
69
+ node that lives in another namespace. Recurse to walk the whole argument.
70
+ `examples/traverse_model.py` prints this outline for a model directory.
71
+
72
+ ```python
73
+ from gsn_model_parser import load_model
74
+
75
+ def walk(node, indent=0, seen=None):
76
+ if seen is None:
77
+ seen = set()
78
+ print(f"{' ' * indent}{node.node_type} {node.name}")
79
+ if node.node_id in seen:
80
+ return
81
+ seen.add(node.node_id)
82
+ for context in node.get_in_context_ofs():
83
+ walk(context, indent + 1, seen)
84
+ for child in node.get_solved_bys():
85
+ walk(child, indent + 1, seen)
86
+
87
+ model = load_model("/path/to/sample-models/control-system")
88
+ for root in model.get_roots():
89
+ walk(root)
90
+ ```
91
+
92
+ #### Compute Propagated Opinions
93
+ `get_propagated_opinion` derives the subjective-logic opinion of a node from its `solvedBy` and `inContextOf` links. On a top-level node that walks the whole argument underneath it. Solutions and assumptions still use their stored opinion; a goal or strategy is computed.
94
+
95
+ ```python
96
+ from gsn_model_parser import load_model
97
+
98
+ model = load_model("/path/to/sample-models/simple")
99
+ for root in model.get_roots():
100
+ print(root.name, root.get_propagated_opinion())
101
+ ```
102
+
103
+ #### Updating Opinions
104
+ ``get_gsn_nodes`` still returns the flat node map and a ``persist`` callback:
105
+
106
+ ```python
107
+ from gsn_model_parser import get_gsn_nodes
108
+
109
+ nodes, persist = get_gsn_nodes("/path/to/sample-models/simple", as_dict=True)
110
+
111
+ for node in nodes.values():
112
+ print(node.name, node.node_type, node.opinion, node.get_content_hash(nodes))
113
+ if node.node_type == 'SolutionNode':
114
+ node.opinion = {'b': 1.0, 'd': 0.0, 'u': 0.0, 'a': 0.5}
115
+
116
+ persist("/path/to/out.gsn") # one combined file
117
+ persist("/path/to/out-dir") # original filenames, one file per source .gsn
118
+ ```
119
+
120
+ ## Tests
121
+
122
+ Unit tests parse the sample models under [`../sample-models/`](../sample-models/). From this directory, with the venv activated:
123
+
124
+ ```shell
125
+ pip install -e .
126
+ python -m unittest discover -s test -p '*test*.py'
127
+ ```
128
+
129
+ ## Grammar
130
+
131
+ The textX grammar [`src/gsn_model_parser/gsn.tx`](src/gsn_model_parser/gsn.tx) tracks [`Assurance.xtext`](../gsn-xtext/edu.vanderbilt.isis.caid.assurancedsl/src/main/java/edu/vanderbilt/isis/caid/assurancedsl/Assurance.xtext). When the `.gsn` language changes, update both.
@@ -0,0 +1,47 @@
1
+ [build-system]
2
+ requires = ["setuptools >= 77.0.3"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "gsn-model-parser"
7
+ version = "0.16.10"
8
+ description = "Parse Goal Structuring Notation (.gsn) models and propagate subjective-logic opinions"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ keywords = ["gsn", "goal-structuring-notation", "assurance", "subjective-logic"]
14
+ authors = [
15
+ { name = "Patrik Meijer", email = "patrik.meijer@vanderbilt.edu" },
16
+ { name = "Mark Wutka", email = "mark.wutka@vanderbilt.edu" },
17
+ ]
18
+ classifiers = [
19
+ "Development Status :: 4 - Beta",
20
+ "Intended Audience :: Developers",
21
+ "Intended Audience :: Science/Research",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
27
+ "Topic :: Scientific/Engineering",
28
+ ]
29
+ dependencies = [
30
+ "textX==4.4.0",
31
+ ]
32
+
33
+ [project.urls]
34
+ Homepage = "https://git-dains-vu.isis.vanderbilt.edu/dains/tools/caid/gsn-domain"
35
+ Issues = "https://git-dains-vu.isis.vanderbilt.edu/dains/tools/caid/gsn-domain/-/issues"
36
+
37
+ [project.scripts]
38
+ gsn-model-parser = "gsn_model_parser.parser:main"
39
+
40
+ [tool.setuptools]
41
+ include-package-data = true
42
+
43
+ [tool.setuptools.packages.find]
44
+ where = ["src"]
45
+
46
+ [tool.setuptools.package-data]
47
+ gsn_model_parser = ["gsn.tx"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ """Parse Goal Structuring Notation (.gsn) models."""
2
+
3
+ from gsn_model_parser.parser import GSNModel, GSNNode, get_gsn_nodes, list_solutions, load_model
4
+
5
+ __all__ = ['GSNModel', 'GSNNode', 'get_gsn_nodes', 'list_solutions', 'load_model']
@@ -0,0 +1,3 @@
1
+ from gsn_model_parser.parser import main
2
+
3
+ main()
@@ -0,0 +1,218 @@
1
+ AssuranceModel:
2
+ assurancemodels += GSNDefinition*
3
+ ;
4
+
5
+ GSNDefinition:
6
+ Goals|Strategies|Contexts|Assumptions|Justifications|Solutions
7
+ ;
8
+
9
+ Goals:
10
+ 'GOALS' name=ID '{' goals *= GoalNode '}'
11
+ ;
12
+
13
+ Strategies:
14
+ 'STRATEGIES' name=ID '{' strategies *= StrategyNode '}'
15
+ ;
16
+
17
+ Contexts:
18
+ 'CONTEXTS' name=ID '{' contexts *= ContextNode '}'
19
+ ;
20
+
21
+ Assumptions:
22
+ 'ASSUMPTIONS' name=ID '{' assumptions *= AssumptionNode '}'
23
+ ;
24
+
25
+ Justifications:
26
+ 'JUSTIFICATIONS' name=ID '{' justifications *= JustificationNode '}'
27
+ ;
28
+
29
+ Solutions:
30
+ 'SOLUTIONS' name=ID '{' solutions *= SolutionNode '}'
31
+ ;
32
+
33
+ GoalSupportNodes:
34
+ GoalNode
35
+ | StrategyNode
36
+ | SolutionNode
37
+ | GoalNodeRef
38
+ | StrategyNodeRef
39
+ | SolutionNodeRef
40
+ ;
41
+
42
+ StrategySupportNodes:
43
+ GoalNode
44
+ | StrategyNode
45
+ | GoalNodeRef
46
+ | StrategyNodeRef
47
+ ;
48
+
49
+ ContextNodes:
50
+ ContextNode
51
+ | ContextNodeRef
52
+ ;
53
+
54
+ AssumptionNodes:
55
+ AssumptionNode
56
+ | AssumptionNodeRef
57
+ ;
58
+
59
+ JustificationNodes:
60
+ JustificationNode
61
+ | JustificationNodeRef
62
+ ;
63
+
64
+ ALLNodes:
65
+ GoalNode
66
+ | StrategyNode
67
+ | SolutionNode
68
+ | ContextNode
69
+ | AssumptionNode
70
+ | JustificationNode
71
+ ;
72
+
73
+ GoalNodeRef:
74
+ 'ref_goal' ref=[GoalNode:FQN]
75
+ ;
76
+
77
+ StrategyNodeRef:
78
+ 'ref_strategy' ref=[StrategyNode:FQN]
79
+ ;
80
+
81
+ SolutionNodeRef:
82
+ 'ref_solution' ref=[SolutionNode:FQN]
83
+ ;
84
+
85
+ ContextNodeRef:
86
+ 'ref_context' ref=[ContextNode:FQN]
87
+ ;
88
+
89
+ AssumptionNodeRef:
90
+ 'ref_assumption' ref=[AssumptionNode:FQN]
91
+ ;
92
+
93
+ JustificationNodeRef:
94
+ 'ref_justification' ref=[JustificationNode:FQN]
95
+ ;
96
+
97
+ Number:
98
+ FLOAT | INT
99
+ ;
100
+
101
+ Opinion:
102
+ '(' b=Number ',' d=Number ',' u=Number (',' a=Number)? ')'
103
+ ;
104
+
105
+ WarrantBlock:
106
+ 'warrant' '{'
107
+ ('givenTrue' givenTrue=Opinion)?
108
+ ('givenFalse' givenFalse=Opinion)?
109
+ '}'
110
+ ;
111
+
112
+ GoalChild:
113
+ GoalSupportNodes | ContextNodes | AssumptionNodes | JustificationNodes
114
+ ;
115
+
116
+ StrategyChild:
117
+ StrategySupportNodes | ContextNodes | AssumptionNodes | JustificationNodes
118
+ ;
119
+
120
+ GoalDetails:
121
+ child=GoalChild
122
+ (warrants=WarrantBlock)?
123
+ ;
124
+
125
+ StrategyDetails:
126
+ child=StrategyChild
127
+ (warrants=WarrantBlock)?
128
+ ;
129
+
130
+ GoalNode:
131
+ 'goal' name=ID '{'
132
+ (details=BaseNode)?
133
+ ('status' status=SolutionStatus)?
134
+ (nodedetails *= GoalDetails)
135
+ '}'
136
+ ;
137
+
138
+ StrategyNode:
139
+ 'strategy' name=ID '{'
140
+ (details=BaseNode)?
141
+ ('argument' argumentType=ArgumentType)?
142
+ (nodedetails *= StrategyDetails)
143
+ '}'
144
+ ;
145
+
146
+ ContextNode:
147
+ 'context' name=ID '{'
148
+ (details=BaseNode)?
149
+ '}'
150
+ ;
151
+
152
+ AssumptionNode:
153
+ 'assumption' name=ID '{'
154
+ (details=BaseNode)?
155
+ ('opinion' opinion=Opinion)?
156
+ '}'
157
+ ;
158
+
159
+ JustificationNode:
160
+ 'justification' name=ID '{'
161
+ (details=BaseNode)?
162
+ '}'
163
+ ;
164
+
165
+ SolutionNode:
166
+ 'solution' name=ID '{'
167
+ (details=BaseNode)?
168
+ ('status' status=SolutionStatus)?
169
+ ('opinion' opinion=Opinion)?
170
+ '}'
171
+ ;
172
+
173
+ BaseNode:
174
+ (uuid=UUIDType)?
175
+ (summary=Summary)?
176
+ (info=Description)?
177
+ (labels *= LabelInfo)
178
+ (artifacts *= URI)
179
+ ;
180
+
181
+ SolutionStatus:
182
+ 'NotReviewed' | 'Approved' | 'Disapproved'
183
+ ;
184
+
185
+ ArgumentType:
186
+ 'CONJUNCTION' | 'DISJUNCTION' | 'FUSION' | 'DISCOUNT'
187
+ ;
188
+
189
+ UUIDType:
190
+ 'uuid' info=GUID
191
+ ;
192
+
193
+ Summary:
194
+ 'summary' info=MLString
195
+ ;
196
+
197
+ Description:
198
+ 'info' info=MLString
199
+ ;
200
+
201
+ LabelInfo:
202
+ 'label' name=ID
203
+ ;
204
+
205
+ URI:
206
+ 'artifact' uri=MLString
207
+ ;
208
+
209
+ FQN:
210
+ ID+['.']
211
+ ;
212
+
213
+ MLString: data=/(?ms)\"(.*?)\"/;
214
+ GUID: /\{?[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\}?/;
215
+
216
+ Comment:
217
+ /\/\/.*$/
218
+ ;