well-log-toolkit 0.1.150__py3-none-any.whl → 0.1.152__py3-none-any.whl

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.
@@ -48,6 +48,511 @@ def _sanitize_for_json(obj):
48
48
  return obj
49
49
 
50
50
 
51
+ class SumsAvgResult(dict):
52
+ """
53
+ Dictionary subclass for sums_avg results with reporting capabilities.
54
+
55
+ Behaves exactly like a regular dict but adds the `.report()` method
56
+ for generating formatted reports with cross-well aggregation.
57
+
58
+ Examples
59
+ --------
60
+ >>> results = manager.properties(['PHIE', 'PERM']).filter('Facies').filter_intervals("Zones").sums_avg()
61
+ >>> results['Well_A'] # Normal dict access
62
+ >>> results.report(zones=[...], groups={...}, columns=[...]) # Generate report
63
+ """
64
+
65
+ def report(
66
+ self,
67
+ zones: list[str],
68
+ groups: dict[str, list[str]],
69
+ columns: list[dict],
70
+ print_report: bool = True
71
+ ) -> Optional[dict]:
72
+ """
73
+ Generate a structured report with cross-well aggregation.
74
+
75
+ Parameters
76
+ ----------
77
+ zones : list[str]
78
+ Zone names to include in the report
79
+ groups : dict[str, list[str]]
80
+ Facies grouping, e.g., {"NonNet": ["NonNet", "Slump"], "Net": ["Channel Sand"]}
81
+ columns : list[dict]
82
+ Column specifications, each with:
83
+ - property (str, required): Property name in results
84
+ - stat (str, required): Statistic to extract (mean, std_dev, p10, etc.)
85
+ - label (str, optional): Display name (defaults to stat)
86
+ - format (str, optional): Number format (e.g., ".4f")
87
+ - unit (str, optional): Display unit for printing
88
+ - factor (float, optional): Multiplier for value conversion (default 1.0)
89
+ - agg (str, optional): Cross-well aggregation method:
90
+ - "arithmetic" (default for mean): thickness-weighted arithmetic mean
91
+ - "geometric": thickness-weighted geometric mean (for permeability)
92
+ - "pooled" (default for std_dev): pooled standard deviation
93
+ - "sum": simple sum (for thickness)
94
+ print_report : bool, default True
95
+ If True, print formatted report. If False, return structured data.
96
+
97
+ Returns
98
+ -------
99
+ dict or None
100
+ If print_report=False, returns structured data dict.
101
+ If print_report=True, prints report and returns None.
102
+
103
+ Raises
104
+ ------
105
+ ValueError
106
+ If pooled aggregation is requested but corresponding mean column is missing.
107
+
108
+ Examples
109
+ --------
110
+ >>> results.report(
111
+ ... zones=["Sand 3_SST", "Sand 2_SST"],
112
+ ... groups={"NonNet": ["NonNet", "Slump"], "Net": ["Channel Sand", "LowQ Sand"]},
113
+ ... columns=[
114
+ ... {"property": "CPI_PHIE_2025", "stat": "mean", "label": "por", "format": ".4f"},
115
+ ... {"property": "CPI_PHIE_2025", "stat": "std_dev", "label": "std", "format": ".4f"},
116
+ ... {"property": "CPI_PERM_CALC_2025", "stat": "mean", "label": "perm", "agg": "geometric", "unit": "mD", "format": ".2f"},
117
+ ... ]
118
+ ... )
119
+ """
120
+ # Validate columns
121
+ self._validate_columns(columns)
122
+
123
+ # Generate structured data
124
+ report_data = self._generate_report_data(zones, groups, columns)
125
+
126
+ if print_report:
127
+ self._print_report(report_data, columns)
128
+ return None
129
+ else:
130
+ return report_data
131
+
132
+ def _validate_columns(self, columns: list[dict]) -> None:
133
+ """Validate column specifications."""
134
+ # Check required fields
135
+ for i, col in enumerate(columns):
136
+ if 'property' not in col:
137
+ raise ValueError(f"Column {i} missing required 'property' field")
138
+ if 'stat' not in col:
139
+ raise ValueError(f"Column {i} missing required 'stat' field")
140
+
141
+ # Check pooled std_dev has corresponding mean
142
+ for col in columns:
143
+ agg = col.get('agg')
144
+ stat = col.get('stat')
145
+ prop = col.get('property')
146
+
147
+ # Default agg for std_dev is pooled
148
+ if stat == 'std_dev' and agg is None:
149
+ agg = 'pooled'
150
+
151
+ if agg == 'pooled':
152
+ # Find corresponding mean column
153
+ has_mean = any(
154
+ c.get('property') == prop and c.get('stat') == 'mean'
155
+ for c in columns
156
+ )
157
+ if not has_mean:
158
+ raise ValueError(
159
+ f"Column with property='{prop}' and stat='std_dev' uses pooled aggregation, "
160
+ f"but no corresponding mean column found. Add a column with "
161
+ f"property='{prop}' and stat='mean'."
162
+ )
163
+
164
+ def _get_column_defaults(self, col: dict) -> dict:
165
+ """Get column spec with defaults applied."""
166
+ stat = col.get('stat', 'mean')
167
+
168
+ # Default aggregation based on stat type
169
+ if stat == 'std_dev':
170
+ default_agg = 'pooled'
171
+ else:
172
+ default_agg = 'arithmetic'
173
+
174
+ return {
175
+ 'property': col['property'],
176
+ 'stat': stat,
177
+ 'label': col.get('label', stat),
178
+ 'format': col.get('format', '.4f'),
179
+ 'unit': col.get('unit', ''),
180
+ 'factor': col.get('factor', 1.0),
181
+ 'agg': col.get('agg', default_agg),
182
+ }
183
+
184
+ def _extract_value(self, facies_data: dict, col: dict) -> Optional[float]:
185
+ """Extract a value from facies data based on column spec."""
186
+ col = self._get_column_defaults(col)
187
+ prop = col['property']
188
+ stat = col['stat']
189
+ factor = col['factor']
190
+
191
+ if prop not in facies_data:
192
+ return None
193
+
194
+ prop_data = facies_data[prop]
195
+ if not isinstance(prop_data, dict) or stat not in prop_data:
196
+ return None
197
+
198
+ value = prop_data[stat]
199
+ if value is None:
200
+ return None
201
+
202
+ return value * factor
203
+
204
+ def _generate_report_data(
205
+ self,
206
+ zones: list[str],
207
+ groups: dict[str, list[str]],
208
+ columns: list[dict]
209
+ ) -> dict:
210
+ """Generate structured report data from results."""
211
+ report = {}
212
+
213
+ # Collect data for aggregation
214
+ # Structure: {zone: {group: {facies: {label: [values], "thick": [thicknesses]}}}}
215
+ aggregation_data = {}
216
+
217
+ # Process each well
218
+ for well_name, well_data in self.items():
219
+ if well_name == "Summary":
220
+ continue # Skip if already has summary
221
+
222
+ well_report = {}
223
+
224
+ for zone_name in zones:
225
+ if zone_name not in well_data:
226
+ continue
227
+
228
+ zone_data = well_data[zone_name]
229
+ zone_report = {}
230
+
231
+ # Calculate total zone thickness from all facies
232
+ zone_thickness = 0.0
233
+ for facies_name, facies_data in zone_data.items():
234
+ if isinstance(facies_data, dict) and 'thickness' in facies_data:
235
+ zone_thickness += facies_data['thickness']
236
+
237
+ zone_report['thickness'] = zone_thickness
238
+
239
+ # Process each group
240
+ for group_name, facies_list in groups.items():
241
+ existing_facies = [f for f in facies_list if f in zone_data]
242
+ if not existing_facies:
243
+ continue
244
+
245
+ group_report = {}
246
+ group_thickness = sum(
247
+ zone_data[f]['thickness'] for f in existing_facies
248
+ if isinstance(zone_data[f], dict) and 'thickness' in zone_data[f]
249
+ )
250
+
251
+ group_report['thickness'] = group_thickness
252
+ group_report['fraction'] = group_thickness / zone_thickness if zone_thickness > 0 else 0.0
253
+
254
+ # Process each facies in the group
255
+ for facies_name in existing_facies:
256
+ facies_data = zone_data[facies_name]
257
+ if not isinstance(facies_data, dict):
258
+ continue
259
+
260
+ facies_thickness = facies_data.get('thickness', 0.0)
261
+ if facies_thickness <= 0:
262
+ continue
263
+
264
+ facies_report = {
265
+ 'thickness': facies_thickness,
266
+ 'fraction': facies_thickness / group_thickness if group_thickness > 0 else 0.0,
267
+ }
268
+
269
+ # Extract column values
270
+ for col in columns:
271
+ col_def = self._get_column_defaults(col)
272
+ label = col_def['label']
273
+ value = self._extract_value(facies_data, col)
274
+ facies_report[label] = value
275
+
276
+ group_report[facies_name] = facies_report
277
+
278
+ # Collect for aggregation
279
+ if zone_name not in aggregation_data:
280
+ aggregation_data[zone_name] = {}
281
+ if group_name not in aggregation_data[zone_name]:
282
+ aggregation_data[zone_name][group_name] = {}
283
+ if facies_name not in aggregation_data[zone_name][group_name]:
284
+ aggregation_data[zone_name][group_name][facies_name] = {
285
+ 'thick': [], 'values': {}
286
+ }
287
+
288
+ agg_facies = aggregation_data[zone_name][group_name][facies_name]
289
+ agg_facies['thick'].append(facies_thickness)
290
+
291
+ for col in columns:
292
+ col_def = self._get_column_defaults(col)
293
+ label = col_def['label']
294
+ value = self._extract_value(facies_data, col)
295
+ if label not in agg_facies['values']:
296
+ agg_facies['values'][label] = []
297
+ agg_facies['values'][label].append(value)
298
+
299
+ zone_report[group_name] = group_report
300
+
301
+ if zone_report:
302
+ well_report[zone_name] = zone_report
303
+
304
+ if well_report:
305
+ report[well_name] = well_report
306
+
307
+ # Generate Summary
308
+ summary = self._generate_summary(aggregation_data, columns, zones, groups)
309
+ if summary:
310
+ report['Summary'] = summary
311
+
312
+ return report
313
+
314
+ def _generate_summary(
315
+ self,
316
+ aggregation_data: dict,
317
+ columns: list[dict],
318
+ zones: list[str],
319
+ groups: dict[str, list[str]]
320
+ ) -> dict:
321
+ """Generate cross-well summary using thickness-weighted aggregation."""
322
+ summary = {}
323
+
324
+ # Build lookup for mean values needed by pooled std
325
+ # {(zone, group, facies, property): grand_mean}
326
+ grand_means = {}
327
+
328
+ # First pass: compute all arithmetic means (needed for pooled std)
329
+ for zone_name, zone_agg in aggregation_data.items():
330
+ for group_name, group_agg in zone_agg.items():
331
+ for facies_name, facies_agg in group_agg.items():
332
+ thicks = np.array(facies_agg['thick'])
333
+ total_thick = np.sum(thicks)
334
+ if total_thick <= 0:
335
+ continue
336
+
337
+ for col in columns:
338
+ col_def = self._get_column_defaults(col)
339
+ if col_def['stat'] == 'mean':
340
+ label = col_def['label']
341
+ prop = col_def['property']
342
+ values = facies_agg['values'].get(label, [])
343
+
344
+ valid_mask = [v is not None for v in values]
345
+ if not any(valid_mask):
346
+ continue
347
+
348
+ valid_vals = np.array([v for v, m in zip(values, valid_mask) if m])
349
+ valid_thicks = np.array([t for t, m in zip(thicks, valid_mask) if m])
350
+ valid_total = np.sum(valid_thicks)
351
+
352
+ if valid_total > 0:
353
+ grand_mean = np.sum(valid_vals * valid_thicks) / valid_total
354
+ grand_means[(zone_name, group_name, facies_name, prop)] = grand_mean
355
+
356
+ # Second pass: compute all aggregated values
357
+ for zone_name in zones:
358
+ if zone_name not in aggregation_data:
359
+ continue
360
+
361
+ zone_agg = aggregation_data[zone_name]
362
+ zone_summary = {'thickness': 0.0}
363
+
364
+ for group_name, facies_list in groups.items():
365
+ if group_name not in zone_agg:
366
+ continue
367
+
368
+ group_agg = zone_agg[group_name]
369
+ group_summary = {'thickness': 0.0}
370
+
371
+ for facies_name in facies_list:
372
+ if facies_name not in group_agg:
373
+ continue
374
+
375
+ facies_agg = group_agg[facies_name]
376
+ thicks = np.array(facies_agg['thick'])
377
+ total_thick = np.sum(thicks)
378
+
379
+ if total_thick <= 0:
380
+ continue
381
+
382
+ facies_summary = {'thickness': total_thick, 'fraction': 0.0}
383
+
384
+ for col in columns:
385
+ col_def = self._get_column_defaults(col)
386
+ label = col_def['label']
387
+ prop = col_def['property']
388
+ agg_method = col_def['agg']
389
+
390
+ values = facies_agg['values'].get(label, [])
391
+ valid_mask = [v is not None for v in values]
392
+
393
+ if not any(valid_mask):
394
+ facies_summary[label] = None
395
+ continue
396
+
397
+ valid_vals = np.array([v for v, m in zip(values, valid_mask) if m])
398
+ valid_thicks = np.array([t for t, m in zip(thicks, valid_mask) if m])
399
+ valid_total = np.sum(valid_thicks)
400
+
401
+ if valid_total <= 0:
402
+ facies_summary[label] = None
403
+ continue
404
+
405
+ if agg_method == 'arithmetic':
406
+ # Thickness-weighted arithmetic mean
407
+ agg_value = np.sum(valid_vals * valid_thicks) / valid_total
408
+
409
+ elif agg_method == 'geometric':
410
+ # Thickness-weighted geometric mean
411
+ # Filter out non-positive values for log
412
+ pos_mask = valid_vals > 0
413
+ if not np.any(pos_mask):
414
+ facies_summary[label] = None
415
+ continue
416
+ pos_vals = valid_vals[pos_mask]
417
+ pos_thicks = valid_thicks[pos_mask]
418
+ pos_total = np.sum(pos_thicks)
419
+ agg_value = np.exp(np.sum(np.log(pos_vals) * pos_thicks) / pos_total)
420
+
421
+ elif agg_method == 'pooled':
422
+ # Pooled standard deviation
423
+ # Requires the grand mean from the corresponding mean column
424
+ grand_mean = grand_means.get((zone_name, group_name, facies_name, prop))
425
+ if grand_mean is None:
426
+ facies_summary[label] = None
427
+ continue
428
+
429
+ # Get the corresponding std values (these are the per-well stds)
430
+ # and the per-well means
431
+ mean_label = None
432
+ for c in columns:
433
+ c_def = self._get_column_defaults(c)
434
+ if c_def['property'] == prop and c_def['stat'] == 'mean':
435
+ mean_label = c_def['label']
436
+ break
437
+
438
+ if mean_label is None:
439
+ facies_summary[label] = None
440
+ continue
441
+
442
+ mean_values = facies_agg['values'].get(mean_label, [])
443
+ std_values = values # current column values (stds)
444
+
445
+ # Both must be valid
446
+ combined_mask = [
447
+ m is not None and s is not None
448
+ for m, s in zip(mean_values, std_values)
449
+ ]
450
+ if not any(combined_mask):
451
+ facies_summary[label] = None
452
+ continue
453
+
454
+ combined_means = np.array([v for v, m in zip(mean_values, combined_mask) if m])
455
+ combined_stds = np.array([v for v, m in zip(std_values, combined_mask) if m])
456
+ combined_thicks = np.array([t for t, m in zip(thicks, combined_mask) if m])
457
+ combined_total = np.sum(combined_thicks)
458
+
459
+ # Pooled variance formula:
460
+ # var_pooled = sum(thick * (std^2 + (mean - grand_mean)^2)) / total_thick
461
+ pooled_var = np.sum(
462
+ combined_thicks * (combined_stds**2 + (combined_means - grand_mean)**2)
463
+ ) / combined_total
464
+ agg_value = np.sqrt(pooled_var)
465
+
466
+ elif agg_method == 'sum':
467
+ agg_value = np.sum(valid_vals)
468
+
469
+ else:
470
+ # Default to arithmetic
471
+ agg_value = np.sum(valid_vals * valid_thicks) / valid_total
472
+
473
+ facies_summary[label] = agg_value
474
+
475
+ group_summary[facies_name] = facies_summary
476
+ group_summary['thickness'] += total_thick
477
+
478
+ # Update facies fractions based on group total
479
+ group_thick = group_summary['thickness']
480
+ for facies_name in facies_list:
481
+ if facies_name in group_summary and isinstance(group_summary[facies_name], dict):
482
+ f_thick = group_summary[facies_name].get('thickness', 0)
483
+ group_summary[facies_name]['fraction'] = f_thick / group_thick if group_thick > 0 else 0.0
484
+
485
+ if group_summary['thickness'] > 0:
486
+ zone_summary[group_name] = group_summary
487
+ zone_summary['thickness'] += group_summary['thickness']
488
+
489
+ # Calculate group fractions
490
+ zone_thick = zone_summary['thickness']
491
+ for group_name in groups:
492
+ if group_name in zone_summary and isinstance(zone_summary[group_name], dict):
493
+ g_thick = zone_summary[group_name].get('thickness', 0)
494
+ zone_summary[group_name]['fraction'] = g_thick / zone_thick if zone_thick > 0 else 0.0
495
+
496
+ if zone_summary['thickness'] > 0:
497
+ summary[zone_name] = zone_summary
498
+
499
+ return summary
500
+
501
+ def _print_report(self, report_data: dict, columns: list[dict]) -> None:
502
+ """Print formatted report."""
503
+ indent = " "
504
+
505
+ # Prepare column formatting
506
+ col_defs = [self._get_column_defaults(c) for c in columns]
507
+
508
+ for well_name, well_data in report_data.items():
509
+ print(f"\n{well_name}")
510
+
511
+ for zone_name, zone_data in well_data.items():
512
+ if not isinstance(zone_data, dict):
513
+ continue
514
+
515
+ zone_thick = zone_data.get('thickness', 0)
516
+ print(f"{indent}{zone_name:<13} iso: {zone_thick:>6.2f}m")
517
+
518
+ for group_name, group_data in zone_data.items():
519
+ if group_name == 'thickness' or not isinstance(group_data, dict):
520
+ continue
521
+
522
+ group_thick = group_data.get('thickness', 0)
523
+ group_frac = group_data.get('fraction', 0)
524
+ print(f"{indent*2}-- {group_name:<10} fraction: {group_frac:>7.4f} iso: {group_thick:>6.2f}m")
525
+
526
+ for facies_name, facies_data in group_data.items():
527
+ if facies_name in ('thickness', 'fraction') or not isinstance(facies_data, dict):
528
+ continue
529
+
530
+ f_thick = facies_data.get('thickness', 0)
531
+ f_frac = facies_data.get('fraction', 0)
532
+
533
+ # Build column values string
534
+ col_strs = []
535
+ for col_def in col_defs:
536
+ label = col_def['label']
537
+ fmt = col_def['format']
538
+ unit = col_def['unit']
539
+ value = facies_data.get(label)
540
+
541
+ if value is not None:
542
+ formatted = f"{value:{fmt}}"
543
+ if unit:
544
+ col_strs.append(f"{label}: {formatted:>8}{unit}")
545
+ else:
546
+ col_strs.append(f"{label}: {formatted:>8}")
547
+ else:
548
+ col_strs.append(f"{label}: {'N/A':>8}")
549
+
550
+ col_str = " ".join(col_strs)
551
+ print(f"{indent*2}| {facies_name:<15} frac: {f_frac:>7.4f} iso: {f_thick:>6.2f}m {col_str}")
552
+
553
+ print("")
554
+
555
+
51
556
  def _flatten_to_dataframe(nested_dict: dict, property_name: str) -> pd.DataFrame:
52
557
  """
53
558
  Flatten nested dictionary results into a DataFrame.
@@ -171,11 +676,15 @@ class _ManagerPropertyProxy:
171
676
  well_intervals = intervals
172
677
  elif isinstance(intervals, dict):
173
678
  # Well-specific intervals
679
+ # Check original name, sanitized name, and well_-prefixed sanitized name
174
680
  well_intervals = None
681
+ prefixed_name = f"well_{well.sanitized_name}"
175
682
  if well.name in intervals:
176
683
  well_intervals = intervals[well.name]
177
684
  elif well.sanitized_name in intervals:
178
685
  well_intervals = intervals[well.sanitized_name]
686
+ elif prefixed_name in intervals:
687
+ well_intervals = intervals[prefixed_name]
179
688
  if well_intervals is None:
180
689
  return None # Skip wells not in the dict
181
690
  else:
@@ -1299,7 +1808,7 @@ class _ManagerPropertyProxy:
1299
1808
  arithmetic: Optional[bool] = None,
1300
1809
  precision: int = 6,
1301
1810
  nested: bool = False
1302
- ) -> dict:
1811
+ ) -> SumsAvgResult:
1303
1812
  """
1304
1813
  Compute hierarchical statistics grouped by filters across all wells.
1305
1814
 
@@ -1406,7 +1915,7 @@ class _ManagerPropertyProxy:
1406
1915
  if well_result is not None:
1407
1916
  result[well_name] = well_result
1408
1917
 
1409
- return _sanitize_for_json(result)
1918
+ return SumsAvgResult(_sanitize_for_json(result))
1410
1919
 
1411
1920
  def _compute_sums_avg_for_well(
1412
1921
  self,
@@ -1759,7 +2268,7 @@ class _ManagerMultiPropertyProxy:
1759
2268
  weighted: Optional[bool] = None,
1760
2269
  arithmetic: Optional[bool] = None,
1761
2270
  precision: int = 6
1762
- ) -> dict:
2271
+ ) -> SumsAvgResult:
1763
2272
  """
1764
2273
  Compute statistics for multiple properties across all wells.
1765
2274
 
@@ -1817,7 +2326,7 @@ class _ManagerMultiPropertyProxy:
1817
2326
  if well_result is not None:
1818
2327
  result[well_name] = well_result
1819
2328
 
1820
- return _sanitize_for_json(result)
2329
+ return SumsAvgResult(_sanitize_for_json(result))
1821
2330
 
1822
2331
  def _compute_sums_avg_for_well(
1823
2332
  self,
@@ -1838,7 +2347,9 @@ class _ManagerMultiPropertyProxy:
1838
2347
  return None # Skip wells that don't have this saved filter
1839
2348
  elif isinstance(intervals, dict):
1840
2349
  # Well-specific intervals - check if this well is in the dict
1841
- if well.name not in intervals and well.sanitized_name not in intervals:
2350
+ # Check original name, sanitized name, and well_-prefixed sanitized name
2351
+ prefixed_name = f"well_{well.sanitized_name}"
2352
+ if well.name not in intervals and well.sanitized_name not in intervals and prefixed_name not in intervals:
1842
2353
  return None # Skip wells not in the dict
1843
2354
 
1844
2355
  # Collect results for each property
@@ -1916,11 +2427,15 @@ class _ManagerMultiPropertyProxy:
1916
2427
  well_intervals = intervals
1917
2428
  elif isinstance(intervals, dict):
1918
2429
  # Well-specific intervals
2430
+ # Check original name, sanitized name, and well_-prefixed sanitized name
1919
2431
  well_intervals = None
2432
+ prefixed_name = f"well_{well.sanitized_name}"
1920
2433
  if well.name in intervals:
1921
2434
  well_intervals = intervals[well.name]
1922
2435
  elif well.sanitized_name in intervals:
1923
2436
  well_intervals = intervals[well.sanitized_name]
2437
+ elif prefixed_name in intervals:
2438
+ well_intervals = intervals[prefixed_name]
1924
2439
  if well_intervals is None:
1925
2440
  return None # Skip wells not in the dict
1926
2441
  elif isinstance(intervals, list):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: well-log-toolkit
3
- Version: 0.1.150
3
+ Version: 0.1.152
4
4
  Summary: Fast LAS file processing with lazy loading and filtering for well log analysis
5
5
  Author-email: Kristian dF Kollsgård <kkollsg@gmail.com>
6
6
  License: MIT
@@ -1,7 +1,7 @@
1
1
  well_log_toolkit/__init__.py,sha256=ilJAIIhh68pYfD9I3V53juTEJpoMN8oHpcpEFNpuXAQ,3793
2
2
  well_log_toolkit/exceptions.py,sha256=X_fzC7d4yaBFO9Vx74dEIB6xmI9Agi6_bTU3MPxn6ko,985
3
3
  well_log_toolkit/las_file.py,sha256=Tj0mRfX1aX2s6uug7BBlY1m_mu3G50EGxHGzD0eEedE,53876
4
- well_log_toolkit/manager.py,sha256=F6C8SXSD53FtG16-u3z298ViAj-5cMeNLSAoU77MyXY,140694
4
+ well_log_toolkit/manager.py,sha256=vlOPAJmKNnuZNykZdLwLUPBNwH7BWrKY22hx7FOW0S0,163031
5
5
  well_log_toolkit/operations.py,sha256=z8j8fGBOwoJGUQFy-Vawjq9nm3OD_dUt0oaNh8yuG7o,18515
6
6
  well_log_toolkit/property.py,sha256=XY3BAN76CY6KY8na4iyoz6P-inhDyb821o3gN7ZC3q4,104184
7
7
  well_log_toolkit/regression.py,sha256=JDcRxaODJnFikAdPJyTq8eUV7iY0vCDmvnGufqlojxs,31625
@@ -9,7 +9,7 @@ well_log_toolkit/statistics.py,sha256=cpUbaRGlqyqpGWKtETk9XpXWrMJIIjVacdqEqIBkvq
9
9
  well_log_toolkit/utils.py,sha256=O2KPq4htIoUlL74V2zKftdqqTjRfezU9M-568zPLme0,6866
10
10
  well_log_toolkit/visualization.py,sha256=nnpmFmbj44TbP0fsnLMR1GaKRkqKCEpI6Fd8Cp0oqBc,204716
11
11
  well_log_toolkit/well.py,sha256=n6XfaGSjGtyXCIaAr0ytslIK0DMUY_fSPQ_VCqj8jaU,106173
12
- well_log_toolkit-0.1.150.dist-info/METADATA,sha256=I5J-CdMD7XIL0uhVhHnergVpoFxblRISMCT5KumaiU8,63473
13
- well_log_toolkit-0.1.150.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
14
- well_log_toolkit-0.1.150.dist-info/top_level.txt,sha256=BMOo7OKLcZEnjo0wOLMclwzwTbYKYh31I8RGDOGSBdE,17
15
- well_log_toolkit-0.1.150.dist-info/RECORD,,
12
+ well_log_toolkit-0.1.152.dist-info/METADATA,sha256=jITXa53PORO2xidQsVqm1hvIOPlINDnxlOKctH1YqK0,63473
13
+ well_log_toolkit-0.1.152.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
14
+ well_log_toolkit-0.1.152.dist-info/top_level.txt,sha256=BMOo7OKLcZEnjo0wOLMclwzwTbYKYh31I8RGDOGSBdE,17
15
+ well_log_toolkit-0.1.152.dist-info/RECORD,,