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.
Files changed (27) hide show
  1. package/.github/workflows/test.yml +2 -2
  2. package/frameworks/g-cloud-14/manifests/display_service.yml +1 -0
  3. package/frameworks/g-cloud-14/questions/declaration/authOrgLegalRequirementCompliant.yml +1 -1
  4. package/frameworks/g-cloud-14/questions/declaration/immediateParentCompanyDetailsVATNumber.yml +1 -0
  5. package/frameworks/g-cloud-14/questions/declaration/mandatoryExclusionTaxes.yml +1 -1
  6. package/frameworks/g-cloud-14/questions/declaration/{modernSlaveryComplient.yml → modernSlaveryCompliant.yml} +1 -1
  7. package/frameworks/g-cloud-14/questions/declaration/multiqModernSlaveryRelevant.yml +1 -1
  8. package/frameworks/g-cloud-14/questions/declaration/pscDetails1Conditions.yml +2 -2
  9. package/frameworks/g-cloud-14/questions/declaration/pscDetails2Conditions.yml +2 -2
  10. package/frameworks/g-cloud-14/questions/declaration/pscDetails3Conditions.yml +2 -2
  11. package/frameworks/g-cloud-14/questions/declaration/pscDetails4Conditions.yml +2 -2
  12. package/frameworks/g-cloud-14/questions/declaration/ultimateParentCompanyDetailsVATNumber.yml +1 -0
  13. package/frameworks/g-cloud-14/questions/declaration/vatNumber.yml +2 -1
  14. package/frameworks/g-cloud-14/questions/services/covid19Recovery.yml +4 -1
  15. package/frameworks/g-cloud-14/questions/services/equalOpportunity.yml +4 -1
  16. package/frameworks/g-cloud-14/questions/services/fightingClimateChange.yml +4 -1
  17. package/frameworks/g-cloud-14/questions/services/multiqUsageNotifications.yml +1 -0
  18. package/frameworks/g-cloud-14/questions/services/tacklingEconomicInequality.yml +4 -1
  19. package/frameworks/g-cloud-14/questions/services/usageNotificationsHow.yml +3 -0
  20. package/frameworks/g-cloud-14/questions/services/usageNotificationsHowOther.yml +19 -0
  21. package/frameworks/g-cloud-14/questions/services/wellbeing.yml +4 -1
  22. package/package.json +1 -1
  23. package/requirements-dev.txt +2 -2
  24. package/requirements.txt +1 -1
  25. package/schema_generator/validation.py +23 -7
  26. package/schemas/questions.json +4 -0
  27. 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@v4
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@v4
48
+ uses: actions/setup-python@v5
49
49
  with:
50
50
  python-version: ${{ matrix.python-version }}
51
51
 
@@ -116,6 +116,7 @@
116
116
  - independenceOfResources
117
117
  - usageNotifications
118
118
  - usageNotificationsHow
119
+ - usageNotificationsHowOther
119
120
 
120
121
  - name: Analytics
121
122
  questions:
@@ -1,4 +1,4 @@
1
- name: Confirm you are complient
1
+ name: Confirm you are compliant
2
2
  question: Please provide confirmation that you comply with what is required
3
3
  type: boolean
4
4
  hidden: true
@@ -1,5 +1,6 @@
1
1
  name: VAT number
2
2
  question: VAT number
3
+ hint: This is 9 or 12 numbers, sometimes with ‘GB’ at the start, like 123456789 or GB123456789
3
4
  type: text
4
5
  optional: true
5
6
 
@@ -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 complient
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
@@ -4,7 +4,7 @@ type: multiquestion
4
4
  hidden: true
5
5
 
6
6
  questions:
7
- - modernSlaveryComplient
7
+ - modernSlaveryCompliant
8
8
  - modernSlaveryElectronicAvailable
9
9
  - modernSlaveryElectronicWebAddress
10
10
  - modernSlaveryElectronicReference
@@ -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: "Over 25% up to (and including) 50%"
5
+ - label: "25% or more and less than 50%"
6
6
  value: "more than 25%"
7
- - label: "More than 50% and less than 75%"
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: "Over 25% up to (and including) 50%"
5
+ - label: "25% or more and less than 50%"
6
6
  value: "more than 25%"
7
- - label: "More than 50% and less than 75%"
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: "Over 25% up to (and including) 50%"
5
+ - label: "25% or more and less than 50%"
6
6
  value: "more than 25%"
7
- - label: "More than 50% and less than 75%"
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: "Over 25% up to (and including) 50%"
5
+ - label: "25% or more and less than 50%"
6
6
  value: "more than 25%"
7
- - label: "More than 50% and less than 75%"
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,5 +1,6 @@
1
1
  name: VAT number
2
2
  question: VAT number
3
+ hint: This is 9 or 12 numbers, sometimes with ‘GB’ at the start, like 123456789 or GB123456789
3
4
  type: text
4
5
  optional: true
5
6
 
@@ -1,6 +1,7 @@
1
1
  name: Registered VAT number
2
2
  question: What is your registered VAT number?
3
- hint: 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
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
@@ -10,3 +10,4 @@ type: multiquestion
10
10
  questions:
11
11
  - usageNotifications
12
12
  - usageNotificationsHow
13
+ - usageNotificationsHowOther
@@ -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
@@ -6,6 +6,9 @@ depends:
6
6
  - "on": lot
7
7
  being:
8
8
  - cloud-hosting
9
+ followup:
10
+ usageNotificationsHowOther:
11
+ - other
9
12
 
10
13
  type: checkboxes
11
14
  options:
@@ -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.30",
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"
@@ -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.5
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.90.0
123
+ hypothesis==6.92.0
124
124
  # via -r requirements-dev.in
125
125
  idna==3.4
126
126
  # via
package/requirements.txt CHANGED
@@ -34,7 +34,7 @@ click==7.1.2
34
34
  # via flask
35
35
  contextlib2==21.6.0
36
36
  # via ccs-digitalmarketplace-utils
37
- cryptography==41.0.5
37
+ cryptography==41.0.6
38
38
  # via ccs-digitalmarketplace-utils
39
39
  deepmerge==1.1.0
40
40
  # via -r requirements.in
@@ -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
- word_length_validator = next(
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
- char_length_validator = next(
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
- char_length_validator and char_length_validator.group(1).replace(',', '')
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 word_length:
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'] = r"^$|(^(?:\S+\s+){0,%s}\S+$)" % (int(word_length) - 1)
620
+ limits['pattern'] = rf"^$|(^(?:\S+\s+){{{word_min_length},{int(word_max_length) - 1}}}\S+$)"
605
621
  else:
606
- limits['pattern'] = r"^(?:\S+\s+){0,%s}\S+$" % (int(word_length) - 1)
622
+ limits['pattern'] = rf"^(?:\S+\s+){{{word_min_length},{int(word_max_length) - 1}}}\S+$"
607
623
 
608
624
  return limits
609
625
 
@@ -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 = {"validations": [
150
- {
151
- "name": "under_{0}_words".format(num)
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))