import pytest from pino.verifier import FragrancePipelineVerifier from pathlib import Path import sys sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from scripts.ingest_fraterworks_free_formulas import extract_pdf_text, parse_formula_text def test_classic_accord_evaporation(): # Test formula: 10% Limonene, 20% Linalool, 70% Ethanol base formula = [ {"cas": "5989-27-5", "weight_fraction": 0.10}, # Limonene {"cas": "78-70-6", "weight_fraction": 0.20}, # Linalool {"cas": "64-17-5", "weight_fraction": 0.70} # Ethanol ] verifier = FragrancePipelineVerifier() trajectory = verifier.run_sim(formula, duration_seconds=3600) assert "trajectory" in trajectory assert len(trajectory["trajectory"]) > 0 # Ensure ethanol depletes significantly faster than linalool assert trajectory["depletion_rates"]["64-17-5"] > trajectory["depletion_rates"]["78-70-6"] def test_out_of_applicability_domain_graceful(): # Invalid CAS that PubChem and the registry cannot resolve. formula = [ {"cas": "0000-00-0", "weight_fraction": 0.50}, {"cas": "64-17-5", "weight_fraction": 0.50}, ] verifier = FragrancePipelineVerifier() result = verifier.run_sim(formula, duration_seconds=3600) assert result["status"] == "rejected" assert result["applicability_errors"] def test_ifra_rejection(): # Cinnamaldehyde is restricted/prohibited in the embedded IFRA table. formula = [ {"cas": "104-55-2", "weight_fraction": 0.10}, {"cas": "64-17-5", "weight_fraction": 0.90}, ] verifier = FragrancePipelineVerifier() result = verifier.run_sim(formula, duration_seconds=3600) assert result["status"] == "rejected" assert result["ifra_report"]["passed"] is False def test_five_component_accord_with_registry(): # Classic 5-component accord in ethanol base via the registry path. formula = [ {"cas": "64-17-5", "weight_fraction": 0.70}, {"cas": "5989-27-5", "weight_fraction": 0.10}, {"cas": "78-70-6", "weight_fraction": 0.08}, {"cas": "24851-98-7", "weight_fraction": 0.06}, # Hedione via registry {"cas": "1222-05-5", "weight_fraction": 0.04}, # Galaxolide {"cas": "6790-58-5", "weight_fraction": 0.02}, # Ambroxan ] verifier = FragrancePipelineVerifier() result = verifier.run_sim(formula, duration_seconds=28800, interval_seconds=600) assert result["status"] == "passed" assert len(result["trajectory"]) == 49 # Ethanol depletes faster than musks assert result["depletion_rates"]["64-17-5"] > result["depletion_rates"]["6790-58-5"] def test_fraterworks_parser_skips_multipage_title_rows(): pdf = Path("data/fraterworks_free_pdfs/F_FABULOUS_242463.pdf") if not pdf.exists(): pytest.skip("Fraterworks fixture PDF is not present") parsed = parse_formula_text(extract_pdf_text(pdf)) assert parsed["name"] == "F* Fabulous" assert parsed["formula_code"] == "242463" assert parsed["declared_profile"] == "a leather fragrance" assert parsed["declared_total_parts"] == 1000.0 assert not any(row["name"] == "F* FABULOUS" for row in parsed["raw_formula"]) assert max(row["parts"] for row in parsed["raw_formula"]) == 230.0 def test_fraterworks_parser_handles_title_after_total(): pdf = Path("data/fraterworks_free_pdfs/incense-rori.pdf") if not pdf.exists(): pytest.skip("Fraterworks fixture PDF is not present") parsed = parse_formula_text(extract_pdf_text(pdf)) assert parsed["name"] == "Incense Rori" assert parsed["formula_code"] == "989045" assert parsed["declared_profile"] == "an oriental woody attar" assert parsed["declared_total_parts"] == 1000.0 assert parsed["raw_formula"][0]["name"] == "HelvetolideĀ®" assert not any(row["name"] == "INCENSE RORI" for row in parsed["raw_formula"])