inci_score 2.5.1 → 3.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +34 -0
- data/README.md +14 -25
- data/config/catalog.yml +3 -4
- data/config.ru +2 -2
- data/inci_score.gemspec +2 -3
- data/lib/inci_score/{app.rb → api.rb} +1 -1
- data/lib/inci_score/cli.rb +4 -14
- data/lib/inci_score/computer.rb +15 -6
- data/lib/inci_score/ingredient.rb +45 -0
- data/lib/inci_score/normalizer.rb +1 -1
- data/lib/inci_score/normalizer_rules.rb +0 -24
- data/lib/inci_score/recognizer.rb +25 -7
- data/lib/inci_score/recognizer_rules.rb +19 -22
- data/lib/inci_score/refinements.rb +1 -1
- data/lib/inci_score/response.rb +1 -1
- data/lib/inci_score/score.rb +1 -3
- data/lib/inci_score/scorer.rb +7 -9
- data/lib/inci_score/version.rb +1 -1
- data/lib/inci_score.rb +4 -5
- data/spec/bench/levenshtein_bench.rb +17 -0
- data/spec/bench/normalizer_rules_bench.rb +40 -0
- data/spec/bench/recognizer_rules_bench.rb +24 -0
- data/spec/helper.rb +6 -0
- data/spec/integration/api_spec.rb +23 -0
- data/spec/stubs.rb +170 -0
- data/spec/unit/catalog_spec.rb +7 -0
- data/spec/unit/cli_spec.rb +29 -0
- data/spec/unit/computer_spec.rb +31 -0
- data/spec/unit/ingredient_spec.rb +34 -0
- data/spec/unit/levenshtein_spec.rb +19 -0
- data/spec/unit/normalizer_rules_spec.rb +58 -0
- data/spec/unit/normalizer_spec.rb +31 -0
- data/spec/unit/recognizer_rules_spec.rb +46 -0
- data/spec/unit/recognizer_spec.rb +49 -0
- data/spec/unit/response_spec.rb +8 -0
- data/spec/unit/score_spec.rb +12 -0
- data/spec/unit/scorer_spec.rb +11 -0
- data/spec/unit/server_spec.rb +30 -0
- metadata +24 -23
- data/.gitignore +0 -13
- data/.travis.yml +0 -6
- data/bin/console +0 -7
- data/bin/setup +0 -6
- data/lib/inci_score/fetcher.rb +0 -41
- data/log/.gitignore +0 -4
@@ -0,0 +1,24 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
key = InciScore::Recognizer::Rules::Key
|
4
|
+
levenshtein = InciScore::Recognizer::Rules::Levenshtein
|
5
|
+
digits = InciScore::Recognizer::Rules::Digits
|
6
|
+
tokens = InciScore::Recognizer::Rules::Tokens
|
7
|
+
|
8
|
+
Benchmark.ips do |x|
|
9
|
+
x.report("key") do
|
10
|
+
key.call("aqua", Stubs.catalog)
|
11
|
+
end
|
12
|
+
|
13
|
+
x.report("levenshtein") do
|
14
|
+
levenshtein.call("agua", Stubs.catalog)
|
15
|
+
end
|
16
|
+
|
17
|
+
x.report("digits") do
|
18
|
+
digits.call("olea europaea oil", Stubs.catalog)
|
19
|
+
end
|
20
|
+
|
21
|
+
x.report("tokens") do
|
22
|
+
tokens.call("f588 capric triglyceride", Stubs.catalog)
|
23
|
+
end
|
24
|
+
end
|
data/spec/helper.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
include Rack::Test::Methods
|
4
|
+
|
5
|
+
def app
|
6
|
+
InciScore::Api
|
7
|
+
end
|
8
|
+
|
9
|
+
describe InciScore::Api do
|
10
|
+
Stubs.sources.each_with_index do |src, i|
|
11
|
+
|
12
|
+
status, score = Stubs.statuses[i], Stubs.scores[i]
|
13
|
+
|
14
|
+
it "cosmetic #{i} must get a proper response" do
|
15
|
+
get "/", src: src, precise: true
|
16
|
+
assert last_response.ok?
|
17
|
+
last_response.content_type.must_equal "application/json"
|
18
|
+
body = JSON::parse(last_response.body)
|
19
|
+
body.fetch("score").must_be_close_to score, 0.5
|
20
|
+
body.fetch("valid").must_equal status
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/spec/stubs.rb
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
3
|
+
module Stubs
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def yaml
|
7
|
+
<<-END
|
8
|
+
---
|
9
|
+
parfum: 0
|
10
|
+
phosphatidylcholine: 1
|
11
|
+
1-naphthol: 4
|
12
|
+
1,2,4-benzenetriacetate: 4
|
13
|
+
1,3-bis-(2,4-diaminophenoxy)propane: 4
|
14
|
+
1,5-naphthalenediol: 4
|
15
|
+
2-aminobutanol: 3
|
16
|
+
END
|
17
|
+
end
|
18
|
+
|
19
|
+
def distances
|
20
|
+
[['', '', 0],
|
21
|
+
['elvis', '', 5],
|
22
|
+
['', 'elvis', 5],
|
23
|
+
['elvis', 'elvis', 0],
|
24
|
+
['elvis', 'ELVIS', 0],
|
25
|
+
['elvis', 'elviz', 1],
|
26
|
+
['king', 'the king', 4],
|
27
|
+
['graceland', 'disneyland', 5],
|
28
|
+
["föo", 'foo', 1],
|
29
|
+
["français", "francais", 1],
|
30
|
+
["français", "franæais", 1],
|
31
|
+
["私の名前はポールです", "ぼくの名前はポールです", 2],
|
32
|
+
["elvis\n", 'elvis', 1],
|
33
|
+
["\rking\n", "\nking", 2],
|
34
|
+
['teddybear', "\t\tteddybear\n", 3]]
|
35
|
+
end
|
36
|
+
|
37
|
+
def sources
|
38
|
+
["Ingredients: Aqua, Disodium Laureth Sulfosuccinate, Cocamidopropiyl\nBetaine, Disodium Cocoamphodiacetate, Giyceryi Laurate, PEGJ\nGlyceryi Cocoate, Sodium Lactate, Parfum,\n\nNiacinamide, Glycine, Magnesium Aspanate,\n\nAianine, Lysine, Leucine,A||antoin, PEG-150 E‘—\n\nDistearate, PEG-120 Methyl Glucose Dioleate, \n\nPhenoxyethanoi, CI 61570. 50\n\n \n\n",
|
39
|
+
"\n\nDIENTS: Aqua, Sodium Laureth Sulfate, Sodium Chlor .\ngflfamine, Selaginella Lepidophylla Aerial Extract,PruI|:1es'§:::m\n0.11050“, Irehalose, Gluconolaaone, Glycenn, Guar Hydmyp«mylmmwmu:n\nChloride, Dimethiconol, Parfum, TEA-Dodetylbenzenesulfonate, 015mm\nEDIA, Mica, Carbomer, PPS-12, Citric Acid, Sodium HydmxideJEMuHate,\n\nWnethanolamine, Sodium Benzoate, DMDM Hydantoin, Methykhlowiso-\nIhmzoh'none, Methylisothiazolinone, ButylphenyI Methylpmpionaumm\n(142051), CI 47005, CI 77891.\n\n",
|
40
|
+
"1099488 B - INGREDIENTS : AOUA I WATER.\nSODIUM LAURETH SULFATE, COCO-BETAINE,\nSODIUM LAURYL SULFATE. SODIUM CHLORIDE.\nGLYCOL DISTEARATE, NIACINAMIDE,_ ALCOHOL\nDEI‘IAT., SACCHARUM OFFICINARUM’EXTRACT I\nSUGAR CANE EXTRACT, HYDROXYPROPXLGUAR\nHYDROXVPROPYLTRIMONIUM CHLORIDE. SODIUM\nHYDROXIDE, AMINOPROPYL TRIETHOXYSILANE,\nPOLYOUATERNIUM-SO. CAMELLIA SINENGIS\nLEAF EXTRACT, BENZYL ALCOHOL, LINALOOL,\nPUNICA GRANATUM EXTRACT, 2—OLEAMIDO-1,3-\nOCTADECANEDIOL, ACRYLATES COPOLYMER, PYRUS\nMALUS EXTRACT / APPLE FRUIT EXTRACT, PYRIDOXINE\nHCI. CITRIC ACID, METHYLCHLOROISOTHIAZOUNONE.\nMETHYLISOTHIAZOUNONE, CITRUS MEDICA UMONUM\nPEEL EXTRACT/ LEMON PEEL EXTRACT. HEXYLENE\n\nGLYCOL, HEXYL CINNAMAL, AMYL CINNAMAL.\n\nPARFUM / FRAGRANCE (FIL C1631 07/2).\n\n",
|
41
|
+
"7023 10 - INGREDIENTS: CAPRYLIC/CAPRIC\nfilGLYCERIDE, ISOPROPYL PALMITATE, ISOPROPYL\nMYRISTATE, PARFUM / FRAGRANCE, BENZYL ALCOHOL.\nBENZY L SALICYLATE, COUMARIN. HEXYL CINNAMAL,\nLIMONENE, LINALOOL, OLEA\n\nEUROPAEA 0|L/ OLIVE FRUIT\nOIL, PENTAERYTHRITYL\nTETRA-DI-T-BUTYL\nHYDROXYHYDROCINNAMAIE.\n«11317540710.\n\n",
|
42
|
+
"mom-mansmsms: AQUA/WATER, PETROLATUM, GLVCERIN, PARAFFINUM\nunumum MINERAL OIL. CETEARYL ALCOHOL, DIMETHICONE, Buwnomamtmfigfi‘\nUUERISHEA BUTTER. BENZYL ALCOHOL. BENZVL SALICVLATE, CAPRYLVGLVCERYL\nc1 15510/0RANGE 4, CI 47005/ACID YELLOW 3, COUMARILP‘JVE W M\nCiNNAMAL, LIMONENE, LINALOOL, OLEA EUROPAEA OIL I 0\n6-100 STEARATE, PENTAERYTHRITVL\nTVLH‘IDROXVHVDROCINNAMATE,\nSODIUM anaome, 31mm ACID, PARFUM/\n~ ammo/1).\n\n \n \n \n \n\n",
|
43
|
+
" \n \n \n \n \n \n \n\n \n \n \n\nINGREDIENTszA'oLM- a; V\nETHYLHEXYL PALMITATE - BE -~ x\n\nNIUM CHLORIDE ' SIMMONDSIA CH1 ‘ ~ .‘\nROSPERMUM PARK\" BUTTER ' PARFUM ' BONED?“ ALM\n\n- SODIUM BENZOATE - PANTHENOL ~ GUAR HYDROXYPRUWL-\nTRIMONIUM CHLORIDE - PHOSPHOLlPlDS ‘ C\\TR\\C AC“) -\nGLYCINE SOJA OIL ‘ TOCOPHERYL ACETATE - GLYCOLPm\n\n' GLYCINE SOJA STEROLS ' TOCOPHEROL\n\n \n\n",
|
44
|
+
"11005140-INGREDIENTS:AOUA/WATER,CE[EARYLALCOHOL\nDISTEAROYLETHYL annoxvmvwomw\nMETHOSULFATE,OCTYLDODECANULJEAMAYS J\nSTARCH / CORN STARCH. NIACINAMIDE r\nTOMPHEROLSACCHARUMOFFIUNARUMRM f\n/SJJGARCANEEXTRACT,MN.PIGH|APUNICIFOUAI '\nACEHOLAFRUITEXTRACT,CAMEL|NASATNAO|L/ f\nCAMELINASATIVASEEDOILCAMELUASlNENSlS :\nEXTRACT/CAMELLIASINENSISLEAFEXTRACT, F\nBENZOICACID,LlNALOOL,CAPRYLYLGLYCOL, ,5\nCADRYUUCAPRICTRIGLVCERIDEEYRUSM I\nEXTRACT /APPLE FRUIT EXTRACT, wmnome J\nHcmnmcgfimCIIRUSMEDICALIMONUM ;\nPEEL EXTRACT / LEMON PEEL EXTRACT. j\nPRUNUSARMENIACAKERNELOIL/APRICOT\nxmaommanmmamm ;\nOIL / SUYBEAN OIL, PARFUM I Emacs. ;\n\n(FlL 04391213) /\n\n \n\n",
|
45
|
+
" \n \n \n \n \n \n\nINGREDIENTS: Butane, Isobutane, Propane,\nAluminum Chlorohydrate, PPG-14 Butyl Ether,\nCyclopentasiloxane, Parfum, Disteardimonium\nHectonte, Helianthus Annuus Seed Oil, C12-15\n§kV‘B?”103te,Octyldodecanol, BHT,\nflah'confl. Propylene Carbonate, PEG4.\nEsme, Citric Acid, AI ha-Isomethyl lonone,\n09W. Benzyl alicylate, ButylphenY'\na?p'9na|. CItraI, Geraniol, Hexyl\n'L'm0nene, Linalool.\n\n",
|
46
|
+
"INGREDIENTS : AQUa (wa‘efl'\nslearate, Propylene glycol, F588\ncapric triglyceride, COCOS can\" ‘Wcla\n(Coconul) oil, Isupropyl palmna‘eW’ 09M\nalcohol, Sorbitan palmitate, pom\n\n40, Parfum (Fragrance), DisodiUm E\nCarbomer. Limonene, cum BHr\nSodium hydroxide, cums glands.\n(Grapefruit) fruit extract, SodiUm\ndroacetate, Sodium methyl dehy‘\n\nSorbic acid, Tetrasodium Elm-A CI\n10316 (Ext Yellow 7)‘\n\n",
|
47
|
+
"782208 5‘ » INGREDIEMS: AQUA/WATER ~ GLYCERIN ' DIMETHICONE ' ISOHEXADEUNE ' SILICA\n‘ HVDROXVEIHYIHPERAZINE ETHANE SULFONIC ACID ' ALCOHOL DENAT. ' WE SLY“,- '\nSVNTHETIC WAX ' CI 77163 I BISMUTH OXVCHLORIDE ' CI 77391 / TIIANIUM DIOXIDE ' SECALE\nCEREALE EXTRACT/ RYE SEED EXTRACT ' SODIUM ACRYLATES COPOLVMER ‘ SODIUM\nHVALURONATE ‘ PHENOXYETHANOL ‘ ADENDSINE ~ PEG-10 DIMETHICONE ‘ ETHYLHEXYL\nHYDROXVSTEARATE ' NYLON-12 ' DIMETHICONE/PEG-IO/IS CROSSPOLVMER -\nD|HETHICONE/POLVGLYCERIN-3 CROSSPOLYMER ' PENWLENE GLYCOL ‘ SYNTHEIIC\nFLUORPHLOGOPITE ' BENZVL SALICVLATE - BENZVL ALCOHOL ' LINALOOL ‘ BENZVL BENZOATE '\nCAPRVLIUCAPRIC TRIGLYCERIDE ' CAPRVLYL GLYCOI. ‘ DIPOTASSIUM GLVCVRRHIZATE ' ALPINIA\nGAUNGA LEAF EXTRACT ' DISTEARDIMDNIUN HECTDRITE ' DISOUIUM EDTA - CIIRONELLOL ‘\nPARFUH/ FRAGRANCE. (F.I.L 3166675”).\nIorl MermIzIonI su\n\nM399 V ,\nwww.lorealpans.nt\n\n \n\n \n\n"]
|
48
|
+
end
|
49
|
+
|
50
|
+
def scores
|
51
|
+
[79, 66, 72, 63, 63, 75, 75, 56, 73, 63]
|
52
|
+
end
|
53
|
+
|
54
|
+
def statuses
|
55
|
+
[true, true, true, true, true, true, false, true, true, true]
|
56
|
+
end
|
57
|
+
|
58
|
+
def ingredients
|
59
|
+
[["aqua", "disodium laureth sulfosuccinate", "cocamidopropiyl betaine", "disodium cocoamphodiacetate", "giyceryi laurate", "pegj glyceryi cocoate", "sodium lactate", "parfum", "niacinamide", "glycine", "magnesium aspanate", "aianine", "lysine", "leucine", "allantoin", "peg-150 e- distearate", "peg-120 methyl glucose dioleate", "phenoxyethanoi", "ci 61570", "50"],
|
60
|
+
["aqua", "sodium laureth sulfate", "sodium chlor", "gfamine", "selaginella lepidophylla aerial extract", "pruil1esm 011050", "irehalose", "gluconolaaone", "glycenn", "guar hydmypmylmmwmun chloride", "dimethiconol", "parfum", "tea-dodetylbenzenesulfonate", "015mm edia", "mica", "carbomer", "pps-12", "citric acid", "sodium hydmxidejemuhate", "wnethanolamine", "sodium benzoate", "dmdm hydantoin", "methykhlowiso- ihmzohnone", "methylisothiazolinone", "butylphenyi methylpmpionaumm (142051)", "ci 47005", "ci 77891"],
|
61
|
+
["aoua/water", "sodium laureth sulfate", "coco-betaine", "sodium lauryl sulfate", "sodium chloride", "glycol distearate", "niacinamide", "alcohol deiiat", "saccharum officinarumextract/sugar cane extract", "hydroxypropxlguar hydroxvpropyltrimonium chloride", "sodium hydroxide", "aminopropyl triethoxysilane", "polyouaternium-so", "camellia sinengis leaf extract", "benzyl alcohol", "linalool", "punica granatum extract", "2-oleamido-1", "3- octadecanediol", "acrylates copolymer", "pyrus malus extract / apple fruit extract", "pyridoxine hci", "citric acid", "methylchloroisothiazounone", "methylisothiazounone", "citrus medica umonum peel extract/ lemon peel extract", "hexylene glycol", "hexyl cinnamal", "amyl cinnamal", "parfum / fragrance (fil c1631 07/2)"],
|
62
|
+
["caprylic/capric lglyceride", "isopropyl palmitate", "isopropyl myristate", "parfum / fragrance", "benzyl alcohol", "benzy l salicylate", "coumarin", "hexyl cinnamal", "limonene", "linalool", "olea europaea 0ll/ olive fruit oil", "pentaerythrityl tetra-di-t-butyl hydroxyhydrocinnamaie", "11317540710"],
|
63
|
+
["aqua/water", "petrolatum", "glvcerin", "paraffinum unumum mineral oil", "cetearyl alcohol", "dimethicone", "buwnomamtmg uuerishea butter", "benzyl alcohol", "benzvl salicvlate", "caprylvglvceryl c1 15510/0range 4", "ci 47005/acid yellow 3", "coumarilpjve w m cinnamal", "limonene", "linalool", "olea europaea oil/0 6-100 stearate", "pentaerythritvl tvlhidroxvhvdrocinnamate", "sodium anaome", "31mm acid", "parfum/", "ammo/1)"],
|
64
|
+
["ingredientszaolm- a", "v ethylhexyl palmitate", "be -- x nium chloride", "simmondsia ch1", " rospermum park butter", "parfum", "boned alm", "sodium benzoate", "panthenol", "guar hydroxypruwl- trimonium chloride", "phosphollplds", "ctrc ac)", "glycine soja oil", "tocopheryl acetate", "glycolpm", "glycine soja sterols", "tocopherol"],
|
65
|
+
["aoua/water", "ce[earylalcohol distearoylethyl annoxvmvwomw methosulfate", "octyldodecanuljeamays j starch / corn starch", "niacinamide r tompherolsaccharumoffiunarumrm f /sjjgarcaneextract", "mnpighlapunicifouai", "aceholafruitextract", "camellnasatnaoll/ f camelinasativaseedoilcameluaslnensls", "extract/camelliasinensisleafextract", "f benzoicacid", "llnalool", "caprylylglycol", "5 cadryuucaprictriglvcerideeyrusm/extract /apple fruit extract", "wmnome j hcmnmcgmciirusmedicalimonum", "peel extract / lemon peel extract", "j prunusarmeniacakerneloil/apricot xmaommanmmamm", "oil / suybean oil", "parfum/emacs", "(fll 04391213) /"],
|
66
|
+
["butane", "isobutane", "propane", "aluminum chlorohydrate", "ppg-14 butyl ether", "cyclopentasiloxane", "parfum", "disteardimonium hectonte", "helianthus annuus seed oil", "c12-15 kvb103te", "octyldodecanol", "bht", "ahcon", "propylene carbonate", "peg4", "esme", "citric acid", "ai ha-isomethyl lonone", "09w", "benzyl alicylate", "butylpheny ap9nal", "citrai", "geraniol", "hexyl lm0nene", "linalool"],
|
67
|
+
["aqua (wae slearate", "propylene glycol", "f588 capric triglyceride", "cocos can wcla (coconul) oil", "isupropyl palmnaew 09m alcohol", "sorbitan palmitate", "pom 40", "parfum (fragrance)", "disodium e carbomer", "limonene", "cum bhr sodium hydroxide", "cums glands", "(grapefruit) fruit extract", "sodium droacetate", "sodium methyl dehy sorbic acid", "tetrasodium elm-a ci 10316 (ext yellow 7)"],
|
68
|
+
["aqua/water", "glycerin", "dimethicone", "isohexadeune", "silica", "hvdroxveihyihperazine ethane sulfonic acid", "alcohol denat", " we sly", "-", "svnthetic wax", "ci 77163/bismuth oxvchloride", "ci 77391 / tiianium dioxide", "secale cereale extract/ rye seed extract", "sodium acrylates copolvmer", "sodium hvaluronate", "phenoxyethanol", "adendsine", "peg-10 dimethicone", "ethylhexyl hydroxvstearate", "nylon-12", "dimethicone/peg-io/is crosspolvmer", "dlhethicone/polvglycerin-3 crosspolymer", "penwlene glycol", "syntheiic fluorphlogopite", "benzvl salicvlate", "benzvl alcohol", "linalool", "benzvl benzoate", "caprvliucapric triglyceride", "caprvlyl glycoi", " dipotassium glvcvrrhizate", "alpinia gaunga leaf extract", "disteardimdniun hectdrite", "disouium edta", "ciironellol", "parfuh/ fragrance", "(fil 3166675)", "iorl mermizioni su m399 v", "wwwlorealpansnt"]]
|
69
|
+
end
|
70
|
+
|
71
|
+
def components
|
72
|
+
[["ci 61570", "ci 61570"],
|
73
|
+
["agua", "aqua"],
|
74
|
+
["olea europaea oil", "olea europea"],
|
75
|
+
["f588 capric triglyceride", "caprylic/capric triglyceride"]]
|
76
|
+
end
|
77
|
+
|
78
|
+
def catalog
|
79
|
+
{"parfum"=>0, "bha"=>4, "alanine"=>0, "allantoin"=>0, "aqua"=>0, "ci 61570"=>3, "ci 77491"=>0, "ci 77492"=>1, "cocamidopropyl betaine"=>1, "disodium cocoamphodiacetate"=>0, "disodium laureth sulfosuccinate"=>2, "glyceryl laurate"=>0, "glycine"=>0, "leucine"=>0, "lysine"=>0, "magnesium aspartate"=>0, "niacinamide"=>0, "peg-120 methyl glucose dioleate"=>3, "peg-150 distearate"=>3, "peg-7 glyceryl cocoate"=>3, "phenoxyethanol"=>2, "sodium lactate"=>0, "paraffinum liquidum"=>3, "ci 47005"=>3, "olea europea"=>0, "capryl glycol"=>2, "caprylic/capric triglyceride"=>0, "betula alba leaf water"=>1, "connective tissue extract"=>4, "dmhf"=>3, "acrylamide/sodium acrylate copolymer"=>3}
|
80
|
+
end
|
81
|
+
|
82
|
+
def hazards
|
83
|
+
[[[], 0],
|
84
|
+
[[0,1], 91],
|
85
|
+
[[1,0], 87],
|
86
|
+
[Array.new(10) { 4 }, 0],
|
87
|
+
[Array.new(10) { 3 }, 25],
|
88
|
+
[Array.new(10) { 2 }, 50],
|
89
|
+
[Array.new(10) { 1 }, 75],
|
90
|
+
[Array.new(10) { 0 }, 100],
|
91
|
+
[[4,3,4,4,1,1,0], 46],
|
92
|
+
[[0,1,0,0,2,3,0,4], 74],
|
93
|
+
[[2,4,3,0,0,0,0,4], 63],
|
94
|
+
[[1,0,0,1,0,2,0,4,3], 75],
|
95
|
+
[[0,0,0,0,0,0,0,4], 89],
|
96
|
+
[[4,4,4,4,4,4,4,0], 20],
|
97
|
+
[[0,1,2,3,4] * 10, 61],
|
98
|
+
[[0,1,2,3,4] * 100, 63]]
|
99
|
+
end
|
100
|
+
|
101
|
+
def response
|
102
|
+
InciScore::Response.new(components: %w(aqua dimethicone peg-10), score: 47.18034913243358, valid: true)
|
103
|
+
end
|
104
|
+
|
105
|
+
class Computer
|
106
|
+
def initialize(src:, catalog:, precise: false)
|
107
|
+
@src = src
|
108
|
+
@catalog = catalog
|
109
|
+
@precise = precise
|
110
|
+
end
|
111
|
+
|
112
|
+
def call
|
113
|
+
@src.split(",").map(&:strip).reduce({}) do |acc, component|
|
114
|
+
_component = @catalog[component]
|
115
|
+
next(acc) unless _component
|
116
|
+
acc[component] = _component
|
117
|
+
acc
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class Configuration
|
123
|
+
attr_reader :app, :host, :wrks, :min, :max, :preload
|
124
|
+
|
125
|
+
def initialize(opts = {})
|
126
|
+
yield(self) if block_given?
|
127
|
+
end
|
128
|
+
|
129
|
+
def rackup(f)
|
130
|
+
@app = f
|
131
|
+
end
|
132
|
+
|
133
|
+
def bind(host)
|
134
|
+
@host = host
|
135
|
+
end
|
136
|
+
|
137
|
+
def workers(n)
|
138
|
+
@wrks = n
|
139
|
+
end
|
140
|
+
|
141
|
+
def threads(min, max)
|
142
|
+
@min, @max = min, max
|
143
|
+
end
|
144
|
+
|
145
|
+
def preload_app!
|
146
|
+
@preload = true
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
class Launcher
|
151
|
+
def initialize(config)
|
152
|
+
@config = config
|
153
|
+
end
|
154
|
+
|
155
|
+
def run
|
156
|
+
@config
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
class Server
|
161
|
+
def initialize(port:, preload:)
|
162
|
+
@port = port
|
163
|
+
@preload = preload
|
164
|
+
end
|
165
|
+
|
166
|
+
def run
|
167
|
+
:"run_on_port_#{@port}"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
describe InciScore::CLI do
|
4
|
+
let(:io) { StringIO.new }
|
5
|
+
|
6
|
+
it "must warn if no inci is specified" do
|
7
|
+
InciScore::CLI.new(args: ["aqua"], io: io, catalog: Stubs.catalog).call
|
8
|
+
io.string.must_equal "Specify inci list as: --src=\"aqua, parfum, etc\"\n"
|
9
|
+
end
|
10
|
+
|
11
|
+
it "must print the help" do
|
12
|
+
begin
|
13
|
+
InciScore::CLI.new(args: %w[--help], io: io, catalog: Stubs.catalog).call
|
14
|
+
rescue SystemExit
|
15
|
+
io.string.must_equal "Usage: inci_score --src=\"aqua, parfum, etc\" --precise\n -s, --src=SRC The INCI list: \"aqua, parfum, etc\"\n -p, --precise Compute components more precisely (slower)\n --http=PORT Start HTTP server on the specified port\n -h, --help Prints this help\n"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it "must call computer" do
|
20
|
+
cli = InciScore::CLI.new(args: ["--src=aqua, parfum, peg-10"], io: io, catalog: {"aqua" => 0, "parfum" => 0})
|
21
|
+
cli.call(computer_klass: Stubs::Computer)
|
22
|
+
io.string.must_equal "{\"aqua\"=>0, \"parfum\"=>0}\n"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "must run server" do
|
26
|
+
cli = InciScore::CLI.new(args: ["--http=9292", "--src=agua"], io: io, catalog: Stubs.catalog)
|
27
|
+
cli.call(server_klass: Stubs::Server).must_equal :run_on_port_9292
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
describe InciScore::Computer do
|
4
|
+
let(:computer) { InciScore::Computer.new(src: Stubs.sources[0], catalog: Stubs.catalog) }
|
5
|
+
|
6
|
+
it "must collect components" do
|
7
|
+
computer.call.components.must_equal(["aqua", "disodium laureth sulfosuccinate", "cocamidopropyl betaine", "disodium cocoamphodiacetate", "glyceryl laurate", "peg-7 glyceryl cocoate", "sodium lactate", "parfum", "niacinamide", "glycine", "magnesium aspartate", "alanine", "lysine", "leucine", "allantoin", "peg-150 distearate", "peg-120 methyl glucose dioleate", "phenoxyethanol", "ci 61570"])
|
8
|
+
end
|
9
|
+
|
10
|
+
it "must collect unrecognized components" do
|
11
|
+
computer.call.unrecognized.map(&:to_s).must_equal %w[50]
|
12
|
+
end
|
13
|
+
|
14
|
+
it "must detect valid state" do
|
15
|
+
assert computer.call.valid
|
16
|
+
end
|
17
|
+
|
18
|
+
it "must compute the score" do
|
19
|
+
computer.call.score.must_be_close_to 82, 0.5
|
20
|
+
end
|
21
|
+
|
22
|
+
it "must detect invalid state" do
|
23
|
+
computer = InciScore::Computer.new(src: "ingredients: aqua, noent1, noent2", catalog: Stubs.catalog)
|
24
|
+
refute computer.call.valid
|
25
|
+
end
|
26
|
+
|
27
|
+
it "must overwrite tolerance" do
|
28
|
+
computer = InciScore::Computer.new(src: "ingredients: aqua, noent1, noent2", catalog: Stubs.catalog, tolerance: 75.0)
|
29
|
+
assert computer.call.valid
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
describe InciScore::Ingredient do
|
4
|
+
it "must create instances in bulk" do
|
5
|
+
InciScore::Ingredient.bulk(Stubs.ingredients.first).each do |ingredient|
|
6
|
+
ingredient.must_be_instance_of InciScore::Ingredient
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
it "must be represented as a string" do
|
11
|
+
ingredient = InciScore::Ingredient.new("acrylamidopropyltrimonium chloride/acrylates copolymer")
|
12
|
+
ingredient.to_s.must_equal "acrylamidopropyltrimonium chloride/acrylates copolymer"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "must be represented as a string withoud details" do
|
16
|
+
ingredient = InciScore::Ingredient.new("1,3-bis-(2,4-diaminophenoxy)propane")
|
17
|
+
ingredient.to_s.must_equal "1,3-bis-propane"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "must fetch values with plain name" do
|
21
|
+
ingredient = InciScore::Ingredient.new("aqua")
|
22
|
+
ingredient.values.must_equal ["aqua"]
|
23
|
+
end
|
24
|
+
|
25
|
+
it "must fetch values with syninims" do
|
26
|
+
ingredient = InciScore::Ingredient.new("peg-3/ppg-2 glyceryl/sorbitol hydroxystearate/isostearate")
|
27
|
+
ingredient.values.must_equal ["peg-3", "ppg-2 glyceryl", "sorbitol hydroxystearate/isostearate"]
|
28
|
+
end
|
29
|
+
|
30
|
+
it "must fetch values with details" do
|
31
|
+
ingredient = InciScore::Ingredient.new("camelia sinensis (leave on)")
|
32
|
+
ingredient.values.must_equal ["camelia sinensis"]
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
describe InciScore::Levenshtein do
|
4
|
+
it "must compute edit distance for UTF8 characters" do
|
5
|
+
Stubs.distances.each do |args|
|
6
|
+
t, s, d = *args
|
7
|
+
lev = InciScore::Levenshtein.new(s, t)
|
8
|
+
lev.call.must_equal d
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it "must compute edit distance" do
|
13
|
+
Stubs.distances.first(8).each do |args|
|
14
|
+
t, s, d = *args
|
15
|
+
lev = InciScore::LevenshteinC.new.call(s.downcase, s.size, t.downcase, t.size)
|
16
|
+
lev.must_equal d
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
describe InciScore::Normalizer::Rules do
|
4
|
+
it "must replace invalid chars" do
|
5
|
+
src = "‘ingredients‘:\n\nCOCO—BETANE -_AQUA I WATER\t\t~\n\ng|igeryn"
|
6
|
+
rule = InciScore::Normalizer::Rules::Replacer
|
7
|
+
rule.call(src).must_equal "'ingredients': COCO-BETANE - AQUA/WATER - gligeryn"
|
8
|
+
end
|
9
|
+
|
10
|
+
it "must downcase the source" do
|
11
|
+
src = "AQUA, DIMETHICONE, PEG-10"
|
12
|
+
rule = InciScore::Normalizer::Rules::Downcaser
|
13
|
+
rule.call(src).must_equal src.downcase
|
14
|
+
end
|
15
|
+
|
16
|
+
it "must remove the head from the source" do
|
17
|
+
src = "ingredients: aqua, dimethicone, PEG-10"
|
18
|
+
rule = InciScore::Normalizer::Rules::Beheader
|
19
|
+
rule.call(src).must_equal " aqua, dimethicone, PEG-10"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "must return source if there is no title separator" do
|
23
|
+
src = "aqua, dimethicone, PEG-10"
|
24
|
+
rule = InciScore::Normalizer::Rules::Beheader
|
25
|
+
rule.call(src).must_equal src
|
26
|
+
end
|
27
|
+
|
28
|
+
it "must return source if title separator index is geater than max" do
|
29
|
+
spacer = "*" * InciScore::Normalizer::Rules::Beheader::MAX_INDEX
|
30
|
+
src = "#{spacer} aqua : water : dimethicone"
|
31
|
+
rule = InciScore::Normalizer::Rules::Beheader
|
32
|
+
rule.call(src).must_equal src
|
33
|
+
end
|
34
|
+
|
35
|
+
it "must replace separators with default char" do
|
36
|
+
src = "aqua; dimethicone : PEG-10 - magnesium. parfum"
|
37
|
+
rule = InciScore::Normalizer::Rules::Separator
|
38
|
+
rule.call(src).must_equal "aqua,dimethicone,PEG-10,magnesium,parfum"
|
39
|
+
end
|
40
|
+
|
41
|
+
it "must tokenize string" do
|
42
|
+
src = "aqua/water , PEG-10, magnesium , &/&$$£"
|
43
|
+
rule = InciScore::Normalizer::Rules::Tokenizer
|
44
|
+
rule.call(src).must_equal %w[aqua/water PEG-10 magnesium &/&$$£]
|
45
|
+
end
|
46
|
+
|
47
|
+
it "must sanitize invalid characters" do
|
48
|
+
src = %w[aqua/water PEG&-10 magnesium$ &/&$$£]
|
49
|
+
rule = InciScore::Normalizer::Rules::Sanitizer
|
50
|
+
rule.call(src).must_equal %w[aqua/water PEG-10 magnesium /]
|
51
|
+
end
|
52
|
+
|
53
|
+
it "must remove duplicates" do
|
54
|
+
src = %w[aqua/water magnesium parfum/fragrance magnesium]
|
55
|
+
rule = InciScore::Normalizer::Rules::Uniquifier
|
56
|
+
rule.call(src).must_equal %w[aqua/water magnesium parfum/fragrance]
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "helper"
|
2
|
+
require "inci_score/normalizer"
|
3
|
+
|
4
|
+
describe InciScore::Normalizer do
|
5
|
+
it "must raise an argument error for missing src" do
|
6
|
+
-> { InciScore::Normalizer.new }.must_raise ArgumentError
|
7
|
+
end
|
8
|
+
|
9
|
+
it "must apply all rules to fetch ingredients" do
|
10
|
+
Stubs.sources.each_with_index do |src, i|
|
11
|
+
InciScore::Normalizer.new(src: src).call.must_equal Stubs.ingredients[i]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
it "must apply custom rules" do
|
16
|
+
custom = [->(src) { src.upcase }, ->(src) { src.split("-").map(&:strip) }]
|
17
|
+
normalizer = InciScore::Normalizer.new(src: "aqua/water - parfum - magnesium", rules: custom)
|
18
|
+
normalizer.call.must_equal %w[AQUA/WATER PARFUM MAGNESIUM]
|
19
|
+
end
|
20
|
+
|
21
|
+
it "can accept a block to augment rules" do
|
22
|
+
normalizer = InciScore::Normalizer.new(src: "aqua/water - parfum - magnesium", rules: [->(src) { src.split("-") }])
|
23
|
+
stripper = ->(src) { src.map(&:strip) }
|
24
|
+
capitalizer = ->(src) { src.map(&:capitalize) }
|
25
|
+
normalizer.call do |rules|
|
26
|
+
rules << stripper
|
27
|
+
rules << capitalizer
|
28
|
+
end
|
29
|
+
normalizer.src.must_equal %w[Aqua/water Parfum Magnesium]
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "helper"
|
2
|
+
require "inci_score/recognizer_rules"
|
3
|
+
|
4
|
+
describe InciScore::Recognizer::Rules do
|
5
|
+
let(:catalog) { Stubs.catalog }
|
6
|
+
|
7
|
+
it "must recognize component by key" do
|
8
|
+
rule = InciScore::Recognizer::Rules::Key
|
9
|
+
rule.call("ci 61570", catalog).must_equal "ci 61570"
|
10
|
+
end
|
11
|
+
|
12
|
+
it "must return nil for unfound key" do
|
13
|
+
rule = InciScore::Recognizer::Rules::Key
|
14
|
+
refute rule.call("water", catalog)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "must recognize component by Levenshtein distance" do
|
18
|
+
rule = InciScore::Recognizer::Rules::Levenshtein
|
19
|
+
rule.call("agua", catalog).must_equal "aqua"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "returns nil if too distant" do
|
23
|
+
rule = InciScore::Recognizer::Rules::Levenshtein
|
24
|
+
refute rule.call("water", catalog)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "must recognize component by meaningful digits" do
|
28
|
+
rule = InciScore::Recognizer::Rules::Digits
|
29
|
+
rule.call("olea europaea oil", catalog).must_equal "olea europea"
|
30
|
+
end
|
31
|
+
|
32
|
+
it "must return nil with no matchings" do
|
33
|
+
rule = InciScore::Recognizer::Rules::Digits
|
34
|
+
refute rule.call("water", catalog)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "must recognize component by tokens" do
|
38
|
+
rule = InciScore::Recognizer::Rules::Tokens
|
39
|
+
rule.call("f588 capric triglyceride", catalog).must_equal "caprylic/capric triglyceride"
|
40
|
+
end
|
41
|
+
|
42
|
+
it "must return nil with no matchings" do
|
43
|
+
rule = InciScore::Recognizer::Rules::Tokens
|
44
|
+
refute rule.call("agua", catalog)
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require "helper"
|
2
|
+
require "inci_score/recognizer"
|
3
|
+
|
4
|
+
describe InciScore::Recognizer do
|
5
|
+
let(:rules) { [] }
|
6
|
+
|
7
|
+
it "must recognize the components based on applied rules" do
|
8
|
+
Stubs.components.each do |ingredient, name|
|
9
|
+
recognizer = InciScore::Recognizer.new(ingredient, Stubs.catalog)
|
10
|
+
recognizer.call.name.must_equal name
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it "must skip recognition for empty ingredient" do
|
15
|
+
recognizer = InciScore::Recognizer.new(nil, Stubs.catalog)
|
16
|
+
recognizer.call.must_be_nil
|
17
|
+
end
|
18
|
+
|
19
|
+
it "must recognize by key only" do
|
20
|
+
recognizer = InciScore::Recognizer.new("aqua", Stubs.catalog)
|
21
|
+
recognizer.call
|
22
|
+
recognizer.applied.must_equal InciScore::Recognizer::DEFAULT_RULES[0,1]
|
23
|
+
end
|
24
|
+
|
25
|
+
it "must recognize by key and levenshtein" do
|
26
|
+
recognizer = InciScore::Recognizer.new("agua", Stubs.catalog)
|
27
|
+
recognizer.call
|
28
|
+
recognizer.applied.must_equal InciScore::Recognizer::DEFAULT_RULES[0,2]
|
29
|
+
end
|
30
|
+
|
31
|
+
it "must recognize by key, levenshtein and digits" do
|
32
|
+
recognizer = InciScore::Recognizer.new("olea europaea oil", Stubs.catalog)
|
33
|
+
recognizer.call
|
34
|
+
recognizer.applied.must_equal InciScore::Recognizer::DEFAULT_RULES[0,3]
|
35
|
+
end
|
36
|
+
|
37
|
+
it "must recognize by key, levenshtein, digits and tokens" do
|
38
|
+
recognizer = InciScore::Recognizer.new("f588 capric triglyceride", Stubs.catalog)
|
39
|
+
recognizer.call
|
40
|
+
recognizer.applied.must_equal InciScore::Recognizer::DEFAULT_RULES
|
41
|
+
end
|
42
|
+
|
43
|
+
it "must recognize component precisely" do
|
44
|
+
ingredient = InciScore::Ingredient.new("olio di oliva/olea europea")
|
45
|
+
recognizer = InciScore::Recognizer.new(ingredient, Stubs.catalog)
|
46
|
+
recognizer.call(true)
|
47
|
+
recognizer.applied.must_equal InciScore::Recognizer::DEFAULT_RULES[0,1]
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
require "helper"
|
2
|
+
require "inci_score/response"
|
3
|
+
|
4
|
+
describe InciScore::Response do
|
5
|
+
it "must return a JSON representation" do
|
6
|
+
Stubs.response.to_json.must_equal %{{"components":["aqua","dimethicone","peg-10"],"unrecognized":[],"score":47.18034913243358,"valid":true}}
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "helper"
|
2
|
+
require "inci_score/score"
|
3
|
+
|
4
|
+
describe InciScore::Score do
|
5
|
+
it "must compute score value accordingly" do
|
6
|
+
InciScore::Score.new(3, 0.66).value.must_equal 2.34
|
7
|
+
end
|
8
|
+
|
9
|
+
it "must return zero fr negative score" do
|
10
|
+
InciScore::Score.new(0, 0.66).value.must_equal 0.0
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require "helper"
|
2
|
+
require "inci_score/scorer"
|
3
|
+
|
4
|
+
describe InciScore::Scorer do
|
5
|
+
it "must compute the full score" do
|
6
|
+
Stubs.hazards.each do |args|
|
7
|
+
hazards, score = *args
|
8
|
+
InciScore::Scorer.new(hazards).call.must_be_close_to score, 0.5
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "helper"
|
2
|
+
require "inci_score/server"
|
3
|
+
|
4
|
+
describe InciScore::Server do
|
5
|
+
it "must run with default options" do
|
6
|
+
server = InciScore::Server.new(config_klass: Stubs::Configuration,
|
7
|
+
launcher_klass: Stubs::Launcher)
|
8
|
+
config = server.run
|
9
|
+
config.app.must_equal InciScore::Server::RACKUP_FILE
|
10
|
+
config.host.must_equal "tcp://#{InciScore::Server::DEFAULT_HOST}:9292"
|
11
|
+
config.min.must_equal "0"
|
12
|
+
config.max.must_equal "#{InciScore::Server::CPUs*4}"
|
13
|
+
config.wrks.must_equal Etc.nprocessors
|
14
|
+
refute config.preload
|
15
|
+
end
|
16
|
+
|
17
|
+
it "must run with custom options" do
|
18
|
+
server = InciScore::Server.new(port: 9001, threads: "3:6",
|
19
|
+
workers: 1, preload: true,
|
20
|
+
config_klass: Stubs::Configuration,
|
21
|
+
launcher_klass: Stubs::Launcher)
|
22
|
+
config = server.run
|
23
|
+
config.app.must_equal InciScore::Server::RACKUP_FILE
|
24
|
+
config.host.must_equal "tcp://#{InciScore::Server::DEFAULT_HOST}:9001"
|
25
|
+
config.min.must_equal "3"
|
26
|
+
config.max.must_equal "6"
|
27
|
+
config.wrks.must_be_nil
|
28
|
+
assert config.preload
|
29
|
+
end
|
30
|
+
end
|