on-notice 0.1.0__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.
- on_notice-0.1.0/LICENSE +21 -0
- on_notice-0.1.0/PKG-INFO +245 -0
- on_notice-0.1.0/README.md +222 -0
- on_notice-0.1.0/on_notice/__init__.py +14 -0
- on_notice-0.1.0/on_notice/__main__.py +6 -0
- on_notice-0.1.0/on_notice/cli.py +263 -0
- on_notice-0.1.0/on_notice/ingredients.py +57 -0
- on_notice-0.1.0/on_notice/register.json +3614 -0
- on_notice-0.1.0/on_notice/register.py +233 -0
- on_notice-0.1.0/on_notice.egg-info/PKG-INFO +245 -0
- on_notice-0.1.0/on_notice.egg-info/SOURCES.txt +16 -0
- on_notice-0.1.0/on_notice.egg-info/dependency_links.txt +1 -0
- on_notice-0.1.0/on_notice.egg-info/entry_points.txt +2 -0
- on_notice-0.1.0/on_notice.egg-info/top_level.txt +1 -0
- on_notice-0.1.0/pyproject.toml +37 -0
- on_notice-0.1.0/setup.cfg +4 -0
- on_notice-0.1.0/tests/test_builder.py +146 -0
- on_notice-0.1.0/tests/test_cli.py +274 -0
on_notice-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Waiga Arya
|
|
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.
|
on_notice-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: on-notice
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Which cosmetic ingredients have an EU deadline that has not arrived yet
|
|
5
|
+
Author: Waiga Arya
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: cosmetics,regulation,eu,inci,eur-lex
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Manufacturing
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
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 :: Utilities
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# On Notice
|
|
25
|
+
|
|
26
|
+
Reads a cosmetic ingredient list and reports which ingredients on it are named in
|
|
27
|
+
an EU rule that carries a date still ahead of it, and what that date is.
|
|
28
|
+
|
|
29
|
+
It does not say whether a product may be sold. It reports dates.
|
|
30
|
+
|
|
31
|
+
## Why this one exists
|
|
32
|
+
|
|
33
|
+
The other tools in this set check what is wrong with a document today. A cosmetic
|
|
34
|
+
reformulation takes the better part of a year, so the question that actually costs
|
|
35
|
+
money is the other one: what becomes wrong while the stock is still on the shelf.
|
|
36
|
+
|
|
37
|
+
Regulation (EU) 2026/909 was adopted on 27 April 2026. It restricts Benzyl
|
|
38
|
+
Salicylate in fragrance products to 4 per cent, with lower limits in eight other
|
|
39
|
+
product types, and the date it bites is 1 January 2027. A product on a shelf
|
|
40
|
+
today can be entirely lawful and already be on a clock, and nothing on the pack
|
|
41
|
+
says so.
|
|
42
|
+
|
|
43
|
+
## Use
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
on-notice ingredients.txt
|
|
47
|
+
on-notice --unresolved # everything the register could not settle
|
|
48
|
+
cat list.txt | on-notice -
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Exit code is 1 when something on the list has a date, 0 when nothing does, 2 when
|
|
52
|
+
the register cannot be read.
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
on-notice 0.1.0 register built 2026-09-18
|
|
56
|
+
read 5 ingredient(s) from night-cream.txt
|
|
57
|
+
|
|
58
|
+
2 of them are named in an EU rule with a date still ahead of it.
|
|
59
|
+
|
|
60
|
+
1 January 2027 products that do not meet the new restriction cannot be placed on the Union market
|
|
61
|
+
Benzyl Salicylate
|
|
62
|
+
on the label as Benzyl Salicylate
|
|
63
|
+
named in Commission Regulation (EU) 2026/909, Annex III entry 75
|
|
64
|
+
the rule says From 1 January 2027 cosmetic products containing these substances and not complying with the restrictions shall not be placed on the Union market.
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The register ships inside the package. Nothing leaves your machine.
|
|
68
|
+
|
|
69
|
+
## What it was measured against
|
|
70
|
+
|
|
71
|
+
The corpus is the Open Beauty Facts CSV export, `en.openbeautyfacts.org.products.csv.gz`,
|
|
72
|
+
the copy generated on 18 September 2026 at 00:22 GMT, 64,237 product rows.
|
|
73
|
+
`scripts/measure_corpus.py` reproduces every number below from it. 17,626 of those
|
|
74
|
+
rows carry an ingredient list. 31 of those lists yielded no readable ingredient
|
|
75
|
+
names at all, leaving **17,595 lists read**, median 17 ingredients each. Nothing
|
|
76
|
+
in it was written by this tool or by anybody connected to it.
|
|
77
|
+
|
|
78
|
+
**3,050 of the 17,595, or 17.3 per cent, name at least one substance carrying a
|
|
79
|
+
date that has not arrived.** 2,703 name one such substance, 334 name two, 13 name
|
|
80
|
+
three.
|
|
81
|
+
|
|
82
|
+
Counted by the number of lists naming each:
|
|
83
|
+
|
|
84
|
+
| Substance | Lists | Date | What the date is |
|
|
85
|
+
|---|---:|---|---|
|
|
86
|
+
| Benzyl Salicylate | 1,614 | 1 January 2027 | new restriction starts |
|
|
87
|
+
| Citral | 1,416 | 1 January 2027 | new restriction starts |
|
|
88
|
+
| Diethylamino Hydroxybenzoyl Hexyl Benzoate | 190 | 1 January 2027 | new restriction starts |
|
|
89
|
+
| Pinene | 77 | 1 August 2028 | already in force, sell-through ends |
|
|
90
|
+
| Methyl Salicylate | 56 | 1 August 2028 | already in force, sell-through ends |
|
|
91
|
+
| Retinol | 47 | 1 May 2027 | already in force, sell-through ends |
|
|
92
|
+
| Zinc Acetate | 4 | 1 January 2027 | new restriction starts |
|
|
93
|
+
| Triphenyl Phosphate | 3 | 1 January 2027 | prohibition starts |
|
|
94
|
+
| Geranial | 3 | 1 January 2027 | new restriction starts |
|
|
95
|
+
|
|
96
|
+
Nine of the register's fourteen substances were seen at least once. The other five
|
|
97
|
+
appear in no list it read, which is a fact about this corpus and not evidence that
|
|
98
|
+
nothing uses them.
|
|
99
|
+
|
|
100
|
+
**The corpus is not a sample of the EU market.** Open Beauty Facts is a global,
|
|
101
|
+
volunteer-contributed database with a heavy western European skew. It is what was
|
|
102
|
+
available and reproducible, not a representative frame, and the percentage above
|
|
103
|
+
should be read as a property of that corpus rather than of the market the rules
|
|
104
|
+
govern.
|
|
105
|
+
|
|
106
|
+
## The first number this tool produced was wrong three times, each time in its own favour
|
|
107
|
+
|
|
108
|
+
The first complete run reported **44.0 per cent**. Then **18.6 per cent**. The
|
|
109
|
+
number that survived scrutiny is **17.3 per cent**. Every correction moved it
|
|
110
|
+
down, which is to say every error made the tool look more impressive than it was.
|
|
111
|
+
|
|
112
|
+
**First error: it counted compliance as exposure.** Around fifty of the substances
|
|
113
|
+
in the first register came from Regulation (EU) 2023/1545, titled "as regards
|
|
114
|
+
labelling of fragrance allergens". Quoted from Annex III as it amends it:
|
|
115
|
+
|
|
116
|
+
> The presence of the substance shall be indicated in the list of ingredients
|
|
117
|
+
> referred to in Article 19(1), point (g), when its concentration exceeds:
|
|
118
|
+
> 0,001 % in leave-on products, 0,01 % in rinse-off products.
|
|
119
|
+
|
|
120
|
+
The duty is to **name the substance in the list of ingredients**. So when a tool
|
|
121
|
+
that reads printed ingredient names found Limonene on 5,301 labels, it had found
|
|
122
|
+
5,301 products doing the thing the rule asks for. The products actually at risk
|
|
123
|
+
print only `parfum` and never name the allergen, and no check that reads printed
|
|
124
|
+
names can see a single one of them. The finding pointed the opposite way from
|
|
125
|
+
what a reader would have assumed.
|
|
126
|
+
|
|
127
|
+
**Second error, fixing the first: it threw away a real restriction.** Annex III
|
|
128
|
+
entry 75 as replaced by Regulation 2026/909 carries **both** a 4 per cent maximum
|
|
129
|
+
in fragrance products and the labelling sentence. A first fix matched the
|
|
130
|
+
labelling phrase and filed the whole entry as a labelling duty, discarding a real
|
|
131
|
+
restriction on 1,614 products. Same error, sign reversed.
|
|
132
|
+
|
|
133
|
+
**Third error: the fix had a hole big enough for ten entries.** The strip matched
|
|
134
|
+
only "The presence of **the substance** shall be indicated". Many entries read
|
|
135
|
+
"The presence of the substance **or substances** shall be indicated **as
|
|
136
|
+
'Eucalyptus Globulus Oil'**". Those were never stripped, so the 0,001 per cent
|
|
137
|
+
labelling threshold inside the sentence was read as a concentration limit and ten
|
|
138
|
+
labelling-only entries shipped as findings. One of them, Eucalyptus Globulus Leaf
|
|
139
|
+
Oil at 94 lists, was the fourth row of an earlier draft of the table above.
|
|
140
|
+
|
|
141
|
+
Entries are now classified from their own text, three ways rather than two. The
|
|
142
|
+
labelling sentence is removed; a maximum concentration or permitted product type
|
|
143
|
+
surviving that removal makes it a restriction on use. An entry in Annex II is a
|
|
144
|
+
prohibition and needs no conditions column. An entry whose restriction text was
|
|
145
|
+
never recovered is reported as neither, because promoting it invents a finding
|
|
146
|
+
and dropping it hides a date.
|
|
147
|
+
|
|
148
|
+
A separate defect from the same audit: the chemical name
|
|
149
|
+
`1-(2,6,6-trimethyl-2-cyclohexen-1-yl)-2-buten-1-one (16)` had its trailing
|
|
150
|
+
bracket split off as a synonym, producing a matchable substance named `16`. Any
|
|
151
|
+
label carrying a bare 16 would have matched it.
|
|
152
|
+
|
|
153
|
+
## What it cannot tell you
|
|
154
|
+
|
|
155
|
+
**It cannot tell you whether your product complies.** Every restriction here
|
|
156
|
+
catches products that do not meet a new condition, and those conditions are
|
|
157
|
+
concentrations and product types. A printed ingredient list states no
|
|
158
|
+
concentrations. The tool can tell you a date applies to an ingredient you use. It
|
|
159
|
+
cannot tell you which side of it you are on. The rule text is printed with every
|
|
160
|
+
finding so you can check it yourself.
|
|
161
|
+
|
|
162
|
+
**Some conditions are not formulation choices at all.** The 1 January 2027
|
|
163
|
+
condition on Diethylamino Hydroxybenzoyl Hexyl Benzoate is a limit of 10 ppm on a
|
|
164
|
+
trace impurity in the filter you buy. Several entries turn on a peroxide value.
|
|
165
|
+
Those are answered by a supplier certificate, not by a recipe.
|
|
166
|
+
|
|
167
|
+
**Some of these rules are already in force.** Regulation 2023/1545's placing limb
|
|
168
|
+
passed on 31 July 2026 and Regulation 2024/996's on 1 November 2025. What remains
|
|
169
|
+
is the sell-through window. Seven of the 21 shipped entries are in that state and
|
|
170
|
+
are labelled as such on every run, because calling a rule already in force "not
|
|
171
|
+
yet applying" is simply false.
|
|
172
|
+
|
|
173
|
+
**It cannot see the products at risk under a labelling rule.** 50 substances carry
|
|
174
|
+
a dated duty to be named on the pack above a threshold. A list naming one is
|
|
175
|
+
meeting that duty. The list that omits it is the one at risk and is invisible
|
|
176
|
+
here. These are reported separately on every run and never counted.
|
|
177
|
+
|
|
178
|
+
**The percentage understates in one direction as well as overstating in the
|
|
179
|
+
other.** Annex III entry 379 as inserted by Regulation 2026/909 covers
|
|
180
|
+
"Aluminium-containing ingredients with the exception of those listed in entries
|
|
181
|
+
34, 50, 189, 190 and 192 of Annex III...", with the same 1 January 2027 date.
|
|
182
|
+
There is no honest way to match a class with an exception list against a printed
|
|
183
|
+
name, so it is held back. Aluminium compounds are among the most common things in
|
|
184
|
+
antiperspirants and colour cosmetics, so the true number of products in this
|
|
185
|
+
corpus carrying a 1 January 2027 obligation is materially higher than 17.3 per
|
|
186
|
+
cent. Both sentences belong with the number.
|
|
187
|
+
|
|
188
|
+
**Three entries had no restriction text recoverable.** Several Annex III entries
|
|
189
|
+
print across multiple rows with the conditions on a parent row. Those three are
|
|
190
|
+
reported and never matched.
|
|
191
|
+
|
|
192
|
+
**One sentence was not interpreted.** A sentence mentioning a date and the Union
|
|
193
|
+
market in wording the builder does not recognise is recorded verbatim rather than
|
|
194
|
+
guessed at. `on-notice --unresolved` prints it.
|
|
195
|
+
|
|
196
|
+
**The register is a scan, not the law.** It covers Commission regulations amending
|
|
197
|
+
Regulation (EC) No 1223/2009 from 2013 onward, read from EUR-Lex: 52 listed, 52
|
|
198
|
+
read, 0 that could not be downloaded. 120 dated provisions found, 703 substance
|
|
199
|
+
links made, of which 21 are dated after the build and shipped. 9 provisions could
|
|
200
|
+
not be tied to named substances; every one has a date already in the past, so none
|
|
201
|
+
reaches the shipped register. National rules, rules outside the Cosmetics
|
|
202
|
+
Regulation, and anything adopted after the build date are outside it.
|
|
203
|
+
|
|
204
|
+
**A quiet answer is not a clean bill.** The tool prints what it could not see on
|
|
205
|
+
every run, including runs that find nothing.
|
|
206
|
+
|
|
207
|
+
## How the register is built
|
|
208
|
+
|
|
209
|
+
`scripts/build_register.py` reads each regulation from EUR-Lex, parses the annex
|
|
210
|
+
tables, links footnote markers to the entries carrying them, and extracts the
|
|
211
|
+
dates. Four things about those documents broke a first attempt, all silently:
|
|
212
|
+
|
|
213
|
+
- Regulation 2023/1545 states its deadline as "may be placed on the Union market
|
|
214
|
+
until 31 July 2026", not as "shall not be". A parser looking only for the
|
|
215
|
+
negative form reports nothing for every substance in it.
|
|
216
|
+
- A footnote can put two dates and two restrictions in consecutive sentences. A
|
|
217
|
+
pattern crossing the sentence boundary pairs the first date with the second
|
|
218
|
+
restriction, and every sell-through date comes out wrong.
|
|
219
|
+
- EUR-Lex serves two different markups for footnotes and prints the same marker
|
|
220
|
+
three ways.
|
|
221
|
+
- Annex attribution reads from lead-in wording that takes several forms, so
|
|
222
|
+
entries were filed under the wrong annex while looking entirely correct.
|
|
223
|
+
|
|
224
|
+
One fetch returned HTTP 200 with an empty body, and the build reported it as a
|
|
225
|
+
regulation read with no deadlines in it, which is indistinguishable from the
|
|
226
|
+
truth. Responses are now checked for Official Journal markup before being
|
|
227
|
+
accepted, and a bad one is recorded as a failure rather than cached.
|
|
228
|
+
|
|
229
|
+
`docs/how-it-reads-the-source.md` records what these documents actually look like.
|
|
230
|
+
|
|
231
|
+
## Install
|
|
232
|
+
|
|
233
|
+
Not published to the package index.
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
git clone https://github.com/Waiga/on-notice
|
|
237
|
+
cd on-notice
|
|
238
|
+
pip install -e .
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Python 3.9 or later. No dependencies.
|
|
242
|
+
|
|
243
|
+
## Licence
|
|
244
|
+
|
|
245
|
+
MIT.
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# On Notice
|
|
2
|
+
|
|
3
|
+
Reads a cosmetic ingredient list and reports which ingredients on it are named in
|
|
4
|
+
an EU rule that carries a date still ahead of it, and what that date is.
|
|
5
|
+
|
|
6
|
+
It does not say whether a product may be sold. It reports dates.
|
|
7
|
+
|
|
8
|
+
## Why this one exists
|
|
9
|
+
|
|
10
|
+
The other tools in this set check what is wrong with a document today. A cosmetic
|
|
11
|
+
reformulation takes the better part of a year, so the question that actually costs
|
|
12
|
+
money is the other one: what becomes wrong while the stock is still on the shelf.
|
|
13
|
+
|
|
14
|
+
Regulation (EU) 2026/909 was adopted on 27 April 2026. It restricts Benzyl
|
|
15
|
+
Salicylate in fragrance products to 4 per cent, with lower limits in eight other
|
|
16
|
+
product types, and the date it bites is 1 January 2027. A product on a shelf
|
|
17
|
+
today can be entirely lawful and already be on a clock, and nothing on the pack
|
|
18
|
+
says so.
|
|
19
|
+
|
|
20
|
+
## Use
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
on-notice ingredients.txt
|
|
24
|
+
on-notice --unresolved # everything the register could not settle
|
|
25
|
+
cat list.txt | on-notice -
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Exit code is 1 when something on the list has a date, 0 when nothing does, 2 when
|
|
29
|
+
the register cannot be read.
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
on-notice 0.1.0 register built 2026-09-18
|
|
33
|
+
read 5 ingredient(s) from night-cream.txt
|
|
34
|
+
|
|
35
|
+
2 of them are named in an EU rule with a date still ahead of it.
|
|
36
|
+
|
|
37
|
+
1 January 2027 products that do not meet the new restriction cannot be placed on the Union market
|
|
38
|
+
Benzyl Salicylate
|
|
39
|
+
on the label as Benzyl Salicylate
|
|
40
|
+
named in Commission Regulation (EU) 2026/909, Annex III entry 75
|
|
41
|
+
the rule says From 1 January 2027 cosmetic products containing these substances and not complying with the restrictions shall not be placed on the Union market.
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The register ships inside the package. Nothing leaves your machine.
|
|
45
|
+
|
|
46
|
+
## What it was measured against
|
|
47
|
+
|
|
48
|
+
The corpus is the Open Beauty Facts CSV export, `en.openbeautyfacts.org.products.csv.gz`,
|
|
49
|
+
the copy generated on 18 September 2026 at 00:22 GMT, 64,237 product rows.
|
|
50
|
+
`scripts/measure_corpus.py` reproduces every number below from it. 17,626 of those
|
|
51
|
+
rows carry an ingredient list. 31 of those lists yielded no readable ingredient
|
|
52
|
+
names at all, leaving **17,595 lists read**, median 17 ingredients each. Nothing
|
|
53
|
+
in it was written by this tool or by anybody connected to it.
|
|
54
|
+
|
|
55
|
+
**3,050 of the 17,595, or 17.3 per cent, name at least one substance carrying a
|
|
56
|
+
date that has not arrived.** 2,703 name one such substance, 334 name two, 13 name
|
|
57
|
+
three.
|
|
58
|
+
|
|
59
|
+
Counted by the number of lists naming each:
|
|
60
|
+
|
|
61
|
+
| Substance | Lists | Date | What the date is |
|
|
62
|
+
|---|---:|---|---|
|
|
63
|
+
| Benzyl Salicylate | 1,614 | 1 January 2027 | new restriction starts |
|
|
64
|
+
| Citral | 1,416 | 1 January 2027 | new restriction starts |
|
|
65
|
+
| Diethylamino Hydroxybenzoyl Hexyl Benzoate | 190 | 1 January 2027 | new restriction starts |
|
|
66
|
+
| Pinene | 77 | 1 August 2028 | already in force, sell-through ends |
|
|
67
|
+
| Methyl Salicylate | 56 | 1 August 2028 | already in force, sell-through ends |
|
|
68
|
+
| Retinol | 47 | 1 May 2027 | already in force, sell-through ends |
|
|
69
|
+
| Zinc Acetate | 4 | 1 January 2027 | new restriction starts |
|
|
70
|
+
| Triphenyl Phosphate | 3 | 1 January 2027 | prohibition starts |
|
|
71
|
+
| Geranial | 3 | 1 January 2027 | new restriction starts |
|
|
72
|
+
|
|
73
|
+
Nine of the register's fourteen substances were seen at least once. The other five
|
|
74
|
+
appear in no list it read, which is a fact about this corpus and not evidence that
|
|
75
|
+
nothing uses them.
|
|
76
|
+
|
|
77
|
+
**The corpus is not a sample of the EU market.** Open Beauty Facts is a global,
|
|
78
|
+
volunteer-contributed database with a heavy western European skew. It is what was
|
|
79
|
+
available and reproducible, not a representative frame, and the percentage above
|
|
80
|
+
should be read as a property of that corpus rather than of the market the rules
|
|
81
|
+
govern.
|
|
82
|
+
|
|
83
|
+
## The first number this tool produced was wrong three times, each time in its own favour
|
|
84
|
+
|
|
85
|
+
The first complete run reported **44.0 per cent**. Then **18.6 per cent**. The
|
|
86
|
+
number that survived scrutiny is **17.3 per cent**. Every correction moved it
|
|
87
|
+
down, which is to say every error made the tool look more impressive than it was.
|
|
88
|
+
|
|
89
|
+
**First error: it counted compliance as exposure.** Around fifty of the substances
|
|
90
|
+
in the first register came from Regulation (EU) 2023/1545, titled "as regards
|
|
91
|
+
labelling of fragrance allergens". Quoted from Annex III as it amends it:
|
|
92
|
+
|
|
93
|
+
> The presence of the substance shall be indicated in the list of ingredients
|
|
94
|
+
> referred to in Article 19(1), point (g), when its concentration exceeds:
|
|
95
|
+
> 0,001 % in leave-on products, 0,01 % in rinse-off products.
|
|
96
|
+
|
|
97
|
+
The duty is to **name the substance in the list of ingredients**. So when a tool
|
|
98
|
+
that reads printed ingredient names found Limonene on 5,301 labels, it had found
|
|
99
|
+
5,301 products doing the thing the rule asks for. The products actually at risk
|
|
100
|
+
print only `parfum` and never name the allergen, and no check that reads printed
|
|
101
|
+
names can see a single one of them. The finding pointed the opposite way from
|
|
102
|
+
what a reader would have assumed.
|
|
103
|
+
|
|
104
|
+
**Second error, fixing the first: it threw away a real restriction.** Annex III
|
|
105
|
+
entry 75 as replaced by Regulation 2026/909 carries **both** a 4 per cent maximum
|
|
106
|
+
in fragrance products and the labelling sentence. A first fix matched the
|
|
107
|
+
labelling phrase and filed the whole entry as a labelling duty, discarding a real
|
|
108
|
+
restriction on 1,614 products. Same error, sign reversed.
|
|
109
|
+
|
|
110
|
+
**Third error: the fix had a hole big enough for ten entries.** The strip matched
|
|
111
|
+
only "The presence of **the substance** shall be indicated". Many entries read
|
|
112
|
+
"The presence of the substance **or substances** shall be indicated **as
|
|
113
|
+
'Eucalyptus Globulus Oil'**". Those were never stripped, so the 0,001 per cent
|
|
114
|
+
labelling threshold inside the sentence was read as a concentration limit and ten
|
|
115
|
+
labelling-only entries shipped as findings. One of them, Eucalyptus Globulus Leaf
|
|
116
|
+
Oil at 94 lists, was the fourth row of an earlier draft of the table above.
|
|
117
|
+
|
|
118
|
+
Entries are now classified from their own text, three ways rather than two. The
|
|
119
|
+
labelling sentence is removed; a maximum concentration or permitted product type
|
|
120
|
+
surviving that removal makes it a restriction on use. An entry in Annex II is a
|
|
121
|
+
prohibition and needs no conditions column. An entry whose restriction text was
|
|
122
|
+
never recovered is reported as neither, because promoting it invents a finding
|
|
123
|
+
and dropping it hides a date.
|
|
124
|
+
|
|
125
|
+
A separate defect from the same audit: the chemical name
|
|
126
|
+
`1-(2,6,6-trimethyl-2-cyclohexen-1-yl)-2-buten-1-one (16)` had its trailing
|
|
127
|
+
bracket split off as a synonym, producing a matchable substance named `16`. Any
|
|
128
|
+
label carrying a bare 16 would have matched it.
|
|
129
|
+
|
|
130
|
+
## What it cannot tell you
|
|
131
|
+
|
|
132
|
+
**It cannot tell you whether your product complies.** Every restriction here
|
|
133
|
+
catches products that do not meet a new condition, and those conditions are
|
|
134
|
+
concentrations and product types. A printed ingredient list states no
|
|
135
|
+
concentrations. The tool can tell you a date applies to an ingredient you use. It
|
|
136
|
+
cannot tell you which side of it you are on. The rule text is printed with every
|
|
137
|
+
finding so you can check it yourself.
|
|
138
|
+
|
|
139
|
+
**Some conditions are not formulation choices at all.** The 1 January 2027
|
|
140
|
+
condition on Diethylamino Hydroxybenzoyl Hexyl Benzoate is a limit of 10 ppm on a
|
|
141
|
+
trace impurity in the filter you buy. Several entries turn on a peroxide value.
|
|
142
|
+
Those are answered by a supplier certificate, not by a recipe.
|
|
143
|
+
|
|
144
|
+
**Some of these rules are already in force.** Regulation 2023/1545's placing limb
|
|
145
|
+
passed on 31 July 2026 and Regulation 2024/996's on 1 November 2025. What remains
|
|
146
|
+
is the sell-through window. Seven of the 21 shipped entries are in that state and
|
|
147
|
+
are labelled as such on every run, because calling a rule already in force "not
|
|
148
|
+
yet applying" is simply false.
|
|
149
|
+
|
|
150
|
+
**It cannot see the products at risk under a labelling rule.** 50 substances carry
|
|
151
|
+
a dated duty to be named on the pack above a threshold. A list naming one is
|
|
152
|
+
meeting that duty. The list that omits it is the one at risk and is invisible
|
|
153
|
+
here. These are reported separately on every run and never counted.
|
|
154
|
+
|
|
155
|
+
**The percentage understates in one direction as well as overstating in the
|
|
156
|
+
other.** Annex III entry 379 as inserted by Regulation 2026/909 covers
|
|
157
|
+
"Aluminium-containing ingredients with the exception of those listed in entries
|
|
158
|
+
34, 50, 189, 190 and 192 of Annex III...", with the same 1 January 2027 date.
|
|
159
|
+
There is no honest way to match a class with an exception list against a printed
|
|
160
|
+
name, so it is held back. Aluminium compounds are among the most common things in
|
|
161
|
+
antiperspirants and colour cosmetics, so the true number of products in this
|
|
162
|
+
corpus carrying a 1 January 2027 obligation is materially higher than 17.3 per
|
|
163
|
+
cent. Both sentences belong with the number.
|
|
164
|
+
|
|
165
|
+
**Three entries had no restriction text recoverable.** Several Annex III entries
|
|
166
|
+
print across multiple rows with the conditions on a parent row. Those three are
|
|
167
|
+
reported and never matched.
|
|
168
|
+
|
|
169
|
+
**One sentence was not interpreted.** A sentence mentioning a date and the Union
|
|
170
|
+
market in wording the builder does not recognise is recorded verbatim rather than
|
|
171
|
+
guessed at. `on-notice --unresolved` prints it.
|
|
172
|
+
|
|
173
|
+
**The register is a scan, not the law.** It covers Commission regulations amending
|
|
174
|
+
Regulation (EC) No 1223/2009 from 2013 onward, read from EUR-Lex: 52 listed, 52
|
|
175
|
+
read, 0 that could not be downloaded. 120 dated provisions found, 703 substance
|
|
176
|
+
links made, of which 21 are dated after the build and shipped. 9 provisions could
|
|
177
|
+
not be tied to named substances; every one has a date already in the past, so none
|
|
178
|
+
reaches the shipped register. National rules, rules outside the Cosmetics
|
|
179
|
+
Regulation, and anything adopted after the build date are outside it.
|
|
180
|
+
|
|
181
|
+
**A quiet answer is not a clean bill.** The tool prints what it could not see on
|
|
182
|
+
every run, including runs that find nothing.
|
|
183
|
+
|
|
184
|
+
## How the register is built
|
|
185
|
+
|
|
186
|
+
`scripts/build_register.py` reads each regulation from EUR-Lex, parses the annex
|
|
187
|
+
tables, links footnote markers to the entries carrying them, and extracts the
|
|
188
|
+
dates. Four things about those documents broke a first attempt, all silently:
|
|
189
|
+
|
|
190
|
+
- Regulation 2023/1545 states its deadline as "may be placed on the Union market
|
|
191
|
+
until 31 July 2026", not as "shall not be". A parser looking only for the
|
|
192
|
+
negative form reports nothing for every substance in it.
|
|
193
|
+
- A footnote can put two dates and two restrictions in consecutive sentences. A
|
|
194
|
+
pattern crossing the sentence boundary pairs the first date with the second
|
|
195
|
+
restriction, and every sell-through date comes out wrong.
|
|
196
|
+
- EUR-Lex serves two different markups for footnotes and prints the same marker
|
|
197
|
+
three ways.
|
|
198
|
+
- Annex attribution reads from lead-in wording that takes several forms, so
|
|
199
|
+
entries were filed under the wrong annex while looking entirely correct.
|
|
200
|
+
|
|
201
|
+
One fetch returned HTTP 200 with an empty body, and the build reported it as a
|
|
202
|
+
regulation read with no deadlines in it, which is indistinguishable from the
|
|
203
|
+
truth. Responses are now checked for Official Journal markup before being
|
|
204
|
+
accepted, and a bad one is recorded as a failure rather than cached.
|
|
205
|
+
|
|
206
|
+
`docs/how-it-reads-the-source.md` records what these documents actually look like.
|
|
207
|
+
|
|
208
|
+
## Install
|
|
209
|
+
|
|
210
|
+
Not published to the package index.
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
git clone https://github.com/Waiga/on-notice
|
|
214
|
+
cd on-notice
|
|
215
|
+
pip install -e .
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Python 3.9 or later. No dependencies.
|
|
219
|
+
|
|
220
|
+
## Licence
|
|
221
|
+
|
|
222
|
+
MIT.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""on-notice: which ingredients have an EU deadline that has not arrived yet.
|
|
2
|
+
|
|
3
|
+
The tool reads a cosmetic ingredient list and reports the ingredients on it that
|
|
4
|
+
are named in an EU regulation already adopted but not yet applying, and the date
|
|
5
|
+
each restriction starts. It never says a product is legal or illegal, compliant
|
|
6
|
+
or non-compliant. It reports a date and the rule that carries it.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__version__ = "0.1.0"
|
|
10
|
+
|
|
11
|
+
from .register import Register, Finding, load_register
|
|
12
|
+
from .ingredients import read_ingredients
|
|
13
|
+
|
|
14
|
+
__all__ = ["Register", "Finding", "load_register", "read_ingredients", "__version__"]
|