fieldwork 0.1.0__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 (85) hide show
  1. fieldwork-0.1.0/LICENSE +21 -0
  2. fieldwork-0.1.0/NOTICE +6 -0
  3. fieldwork-0.1.0/PKG-INFO +86 -0
  4. fieldwork-0.1.0/README.pypi.md +62 -0
  5. fieldwork-0.1.0/benchmarks/discovery.py +32 -0
  6. fieldwork-0.1.0/benchmarks/foundation.py +125 -0
  7. fieldwork-0.1.0/docs/.gitkeep +0 -0
  8. fieldwork-0.1.0/docs/algorithms.md +145 -0
  9. fieldwork-0.1.0/docs/architecture.md +67 -0
  10. fieldwork-0.1.0/docs/assets/README.pypi.md +62 -0
  11. fieldwork-0.1.0/docs/assets/availability-topology.html +1 -0
  12. fieldwork-0.1.0/docs/assets/availability-topology.json +2127 -0
  13. fieldwork-0.1.0/docs/assets/availability-topology.png +0 -0
  14. fieldwork-0.1.0/docs/assets/availability-topology.svg +1 -0
  15. fieldwork-0.1.0/docs/assets/availability.html +1 -0
  16. fieldwork-0.1.0/docs/assets/availability.json +4524 -0
  17. fieldwork-0.1.0/docs/assets/availability.png +0 -0
  18. fieldwork-0.1.0/docs/assets/availability.svg +1 -0
  19. fieldwork-0.1.0/docs/assets/census.html +59 -0
  20. fieldwork-0.1.0/docs/assets/census.json +98 -0
  21. fieldwork-0.1.0/docs/assets/census.png +0 -0
  22. fieldwork-0.1.0/docs/assets/census.svg +1 -0
  23. fieldwork-0.1.0/docs/assets/grain.html +59 -0
  24. fieldwork-0.1.0/docs/assets/grain.json +408 -0
  25. fieldwork-0.1.0/docs/assets/grain.png +0 -0
  26. fieldwork-0.1.0/docs/assets/grain.svg +1 -0
  27. fieldwork-0.1.0/docs/assets/manifest.json +26 -0
  28. fieldwork-0.1.0/docs/assets/paths.html +1 -0
  29. fieldwork-0.1.0/docs/assets/paths.json +194 -0
  30. fieldwork-0.1.0/docs/assets/paths.png +0 -0
  31. fieldwork-0.1.0/docs/assets/paths.svg +1 -0
  32. fieldwork-0.1.0/docs/contracts.md +149 -0
  33. fieldwork-0.1.0/docs/development.md +53 -0
  34. fieldwork-0.1.0/docs/index.md +18 -0
  35. fieldwork-0.1.0/docs/investigation.md +114 -0
  36. fieldwork-0.1.0/docs/releases.md +73 -0
  37. fieldwork-0.1.0/examples/investigation.ipynb +140 -0
  38. fieldwork-0.1.0/examples/investigation.py +73 -0
  39. fieldwork-0.1.0/examples/observed_grain_graph.py +61 -0
  40. fieldwork-0.1.0/pyproject.toml +43 -0
  41. fieldwork-0.1.0/scripts/fonts/Lato-Bold.ttf +0 -0
  42. fieldwork-0.1.0/scripts/fonts/Lato-Regular.ttf +0 -0
  43. fieldwork-0.1.0/scripts/fonts/OFL.txt +93 -0
  44. fieldwork-0.1.0/scripts/generate_assets.py +101 -0
  45. fieldwork-0.1.0/src/fieldwork/__init__.py +47 -0
  46. fieldwork-0.1.0/src/fieldwork/_explore/__init__.py +27 -0
  47. fieldwork-0.1.0/src/fieldwork/_explore/_kernels.py +64 -0
  48. fieldwork-0.1.0/src/fieldwork/_explore/census.py +479 -0
  49. fieldwork-0.1.0/src/fieldwork/_explore/encoding.py +264 -0
  50. fieldwork-0.1.0/src/fieldwork/_explore/grain.py +245 -0
  51. fieldwork-0.1.0/src/fieldwork/_explore/grain_graph.py +179 -0
  52. fieldwork-0.1.0/src/fieldwork/_explore/graphics.py +733 -0
  53. fieldwork-0.1.0/src/fieldwork/_explore/orchestration.py +166 -0
  54. fieldwork-0.1.0/src/fieldwork/_explore/relations.py +405 -0
  55. fieldwork-0.1.0/src/fieldwork/_explore/render.py +450 -0
  56. fieldwork-0.1.0/src/fieldwork/_explore/resolved.py +125 -0
  57. fieldwork-0.1.0/src/fieldwork/_explore/result.py +75 -0
  58. fieldwork-0.1.0/src/fieldwork/_explore/roles.py +103 -0
  59. fieldwork-0.1.0/src/fieldwork/_explore/visual_data.py +299 -0
  60. fieldwork-0.1.0/src/fieldwork/availability.py +367 -0
  61. fieldwork-0.1.0/src/fieldwork/discovery.py +251 -0
  62. fieldwork-0.1.0/src/fieldwork/evidence.py +483 -0
  63. fieldwork-0.1.0/src/fieldwork/families.py +73 -0
  64. fieldwork-0.1.0/src/fieldwork/navigation.py +360 -0
  65. fieldwork-0.1.0/src/fieldwork/patterns.py +189 -0
  66. fieldwork-0.1.0/src/fieldwork/presentation.py +530 -0
  67. fieldwork-0.1.0/src/fieldwork/py.typed +0 -0
  68. fieldwork-0.1.0/src/fieldwork/workflow.py +199 -0
  69. fieldwork-0.1.0/tests/discovery/test_context_adapter.py +149 -0
  70. fieldwork-0.1.0/tests/discovery/test_examples.py +19 -0
  71. fieldwork-0.1.0/tests/discovery/test_journeys.py +365 -0
  72. fieldwork-0.1.0/tests/discovery/test_workflow.py +433 -0
  73. fieldwork-0.1.0/tests/foundation/__init__.py +1 -0
  74. fieldwork-0.1.0/tests/foundation/oracle.py +43 -0
  75. fieldwork-0.1.0/tests/foundation/test_census.py +50 -0
  76. fieldwork-0.1.0/tests/foundation/test_contracts.py +92 -0
  77. fieldwork-0.1.0/tests/foundation/test_differential.py +54 -0
  78. fieldwork-0.1.0/tests/foundation/test_grain_graph.py +136 -0
  79. fieldwork-0.1.0/tests/foundation/test_grain_relations.py +79 -0
  80. fieldwork-0.1.0/tests/foundation/test_graphics.py +226 -0
  81. fieldwork-0.1.0/tests/foundation/test_joint_counts.py +41 -0
  82. fieldwork-0.1.0/tests/foundation/test_render.py +293 -0
  83. fieldwork-0.1.0/tests/foundation/test_resolved.py +212 -0
  84. fieldwork-0.1.0/tests/foundation/test_scopes.py +108 -0
  85. fieldwork-0.1.0/uv.lock +789 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Beatrice Brown-Mulry
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
fieldwork-0.1.0/NOTICE ADDED
@@ -0,0 +1,6 @@
1
+ Fieldwork exploration foundation, tests and benchmark extracted from bea-tools
2
+ by Beatrice Brown-Mulry, declared MIT in its pyproject.toml.
3
+ Source: https://github.com/beatrice-b-m/bea-tools
4
+ Commit: 4e4f1704cbca91a314652b0c99f86d8f4a2c4f90
5
+ The source repository contained no standalone license text at extraction.
6
+ No DICOM, sampling code, or compatibility accessors were migrated.
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.4
2
+ Name: fieldwork
3
+ Version: 0.1.0
4
+ Summary: Explore unfamiliar data through patterns, evidence, and reproducible investigations
5
+ Author: Beatrice Brown-Mulry
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ License-File: NOTICE
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Requires-Dist: numpy>=1.26
16
+ Requires-Dist: pandas>=2.2.3
17
+ Requires-Dist: wcwidth>=0.2 ; extra == 'unicode'
18
+ Requires-Python: >=3.11, <3.15
19
+ Project-URL: Source, https://github.com/beatrice-b-m/fieldwork
20
+ Project-URL: Documentation, https://fieldwork.beabm.dev
21
+ Project-URL: Issues, https://github.com/beatrice-b-m/fieldwork/issues
22
+ Provides-Extra: unicode
23
+ Description-Content-Type: text/markdown
24
+
25
+ # Fieldwork
26
+
27
+ Explore unfamiliar data through patterns, source-row evidence, and reproducible
28
+ investigations. Fieldwork is a Python toolkit for researchers working with pandas
29
+ dataframes: find availability families, examine dependencies and candidate grains,
30
+ then choose a useful census path through the data.
31
+
32
+ **Alpha release.** Python 3.11–3.14; pandas and NumPy are the only required runtime
33
+ dependencies.
34
+
35
+ ![Availability in the worked example](https://raw.githubusercontent.com/beatrice-b-m/fieldwork/v0.1.0/docs/assets/availability.png)
36
+
37
+ ```bash
38
+ pip install fieldwork
39
+ ```
40
+
41
+ ```python
42
+ import pandas as pd
43
+ import fieldwork as fw
44
+
45
+ df = pd.DataFrame({
46
+ "site": ["North", "North", "South", "South"],
47
+ "exam": [1, 1, 2, 2],
48
+ "image": [10, 11, 20, 21],
49
+ "report": ["ok", "ok", "ok", None],
50
+ })
51
+ overview = fw.explore(df)
52
+ availability = fw.missingness(df, entity="exam", min_implication=0.75)
53
+ paths = fw.suggest_paths(df, features=["site", "exam"])
54
+ tree = paths.best.census(df)
55
+ print(tree)
56
+ ```
57
+
58
+ Use `result.to_frame()` for discovery tables and `result.inspect(df, finding_id,
59
+ exceptions=True)` for the saved example rows. Duplicate indexes are supported;
60
+ inspection checks the ordered source dataset. Use `result.select(df, finding_id)`
61
+ to recover the complete matching population as a `Scope`, then pass that scope to
62
+ `missingness` or `suggest_paths`. `paths.best.census(df)` preserves the selected
63
+ population and sentinel conventions. Choose `unit="entities"` with `entity=` for
64
+ equal entity weights, or keep the default `unit="rows"`. Browse connected feature
65
+ evidence with `overview.relationships("image")`. Save results with `to_dict()`, export
66
+ with `render_svg()` or `render_html()`, and reapply a `Recipe` to later deliveries.
67
+
68
+ ![Observed census from the worked example](https://raw.githubusercontent.com/beatrice-b-m/fieldwork/v0.1.0/docs/assets/census.png)
69
+
70
+ The single-table workflow includes independent levels, contextual pair summaries,
71
+ joint counts and absence, exact grain graphs, bounded approximate dependency
72
+ discovery, five census-path objectives, availability signatures and entity summaries,
73
+ string and numeric patterns, scoped investigations, and delivery comparisons.
74
+ Topology-only exports retain structure while suppressing quantitative evidence.
75
+ Automatic related-table discovery is a later extension.
76
+
77
+ - [User documentation source](https://github.com/beatrice-b-m/fieldwork-docs)
78
+ - [Developer documentation](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/docs/index.md): architecture, contracts, algorithms, releases
79
+ - [Investigation journey and units](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/docs/investigation.md)
80
+ - [Executable investigation](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/examples/investigation.py) and [notebook](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/examples/investigation.ipynb)
81
+ - [MIT license](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/LICENSE) and [extraction provenance](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/NOTICE)
82
+
83
+ For development: `uv sync --locked`, `uv run pytest`, and `uv build`. Documentation
84
+ images are generated from executable examples using the public renderers. Run
85
+ `uv run python scripts/generate_assets.py` after behavior or styling changes;
86
+ CI checks concurrence and release builds regenerate the assets.
@@ -0,0 +1,62 @@
1
+ # Fieldwork
2
+
3
+ Explore unfamiliar data through patterns, source-row evidence, and reproducible
4
+ investigations. Fieldwork is a Python toolkit for researchers working with pandas
5
+ dataframes: find availability families, examine dependencies and candidate grains,
6
+ then choose a useful census path through the data.
7
+
8
+ **Alpha release.** Python 3.11–3.14; pandas and NumPy are the only required runtime
9
+ dependencies.
10
+
11
+ ![Availability in the worked example](https://raw.githubusercontent.com/beatrice-b-m/fieldwork/v0.1.0/docs/assets/availability.png)
12
+
13
+ ```bash
14
+ pip install fieldwork
15
+ ```
16
+
17
+ ```python
18
+ import pandas as pd
19
+ import fieldwork as fw
20
+
21
+ df = pd.DataFrame({
22
+ "site": ["North", "North", "South", "South"],
23
+ "exam": [1, 1, 2, 2],
24
+ "image": [10, 11, 20, 21],
25
+ "report": ["ok", "ok", "ok", None],
26
+ })
27
+ overview = fw.explore(df)
28
+ availability = fw.missingness(df, entity="exam", min_implication=0.75)
29
+ paths = fw.suggest_paths(df, features=["site", "exam"])
30
+ tree = paths.best.census(df)
31
+ print(tree)
32
+ ```
33
+
34
+ Use `result.to_frame()` for discovery tables and `result.inspect(df, finding_id,
35
+ exceptions=True)` for the saved example rows. Duplicate indexes are supported;
36
+ inspection checks the ordered source dataset. Use `result.select(df, finding_id)`
37
+ to recover the complete matching population as a `Scope`, then pass that scope to
38
+ `missingness` or `suggest_paths`. `paths.best.census(df)` preserves the selected
39
+ population and sentinel conventions. Choose `unit="entities"` with `entity=` for
40
+ equal entity weights, or keep the default `unit="rows"`. Browse connected feature
41
+ evidence with `overview.relationships("image")`. Save results with `to_dict()`, export
42
+ with `render_svg()` or `render_html()`, and reapply a `Recipe` to later deliveries.
43
+
44
+ ![Observed census from the worked example](https://raw.githubusercontent.com/beatrice-b-m/fieldwork/v0.1.0/docs/assets/census.png)
45
+
46
+ The single-table workflow includes independent levels, contextual pair summaries,
47
+ joint counts and absence, exact grain graphs, bounded approximate dependency
48
+ discovery, five census-path objectives, availability signatures and entity summaries,
49
+ string and numeric patterns, scoped investigations, and delivery comparisons.
50
+ Topology-only exports retain structure while suppressing quantitative evidence.
51
+ Automatic related-table discovery is a later extension.
52
+
53
+ - [User documentation source](https://github.com/beatrice-b-m/fieldwork-docs)
54
+ - [Developer documentation](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/docs/index.md): architecture, contracts, algorithms, releases
55
+ - [Investigation journey and units](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/docs/investigation.md)
56
+ - [Executable investigation](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/examples/investigation.py) and [notebook](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/examples/investigation.ipynb)
57
+ - [MIT license](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/LICENSE) and [extraction provenance](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/NOTICE)
58
+
59
+ For development: `uv sync --locked`, `uv run pytest`, and `uv build`. Documentation
60
+ images are generated from executable examples using the public renderers. Run
61
+ `uv run python scripts/generate_assets.py` after behavior or styling changes;
62
+ CI checks concurrence and release builds regenerate the assets.
@@ -0,0 +1,32 @@
1
+ """Measure end-to-end discovery and strict JSON result size on seeded wide data."""
2
+ import argparse
3
+ import json
4
+ from pathlib import Path
5
+ import time
6
+
7
+ import numpy as np
8
+ import pandas as pd
9
+ import fieldwork as fw
10
+
11
+ parser = argparse.ArgumentParser()
12
+ parser.add_argument('--rows', type=int, default=2000)
13
+ parser.add_argument('--columns', type=int, default=24)
14
+ parser.add_argument('--output', type=Path, required=True)
15
+ args = parser.parse_args()
16
+ rng = np.random.default_rng(721)
17
+ frame = pd.DataFrame({f'field_{i}': np.where(rng.random(args.rows) < .7, np.nan,
18
+ rng.integers(0, 8, args.rows)) for i in range(args.columns)})
19
+ frame['entity'] = np.arange(args.rows)//4
20
+ records = []
21
+ for name, run in [('missingness', lambda: fw.missingness(frame, entity='entity')),
22
+ ('dependencies', lambda: fw.discover_dependencies(frame, max_candidates=12)),
23
+ ('paths', lambda: fw.suggest_paths(frame, max_candidates=60))]:
24
+ start = time.perf_counter()
25
+ result = run()
26
+ elapsed = time.perf_counter()-start
27
+ encoded = json.dumps(result.to_dict(), allow_nan=False).encode()
28
+ records.append({'operation': name, 'seconds': elapsed, 'result_bytes': len(encoded),
29
+ 'coverage': result['coverage']})
30
+ args.output.write_text(json.dumps({'rows': args.rows, 'columns': len(frame.columns),
31
+ 'results': records}, indent=2)+'\n')
32
+ print(args.output.read_text())
@@ -0,0 +1,125 @@
1
+ """Reproducible synthetic end-to-end benchmark for the hierarchy explorer."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import hashlib
7
+ import json
8
+ import platform
9
+ import resource
10
+ import statistics
11
+ import time
12
+ import tracemalloc
13
+ from pathlib import Path
14
+
15
+ import numpy as np
16
+ import pandas as pd
17
+
18
+ from fieldwork import explore, grain, levels, render_plaintext
19
+
20
+
21
+ def fixture(name: str, rows: int, seed: int) -> pd.DataFrame:
22
+ rng = np.random.default_rng(seed)
23
+ cardinalities = {
24
+ "low3": (2, 4, 8),
25
+ "low6": (4, 4, 4, 4, 4, 4),
26
+ "high6": (100, 100, 100, 100, 100, 100),
27
+ "mixed6": (17_000, 50_000, 2, 4, 8, 10_000),
28
+ }[name]
29
+ return pd.DataFrame(
30
+ {
31
+ f"c{index}": np.char.add("v", rng.integers(0, cardinality, rows).astype(str))
32
+ for index, cardinality in enumerate(cardinalities)
33
+ }
34
+ )
35
+
36
+
37
+ def workload(frame: pd.DataFrame, name: str):
38
+ columns = frame.columns.tolist()
39
+ if name == "s1":
40
+ return levels(frame, columns, top_n=5)
41
+ if name == "s3":
42
+ return grain(frame, columns[:2])
43
+ result = explore(
44
+ frame,
45
+ columns[:6],
46
+ candidate_keys=columns[:2],
47
+ top_n=5,
48
+ max_nodes=10_000,
49
+ include_pairs=True,
50
+ )
51
+ if name == "render":
52
+ return render_plaintext(result, max_lines=200)
53
+ return json.dumps(result.to_dict(), allow_nan=False, separators=(",", ":"))
54
+
55
+
56
+ def main() -> None:
57
+ parser = argparse.ArgumentParser()
58
+ parser.add_argument("--suite", default="acceptance", choices=["smoke", "acceptance"])
59
+ parser.add_argument("--fixture", default="low6", choices=["low3", "low6", "high6", "mixed6"])
60
+ parser.add_argument("--workload", default="explore", choices=["s1", "s3", "explore", "render"])
61
+ parser.add_argument("--rows", type=int)
62
+ parser.add_argument("--seed", type=int, default=721)
63
+ parser.add_argument("--repeats", type=int, default=7)
64
+ parser.add_argument("--output", type=Path, required=True)
65
+ args = parser.parse_args()
66
+ rows = args.rows or (10_000 if args.suite == "smoke" else 150_000)
67
+ frame = fixture(args.fixture, rows, args.seed)
68
+ workload(frame, args.workload) # warmup
69
+ times = []
70
+ baseline_rss = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
71
+ for _ in range(args.repeats):
72
+ start = time.perf_counter()
73
+ workload(frame, args.workload)
74
+ times.append(time.perf_counter() - start)
75
+ # Allocation tracing is a separate run because it changes timings.
76
+ tracemalloc.start()
77
+ traced_output = workload(frame, args.workload)
78
+ _, traced_peak = tracemalloc.get_traced_memory()
79
+ peak_rss = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
80
+ tracemalloc.stop()
81
+ canonical = (
82
+ traced_output
83
+ if isinstance(traced_output, str)
84
+ else json.dumps(traced_output.to_dict(), allow_nan=False)
85
+ )
86
+ record = {
87
+ "fixture": args.fixture,
88
+ "workload": args.workload,
89
+ "rows": rows,
90
+ "columns": len(frame.columns),
91
+ "observed_cardinalities": {
92
+ column: int(frame[column].nunique(dropna=False)) for column in frame.columns
93
+ },
94
+ "seed": args.seed,
95
+ "repeats": args.repeats,
96
+ "seconds": {
97
+ "median": statistics.median(times),
98
+ "minimum": min(times),
99
+ "maximum": max(times),
100
+ "spread": max(times) - min(times),
101
+ "raw": times,
102
+ },
103
+ "memory": {
104
+ "baseline_peak_rss_platform_units": baseline_rss,
105
+ "absolute_peak_rss_platform_units": peak_rss,
106
+ "incremental_peak_rss_platform_units": max(0, peak_rss - baseline_rss),
107
+ "tracemalloc_peak_bytes": traced_peak,
108
+ },
109
+ "output_bytes": len(canonical.encode()),
110
+ "output_sha256": hashlib.sha256(canonical.encode()).hexdigest(),
111
+ "environment": {
112
+ "python": platform.python_version(),
113
+ "platform": platform.platform(),
114
+ "pandas": pd.__version__,
115
+ "numpy": np.__version__,
116
+ },
117
+ }
118
+ args.output.mkdir(parents=True, exist_ok=True)
119
+ destination = args.output / f"{args.fixture}-{args.workload}-{args.seed}.json"
120
+ destination.write_text(json.dumps(record, indent=2, allow_nan=False) + "\n")
121
+ print(destination)
122
+
123
+
124
+ if __name__ == "__main__":
125
+ main()
File without changes
@@ -0,0 +1,145 @@
1
+ # Discovery algorithms and budgets
2
+
3
+ ## Availability
4
+
5
+ Features are encoded once per operation. Row presence or explicit any/all entity
6
+ aggregation provides the analysis masks. Repeated boolean availability signatures
7
+ are ranked by descending analysis-unit count with lexical signature ties. `max_signatures=50`
8
+ limits stored signatures, with omitted row mass reported. Identical masks form
9
+ families even for always-missing or always-present columns.
10
+
11
+ Pairs are enumerated in input-column combination order, bounded by `max_pairs=200`.
12
+ For A and B, presence Jaccard is both-present / either-present. If neither is ever
13
+ present, it is undefined, never perfect similarity. Agreement additionally includes
14
+ co-absence and is reported separately. A implies B has conditional presence
15
+ both-present / A-present, an exception rate, and B's baseline presence. No antecedent
16
+ support means no implication finding. Similarity and implication thresholds default
17
+ to 0.8 and 0.9. Mutually exclusive pairs require each field to have observed support
18
+ and no co-presence; exact families let users interpret exclusive field groups.
19
+
20
+ Context groups are joint combinations in first-observed order, bounded by
21
+ `max_contexts=32`. Per-feature entity counts operate over all eligible distinct
22
+ keys, without a display truncation affecting their denominator.
23
+
24
+ ## Dependencies and candidate grain
25
+
26
+ Enumerate determinants by size then input-column order, up to `max_key_size=2` and
27
+ `max_candidates=100`. Report the combinatorial candidate space and tested count.
28
+ Every selected non-key feature is a target; all rows are evaluated. User-specified
29
+ contexts add up to `max_contexts=32` conditional populations plus the global population.
30
+
31
+ Modal accuracy = 1 − minimum rows needing target-value repair / evaluated rows.
32
+ Within each determinant group, choose its most frequent target; canonical code order
33
+ breaks ties. Nonmodal rows are representative exceptions. Also retain all violating
34
+ groups, affected rows, group violation rate, and repeated-group support. This
35
+ separates approximate mapping quality from coverage and singleton effects.
36
+ `min_accuracy=0.95` controls finding emission, not which tests are computed.
37
+
38
+ Candidates report groups, uniqueness, repeated groups/rows, complete-case exclusions,
39
+ and exactly determined targets. A unique row ID therefore remains distinguishable
40
+ from a useful repeated entity grouping. Conditional dependencies use their own
41
+ populations. The embedded foundation grain graph admits exact dependencies only,
42
+ collapses equivalent candidates, retains cross-cutting structure, and checks
43
+ population compatibility. Graph work is additionally bounded by the same candidate
44
+ set; pairwise candidate comparisons may dominate runtime.
45
+
46
+ ## Census paths
47
+
48
+ `max_features=20` bounds the eligible input features (steering columns survive the
49
+ budget), and `max_pairs=200` bounds pair nesting inference. Constants are omitted
50
+ unless required. Equivalent pairs are retained as aliases. If fine determines coarse
51
+ but coarse does not determine fine, the soft nesting edge is coarse → fine.
52
+
53
+ A deterministic beam search extends ordered prefixes; it defaults to four dimensions,
54
+ beam width 12, 200 evaluated extensions, three returned paths, and display budget 40.
55
+ Input order defines feature coverage; lexical path order breaks score ties. If the
56
+ search budget ends, shorter valid paths may be returned. Coverage records returned
57
+ and requested depth; unsatisfied required columns can yield no recommendation.
58
+
59
+ For each path, count observed distinct prefixes at every depth. Base score is:
60
+
61
+ ```text
62
+ sum(prefix_counts) / display_budget
63
+ + sum(max(0, prefix_count - display_budget)) / display_budget
64
+ + 2 * redundant_steps
65
+ + 3 * equivalent_pair_steps
66
+ ```
67
+
68
+ A redundant step leaves the prefix count unchanged. Lower scores rank first.
69
+ Structure and context objectives add eight per reversed supported nesting edge.
70
+ Compact uses the base score. Target adds twelve times summed within-prefix modal
71
+ impurity of the target values. Availability adds twelve times the same impurity for
72
+ full boolean signatures. Impurity is rows outside each group's mode / input rows.
73
+ The target itself is excluded from browsing candidates unless explicitly required.
74
+
75
+ `start_with` fixes the leading dimensions. `before` enforces acyclic precedence and
76
+ includes its referenced features; `exclude` removes candidates. Conflicting or
77
+ unfittable steering raises `ValueError`. Context requires `start_with`; target
78
+ requires `target`. These are observed-prefix heuristics, not an exhaustive optimizer
79
+ or an order-invariant joint-information score.
80
+
81
+ ## Value patterns
82
+
83
+ String formats replace digit runs with `9` and ASCII letter runs with `A`; report
84
+ three-character prefixes and lengths. `max_patterns=10` bounds displayed counts.
85
+ Indexed-name families are explicitly name evidence, augmented by identical presence
86
+ when observed. Numeric summaries use finite values, observed minimum spacing, and
87
+ an allclose grid check. Offset and ratio checks require at least two finite paired
88
+ rows; ratios exclude zero denominators. Tolerances are rtol 1e-5 and atol 1e-8.
89
+ They are simple measured relationships, not fitted latent models. Context constancy
90
+ reports how many populated context groups have a single populated target value.
91
+
92
+ ### Population-compatible grain views
93
+
94
+ Discovery builds one view per distinct, nonempty candidate complete-case mask.
95
+ Each view includes candidates whose supported rows contain that mask. The anchor
96
+ candidate guarantees their common complete-case population is exactly that mask;
97
+ the foundation still recomputes and checks every combined relationship on it.
98
+ Views are ordered by descending population, then candidate enumeration order.
99
+ `exact_grain` is the first view; `grain_views` retains every view, candidate IDs,
100
+ source positions and population accounting. No relation is composed across views.
101
+ All candidates remain in `candidates`, with view membership; unsupported candidates
102
+ also appear in `graph_selection.excluded` with `no_evaluated_support`. With
103
+ `dropna=False`, all candidates share the scoped population.
104
+
105
+ Presentation ranks supported repeated groupings before unique identifiers,
106
+ constants, and candidates without evaluated support. Within each class, more exact
107
+ determined targets and repeated rows rank first, with shorter keys and lexical
108
+ order breaking ties. A one-group candidate is a constant, including a singleton
109
+ population. Ranking changes presentation only; the complete evaluated set remains
110
+ available in enumeration order. Conditional findings carry typed context predicates
111
+ in `structure.context`; readable statements name the feature, value and scalar type.
112
+ Topology retains these predicates while suppressing measurements and selectors.
113
+
114
+ ### Recommendation reasons and diversity
115
+
116
+ Every path stores `reasons` for observed prefix branching/overflow, supported
117
+ nesting, redundant dimensions and equivalent partitions, and availability
118
+ separation. Target searches also report target separation. Separation is the
119
+ within-prefix nonmodal row fraction at each depth; explanations quote these
120
+ measured values and the actual feature names. Numerical reasons stay in full
121
+ presentations; topology retains only structural path and alias findings.
122
+
123
+ At each search depth the beam keeps the best order per selected feature set,
124
+ reserving slots for different browsing choices. Future extensions depend on that
125
+ set, while accumulated prefix costs retain the ordering evidence. Returned paths
126
+ also collapse alias substitutions. Fewer than `n_paths` are returned when no
127
+ meaningfully different evaluated feature sets exist; reverse permutations are not
128
+ advertised as alternatives. Explicit start order and precedence remain binding.
129
+
130
+ ### Connected feature evidence
131
+
132
+ The overview includes value-pattern discovery and a `feature_network` with
133
+ feature nodes, typed relationships, and connected components. Availability
134
+ identity/similarity/implication/exclusion, indexed names, equivalent value
135
+ partitions and exact/approximate dependencies remain separate relationship types.
136
+ A connected component means reachability through this evidence, not equivalence
137
+ or a composed functional dependency. Composite determinants and typed context
138
+ predicates remain explicit. Each relationship links to its section finding,
139
+ overview finding, counting unit and population reference. Pair-specific FD
140
+ populations stay in the referenced finding; the network does not replace the
141
+ compatible exact grain views. `overview.relationships(feature, kinds=[...])`
142
+ returns a dataframe for filtering and following evidence to `inspect`/`select`.
143
+ Saved HTML provides feature disclosures and links to the supporting findings.
144
+ Topology removes evidence pointers and populations while retaining relation types,
145
+ direction and context. Its relationship order is canonical.
@@ -0,0 +1,67 @@
1
+ # Architecture
2
+
3
+ Fieldwork is a dataframe-in/result-out library. Runtime dependencies are pandas
4
+ and NumPy. Optional rendering tools used by release builds are development
5
+ requirements only. Python 3.11–3.14 are supported, with a committed universal uv lock.
6
+
7
+ ## Follow one investigation
8
+
9
+ `explore(df)` calls path search, availability analysis, and a bounded single-key
10
+ dependency search, plus bounded value-pattern analysis. It returns an overview containing individual saved results and
11
+ a census preview. Its compact text view presents families, signatures, candidate
12
+ grains and recommended paths together. `explore(df, dimensions)` delegates to the original composition
13
+ API; explicit dimension order remains authoritative. Common context settings are
14
+ applied to a private normalized frame and source accounting is retained in all
15
+ derived scopes; incompatible search options are rejected. `discovery={...}` steers the
16
+ implicit path search; common `features`, `scope`, `missing`, and `table_id` parameters
17
+ also flow into overview evidence.
18
+
19
+ Discovery modules call `evidence.prepare` to identify the ordered dataset, apply a
20
+ scope, encode values, and compute native/sentinel availability without modifying
21
+ source values. Findings carry table-qualified feature references and bounded source
22
+ positions. Presentation consumes saved evidence, never the original dataframe.
23
+
24
+ ## Module map
25
+
26
+ | Module | Responsibility |
27
+ | --- | --- |
28
+ | `_explore/encoding.py`, `_kernels.py` | Canonical scalar identity and counting |
29
+ | `_explore/census.py` | Independent levels, bounded ordered observed prefixes |
30
+ | `_explore/grain.py`, `grain_graph.py` | Exact FDs, scope compatibility, equivalence, DAG |
31
+ | `_explore/relations.py`, `roles.py` | Pair contexts/absence, joint counts, schema suggestions |
32
+ | `_explore/result.py`, `resolved.py` | Foundation result model and readable references |
33
+ | `_explore/visual_data.py`, `render.py`, `graphics.py` | Foundation projections and renderers |
34
+ | `evidence.py` | Discovery results, scopes, fingerprints, source inspection |
35
+ | `availability.py` | Presence signatures, families, implications, entity summaries |
36
+ | `discovery.py` | Supplied search bounds, exact/approximate/conditional FDs |
37
+ | `navigation.py` | Deterministic beam search and objective-specific prefix costs |
38
+ | `families.py` | Typed feature relationships linked to section findings |
39
+ | `patterns.py` | Populated strings/numbers, indexed families, context constancy |
40
+ | `workflow.py` | Composition, recipes, delivery comparisons |
41
+ | `presentation.py` | Discovery projections and dispatch to foundation renderers |
42
+
43
+ ## Extension boundaries
44
+
45
+ New patterns should add named measurements, population accounting, an example
46
+ selection limit, and a presentation test for full and topology exports. Add an
47
+ operation to the explicit Recipe allowlist only when its arguments serialize to
48
+ JSON and can be safely reapplied to a new frame. No arbitrary Python evaluation is
49
+ used for recipes.
50
+
51
+ Related-table automatic discovery, adaptive branch-specific census orders, cached
52
+ sessions, and external discovery engines remain future extensions. Table identity
53
+ is present in new feature references; foundation references are local to the
54
+ source's `table_id`. The current public APIs analyze one dataframe at a time.
55
+
56
+ ## Extraction
57
+
58
+ The foundation, regression tests, benchmark and grain example came from bea-tools
59
+ commit `4e4f1704cbca91a314652b0c99f86d8f4a2c4f90`; see `NOTICE`. Imports were
60
+ rewritten, accessors omitted, branding updated, and repeated interpretive disclaimers
61
+ removed. Quantitative evidence, populations, and topology disclosure behavior remain.
62
+ No DICOM, sampling dependencies, or compatibility shims were migrated.
63
+
64
+ Overview discovery also accepts `by`, `entity`, `unit`, and `entity_presence`.
65
+ Context columns flow to availability, dependency and value-pattern sections.
66
+ Entity settings apply to availability; census path scores and exact/approximate
67
+ value dependencies continue to count rows. Each linked finding identifies its unit.
@@ -0,0 +1,62 @@
1
+ # Fieldwork
2
+
3
+ Explore unfamiliar data through patterns, source-row evidence, and reproducible
4
+ investigations. Fieldwork is a Python toolkit for researchers working with pandas
5
+ dataframes: find availability families, examine dependencies and candidate grains,
6
+ then choose a useful census path through the data.
7
+
8
+ **Alpha release.** Python 3.11–3.14; pandas and NumPy are the only required runtime
9
+ dependencies.
10
+
11
+ ![Availability in the worked example](https://raw.githubusercontent.com/beatrice-b-m/fieldwork/v0.1.0/docs/assets/availability.png)
12
+
13
+ ```bash
14
+ pip install fieldwork
15
+ ```
16
+
17
+ ```python
18
+ import pandas as pd
19
+ import fieldwork as fw
20
+
21
+ df = pd.DataFrame({
22
+ "site": ["North", "North", "South", "South"],
23
+ "exam": [1, 1, 2, 2],
24
+ "image": [10, 11, 20, 21],
25
+ "report": ["ok", "ok", "ok", None],
26
+ })
27
+ overview = fw.explore(df)
28
+ availability = fw.missingness(df, entity="exam", min_implication=0.75)
29
+ paths = fw.suggest_paths(df, features=["site", "exam"])
30
+ tree = paths.best.census(df)
31
+ print(tree)
32
+ ```
33
+
34
+ Use `result.to_frame()` for discovery tables and `result.inspect(df, finding_id,
35
+ exceptions=True)` for the saved example rows. Duplicate indexes are supported;
36
+ inspection checks the ordered source dataset. Use `result.select(df, finding_id)`
37
+ to recover the complete matching population as a `Scope`, then pass that scope to
38
+ `missingness` or `suggest_paths`. `paths.best.census(df)` preserves the selected
39
+ population and sentinel conventions. Choose `unit="entities"` with `entity=` for
40
+ equal entity weights, or keep the default `unit="rows"`. Browse connected feature
41
+ evidence with `overview.relationships("image")`. Save results with `to_dict()`, export
42
+ with `render_svg()` or `render_html()`, and reapply a `Recipe` to later deliveries.
43
+
44
+ ![Observed census from the worked example](https://raw.githubusercontent.com/beatrice-b-m/fieldwork/v0.1.0/docs/assets/census.png)
45
+
46
+ The single-table workflow includes independent levels, contextual pair summaries,
47
+ joint counts and absence, exact grain graphs, bounded approximate dependency
48
+ discovery, five census-path objectives, availability signatures and entity summaries,
49
+ string and numeric patterns, scoped investigations, and delivery comparisons.
50
+ Topology-only exports retain structure while suppressing quantitative evidence.
51
+ Automatic related-table discovery is a later extension.
52
+
53
+ - [User documentation source](https://github.com/beatrice-b-m/fieldwork-docs)
54
+ - [Developer documentation](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/docs/index.md): architecture, contracts, algorithms, releases
55
+ - [Investigation journey and units](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/docs/investigation.md)
56
+ - [Executable investigation](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/examples/investigation.py) and [notebook](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/examples/investigation.ipynb)
57
+ - [MIT license](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/LICENSE) and [extraction provenance](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/NOTICE)
58
+
59
+ For development: `uv sync --locked`, `uv run pytest`, and `uv build`. Documentation
60
+ images are generated from executable examples using the public renderers. Run
61
+ `uv run python scripts/generate_assets.py` after behavior or styling changes;
62
+ CI checks concurrence and release builds regenerate the assets.