PFAS Definition Checker

Check molecules against regulatory PFAS definitions

Select Definitions to Check:

Definition Criteria (Click to edit/configure):

  • OECD: Contains at least one perfluorinated methyl group (CFā‚ƒ-) or one perfluorinated methylene group (–CF₂–)
  • EU: Contains at least one of the moieties: -CFā‚‚-, -CFā‚ƒ, or any fluorinated structure meeting fluorine ratio requirements
  • OPPT 2023: Contains at least one of the following structures: R-CFā‚‚-CFā‚‚-R', R-CFā‚‚-CFā‚ƒ, R-CFā‚ƒ
  • UK: Contains at least one perfluorinated alkyl moiety (Cā‚™Fā‚‚ā‚™ā‚Šā‚-) with n ≄ 1
  • PFASSTRUCTv5: Custom structural patterns with optional fluorine ratio criteria

Note: SMARTS patterns for each definition can be configured. Current implementation uses placeholder patterns.

🔗 API Usage Examples (Python / R / cURL / PowerShell)

Check against all definitions (Python)

import requests

smiles_list = [
    "FC(F)(F)CCCC(=O)O",       # PFBA   — should match all
    "FC(F)(F)CC(F)(F)F",        # perfluorobutane
    "CCCCCCCC",                 # octane — should match none
]

resp = requests.post("https://chem.cogitopia.dev/check-definitions", json={
    "molecules": smiles_list
    # omit "definitions" to check against all five
})
data = resp.json()

# Print results table — pad each mark to column header width
defs  = list(data["results"][0]["definitions"].keys())
col_w = [len(d.upper()) for d in defs]
hdr   = f"{'SMILES':<32}  " + "  ".join(d.upper() for d in defs)
print(hdr)
print("-" * len(hdr))
for r in data["results"]:
    flags = "  ".join(("✓" if v["matched"] else "✗").center(w)
                       for v, w in zip(r["definitions"].values(), col_w))
    print(f"{r['smiles'][:32]:<32}  {flags}")

# Statistics summary
print("\nStatistics:")
for def_id, stats in data["statistics"].items():
    print(f"  {def_id}: {stats['matched']}/{stats['total']} matched")

Check definitions (R)

library(httr); library(jsonlite)

# omit "definitions" to check against all five
body <- list(
  molecules = list("FC(F)(F)CCCC(=O)O", "FC(F)(F)CC(F)(F)F", "CCCCCCCC")
)

resp <- POST("https://chem.cogitopia.dev/check-definitions",
             body = toJSON(body, auto_unbox = TRUE),
             content_type_json())

data <- content(resp, "parsed")

# Aligned results table
defs  <- names(data$results[[1]]$definitions)
col_w <- nchar(toupper(defs))
hdr   <- paste0(sprintf("%-32s  ", "SMILES"),
                paste(toupper(defs), collapse = "  "))
cat(hdr, "\n")
cat(strrep("-", nchar(hdr)), "\n")
for (r in data$results) {
  marks <- mapply(function(d, w) {
    sym <- if (r$definitions[[d]]$matched) "\u2713" else "\u2717"
    formatC(sym, width = w, flag = "-")
  }, defs, col_w, SIMPLIFY = TRUE)
  cat(sprintf("%-32s  %s\n", substr(r$smiles, 1, 32),
              paste(marks, collapse = "  ")))
}

# Statistics
for (def_id in names(data$statistics)) {
  s <- data$statistics[[def_id]]
  cat(sprintf("%-12s  %d/%d matched\n", def_id, s$matched, s$total))
}

Check definitions (cURL)

curl -s -X POST https://chem.cogitopia.dev/check-definitions \
  -H "Content-Type: application/json" \
  -d '{"molecules":["FC(F)(F)CCCC(=O)O","FC(F)(F)CC(F)(F)F","CCCCCCCC"]}' \
  | jq '.results[] | {smiles, matched: [.definitions|to_entries[]|select(.value.matched).key]}'

Check definitions (PowerShell)

$body = @{
    molecules = @("FC(F)(F)CCCC(=O)O", "FC(F)(F)CC(F)(F)F", "CCCCCCCC")
    # omit definitions to check all five
} | ConvertTo-Json

$data = Invoke-RestMethod `
    -Uri "https://chem.cogitopia.dev/check-definitions" `
    -Method Post -Body $body -ContentType "application/json"

# Statistics table
$data.statistics.PSObject.Properties | ForEach-Object {
    Write-Host "$($_.Name): $($_.Value.matched)/$($_.Value.total) matched"
}

# Per-molecule results
$data.results | ForEach-Object {
    $r = $_
    $matched = $r.definitions.PSObject.Properties |
        Where-Object { $_.Value.matched } |
        Select-Object -ExpandProperty Name
    Write-Host "$($r.smiles): MATCHED [$($matched -join ', ')]"
}
Climate Responsible Hosting: This web application is hosted on Infomaniak servers powered by 100% local renewable energy (60% hydraulic power and 40% solar power). The data center recovers and redistributes its waste heat to neighboring households.