ccs-digitalmarketplace-frameworks 1.8.30 → 1.8.32
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.
- package/.github/workflows/test.yml +2 -2
- package/frameworks/g-cloud-14/manifests/display_service.yml +1 -0
- package/frameworks/g-cloud-14/questions/declaration/authOrgLegalRequirementCompliant.yml +1 -1
- package/frameworks/g-cloud-14/questions/declaration/immediateParentCompanyDetailsVATNumber.yml +1 -0
- package/frameworks/g-cloud-14/questions/declaration/mandatoryExclusionTaxes.yml +1 -1
- package/frameworks/g-cloud-14/questions/declaration/{modernSlaveryComplient.yml → modernSlaveryCompliant.yml} +1 -1
- package/frameworks/g-cloud-14/questions/declaration/multiqModernSlaveryRelevant.yml +1 -1
- package/frameworks/g-cloud-14/questions/declaration/pscDetails1Conditions.yml +2 -2
- package/frameworks/g-cloud-14/questions/declaration/pscDetails2Conditions.yml +2 -2
- package/frameworks/g-cloud-14/questions/declaration/pscDetails3Conditions.yml +2 -2
- package/frameworks/g-cloud-14/questions/declaration/pscDetails4Conditions.yml +2 -2
- package/frameworks/g-cloud-14/questions/declaration/ultimateParentCompanyDetailsVATNumber.yml +1 -0
- package/frameworks/g-cloud-14/questions/declaration/vatNumber.yml +2 -1
- package/frameworks/g-cloud-14/questions/services/covid19Recovery.yml +4 -1
- package/frameworks/g-cloud-14/questions/services/equalOpportunity.yml +4 -1
- package/frameworks/g-cloud-14/questions/services/fightingClimateChange.yml +4 -1
- package/frameworks/g-cloud-14/questions/services/multiqUsageNotifications.yml +1 -0
- package/frameworks/g-cloud-14/questions/services/tacklingEconomicInequality.yml +4 -1
- package/frameworks/g-cloud-14/questions/services/usageNotificationsHow.yml +3 -0
- package/frameworks/g-cloud-14/questions/services/usageNotificationsHowOther.yml +19 -0
- package/frameworks/g-cloud-14/questions/services/wellbeing.yml +4 -1
- package/package.json +1 -1
- package/requirements-dev.txt +2 -2
- package/requirements.txt +1 -1
- package/schema_generator/validation.py +23 -7
- package/schemas/questions.json +4 -0
- package/tests/test_generate_validation_schema.py +65 -5
|
@@ -12,7 +12,7 @@ jobs:
|
|
|
12
12
|
- uses: actions/checkout@v4
|
|
13
13
|
|
|
14
14
|
- name: Set up Python ${{ matrix.python-version }}
|
|
15
|
-
uses: actions/setup-python@
|
|
15
|
+
uses: actions/setup-python@v5
|
|
16
16
|
with:
|
|
17
17
|
python-version: ${{ matrix.python-version }}
|
|
18
18
|
|
|
@@ -45,7 +45,7 @@ jobs:
|
|
|
45
45
|
- uses: actions/checkout@v4
|
|
46
46
|
|
|
47
47
|
- name: Set up Python ${{ matrix.python-version }}
|
|
48
|
-
uses: actions/setup-python@
|
|
48
|
+
uses: actions/setup-python@v5
|
|
49
49
|
with:
|
|
50
50
|
python-version: ${{ matrix.python-version }}
|
|
51
51
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
name: Taxes and social security contributions
|
|
1
|
+
name: Taxes and social security contributions
|
|
2
2
|
question: Please confirm that you have met all your obligations relating to the payment of taxes and social security contributions, both in the country in which you are established and in the UK.
|
|
3
3
|
question_advice: |
|
|
4
4
|
The detailed grounds for mandatory and discretionary exclusion of a supplier for non-payment of taxes and social security contributions, are set out on the
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
name: Modern slavery - Are you
|
|
1
|
+
name: Modern slavery - Are you compliant
|
|
2
2
|
question: Are you compliant with the annual reporting requirements contained within <a class="govuk-link" href="http://www.legislation.gov.uk/ukpga/2015/30/section/54/enacted" target="_blank" rel="noopener noreferrer">Section 54 of the Act 2015 (opens in new tab)</a>?
|
|
3
3
|
|
|
4
4
|
type: boolean
|
|
@@ -2,9 +2,9 @@ name: Conditions for being a PSC
|
|
|
2
2
|
question: Which conditions for being a PSC are met?
|
|
3
3
|
type: radios
|
|
4
4
|
options:
|
|
5
|
-
- label: "
|
|
5
|
+
- label: "25% or more and less than 50%"
|
|
6
6
|
value: "more than 25%"
|
|
7
|
-
- label: "
|
|
7
|
+
- label: "50% or more and less than 75%"
|
|
8
8
|
value: "more than 50%"
|
|
9
9
|
- label: "75% or more"
|
|
10
10
|
value: "more than 75%"
|
|
@@ -2,9 +2,9 @@ name: Conditions for being a PSC
|
|
|
2
2
|
question: Which conditions for being a PSC are met?
|
|
3
3
|
type: radios
|
|
4
4
|
options:
|
|
5
|
-
- label: "
|
|
5
|
+
- label: "25% or more and less than 50%"
|
|
6
6
|
value: "more than 25%"
|
|
7
|
-
- label: "
|
|
7
|
+
- label: "50% or more and less than 75%"
|
|
8
8
|
value: "more than 50%"
|
|
9
9
|
- label: "75% or more"
|
|
10
10
|
value: "more than 75%"
|
|
@@ -2,9 +2,9 @@ name: Conditions for being a PSC
|
|
|
2
2
|
question: Which conditions for being a PSC are met?
|
|
3
3
|
type: radios
|
|
4
4
|
options:
|
|
5
|
-
- label: "
|
|
5
|
+
- label: "25% or more and less than 50%"
|
|
6
6
|
value: "more than 25%"
|
|
7
|
-
- label: "
|
|
7
|
+
- label: "50% or more and less than 75%"
|
|
8
8
|
value: "more than 50%"
|
|
9
9
|
- label: "75% or more"
|
|
10
10
|
value: "more than 75%"
|
|
@@ -2,9 +2,9 @@ name: Conditions for being a PSC
|
|
|
2
2
|
question: Which conditions for being a PSC are met?
|
|
3
3
|
type: radios
|
|
4
4
|
options:
|
|
5
|
-
- label: "
|
|
5
|
+
- label: "25% or more and less than 50%"
|
|
6
6
|
value: "more than 25%"
|
|
7
|
-
- label: "
|
|
7
|
+
- label: "50% or more and less than 75%"
|
|
8
8
|
value: "more than 50%"
|
|
9
9
|
- label: "75% or more"
|
|
10
10
|
value: "more than 75%"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
name: Registered VAT number
|
|
2
2
|
question: What is your registered VAT number?
|
|
3
|
-
|
|
3
|
+
question_advice: You can view <a class="govuk-link" href="https://www.gov.uk/register-for-vat" for guidance" target="_blank" rel="noopener noreferrer">Register for VAT (opens in new tab)</a> for guidance
|
|
4
|
+
hint: This is 9 or 12 numbers, sometimes with ‘GB’ at the start, like 123456789 or GB123456789
|
|
4
5
|
|
|
5
6
|
type: text
|
|
6
7
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: Covid-19 recovery
|
|
2
2
|
question: Covid-19 recovery
|
|
3
|
-
|
|
3
|
+
hint: Your answer must be at least 10 words
|
|
4
4
|
depends:
|
|
5
5
|
- "on": lot
|
|
6
6
|
being:
|
|
@@ -10,6 +10,7 @@ depends:
|
|
|
10
10
|
|
|
11
11
|
type: textbox_large
|
|
12
12
|
max_length_in_words: 300
|
|
13
|
+
min_length_in_words: 10
|
|
13
14
|
hidden: true
|
|
14
15
|
|
|
15
16
|
validations:
|
|
@@ -19,3 +20,5 @@ validations:
|
|
|
19
20
|
message: Your answer must be no more than 300 words
|
|
20
21
|
- name: under_character_limit
|
|
21
22
|
message: Description must be 3000 characters or fewer
|
|
23
|
+
- name: over_10_words
|
|
24
|
+
message: Your answer must be at least 10 words
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: Equal opportunity
|
|
2
2
|
question: Equal opportunity
|
|
3
|
-
|
|
3
|
+
hint: Your answer must be at least 10 words
|
|
4
4
|
depends:
|
|
5
5
|
- "on": lot
|
|
6
6
|
being:
|
|
@@ -10,6 +10,7 @@ depends:
|
|
|
10
10
|
|
|
11
11
|
type: textbox_large
|
|
12
12
|
max_length_in_words: 300
|
|
13
|
+
min_length_in_words: 10
|
|
13
14
|
hidden: true
|
|
14
15
|
|
|
15
16
|
validations:
|
|
@@ -19,3 +20,5 @@ validations:
|
|
|
19
20
|
message: Your answer must be no more than 300 words
|
|
20
21
|
- name: under_character_limit
|
|
21
22
|
message: Description must be 3000 characters or fewer
|
|
23
|
+
- name: over_10_words
|
|
24
|
+
message: Your answer must be at least 10 words
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: Fighting climate change
|
|
2
2
|
question: Fighting climate change
|
|
3
|
-
|
|
3
|
+
hint: Your answer must be at least 10 words
|
|
4
4
|
depends:
|
|
5
5
|
- "on": lot
|
|
6
6
|
being:
|
|
@@ -10,6 +10,7 @@ depends:
|
|
|
10
10
|
|
|
11
11
|
type: textbox_large
|
|
12
12
|
max_length_in_words: 300
|
|
13
|
+
min_length_in_words: 10
|
|
13
14
|
hidden: true
|
|
14
15
|
|
|
15
16
|
validations:
|
|
@@ -19,3 +20,5 @@ validations:
|
|
|
19
20
|
message: Your answer must be no more than 300 words
|
|
20
21
|
- name: under_character_limit
|
|
21
22
|
message: Description must be 3000 characters or fewer
|
|
23
|
+
- name: over_10_words
|
|
24
|
+
message: Your answer must be at least 10 words
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: Tackling economic inequality
|
|
2
2
|
question: Tackling economic inequality
|
|
3
|
-
|
|
3
|
+
hint: Your answer must be at least 10 words
|
|
4
4
|
depends:
|
|
5
5
|
- "on": lot
|
|
6
6
|
being:
|
|
@@ -10,6 +10,7 @@ depends:
|
|
|
10
10
|
|
|
11
11
|
type: textbox_large
|
|
12
12
|
max_length_in_words: 300
|
|
13
|
+
min_length_in_words: 10
|
|
13
14
|
hidden: true
|
|
14
15
|
|
|
15
16
|
validations:
|
|
@@ -19,3 +20,5 @@ validations:
|
|
|
19
20
|
message: Your answer must be no more than 300 words
|
|
20
21
|
- name: under_character_limit
|
|
21
22
|
message: Description must be 3000 characters or fewer
|
|
23
|
+
- name: over_10_words
|
|
24
|
+
message: Your answer must be at least 10 words
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: Other usage reporting
|
|
2
|
+
question: Describe the other ways users are notified if usage nears service limits
|
|
3
|
+
|
|
4
|
+
hidden: true
|
|
5
|
+
depends:
|
|
6
|
+
- "on": lot
|
|
7
|
+
being:
|
|
8
|
+
- cloud-hosting
|
|
9
|
+
|
|
10
|
+
type: textbox_large
|
|
11
|
+
max_length_in_words: 200
|
|
12
|
+
|
|
13
|
+
validations:
|
|
14
|
+
- name: answer_required
|
|
15
|
+
message: You need to answer this question because you selected ‘other’ for ‘How are users notified if usage nears service limits?’
|
|
16
|
+
- name: under_200_words
|
|
17
|
+
message: Your answer must be no more than 200 words
|
|
18
|
+
- name: under_character_limit
|
|
19
|
+
message: Your answer must be 2000 characters or fewer
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: Wellbeing
|
|
2
2
|
question: Wellbeing
|
|
3
|
-
|
|
3
|
+
hint: Your answer must be at least 10 words
|
|
4
4
|
depends:
|
|
5
5
|
- "on": lot
|
|
6
6
|
being:
|
|
@@ -10,6 +10,7 @@ depends:
|
|
|
10
10
|
|
|
11
11
|
type: textbox_large
|
|
12
12
|
max_length_in_words: 300
|
|
13
|
+
min_length_in_words: 10
|
|
13
14
|
hidden: true
|
|
14
15
|
|
|
15
16
|
validations:
|
|
@@ -19,3 +20,5 @@ validations:
|
|
|
19
20
|
message: Your answer must be no more than 300 words
|
|
20
21
|
- name: under_character_limit
|
|
21
22
|
message: Description must be 3000 characters or fewer
|
|
23
|
+
- name: over_10_words
|
|
24
|
+
message: Your answer must be at least 10 words
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccs-digitalmarketplace-frameworks",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.32",
|
|
4
4
|
"description": "Data files for Digital Marketplace’s procurement frameworks",
|
|
5
5
|
"repository": "git@github.com:Crown-Commercial-Service/ccs-digitalmarketplace-frameworks",
|
|
6
6
|
"author": "enquiries@digitalmarketplace.service.gov.uk"
|
package/requirements-dev.txt
CHANGED
|
@@ -60,7 +60,7 @@ contextlib2==21.6.0
|
|
|
60
60
|
# via
|
|
61
61
|
# -r requirements.txt
|
|
62
62
|
# ccs-digitalmarketplace-utils
|
|
63
|
-
cryptography==41.0.
|
|
63
|
+
cryptography==41.0.6
|
|
64
64
|
# via
|
|
65
65
|
# -r requirements.txt
|
|
66
66
|
# ccs-digitalmarketplace-utils
|
|
@@ -120,7 +120,7 @@ govuk-country-register==0.5.0
|
|
|
120
120
|
# via
|
|
121
121
|
# -r requirements.txt
|
|
122
122
|
# ccs-digitalmarketplace-utils
|
|
123
|
-
hypothesis==6.
|
|
123
|
+
hypothesis==6.92.0
|
|
124
124
|
# via -r requirements-dev.in
|
|
125
125
|
idna==3.4
|
|
126
126
|
# via
|
package/requirements.txt
CHANGED
|
@@ -575,14 +575,21 @@ def parse_question_limits(question, for_items=False):
|
|
|
575
575
|
Converts word and character length validators into JSON Schema-compatible maxLength and regex validators.
|
|
576
576
|
"""
|
|
577
577
|
limits = {}
|
|
578
|
-
|
|
578
|
+
word_max_length_validator = next(
|
|
579
579
|
iter(filter(None, (
|
|
580
580
|
re.match(r'under_(\d+)_words', validator['name'])
|
|
581
581
|
for validator in question.get('validations', [])
|
|
582
582
|
))),
|
|
583
583
|
None
|
|
584
584
|
)
|
|
585
|
-
|
|
585
|
+
word_min_length_validator = next(
|
|
586
|
+
iter(filter(None, (
|
|
587
|
+
re.match(r'over_(\d+)_words', validator['name'])
|
|
588
|
+
for validator in question.get('validations', [])
|
|
589
|
+
))),
|
|
590
|
+
None
|
|
591
|
+
)
|
|
592
|
+
char_max_length_validator = next(
|
|
586
593
|
iter(filter(None, (
|
|
587
594
|
re.search(r'([\d,]+)', validator['message'])
|
|
588
595
|
for validator in question.get('validations', [])
|
|
@@ -592,18 +599,27 @@ def parse_question_limits(question, for_items=False):
|
|
|
592
599
|
)
|
|
593
600
|
|
|
594
601
|
char_length = question.get('max_length') or (
|
|
595
|
-
|
|
602
|
+
char_max_length_validator and char_max_length_validator.group(1).replace(',', '')
|
|
603
|
+
)
|
|
604
|
+
word_max_length = (
|
|
605
|
+
question.get('max_length_in_words')
|
|
606
|
+
or (word_max_length_validator and word_max_length_validator.group(1))
|
|
596
607
|
)
|
|
597
|
-
word_length = question.get('max_length_in_words') or (word_length_validator and word_length_validator.group(1))
|
|
598
608
|
|
|
599
609
|
if char_length:
|
|
600
610
|
limits['maxLength'] = int(char_length)
|
|
601
611
|
|
|
602
|
-
if
|
|
612
|
+
if word_max_length:
|
|
613
|
+
word_min_length = int(
|
|
614
|
+
question.get('min_length_in_words')
|
|
615
|
+
or (word_min_length_validator and word_min_length_validator.group(1))
|
|
616
|
+
or 1
|
|
617
|
+
) - 1
|
|
618
|
+
|
|
603
619
|
if not for_items and question.get('optional'):
|
|
604
|
-
limits['pattern'] =
|
|
620
|
+
limits['pattern'] = rf"^$|(^(?:\S+\s+){{{word_min_length},{int(word_max_length) - 1}}}\S+$)"
|
|
605
621
|
else:
|
|
606
|
-
limits['pattern'] =
|
|
622
|
+
limits['pattern'] = rf"^(?:\S+\s+){{{word_min_length},{int(word_max_length) - 1}}}\S+$"
|
|
607
623
|
|
|
608
624
|
return limits
|
|
609
625
|
|
package/schemas/questions.json
CHANGED
|
@@ -160,6 +160,7 @@
|
|
|
160
160
|
"not_a_number",
|
|
161
161
|
"not_money_format",
|
|
162
162
|
"not_required_value",
|
|
163
|
+
"over_10_words",
|
|
163
164
|
"under_10_words",
|
|
164
165
|
"under_20_words",
|
|
165
166
|
"under_30_words",
|
|
@@ -249,6 +250,9 @@
|
|
|
249
250
|
"decimal_place_restriction": {
|
|
250
251
|
"type": "boolean"
|
|
251
252
|
},
|
|
253
|
+
"min_length_in_words": {
|
|
254
|
+
"type": "integer"
|
|
255
|
+
},
|
|
252
256
|
"max_length_in_words": {
|
|
253
257
|
"type": "integer"
|
|
254
258
|
},
|
|
@@ -146,11 +146,13 @@ def test_uri_property(id_name):
|
|
|
146
146
|
def test_parse_question_limits_word_count(num):
|
|
147
147
|
assume(not isnan(num))
|
|
148
148
|
|
|
149
|
-
question = {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
149
|
+
question = {
|
|
150
|
+
"validations": [
|
|
151
|
+
{
|
|
152
|
+
"name": "under_{0}_words".format(num)
|
|
153
|
+
}
|
|
154
|
+
]
|
|
155
|
+
}
|
|
154
156
|
result = parse_question_limits(question)
|
|
155
157
|
assert 'pattern' in result.keys()
|
|
156
158
|
expected = '^(?:\\S+\\s+){0,' + str(num - 1) + '}\\S+$'
|
|
@@ -169,6 +171,39 @@ def test_parse_question_limits_word_count(num):
|
|
|
169
171
|
assert m is None
|
|
170
172
|
|
|
171
173
|
|
|
174
|
+
@given(st.integers(min_value=1, max_value=65534))
|
|
175
|
+
def test_parse_question_requires_minimum_word_count(num):
|
|
176
|
+
assume(not isnan(num))
|
|
177
|
+
|
|
178
|
+
question = {
|
|
179
|
+
"validations": [
|
|
180
|
+
{
|
|
181
|
+
"name": "over_{0}_words".format(num),
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"name": "under_{0}_words".format(num + 10)
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
result = parse_question_limits(question)
|
|
189
|
+
assert 'pattern' in result.keys()
|
|
190
|
+
expected = '^(?:\\S+\\s+){' + str(num - 1) + ',' + str(num + 9) + '}\\S+$'
|
|
191
|
+
assert result['pattern'] == expected
|
|
192
|
+
matcher = re.compile(result['pattern'])
|
|
193
|
+
|
|
194
|
+
# Now test the regex works!
|
|
195
|
+
range_list = range(num)
|
|
196
|
+
generated_str = ' '.join(['a' for x in range_list])
|
|
197
|
+
m = re.search(matcher, generated_str)
|
|
198
|
+
assert m is not None
|
|
199
|
+
|
|
200
|
+
# now with one that's too short
|
|
201
|
+
range_list = range(num - 1)
|
|
202
|
+
generated_str = ' '.join(['a' for x in range_list])
|
|
203
|
+
m = re.search(matcher, generated_str)
|
|
204
|
+
assert m is None
|
|
205
|
+
|
|
206
|
+
|
|
172
207
|
@given(st.integers(min_value=1, max_value=65534))
|
|
173
208
|
def test_parse_question_limits_word_count_optional(num):
|
|
174
209
|
assume(not isnan(num))
|
|
@@ -191,6 +226,31 @@ def test_parse_question_limits_word_count_optional(num):
|
|
|
191
226
|
assert m is not None
|
|
192
227
|
|
|
193
228
|
|
|
229
|
+
@given(st.integers(min_value=1, max_value=65534))
|
|
230
|
+
def test_parse_question_requires_minimum_word_count_optional(num):
|
|
231
|
+
assume(not isnan(num))
|
|
232
|
+
|
|
233
|
+
question = {
|
|
234
|
+
"validations": [
|
|
235
|
+
{
|
|
236
|
+
"name": "over_{0}_words".format(num),
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"name": "under_{0}_words".format(num + 10)
|
|
240
|
+
}
|
|
241
|
+
],
|
|
242
|
+
"optional": True
|
|
243
|
+
}
|
|
244
|
+
result = parse_question_limits(question)
|
|
245
|
+
assert 'pattern' in result.keys()
|
|
246
|
+
assert result['pattern'].startswith("^$")
|
|
247
|
+
|
|
248
|
+
# test empty condition
|
|
249
|
+
matcher = re.compile(result['pattern'])
|
|
250
|
+
m = re.search(matcher, "")
|
|
251
|
+
assert m is not None
|
|
252
|
+
|
|
253
|
+
|
|
194
254
|
@given(st.integers(min_value=1))
|
|
195
255
|
def test_parse_question_limits_char_count(num):
|
|
196
256
|
assume(not isnan(num))
|