tabalyst 0.1.0__tar.gz → 0.1.2__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 (38) hide show
  1. {tabalyst-0.1.0/src/tabalyst.egg-info → tabalyst-0.1.2}/PKG-INFO +15 -14
  2. {tabalyst-0.1.0 → tabalyst-0.1.2}/README.md +14 -13
  3. {tabalyst-0.1.0 → tabalyst-0.1.2}/pyproject.toml +1 -1
  4. {tabalyst-0.1.0 → tabalyst-0.1.2}/src/tabalyst/analysis.py +102 -5
  5. {tabalyst-0.1.0 → tabalyst-0.1.2}/src/tabalyst/models.py +38 -1
  6. {tabalyst-0.1.0 → tabalyst-0.1.2}/src/tabalyst/reporting.py +21 -2
  7. tabalyst-0.1.2/src/tabalyst/static/report.js +442 -0
  8. tabalyst-0.1.2/src/tabalyst/static/theme.css +484 -0
  9. tabalyst-0.1.2/src/tabalyst/templates/report.html +83 -0
  10. {tabalyst-0.1.0 → tabalyst-0.1.2/src/tabalyst.egg-info}/PKG-INFO +15 -14
  11. {tabalyst-0.1.0 → tabalyst-0.1.2}/tests/test_analysis.py +21 -1
  12. {tabalyst-0.1.0 → tabalyst-0.1.2}/tests/test_cli.py +3 -2
  13. {tabalyst-0.1.0 → tabalyst-0.1.2}/tests/test_public_api.py +2 -2
  14. {tabalyst-0.1.0 → tabalyst-0.1.2}/tests/test_reporting.py +37 -16
  15. tabalyst-0.1.0/src/tabalyst/static/report.js +0 -500
  16. tabalyst-0.1.0/src/tabalyst/static/theme.css +0 -411
  17. tabalyst-0.1.0/src/tabalyst/templates/report.html +0 -318
  18. {tabalyst-0.1.0 → tabalyst-0.1.2}/LICENSE +0 -0
  19. {tabalyst-0.1.0 → tabalyst-0.1.2}/MANIFEST.in +0 -0
  20. {tabalyst-0.1.0 → tabalyst-0.1.2}/examples/README.md +0 -0
  21. {tabalyst-0.1.0 → tabalyst-0.1.2}/examples/config.json +0 -0
  22. {tabalyst-0.1.0 → tabalyst-0.1.2}/examples/input/basic.csv +0 -0
  23. {tabalyst-0.1.0 → tabalyst-0.1.2}/examples/input/insurance-customers.csv +0 -0
  24. {tabalyst-0.1.0 → tabalyst-0.1.2}/setup.cfg +0 -0
  25. {tabalyst-0.1.0 → tabalyst-0.1.2}/src/tabalyst/__init__.py +0 -0
  26. {tabalyst-0.1.0 → tabalyst-0.1.2}/src/tabalyst/__main__.py +0 -0
  27. {tabalyst-0.1.0 → tabalyst-0.1.2}/src/tabalyst/_version.py +0 -0
  28. {tabalyst-0.1.0 → tabalyst-0.1.2}/src/tabalyst/cli.py +0 -0
  29. {tabalyst-0.1.0 → tabalyst-0.1.2}/src/tabalyst/config.py +0 -0
  30. {tabalyst-0.1.0 → tabalyst-0.1.2}/src/tabalyst/errors.py +0 -0
  31. {tabalyst-0.1.0 → tabalyst-0.1.2}/src/tabalyst/execution_log.py +0 -0
  32. {tabalyst-0.1.0 → tabalyst-0.1.2}/src/tabalyst/ingestion.py +0 -0
  33. {tabalyst-0.1.0 → tabalyst-0.1.2}/src/tabalyst/service.py +0 -0
  34. {tabalyst-0.1.0 → tabalyst-0.1.2}/src/tabalyst.egg-info/SOURCES.txt +0 -0
  35. {tabalyst-0.1.0 → tabalyst-0.1.2}/src/tabalyst.egg-info/dependency_links.txt +0 -0
  36. {tabalyst-0.1.0 → tabalyst-0.1.2}/src/tabalyst.egg-info/entry_points.txt +0 -0
  37. {tabalyst-0.1.0 → tabalyst-0.1.2}/src/tabalyst.egg-info/requires.txt +0 -0
  38. {tabalyst-0.1.0 → tabalyst-0.1.2}/src/tabalyst.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tabalyst
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Open-source data profiling engine for structured JSON and customizable HTML reports.
5
5
  Author: Gregory Borelli
6
6
  License-Expression: MIT
@@ -61,7 +61,7 @@ python -m pip install -e ".[dev]"
61
61
  Pass the CSV source and the complete HTML report path as positional arguments:
62
62
 
63
63
  ```console
64
- tabalyst data.csv reports/client-a.html
64
+ tabalyst data.csv reports/report.html
65
65
  ```
66
66
 
67
67
  The report path must end in `.html`. Parent directories are created
@@ -69,8 +69,8 @@ automatically, and existing artifacts are replaced. A successful run creates:
69
69
 
70
70
  ```text
71
71
  reports/
72
- |-- client-a.html
73
- |-- client-a.json
72
+ |-- report.html
73
+ |-- report.json
74
74
  `-- executions.json
75
75
  ```
76
76
 
@@ -81,8 +81,8 @@ several named reports can coexist there.
81
81
  Common options are:
82
82
 
83
83
  ```console
84
- tabalyst data.csv reports/client-a.html --separator ";" --encoding cp1252
85
- tabalyst data.csv reports/client-a.html --config tabalyst.json
84
+ tabalyst data.csv reports/report.html --separator ";" --encoding cp1252
85
+ tabalyst data.csv reports/report.html --config tabalyst.json
86
86
  tabalyst --help
87
87
  tabalyst --version
88
88
  ```
@@ -96,7 +96,7 @@ import tabalyst
96
96
 
97
97
  result = tabalyst.analyze(
98
98
  "data.csv",
99
- "reports/client-a.html",
99
+ "reports/report.html",
100
100
  separator=";",
101
101
  encoding="cp1252",
102
102
  config_path="tabalyst.json",
@@ -162,21 +162,20 @@ See the complete layout and maintenance notes in the
162
162
  - Physical and semantic type inference with explicit confidence and error rates.
163
163
  - Numeric, date, string-length, normalization, and value-distribution profiles.
164
164
  - A bounded raw-data preview while all records are analyzed.
165
- - Sortable and filterable HTML tables.
165
+ - Sortable and filterable HTML tables in the self-contained Signature report.
166
166
 
167
167
  Raw strings are preserved. The complete CSV is currently loaded into memory.
168
- Report data remains local and Tabalyst adds no telemetry or remote analysis.
169
- Bootstrap, DataTables, and Google Fonts are loaded from pinned CDNs for the full
170
- interactive presentation; the Tabalyst template, theme, and report JavaScript are
171
- included in the Python package.
168
+ Report data remains local and Tabalyst adds no telemetry or remote analysis. The
169
+ report embeds its design system, font subsets and interaction code, so it works
170
+ without CDN or network access.
172
171
 
173
172
  ## Compatibility with alpha commands
174
173
 
175
174
  The earlier commands remain available during the `0.1.x` transition:
176
175
 
177
176
  ```console
178
- tabalyst analyze data.csv -o reports/client-a.html
179
- tabalyst render reports/client-a.json -o reports/regenerated.html
177
+ tabalyst analyze data.csv -o reports/report.html
178
+ tabalyst render reports/report.json -o reports/regenerated.html
180
179
  ```
181
180
 
182
181
  The alpha `analyze` form also retains automatic `tabalyst.json` discovery,
@@ -194,6 +193,8 @@ python -m build
194
193
  The build creates a wheel and source distribution under `dist/`. Release steps,
195
194
  including the clean-wheel smoke test and PyPI Trusted Publishing setup, are in
196
195
  [RELEASING.md](https://github.com/loribel-labs/tabalyst/blob/main/RELEASING.md).
196
+ The packaging architecture and automated publication flow are documented in
197
+ [docs/python-package-and-release.md](https://github.com/loribel-labs/tabalyst/blob/main/docs/python-package-and-release.md).
197
198
 
198
199
  Report format details and internal boundaries are documented in
199
200
  [docs/architecture.md](https://github.com/loribel-labs/tabalyst/blob/main/docs/architecture.md).
@@ -27,7 +27,7 @@ python -m pip install -e ".[dev]"
27
27
  Pass the CSV source and the complete HTML report path as positional arguments:
28
28
 
29
29
  ```console
30
- tabalyst data.csv reports/client-a.html
30
+ tabalyst data.csv reports/report.html
31
31
  ```
32
32
 
33
33
  The report path must end in `.html`. Parent directories are created
@@ -35,8 +35,8 @@ automatically, and existing artifacts are replaced. A successful run creates:
35
35
 
36
36
  ```text
37
37
  reports/
38
- |-- client-a.html
39
- |-- client-a.json
38
+ |-- report.html
39
+ |-- report.json
40
40
  `-- executions.json
41
41
  ```
42
42
 
@@ -47,8 +47,8 @@ several named reports can coexist there.
47
47
  Common options are:
48
48
 
49
49
  ```console
50
- tabalyst data.csv reports/client-a.html --separator ";" --encoding cp1252
51
- tabalyst data.csv reports/client-a.html --config tabalyst.json
50
+ tabalyst data.csv reports/report.html --separator ";" --encoding cp1252
51
+ tabalyst data.csv reports/report.html --config tabalyst.json
52
52
  tabalyst --help
53
53
  tabalyst --version
54
54
  ```
@@ -62,7 +62,7 @@ import tabalyst
62
62
 
63
63
  result = tabalyst.analyze(
64
64
  "data.csv",
65
- "reports/client-a.html",
65
+ "reports/report.html",
66
66
  separator=";",
67
67
  encoding="cp1252",
68
68
  config_path="tabalyst.json",
@@ -128,21 +128,20 @@ See the complete layout and maintenance notes in the
128
128
  - Physical and semantic type inference with explicit confidence and error rates.
129
129
  - Numeric, date, string-length, normalization, and value-distribution profiles.
130
130
  - A bounded raw-data preview while all records are analyzed.
131
- - Sortable and filterable HTML tables.
131
+ - Sortable and filterable HTML tables in the self-contained Signature report.
132
132
 
133
133
  Raw strings are preserved. The complete CSV is currently loaded into memory.
134
- Report data remains local and Tabalyst adds no telemetry or remote analysis.
135
- Bootstrap, DataTables, and Google Fonts are loaded from pinned CDNs for the full
136
- interactive presentation; the Tabalyst template, theme, and report JavaScript are
137
- included in the Python package.
134
+ Report data remains local and Tabalyst adds no telemetry or remote analysis. The
135
+ report embeds its design system, font subsets and interaction code, so it works
136
+ without CDN or network access.
138
137
 
139
138
  ## Compatibility with alpha commands
140
139
 
141
140
  The earlier commands remain available during the `0.1.x` transition:
142
141
 
143
142
  ```console
144
- tabalyst analyze data.csv -o reports/client-a.html
145
- tabalyst render reports/client-a.json -o reports/regenerated.html
143
+ tabalyst analyze data.csv -o reports/report.html
144
+ tabalyst render reports/report.json -o reports/regenerated.html
146
145
  ```
147
146
 
148
147
  The alpha `analyze` form also retains automatic `tabalyst.json` discovery,
@@ -160,6 +159,8 @@ python -m build
160
159
  The build creates a wheel and source distribution under `dist/`. Release steps,
161
160
  including the clean-wheel smoke test and PyPI Trusted Publishing setup, are in
162
161
  [RELEASING.md](https://github.com/loribel-labs/tabalyst/blob/main/RELEASING.md).
162
+ The packaging architecture and automated publication flow are documented in
163
+ [docs/python-package-and-release.md](https://github.com/loribel-labs/tabalyst/blob/main/docs/python-package-and-release.md).
163
164
 
164
165
  Report format details and internal boundaries are documented in
165
166
  [docs/architecture.md](https://github.com/loribel-labs/tabalyst/blob/main/docs/architecture.md).
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tabalyst"
7
- version = "0.1.0"
7
+ version = "0.1.2"
8
8
  description = "Open-source data profiling engine for structured JSON and customizable HTML reports."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -14,6 +14,8 @@ from tabalyst.config import AnalysisConfig
14
14
  from tabalyst.ingestion import CsvDataset
15
15
  from tabalyst.models import (
16
16
  ColumnProfile,
17
+ DatasetDateColumnSummary,
18
+ DatasetDateSummary,
17
19
  DatasetProfile,
18
20
  DatasetSummary,
19
21
  DateBreakdownItem,
@@ -313,10 +315,15 @@ def build_date_profile(
313
315
  return (
314
316
  DateProfile(
315
317
  status=status,
318
+ present_count=total,
316
319
  valid_count=valid_count,
320
+ valid_percent=percent(valid_count, total),
317
321
  ambiguous_count=ambiguous_count,
322
+ ambiguous_percent=percent(ambiguous_count, total),
318
323
  invalid_date_count=invalid_count,
324
+ invalid_date_percent=percent(invalid_count, total),
319
325
  not_date_count=not_date_count,
326
+ not_date_percent=percent(not_date_count, total),
320
327
  resolved_ambiguous_order=resolved_order,
321
328
  ambiguous_order_source=resolution_source,
322
329
  formats=format_items,
@@ -406,15 +413,19 @@ def build_string_profile(
406
413
  status = "very_long"
407
414
  distribution = []
408
415
  if maximum <= settings.length_distribution_max_length:
416
+ length_counts = lengths.value_counts()
417
+ maximum_length_count = int(length_counts.max())
409
418
  for length in sorted({int(value) for value in lengths}):
410
419
  matching = [
411
420
  (value, count) for value, count in frequencies if len(value) == length
412
421
  ]
422
+ length_count = sum(count for _, count in matching)
413
423
  distribution.append(
414
424
  StringLengthDistribution(
415
425
  length=length,
416
- count=sum(count for _, count in matching),
417
- percent=percent(sum(count for _, count in matching), len(present)),
426
+ count=length_count,
427
+ percent=percent(length_count, len(present)),
428
+ relative_percent=percent(length_count, maximum_length_count),
418
429
  distinct_count=len(matching),
419
430
  examples=[
420
431
  StringLengthExample(value=value, count=count)
@@ -432,6 +443,11 @@ def build_string_profile(
432
443
  distinct_length_count=int(lengths.nunique()),
433
444
  fixed_length=minimum if minimum == maximum else None,
434
445
  length_distribution=distribution,
446
+ representative_examples=[
447
+ item.examples[0].value
448
+ for item in sorted(distribution, key=lambda item: -item.count)[:3]
449
+ if item.examples
450
+ ],
435
451
  )
436
452
 
437
453
 
@@ -632,7 +648,11 @@ def analyze_column(
632
648
  stats = [numbers.min(), numbers.max(), numbers.mean(), numbers.median()]
633
649
  if numbers.notna().all() and all(math.isfinite(value) for value in stats):
634
650
  numeric = NumericStats(
635
- minimum=stats[0], maximum=stats[1], mean=stats[2], median=stats[3]
651
+ minimum=stats[0],
652
+ maximum=stats[1],
653
+ range=stats[1] - stats[0],
654
+ mean=stats[2],
655
+ median=stats[3],
636
656
  )
637
657
  value_profile = build_value_profile(
638
658
  frequencies,
@@ -649,6 +669,7 @@ def analyze_column(
649
669
  if enum and semantic_type is None:
650
670
  semantic_type = "enum"
651
671
  string_profile = build_string_profile(present, frequencies, inferred, config)
672
+ missing_count = int(absent.sum())
652
673
  return ColumnProfile(
653
674
  id=f"column_{position}",
654
675
  name=name if name is not None else str(values.name or ""),
@@ -658,8 +679,9 @@ def analyze_column(
658
679
  type_confidence=round(type_confidence, 4),
659
680
  type_error_count=type_error_count,
660
681
  type_error_percent=type_error_percent,
661
- missing_count=int(absent.sum()),
662
- missing_percent=percent(int(absent.sum()), len(values)),
682
+ missing_count=missing_count,
683
+ missing_percent=percent(missing_count, len(values)),
684
+ with_issues=missing_count > 0 or inferred == "mixed",
663
685
  normalization=normalization,
664
686
  distinct_count=len(frequencies),
665
687
  examples=[
@@ -691,6 +713,40 @@ def analyze_dataset(dataset: CsvDataset, config: AnalysisConfig) -> DatasetProfi
691
713
  empty = [column.id for column in columns if column.inferred_type == "empty"]
692
714
  constant = [column.id for column in columns if column.distinct_count == 1]
693
715
  mixed = [column.id for column in columns if column.inferred_type == "mixed"]
716
+ numeric_columns = [column for column in columns if column.numeric is not None]
717
+ date_columns = [column for column in columns if column.date_profile is not None]
718
+ string_columns = [column for column in columns if column.string_profile is not None]
719
+ inferred_type_counts = Counter(column.inferred_type for column in columns)
720
+ semantic_type_counts = Counter(column.semantic_type or "none" for column in columns)
721
+ date_present_count = sum(
722
+ column.date_profile.present_count for column in date_columns if column.date_profile
723
+ )
724
+ date_valid_count = sum(
725
+ column.date_profile.valid_count for column in date_columns if column.date_profile
726
+ )
727
+ date_ambiguous_count = sum(
728
+ column.date_profile.ambiguous_count
729
+ for column in date_columns
730
+ if column.date_profile
731
+ )
732
+ date_invalid_count = sum(
733
+ column.date_profile.invalid_date_count
734
+ for column in date_columns
735
+ if column.date_profile
736
+ )
737
+ date_not_date_count = sum(
738
+ column.date_profile.not_date_count
739
+ for column in date_columns
740
+ if column.date_profile
741
+ )
742
+ maximum_column_ambiguous_count = max(
743
+ (
744
+ column.date_profile.ambiguous_count
745
+ for column in date_columns
746
+ if column.date_profile
747
+ ),
748
+ default=0,
749
+ )
694
750
  issues = []
695
751
 
696
752
  def add_issue(
@@ -774,6 +830,47 @@ def analyze_dataset(dataset: CsvDataset, config: AnalysisConfig) -> DatasetProfi
774
830
  empty_row_count=int(absent.all(axis=1).sum()),
775
831
  empty_column_count=len(empty),
776
832
  constant_column_count=len(constant),
833
+ with_issues_column_count=sum(column.with_issues for column in columns),
834
+ numeric_column_count=len(numeric_columns),
835
+ date_column_count=len(date_columns),
836
+ string_column_count=len(string_columns),
837
+ inferred_type_counts=dict(inferred_type_counts),
838
+ inferred_type_percents={
839
+ name: percent(count, len(columns))
840
+ for name, count in inferred_type_counts.items()
841
+ },
842
+ semantic_type_counts=dict(semantic_type_counts),
843
+ semantic_type_percents={
844
+ name: percent(count, len(columns))
845
+ for name, count in semantic_type_counts.items()
846
+ },
847
+ ),
848
+ date_summary=(
849
+ DatasetDateSummary(
850
+ present_count=date_present_count,
851
+ valid_count=date_valid_count,
852
+ ambiguous_count=date_ambiguous_count,
853
+ ambiguous_percent=percent(date_ambiguous_count, date_present_count),
854
+ invalid_date_count=date_invalid_count,
855
+ not_date_count=date_not_date_count,
856
+ maximum_column_ambiguous_count=maximum_column_ambiguous_count,
857
+ columns=[
858
+ DatasetDateColumnSummary(
859
+ column_id=column.id,
860
+ name=column.name,
861
+ position=column.position,
862
+ ambiguous_count=column.date_profile.ambiguous_count,
863
+ relative_ambiguous_percent=percent(
864
+ column.date_profile.ambiguous_count,
865
+ maximum_column_ambiguous_count,
866
+ ),
867
+ )
868
+ for column in date_columns
869
+ if column.date_profile and column.date_profile.ambiguous_count
870
+ ],
871
+ )
872
+ if date_columns
873
+ else None
777
874
  ),
778
875
  columns=columns,
779
876
  issues=issues,
@@ -23,6 +23,7 @@ class SourceInfo(ResultModel):
23
23
  class NumericStats(ResultModel):
24
24
  minimum: FiniteFloat
25
25
  maximum: FiniteFloat
26
+ range: FiniteFloat
26
27
  mean: FiniteFloat
27
28
  median: FiniteFloat
28
29
 
@@ -52,10 +53,15 @@ class DateBreakdownItem(ResultModel):
52
53
 
53
54
  class DateProfile(ResultModel):
54
55
  status: Literal["valid", "multiple_formats", "mixed", "ambiguous", "invalid"]
56
+ present_count: int
55
57
  valid_count: int
58
+ valid_percent: float
56
59
  ambiguous_count: int
60
+ ambiguous_percent: float
57
61
  invalid_date_count: int
62
+ invalid_date_percent: float
58
63
  not_date_count: int
64
+ not_date_percent: float
59
65
  resolved_ambiguous_order: Literal["MDY", "DMY"] | None = None
60
66
  ambiguous_order_source: Literal["config", "column"] | None = None
61
67
  formats: list[DateFormatCount]
@@ -73,6 +79,7 @@ class StringLengthDistribution(ResultModel):
73
79
  length: int
74
80
  count: int
75
81
  percent: float
82
+ relative_percent: float
76
83
  distinct_count: int
77
84
  examples: list[StringLengthExample]
78
85
 
@@ -87,6 +94,7 @@ class StringProfile(ResultModel):
87
94
  distinct_length_count: int
88
95
  fixed_length: int | None = None
89
96
  length_distribution: list[StringLengthDistribution]
97
+ representative_examples: list[str]
90
98
 
91
99
 
92
100
  class ValueOccurrence(ResultModel):
@@ -122,6 +130,7 @@ class ColumnProfile(ResultModel):
122
130
  type_error_percent: float | None
123
131
  missing_count: int
124
132
  missing_percent: float
133
+ with_issues: bool
125
134
  normalization: NormalizationStats
126
135
  distinct_count: int
127
136
  examples: list[str]
@@ -145,6 +154,33 @@ class DatasetSummary(ResultModel):
145
154
  empty_row_count: int
146
155
  empty_column_count: int
147
156
  constant_column_count: int
157
+ with_issues_column_count: int
158
+ numeric_column_count: int
159
+ date_column_count: int
160
+ string_column_count: int
161
+ inferred_type_counts: dict[str, int]
162
+ inferred_type_percents: dict[str, float]
163
+ semantic_type_counts: dict[str, int]
164
+ semantic_type_percents: dict[str, float]
165
+
166
+
167
+ class DatasetDateColumnSummary(ResultModel):
168
+ column_id: str
169
+ name: str
170
+ position: int
171
+ ambiguous_count: int
172
+ relative_ambiguous_percent: float
173
+
174
+
175
+ class DatasetDateSummary(ResultModel):
176
+ present_count: int
177
+ valid_count: int
178
+ ambiguous_count: int
179
+ ambiguous_percent: float
180
+ invalid_date_count: int
181
+ not_date_count: int
182
+ maximum_column_ambiguous_count: int
183
+ columns: list[DatasetDateColumnSummary]
148
184
 
149
185
 
150
186
  class Issue(ResultModel):
@@ -163,12 +199,13 @@ class PreviewRow(ResultModel):
163
199
 
164
200
  class DatasetProfile(ResultModel):
165
201
  format_version: Literal["0.1.0a"] = "0.1.0a"
166
- format_revision: Literal[1] = 1
202
+ format_revision: Literal[2] = 2
167
203
  generated_at: datetime
168
204
  processing_seconds: FiniteFloat
169
205
  source: SourceInfo
170
206
  config: AnalysisConfig
171
207
  summary: DatasetSummary
208
+ date_summary: DatasetDateSummary | None = None
172
209
  columns: list[ColumnProfile]
173
210
  issues: list[Issue]
174
211
  preview: list[PreviewRow]
@@ -1,6 +1,5 @@
1
1
  """Render self-contained analysis results as an interactive HTML report."""
2
2
 
3
- from collections import Counter
4
3
  from importlib.resources import files
5
4
  from pathlib import Path
6
5
 
@@ -45,10 +44,30 @@ def render_report(profile: DatasetProfile) -> str:
45
44
  template = environment.from_string(
46
45
  resources.joinpath("templates/report.html").read_text(encoding="utf-8")
47
46
  )
47
+ type_colors = {
48
+ "text": "var(--d2)",
49
+ "mixed": "var(--attn)",
50
+ "integer": "var(--d3)",
51
+ "number": "var(--d1)",
52
+ "boolean": "var(--ok)",
53
+ "date": "var(--d1)",
54
+ "empty": "var(--line-strong)",
55
+ }
56
+ semantic_colors = {
57
+ "enum": "var(--d2)",
58
+ "date": "var(--d1)",
59
+ "none": "var(--d3)",
60
+ }
48
61
  return template.render(
49
62
  report=profile,
50
63
  tabalyst_version=get_version(),
51
- types=Counter(column.inferred_type for column in profile.columns),
64
+ source_stem=Path(profile.source.filename).stem,
65
+ source_suffix=Path(profile.source.filename).suffix,
66
+ numeric_columns=[column for column in profile.columns if column.numeric],
67
+ date_columns=[column for column in profile.columns if column.date_profile],
68
+ string_columns=[column for column in profile.columns if column.string_profile],
69
+ type_colors=type_colors,
70
+ semantic_colors=semantic_colors,
52
71
  theme_css=resources.joinpath("static/theme.css").read_text(encoding="utf-8"),
53
72
  report_js=resources.joinpath("static/report.js").read_text(encoding="utf-8"),
54
73
  )