PFASGroups 3.4.2__tar.gz → 3.4.4__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.
Files changed (82) hide show
  1. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/PFASDefinitionModel.py +114 -42
  2. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/data/PFAS_definitions_smarts.json +4 -4
  3. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/parser.py +30 -8
  4. {pfasgroups-3.4.2 → pfasgroups-3.4.4/PFASGroups.egg-info}/PKG-INFO +5 -1
  5. {pfasgroups-3.4.2/PFASGroups.egg-info → pfasgroups-3.4.4}/PKG-INFO +5 -1
  6. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/README.md +4 -0
  7. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/workers.py +7 -2
  8. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/pyproject.toml +1 -1
  9. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/setup.cfg +1 -1
  10. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/HalogenGroups/__init__.py +0 -0
  11. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/ComponentsSolverModel.py +0 -0
  12. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/HalogenGroupModel.py +0 -0
  13. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/PFASEmbeddings.py +0 -0
  14. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/__init__.py +0 -0
  15. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/cli.py +0 -0
  16. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/core.py +0 -0
  17. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/data/Halogen_groups_smarts.json +0 -0
  18. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/data/component_smarts.json +0 -0
  19. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/data/component_smarts_halogens.json +0 -0
  20. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/data/diatomic_bonds_dict.json +0 -0
  21. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/draw_mols.py +0 -0
  22. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/embeddings.py +0 -0
  23. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/fragmentation.py +0 -0
  24. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/generate_homologues.py +0 -0
  25. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/generate_mol.py +0 -0
  26. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/generate_paper_figures.py +0 -0
  27. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/getter.py +0 -0
  28. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/group_features.py +0 -0
  29. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/homologue_series.py +0 -0
  30. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/prioritise.py +0 -0
  31. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups.egg-info/SOURCES.txt +0 -0
  32. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups.egg-info/dependency_links.txt +0 -0
  33. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups.egg-info/entry_points.txt +0 -0
  34. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups.egg-info/requires.txt +0 -0
  35. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups.egg-info/top_level.txt +0 -0
  36. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/__init__.py +0 -0
  37. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/__main__.py +0 -0
  38. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/app.py +0 -0
  39. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/data/help.html +0 -0
  40. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/style.py +0 -0
  41. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/tabs/__init__.py +0 -0
  42. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/tabs/chemspace_tab.py +0 -0
  43. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/tabs/classify_tab.py +0 -0
  44. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/tabs/definition_tester_tab.py +0 -0
  45. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/tabs/help_tab.py +0 -0
  46. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/tabs/modelling_tab.py +0 -0
  47. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/tabs/prioritise_tab.py +0 -0
  48. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/tabs/results_tab.py +0 -0
  49. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/utils/__init__.py +0 -0
  50. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/utils/chemspace.py +0 -0
  51. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/utils/export_dialog.py +0 -0
  52. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/utils/fingerprints.py +0 -0
  53. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/utils/io_readers.py +0 -0
  54. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/utils/modelling.py +0 -0
  55. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/utils/mol_renderer.py +0 -0
  56. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_bde_resistance.py +0 -0
  57. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_component_fractions.py +0 -0
  58. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_component_ratios.py +0 -0
  59. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_comprehensive_metrics.py +0 -0
  60. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_database_integration.py +0 -0
  61. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_definition_comparison.py +0 -0
  62. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_fingerprints_vs_txppfas.py +0 -0
  63. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_fraction_analysis.py +0 -0
  64. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_generate_homologues.py +0 -0
  65. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_halogen_groups_smarts.py +0 -0
  66. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_linker_smarts.py +0 -0
  67. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_metrics.py +0 -0
  68. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_metrics_detailed.py +0 -0
  69. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_n_spacer_ring_size.py +0 -0
  70. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_parser_wildcard.py +0 -0
  71. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_pfasstructv5.py +0 -0
  72. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_prioritise.py +0 -0
  73. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_readme_and_docs_examples.py +0 -0
  74. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_results_fingerprint.py +0 -0
  75. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_results_model.py +0 -0
  76. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_results_sql.py +0 -0
  77. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_run_groups_definitions_tests.py +0 -0
  78. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_smarts_atom_count.py +0 -0
  79. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_smarts_matching.py +0 -0
  80. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_smarts_precompute.py +0 -0
  81. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_summary.py +0 -0
  82. {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_telomer_validation.py +0 -0
@@ -64,21 +64,21 @@ class PFASDefinition:
64
64
  def __str__(self):
65
65
  return self.name
66
66
 
67
- def applies_to_molecule(self,
68
- mol_or_smiles: Union[Chem.Mol, str],
67
+ def applies_to_molecule(self,
68
+ mol_or_smiles: Union[Chem.Mol, str],
69
69
  formula: Optional[Dict[str, int]] = None,
70
70
  **kwargs) -> bool:
71
71
  """Check if this PFAS definition applies to a given molecule.
72
-
72
+
73
73
  This method evaluates whether a molecule meets the structural and/or compositional
74
74
  criteria defined by this PFASDefinition. The evaluation logic depends on the
75
75
  requireBoth flag:
76
-
76
+
77
77
  - If requireBoth=False (default): Returns True if EITHER SMARTS matches OR
78
78
  fluorine ratio is met (logical OR)
79
79
  - If requireBoth=True: Returns True only if BOTH SMARTS matches AND fluorine
80
80
  ratio are met (logical AND)
81
-
81
+
82
82
  Parameters
83
83
  ----------
84
84
  mol_or_smiles : Union[Chem.Mol, str]
@@ -88,16 +88,16 @@ class PFASDefinition:
88
88
  If None, will be computed from the molecule.
89
89
  **kwargs : dict
90
90
  Additional parameters:
91
-
91
+
92
92
  - include_hydrogen (bool): Whether to include H in fluorine ratio calculation.
93
93
  Defaults to self.includeHydrogen
94
94
  - require_both (bool): Override the instance's requireBoth setting
95
-
95
+
96
96
  Returns
97
97
  -------
98
98
  bool
99
99
  True if the molecule meets the definition criteria, False otherwise
100
-
100
+
101
101
  Examples
102
102
  --------
103
103
  >>> pfas_def = PFASDefinition(
@@ -108,45 +108,101 @@ class PFASDefinition:
108
108
  True
109
109
  >>> pfas_def.applies_to_molecule("CCCCCC") # No fluorine
110
110
  False
111
-
111
+
112
112
  Notes
113
113
  -----
114
114
  - SMARTS patterns are checked using substructure matching (HasSubstructMatch)
115
- - Fluorine ratio is calculated as: F_count / total_atom_count
115
+ - Fluorine ratio is mass-weighted: F mass / total mass (see `match_details`
116
+ for the breakdown of which criterion -- SMARTS, ratio, or both -- was met,
117
+ and the actual computed ratio value)
116
118
  - Invalid SMILES strings return False
117
119
  """
120
+ return self.match_details(mol_or_smiles, formula=formula, **kwargs)['applies']
121
+
122
+ def match_details(self,
123
+ mol_or_smiles: Union[Chem.Mol, str],
124
+ formula: Optional[Dict[str, int]] = None,
125
+ **kwargs) -> Dict:
126
+ """Evaluate a molecule against this definition and report each criterion separately.
127
+
128
+ Unlike `applies_to_molecule`, which collapses everything to a single bool,
129
+ this returns the SMARTS-match result, the fluorine-ratio result, and the
130
+ actual computed ratio value individually -- useful for explaining *why* a
131
+ molecule matched (e.g. distinguishing a genuine structural PFAS from one
132
+ that only clears a fluorine-content threshold).
133
+
134
+ Parameters
135
+ ----------
136
+ mol_or_smiles : Union[Chem.Mol, str]
137
+ Input molecule as RDKit Mol object or SMILES string
138
+ formula : Optional[Dict[str, int]], default=None
139
+ Pre-computed molecular formula as {element: count} dictionary.
140
+ If None, will be computed from the molecule.
141
+ **kwargs : dict
142
+ Same as `applies_to_molecule` (`include_hydrogen`, `require_both`).
143
+
144
+ Returns
145
+ -------
146
+ dict
147
+ - 'valid' (bool): False if the input SMILES could not be parsed
148
+ - 'smarts_match' (bool): whether any SMARTS pattern matched
149
+ - 'fluorine_ratio' (float or None): the computed ratio, or None if this
150
+ definition has no fluorineRatio threshold
151
+ - 'fluorine_ratio_threshold' (float or None): this definition's threshold
152
+ - 'ratio_match' (bool): whether the ratio met the threshold (True if no
153
+ threshold is defined, matching `applies_to_molecule`'s OR semantics)
154
+ - 'applies' (bool): the same overall result `applies_to_molecule` returns
155
+ """
118
156
  # Convert SMILES to Mol if needed
119
157
  if isinstance(mol_or_smiles, str):
120
158
  mol = Chem.MolFromSmiles(mol_or_smiles)
121
159
  if mol is None:
122
- return False
160
+ return {
161
+ 'valid': False,
162
+ 'smarts_match': False,
163
+ 'fluorine_ratio': None,
164
+ 'fluorine_ratio_threshold': self.fluorineRatio,
165
+ 'ratio_match': False,
166
+ 'applies': False,
167
+ }
123
168
  else:
124
169
  mol = mol_or_smiles
125
-
170
+
126
171
  # Check SMARTS matches
127
172
  smarts_match = False
128
173
  for pattern in self.smarts_patterns:
129
174
  if mol.HasSubstructMatch(pattern):
130
175
  smarts_match = True
131
176
  break
132
-
177
+
133
178
  # Check fluorine ratio if defined
134
179
  ratio_match = True # Default to True if no ratio requirement
180
+ fluorine_ratio_value = None
135
181
  if self.fluorineRatio is not None:
136
182
  if formula is None:
137
183
  formula = self._compute_formula(mol, kwargs.get("include_hydrogen", self.includeHydrogen))
138
-
139
- ratio_match = self._check_fluorine_ratio(formula, kwargs.get("include_hydrogen", self.includeHydrogen))
140
-
184
+
185
+ fluorine_ratio_value = self._compute_fluorine_ratio(formula, kwargs.get("include_hydrogen", self.includeHydrogen))
186
+ ratio_match = fluorine_ratio_value >= self.fluorineRatio
187
+
141
188
  # Apply logic based on require_both
142
189
  if kwargs.get("require_both", self.requireBoth):
143
- return smarts_match and ratio_match
144
- else:
190
+ applies = smarts_match and ratio_match
191
+ elif self.fluorineRatio is None:
145
192
  # If no fluorine ratio is defined, only check SMARTS
146
- if self.fluorineRatio is None:
147
- return smarts_match
193
+ applies = smarts_match
194
+ else:
148
195
  # Otherwise, SMARTS OR ratio
149
- return smarts_match or ratio_match
196
+ applies = smarts_match or ratio_match
197
+
198
+ return {
199
+ 'valid': True,
200
+ 'smarts_match': smarts_match,
201
+ 'fluorine_ratio': round(fluorine_ratio_value, 4) if fluorine_ratio_value is not None else None,
202
+ 'fluorine_ratio_threshold': self.fluorineRatio,
203
+ 'ratio_match': ratio_match,
204
+ 'applies': applies,
205
+ }
150
206
 
151
207
  def _compute_formula(self, mol: Chem.Mol, include_hydrogen: bool) -> Dict[str, int]:
152
208
  """Compute molecular formula as element count dictionary.
@@ -175,38 +231,54 @@ class PFASDefinition:
175
231
 
176
232
  return formula
177
233
 
178
- def _check_fluorine_ratio(self, formula: Dict[str, int], include_hydrogen: bool) -> bool:
179
- """Check if the fluorine ratio in a molecular formula meets the threshold.
180
-
234
+ def _compute_fluorine_ratio(self, formula: Dict[str, int], include_hydrogen: bool) -> float:
235
+ """Compute the mass-weighted fluorine ratio of a molecular formula.
236
+
237
+ Mass-weighted (F mass / total mass):
238
+ `fluorine_ratio = F_mass / (molecular_weight - H_mass)`.
239
+
181
240
  Parameters
182
241
  ----------
183
242
  formula : Dict[str, int]
184
243
  Molecular formula as {element: count} dictionary
185
244
  include_hydrogen : bool
186
- If True, includes hydrogen atoms in total atom count.
187
- If False, excludes hydrogen from total (heavy atoms only)
188
-
245
+ If True, includes hydrogen mass in the total mass.
246
+ If False, excludes hydrogen mass from total (heavy atoms only) --
247
+ this is the "heavy atoms only" mode PFASSTRUCTv5 uses.
248
+
189
249
  Returns
190
250
  -------
191
- bool
192
- True if (F_count / total_atoms) >= self.fluorineRatio, False otherwise
193
-
194
- Notes
195
- -----
196
- - Returns False if total_atoms is 0
197
- - Formula with no fluorine (F_count=0) will fail unless fluorineRatio=0
251
+ float
252
+ F_mass / total_mass, or 0.0 if total_mass is 0
198
253
  """
254
+ from rdkit.Chem import GetPeriodicTable
255
+ pt = GetPeriodicTable()
256
+
199
257
  f_count = formula.get('F', 0)
200
-
258
+ f_mass = f_count * pt.GetAtomicWeight('F')
259
+
201
260
  if include_hydrogen:
202
- total_atoms = sum(formula.values())
261
+ total_mass = sum(count * pt.GetAtomicWeight(sym) for sym, count in formula.items())
203
262
  else:
204
- total_atoms = sum(v for k, v in formula.items() if k != 'H')
205
-
206
- if total_atoms == 0:
207
- return False
208
-
209
- ratio = f_count / total_atoms
263
+ total_mass = sum(count * pt.GetAtomicWeight(sym) for sym, count in formula.items() if sym != 'H')
264
+
265
+ if total_mass == 0:
266
+ return 0.0
267
+
268
+ return f_mass / total_mass
269
+
270
+ def _check_fluorine_ratio(self, formula: Dict[str, int], include_hydrogen: bool) -> bool:
271
+ """Check if the fluorine ratio in a molecular formula meets the threshold.
272
+
273
+ Thin boolean wrapper around `_compute_fluorine_ratio`, kept for backward
274
+ compatibility with any external callers of this (nominally private) method.
275
+
276
+ Returns
277
+ -------
278
+ bool
279
+ True if (F_mass / total_mass) >= self.fluorineRatio, False otherwise
280
+ """
281
+ ratio = self._compute_fluorine_ratio(formula, include_hydrogen)
210
282
  return ratio >= self.fluorineRatio
211
283
 
212
284
  def test(self, test_data=None):
@@ -134,11 +134,13 @@
134
134
  ],
135
135
  "fluorineRatio": 0.3,
136
136
  "includeHydrogen": false,
137
- "description": "Custom structural patterns or F ratio ≥ 0.3 (heavy atoms only)",
137
+ "description": "Custom structural patterns or F ratio >= 0.3, mass-weighted (F mass / heavy-atom mass).",
138
138
  "test": {
139
139
  "category": "definition",
140
140
  "examples": {
141
141
  "positives": [
142
+ "C1=COC(=C1)C=C(S(=O)(=O)C(F)(F)F)S(=O)(=O)C(F)(F)F",
143
+ "COC(=C(F)Cl)F",
142
144
  "C(=O)(C(F)(F)Cl)C(F)(F)Cl",
143
145
  "COC(C(OC(C(=O)C(F)(F)F)(F)F)F)(F)F",
144
146
  "C(C(=O)CC(C(=O)C(F)(F)F)(F)F)C(C(=O)C(F)(F)F)(F)F",
@@ -160,9 +162,7 @@
160
162
  ],
161
163
  "negatives": [
162
164
  "CCCCC(CC(C(F)(F)Br)(F)Cl)Br",
163
- "C(C(OC(Cl)(Cl)Cl)(F)F)(F)Cl",
164
- "C1=COC(=C1)C=C(S(=O)(=O)C(F)(F)F)S(=O)(=O)C(F)(F)F",
165
- "COC(=C(F)Cl)F"
165
+ "C(C(OC(Cl)(Cl)Cl)(F)F)(F)Cl"
166
166
  ]
167
167
  }
168
168
  }
@@ -179,12 +179,21 @@ def parse_definitions_in_mol(mol, **kwargs):
179
179
  pfas_definitions = kwargs.get('pfas_definitions')
180
180
  for pdef in pfas_definitions:
181
181
  matched = False
182
+ best_details = None
182
183
  for mol in frags:
183
- if pdef.applies_to_molecule(mol_or_smiles=mol, **kwargs) is True:
184
+ details = pdef.match_details(mol_or_smiles=mol, **kwargs)
185
+ # Keep the most informative details across fragments: prefer the
186
+ # fragment that actually matched, and among matches prefer the one
187
+ # with the higher fluorine ratio (most representative of the PFAS
188
+ # fragment rather than an unrelated counterion/fragment).
189
+ if details['applies'] is True:
184
190
  matched = True
185
- break
191
+ if best_details is None or (details['fluorine_ratio'] or 0) > (best_details['fluorine_ratio'] or 0):
192
+ best_details = details
193
+ elif best_details is None:
194
+ best_details = details
186
195
  if matched is True:
187
- definition_matches.append(pdef)
196
+ definition_matches.append((pdef, best_details))
188
197
  return definition_matches
189
198
 
190
199
 
@@ -696,9 +705,7 @@ def setup_halogen_groups_database(
696
705
  ):
697
706
  """Set up halogen groups metadata tables in a database.
698
707
 
699
- This function creates tables to store halogen group definitions and SMARTS patterns,
700
- similar to the load_pfas_groups function in zeropmdb.
701
-
708
+ This function creates tables to store halogen group definitions and SMARTS patterns.
702
709
  Parameters
703
710
  ----------
704
711
  conn : str or sqlalchemy.engine.Engine
@@ -873,6 +880,12 @@ def parse_mols(mols, output_format='list', include_PFAS_definitions=True,
873
880
  - ``'id'``: integer definition ID
874
881
  - ``'definition_name'``: definition name
875
882
  - ``'halogen'``: ``'F'`` (definitions only match when F is enabled)
883
+ - ``'smarts_match'``: bool, whether any of the definition's SMARTS patterns matched
884
+ - ``'fluorine_ratio'``: float or None, the computed mass-weighted F ratio
885
+ (None if this definition has no fluorineRatio threshold)
886
+ - ``'fluorine_ratio_threshold'``: float or None, the definition's configured threshold
887
+ - ``'ratio_match'``: bool, whether the ratio met the threshold (True if no
888
+ threshold is defined)
876
889
  """
877
890
 
878
891
  real_halogens, include_h_components, include_wildcards = _normalize_halogen_controls(halogens)
@@ -1130,7 +1143,12 @@ def parse_mols(mols, output_format='list', include_PFAS_definitions=True,
1130
1143
  'id': definition.id,
1131
1144
  'definition_name': definition.name,
1132
1145
  'type':'PFASdefinition',
1133
- 'halogen': 'F'} for definition in definitions])
1146
+ 'halogen': 'F',
1147
+ 'smarts_match': details['smarts_match'],
1148
+ 'fluorine_ratio': details['fluorine_ratio'],
1149
+ 'fluorine_ratio_threshold': details['fluorine_ratio_threshold'],
1150
+ 'ratio_match': details['ratio_match'],
1151
+ } for definition, details in definitions])
1134
1152
  # Convert results to list format (one entry per molecule)
1135
1153
  results_list = [r for r in results.values()]
1136
1154
  # Format output based on requested format
@@ -1154,7 +1172,11 @@ def parse_mols(mols, output_format='list', include_PFAS_definitions=True,
1154
1172
  'smiles': entry['smiles'],
1155
1173
  'match_id': match['match_id'],
1156
1174
  'match_name': match['definition_name'],
1157
- 'match_type': match['type']
1175
+ 'match_type': match['type'],
1176
+ 'smarts_match': match.get('smarts_match'),
1177
+ 'fluorine_ratio': match.get('fluorine_ratio'),
1178
+ 'fluorine_ratio_threshold': match.get('fluorine_ratio_threshold'),
1179
+ 'ratio_match': match.get('ratio_match'),
1158
1180
  })
1159
1181
  df = pd.DataFrame(rows)
1160
1182
  return df.to_csv(index=False) if output_format == 'csv' else df
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PFASGroups
3
- Version: 3.4.2
3
+ Version: 3.4.4
4
4
  Summary: A comprehensive cheminformatics package for automated detection, classification, and analysis of halogenated substances, with a focus on per- and polyfluoroalkyl substances (PFAS). Combines SMARTS pattern matching, molecular formula constraints, and graph-based pathfinding to identify 119 groups. Supports creations of embeddings with graph metrics for Machine Learning workflows.
5
5
  Author: Luc T. Miaz
6
6
  Author-email: "Luc T. Miaz" <luc@miaz.ch>
@@ -846,6 +846,10 @@ See [USER_GUIDE.md](USER_GUIDE.md) for comprehensive examples including:
846
846
  - Integration with pandas and scikit-learn
847
847
 
848
848
  ## Summary of changes by version
849
+ - **Version 3.4.4**: Add fluorine ratio and structural match to returned results (for debugging or diplay).
850
+
851
+ - **Version 3.4.3**: Fixed ratio error, specific to PFASSTRUCTv5
852
+
849
853
  - **Version 3.4.2**: Fixed ambiguity with OPPT 2023.
850
854
 
851
855
  - **Version 3.4.1**: Added generic embedding for non-halogenated specific compounds which includes wildcard-components functional groups and halogen component size. Fixed H components and added cyclic and aryl H components.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PFASGroups
3
- Version: 3.4.2
3
+ Version: 3.4.4
4
4
  Summary: A comprehensive cheminformatics package for automated detection, classification, and analysis of halogenated substances, with a focus on per- and polyfluoroalkyl substances (PFAS). Combines SMARTS pattern matching, molecular formula constraints, and graph-based pathfinding to identify 119 groups. Supports creations of embeddings with graph metrics for Machine Learning workflows.
5
5
  Author: Luc T. Miaz
6
6
  Author-email: "Luc T. Miaz" <luc@miaz.ch>
@@ -846,6 +846,10 @@ See [USER_GUIDE.md](USER_GUIDE.md) for comprehensive examples including:
846
846
  - Integration with pandas and scikit-learn
847
847
 
848
848
  ## Summary of changes by version
849
+ - **Version 3.4.4**: Add fluorine ratio and structural match to returned results (for debugging or diplay).
850
+
851
+ - **Version 3.4.3**: Fixed ratio error, specific to PFASSTRUCTv5
852
+
849
853
  - **Version 3.4.2**: Fixed ambiguity with OPPT 2023.
850
854
 
851
855
  - **Version 3.4.1**: Added generic embedding for non-halogenated specific compounds which includes wildcard-components functional groups and halogen component size. Fixed H components and added cyclic and aryl H components.
@@ -798,6 +798,10 @@ See [USER_GUIDE.md](USER_GUIDE.md) for comprehensive examples including:
798
798
  - Integration with pandas and scikit-learn
799
799
 
800
800
  ## Summary of changes by version
801
+ - **Version 3.4.4**: Add fluorine ratio and structural match to returned results (for debugging or diplay).
802
+
803
+ - **Version 3.4.3**: Fixed ratio error, specific to PFASSTRUCTv5
804
+
801
805
  - **Version 3.4.2**: Fixed ambiguity with OPPT 2023.
802
806
 
803
807
  - **Version 3.4.1**: Added generic embedding for non-halogenated specific compounds which includes wildcard-components functional groups and halogen component size. Fixed H components and added cyclic and aryl H components.
@@ -146,15 +146,20 @@ class DefinitionTestWorker(Worker):
146
146
  self.progress.emit(50)
147
147
 
148
148
  matched = parse_definitions_in_mol(mol)
149
- matched_ids = {d.id for d in matched}
149
+ matched_details = {pdef.id: details for pdef, details in matched}
150
150
 
151
151
  results = []
152
152
  for d in definitions:
153
+ details = matched_details.get(d.id)
153
154
  results.append({
154
155
  "id": d.id,
155
156
  "name": d.name,
156
157
  "description": d.description,
157
- "passed": d.id in matched_ids,
158
+ "passed": d.id in matched_details,
159
+ "smarts_match": details["smarts_match"] if details else False,
160
+ "fluorine_ratio": details["fluorine_ratio"] if details else None,
161
+ "fluorine_ratio_threshold": d.fluorineRatio,
162
+ "ratio_match": details["ratio_match"] if details else False,
158
163
  })
159
164
 
160
165
  self.progress.emit(100)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "PFASGroups"
7
- version = "3.4.2"
7
+ version = "3.4.4"
8
8
  description = "A comprehensive cheminformatics package for automated detection, classification, and analysis of halogenated substances, with a focus on per- and polyfluoroalkyl substances (PFAS). Combines SMARTS pattern matching, molecular formula constraints, and graph-based pathfinding to identify 119 groups. Supports creations of embeddings with graph metrics for Machine Learning workflows."
9
9
  authors = [
10
10
  { name = "Luc T. Miaz", email = "luc@miaz.ch" }
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = PFASGroups
3
- version = 3.4.2
3
+ version = 3.4.4
4
4
  description = A comprehensive cheminformatics package for automated detection, classification, and analysis of halogenated substances, with a focus on per- and polyfluoroalkyl substances (PFAS). Supports multi-halogen fingerprinting (F, Cl, Br, I) with per/poly saturation filtering and stackable fingerprint vectors.
5
5
  long_description = file: README.md
6
6
  long_description_content_type = text/markdown
File without changes
File without changes
File without changes
File without changes
File without changes