PFASGroups 3.4.3__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.3 → pfasgroups-3.4.4}/PFASGroups/PFASDefinitionModel.py +98 -39
  2. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups/data/PFAS_definitions_smarts.json +1 -1
  3. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups/parser.py +30 -8
  4. {pfasgroups-3.4.3 → pfasgroups-3.4.4/PFASGroups.egg-info}/PKG-INFO +3 -1
  5. {pfasgroups-3.4.3/PFASGroups.egg-info → pfasgroups-3.4.4}/PKG-INFO +3 -1
  6. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/README.md +2 -0
  7. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/gui/workers.py +7 -2
  8. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/pyproject.toml +1 -1
  9. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/setup.cfg +1 -1
  10. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/HalogenGroups/__init__.py +0 -0
  11. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups/ComponentsSolverModel.py +0 -0
  12. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups/HalogenGroupModel.py +0 -0
  13. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups/PFASEmbeddings.py +0 -0
  14. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups/__init__.py +0 -0
  15. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups/cli.py +0 -0
  16. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups/core.py +0 -0
  17. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups/data/Halogen_groups_smarts.json +0 -0
  18. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups/data/component_smarts.json +0 -0
  19. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups/data/component_smarts_halogens.json +0 -0
  20. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups/data/diatomic_bonds_dict.json +0 -0
  21. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups/draw_mols.py +0 -0
  22. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups/embeddings.py +0 -0
  23. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups/fragmentation.py +0 -0
  24. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups/generate_homologues.py +0 -0
  25. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups/generate_mol.py +0 -0
  26. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups/generate_paper_figures.py +0 -0
  27. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups/getter.py +0 -0
  28. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups/group_features.py +0 -0
  29. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups/homologue_series.py +0 -0
  30. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups/prioritise.py +0 -0
  31. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups.egg-info/SOURCES.txt +0 -0
  32. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups.egg-info/dependency_links.txt +0 -0
  33. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups.egg-info/entry_points.txt +0 -0
  34. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups.egg-info/requires.txt +0 -0
  35. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/PFASGroups.egg-info/top_level.txt +0 -0
  36. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/gui/__init__.py +0 -0
  37. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/gui/__main__.py +0 -0
  38. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/gui/app.py +0 -0
  39. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/gui/data/help.html +0 -0
  40. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/gui/style.py +0 -0
  41. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/gui/tabs/__init__.py +0 -0
  42. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/gui/tabs/chemspace_tab.py +0 -0
  43. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/gui/tabs/classify_tab.py +0 -0
  44. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/gui/tabs/definition_tester_tab.py +0 -0
  45. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/gui/tabs/help_tab.py +0 -0
  46. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/gui/tabs/modelling_tab.py +0 -0
  47. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/gui/tabs/prioritise_tab.py +0 -0
  48. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/gui/tabs/results_tab.py +0 -0
  49. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/gui/utils/__init__.py +0 -0
  50. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/gui/utils/chemspace.py +0 -0
  51. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/gui/utils/export_dialog.py +0 -0
  52. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/gui/utils/fingerprints.py +0 -0
  53. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/gui/utils/io_readers.py +0 -0
  54. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/gui/utils/modelling.py +0 -0
  55. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/gui/utils/mol_renderer.py +0 -0
  56. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_bde_resistance.py +0 -0
  57. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_component_fractions.py +0 -0
  58. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_component_ratios.py +0 -0
  59. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_comprehensive_metrics.py +0 -0
  60. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_database_integration.py +0 -0
  61. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_definition_comparison.py +0 -0
  62. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_fingerprints_vs_txppfas.py +0 -0
  63. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_fraction_analysis.py +0 -0
  64. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_generate_homologues.py +0 -0
  65. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_halogen_groups_smarts.py +0 -0
  66. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_linker_smarts.py +0 -0
  67. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_metrics.py +0 -0
  68. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_metrics_detailed.py +0 -0
  69. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_n_spacer_ring_size.py +0 -0
  70. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_parser_wildcard.py +0 -0
  71. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_pfasstructv5.py +0 -0
  72. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_prioritise.py +0 -0
  73. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_readme_and_docs_examples.py +0 -0
  74. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_results_fingerprint.py +0 -0
  75. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_results_model.py +0 -0
  76. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_results_sql.py +0 -0
  77. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_run_groups_definitions_tests.py +0 -0
  78. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_smarts_atom_count.py +0 -0
  79. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_smarts_matching.py +0 -0
  80. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_smarts_precompute.py +0 -0
  81. {pfasgroups-3.4.3 → pfasgroups-3.4.4}/tests/test_summary.py +0 -0
  82. {pfasgroups-3.4.3 → 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,16 +231,11 @@ 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.
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.
180
236
 
181
- Mass-weighted (F mass / total mass), matching the zeropmdb production
182
- database's `fluorine_ratio` field (see `Update_CFHNOPS.sql`):
183
- `fluorine_ratio = F_mass / (molecular_weight - H_mass)`. This is NOT an
184
- atom-count ratio -- a molecule with heavy non-F/non-H atoms (e.g. Cl, Br)
185
- will have a lower ratio per atom than per unit mass would suggest, and
186
- vice versa for light atoms (C, N, O), so the two formulas can disagree
187
- on which side of a threshold like 0.3 a given molecule falls.
237
+ Mass-weighted (F mass / total mass):
238
+ `fluorine_ratio = F_mass / (molecular_weight - H_mass)`.
188
239
 
189
240
  Parameters
190
241
  ----------
@@ -197,13 +248,8 @@ class PFASDefinition:
197
248
 
198
249
  Returns
199
250
  -------
200
- bool
201
- True if (F_mass / total_mass) >= self.fluorineRatio, False otherwise
202
-
203
- Notes
204
- -----
205
- - Returns False if total_mass is 0
206
- - 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
207
253
  """
208
254
  from rdkit.Chem import GetPeriodicTable
209
255
  pt = GetPeriodicTable()
@@ -217,9 +263,22 @@ class PFASDefinition:
217
263
  total_mass = sum(count * pt.GetAtomicWeight(sym) for sym, count in formula.items() if sym != 'H')
218
264
 
219
265
  if total_mass == 0:
220
- return False
266
+ return 0.0
267
+
268
+ return f_mass / total_mass
221
269
 
222
- ratio = f_mass / total_mass
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)
223
282
  return ratio >= self.fluorineRatio
224
283
 
225
284
  def test(self, test_data=None):
@@ -134,7 +134,7 @@
134
134
  ],
135
135
  "fluorineRatio": 0.3,
136
136
  "includeHydrogen": false,
137
- "description": "Custom structural patterns or F ratio >= 0.3, mass-weighted (F mass / heavy-atom mass), matching the zeropmdb production database's fluorine_ratio field. Not an atom-count ratio: molecules with heavier non-F/non-H atoms (Cl, Br, S...) score lower per unit mass than per atom, and vice versa for lighter atoms (C, N, O).",
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": {
@@ -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.3
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,8 @@ 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
+
849
851
  - **Version 3.4.3**: Fixed ratio error, specific to PFASSTRUCTv5
850
852
 
851
853
  - **Version 3.4.2**: Fixed ambiguity with OPPT 2023.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PFASGroups
3
- Version: 3.4.3
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,8 @@ 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
+
849
851
  - **Version 3.4.3**: Fixed ratio error, specific to PFASSTRUCTv5
850
852
 
851
853
  - **Version 3.4.2**: Fixed ambiguity with OPPT 2023.
@@ -798,6 +798,8 @@ 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
+
801
803
  - **Version 3.4.3**: Fixed ratio error, specific to PFASSTRUCTv5
802
804
 
803
805
  - **Version 3.4.2**: Fixed ambiguity with OPPT 2023.
@@ -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.3"
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.3
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