Skip to content

tdfs4ds.workflow_assessment

tdfs4ds.workflow_assessment

Workflow assessment: is the pipeline well built? (0.3.3.25)

store_consistency answers "do the catalogs and the storage line up?". This module answers a different question: is the workflow well implemented?

  • Entity keys (area entity). Users often put every id column they know into entity_id. A column the others determine splits nothing, yet it widens the primary index of every storage table, so a lookup by the real key cannot use it, and an attribute that changes over time starts a new entity at each change. The scan counts distinct key tuples in the storage tables, over the whole history and now, with each column left out in turn.
  • Order and synchronisation (area time). Did every process run after the processes it reads, at each business date? Do the two ends of a dependency follow the same TimeManager? Is a TimeManager-driven process reading a dataset that does not move with its valid time (point-in-time leakage)?
  • Runs (area runs). FilterManager segments missing or failed at the latest date, hollow runs (COMPLETED,0 rows), rows stuck at RUNNING.
  • Process SQL (area process). Live SQL that differs from the stored version, and histories computed under several versions.
  • Storage (area storage). Primary-index skew, entity columns typed differently across tables, entity keys contained in other ones.

Three layers, like the other report sections: :func:collect_workflow_inputs is the only I/O, :func:build_workflow_model is pure, :func:render_workflow_html returns HTML. :func:assess_workflow runs the first two.

Rules:

  • No LLM. Every check is a count, a dictionary read, a graph walk or a hash comparison; every text is a fixed template. A test guards the imports.
  • Every finding names its fix and nothing here runs it.
  • A check that cannot run is listed in unverifiable with its reason; it is never reported as passing.
  • A dependency between entity columns is observed in the data, never a rule: the finding says so and asks for the business to confirm.
  • Primary store only.

AREAS module-attribute

AREAS = ['lineage', 'entity', 'time', 'runs', 'process', 'storage']

AREA_TITLES module-attribute

AREA_TITLES = {'lineage': 'Lineage integrity', 'entity': 'Entity keys', 'time': 'Order and synchronisation', 'runs': 'Runs and segments', 'process': 'Process SQL', 'storage': 'Storage layout'}

WORKFLOW_KIND_LABELS module-attribute

WORKFLOW_KIND_LABELS = {'redundant_key_column': 'Entity column determined by the other key columns', 'key_column_changes_over_time': 'Entity column that changes over time', 'entity_key_superset': 'Entity key containing another entity key', 'entity_column_type_mismatch': 'Entity column typed differently across tables', 'storage_skew': 'Storage table skewed across AMPs', 'entity_pi_inconsistent': 'Storage tables of one entity with different primary indexes', 'pi_not_aligned_with_source': 'Primary index not aligned with the largest source', 'partition_not_aligned_with_source': 'Partitioning not aligned with the largest source', 'segments_rescan_source': 'FilterManager segments each reading the whole source', 'segment_filter_defeats_pruning': 'Segment filter on the partitioning column that the SQL keeps from pruning', 'time_filter_by_join': 'TimeManager read through a join instead of a scalar filter', 'time_filter_not_on_partitioning': 'Business date compared with a column the source is not partitioned on', 'time_filter_defeats_pruning': 'Business-date filter on the partitioning column that the SQL keeps from pruning', 'fs_partition_not_on_filter': 'Storage tables not partitioned on the segmentation columns', 'ran_before_upstream': 'Process started before its upstream had finished', 'upstream_recomputed_after': 'Upstream recomputed after the process ran', 'upstream_missing_at_date': "Upstream not computed at the process's business date", 'completed_on_failed_upstream': 'Process completed on a failed or empty upstream', 'ahead_of_upstream': 'Process ahead of its upstream in business time', 'mixed_time_modes': 'Dependency between real time and a TimeManager', 'different_time_drivers': 'Dependency between two independent TimeManagers', 'process_not_on_domain_clock': 'Process of a data domain not on the domain clock', 'processes_share_time_filtered_view': 'Processes sharing the domain clock through a view', 'point_in_time_leak': 'TimeManager process reading a current dataset', 'fixed_as_of_input': 'TimeManager process reading a fixed-date dataset', 'input_other_time_manager': 'Process reading a dataset on another TimeManager', 'segments_missing': 'FilterManager segments not computed at the latest date', 'segment_failed': 'FilterManager segment failed at the latest date', 'hollow_run': 'Run that wrote no rows', 'stuck_running': 'Run still marked RUNNING', 'sql_drift': 'Live SQL different from the stored version', 'history_mixes_versions': 'History computed under several SQL versions', 'dataset_reads_removed_process': 'Dataset reading the values of a removed process', 'dataset_reads_removed_feature': 'Dataset reading a feature removed from the catalog', 'dataset_reads_stale_table': 'Dataset reading a storage table the catalog no longer uses for the feature', 'dataset_reads_missing_table': 'Dataset reading a storage table that no longer exists', 'dataset_feature_no_rows': 'Dataset feature with no stored rows', 'relocation_reverted': 'Relocated feature registered or written in its old table again (older client)', 'feature_without_process': 'Feature no process computes any more', 'process_view_missing': 'Process whose view no longer exists', 'dataset_view_missing': 'Dataset whose view no longer exists', 'process_reads_missing_object': 'Process view reading an object that no longer exists', 'process_view_broken': 'Process view that no longer compiles', 'process_reads_unregistered_dataset': 'Process view reading a dataset no longer registered', 'lineage_edge_to_unknown': 'Lineage edge to a view that is no registered process', 'dataset_reads_other_store': 'Dataset reading another feature store (not checked from this one)', 'view_does_not_compile': 'View that does not compile (EXPLAIN fails)', 'view_not_verifiable': 'View this user may not EXPLAIN (missing right): not verified', 'process_reads_inaccessible_object': 'Process view reading an object this user has no right on', 'lineage_stops_no_access': 'Lineage stopping at a view whose definition this user cannot read', 'lineage_not_recorded': 'View whose inputs the lineage graph does not record', 'lineage_stops_unreadable': 'Lineage stopping at a view SHOW VIEW refuses', 'views_not_compiled': 'Views not compiled (over the limit)', 'lineage_split': 'Lineage made of separate networks', 'lineage_isolated': 'Process or dataset with no lineage link', 'process_not_read': 'Process nobody reads (dead end)', 'detached_branch': 'Lineage branch no process or dataset reads (dead branch)'}

LINEAGE_KINDS module-attribute

LINEAGE_KINDS = ('dataset_reads_removed_process', 'dataset_reads_removed_feature', 'feature_without_process', 'process_view_missing', 'dataset_view_missing', 'process_reads_missing_object', 'process_view_broken', 'process_reads_unregistered_dataset', 'lineage_edge_to_unknown', 'dataset_reads_stale_table', 'dataset_reads_missing_table', 'process_reads_inaccessible_object')

STORAGE_LINEAGE_KINDS module-attribute

STORAGE_LINEAGE_KINDS = ('dataset_feature_no_rows', 'relocation_reverted')

CHECKS module-attribute

CHECKS = ('entity_scan', 'entity_layout', 'skew', 'distribution', 'order', 'leakage', 'segments', 'sql', 'integrity', 'network', 'dataset_rows')

CHEAP_CHECKS module-attribute

CHEAP_CHECKS = tuple(c for c in CHECKS if c not in ('entity_scan', 'dataset_rows'))

_FAIL module-attribute

_FAIL = 'FAIL'

_RUNNING module-attribute

_RUNNING = 'RUNNING'

_FU_COLUMNS module-attribute

_FU_COLUMNS = ['pid', 'run_id', 'start', 'end', 'status', 'vdate', 'dkey', 'seg']

PROBE_TABLE module-attribute

PROBE_TABLE = 'TDFS4DS_WA_PROBE'

_PID_RE module-attribute

_PID_RE = None

DIMENSIONS module-attribute

DIMENSIONS = [('lineage', 'Lineage', ('integrity', 'network')), ('entity', 'Entity keys', ('entity_scan',)), ('order', 'Order and time', ('order',)), ('inputs', 'Inputs in time', ('leakage',)), ('runs', 'Runs', ('segments',)), ('sql', 'Process SQL', ('sql',)), ('storage', 'Storage and pruning', ('distribution', 'skew'))]

_LEAKAGE_KINDS module-attribute

_LEAKAGE_KINDS = {'point_in_time_leak', 'fixed_as_of_input', 'input_other_time_manager'}

_CHECK_DIMENSION module-attribute

_CHECK_DIMENSION = {'integrity': 'lineage', 'network': 'lineage', 'dataset_rows': 'lineage', 'entity_scan': 'entity', 'order': 'order', 'leakage': 'inputs', 'segments': 'runs', 'runs': 'runs', 'sql': 'sql', 'distribution': 'storage', 'storage': 'storage'}

_SEVERITY_RANK module-attribute

_SEVERITY_RANK = {'error': 3, 'warning': 2, 'info': 1}

_EXEC_CSS module-attribute

_EXEC_CSS = '<style>\n.twa-exec{margin:4px 0 18px}\n.twa-exec h3{margin:0 0 8px;font-size:1.05rem}\n.twa-cards{display:flex;flex-wrap:wrap;gap:10px;margin:6px 0 10px}\n.twa-card{border:1px solid rgba(127,127,127,.35);border-left:4px solid #FF5F00;border-radius:8px;padding:6px 12px;min-width:110px}\n.twa-card b{display:block;font-size:1.35rem;line-height:1.2}\n.twa-card span{font-size:.78rem;opacity:.8}\n.twa-card.e{border-left-color:#C62828}.twa-card.w{border-left-color:#EF6C00}\n.twa-card.i{border-left-color:#2F6E9E}.twa-card.o{border-left-color:#2E7D32}\n.twa-verdict{margin:4px 0 8px}\n.twa-top{margin:0 0 12px 18px;padding:0}\n.twa-gridwrap{overflow-x:auto;max-width:100%}\ntable.twa-grid{border-collapse:separate;border-spacing:3px;font-size:.82rem}\ntable.twa-grid th{font-weight:600;text-align:left;padding:2px 6px;white-space:nowrap}\ntable.twa-grid th.d{text-align:center;max-width:90px;white-space:normal}\ntable.twa-grid td.p{padding:2px 8px;white-space:nowrap;max-width:320px;overflow:hidden;text-overflow:ellipsis}\ntable.twa-grid tr.wave td{padding:8px 4px 2px;font-weight:600;color:#FF5F00;border-bottom:1px solid rgba(127,127,127,.3)}\ntd.twa-c{width:74px;text-align:center;color:#fff;border-radius:4px;font-weight:700}\ntd.twa-error{background:#C62828}td.twa-warning{background:#EF6C00}td.twa-info{background:#2F6E9E}\ntd.twa-ok{background:#2E7D32}td.twa-na{background:rgba(127,127,127,.35);color:inherit;font-weight:400}\n.twa-legend{font-size:.78rem;opacity:.85;margin-top:6px}\n.twa-legend i{display:inline-block;width:12px;height:12px;border-radius:3px;vertical-align:middle;margin:0 4px 0 10px}\n</style>'

_CELL_MARK module-attribute

_CELL_MARK = {'error': '&#10006;', 'warning': '&#9650;', 'info': '&#9679;', 'ok': '&#10003;', 'n/a': '&ndash;'}

_CELL_WORD module-attribute

_CELL_WORD = {'error': 'error', 'warning': 'warning', 'info': 'info', 'ok': 'nothing to report', 'n/a': 'not checked'}

_MD_MARK module-attribute

_MD_MARK = {'error': '✖ error', 'warning': '▲ warning', 'info': '● info', 'ok': '✓', 'n/a': '–'}

logger_safe

logger_safe(level, message, *args, **kwargs)

Wrapper around the global logger that only emits logs when tdfs4ds.DISPLAY_LOGS is True. level is a string like "info", "error", etc.

qualified

qualified(database, object_name, force=False)

Build db.obj with each part quoted only as needed.

qualified('TDB', 'FS_X') == 'TDB.FS_X' (unchanged for ordinary names); qualified('SVC@CORP', 'FS_X') == '"SVC@CORP".FS_X'. An empty/None database yields the bare object name, so unqualified callers keep working.

force=True quotes both parts unconditionally — only for the sites that have always emitted quotes and whose exact text is already persisted (dataset view DDL, returned volatile-table names).

sql_literal

sql_literal(value)

Escape value for use inside a single-quoted SQL string literal.

For the DBC probes and catalog predicates that compare a name as data (WHERE DatabaseName = '…'). The caller supplies the surrounding quotes.

_finding

_finding(area, kind, obj, detail, severity, fix='', pids=None, **ref)

A finding. pids: the processes it concerns (the executive summary places it on their rows); ref: entity=(DOMAIN, KEY) or table='DB.T' for the findings that are mapped to processes later, in the model.

_tag

_tag(findings: list, pids) -> None

Place findings (a slice just produced) on the processes pids, unless a finding already names its own.

_u

_u(v) -> str

_clean

_clean(v) -> Optional[str]

_ts

_ts(v)

A UTC pandas Timestamp, or None.

_day

_day(t) -> str

_fmt_dates

_fmt_dates(dates, limit=5) -> str

_short

_short(view) -> str

prepare_followup

prepare_followup(followup) -> pd.DataFrame

FS_FOLLOW_UP rows -> pid, run_id, start, end, status, vdate, dkey, seg.

vdate is the business date (VALIDTIME_DATE, NaT for a real-time run), dkey its day as text ('' in real time), seg the applied filter ('' when unsegmented). Pure.

_is_ok

_is_ok(s) -> bool

_is_hollow

_is_hollow(s) -> bool

_is_fail

_is_fail(s) -> bool

date_states

date_states(fu: DataFrame) -> Dict[tuple, dict]

{(pid, dkey): state} for the TimeManager rows (dkey != '').

A state describes the last computation at that business date, per segment: ok_start / ok_end (first start and last end of the latest COMPLETED row of each segment), failed / hollow (a segment whose latest row failed / wrote nothing, with its start), completed, vdate. Pure.

time_groups

time_groups(time_model: Optional[dict]) -> Dict[str, Any]

{"driver": {pid: manager}, "group": {manager: group id}} from the time-driver model. A manager outside any synchronised / clone group is its own group. Pure.

_same_group

_same_group(tg, a, b) -> Optional[bool]

True / False when both processes have a known driver, else None.

direct_edges

direct_edges(edges) -> set

The process dependencies without the transitive ones (q -> p). A cycle keeps every edge. Pure.

domain_clock_findings

domain_clock_findings(time_model: Optional[dict], names: Optional[dict] = None) -> List[dict]

A process of a data domain that has a clock (0.3.6.0) but reads no manager on its dates (or another one): run(data_domain=...) / roll_out(data_domain=...) cannot keep it in step. Pure.

order_findings

order_findings(edges, fu: DataFrame, tg: dict, names: Optional[dict] = None) -> List[dict]

Findings for each dependency q -> p (q upstream). Pure.

TimeManager rows are compared business date by business date; real-time rows by overlap of the runs only (two real-time processes on different cadences are normal).

input_datasets

input_datasets(G, node, process_nodes: set) -> List[str]

Dataset nodes a process view reads, walking through plain views and stopping at datasets and at other processes' views. Pure.

leakage_findings

leakage_findings(G, processes, tg, dataset_modes, tm_pids=(), names=None) -> tuple

(findings, unverifiable). A process counts as TimeManager-driven when its view reads a manager or its runs carry a business date. Pure.

segment_findings

segment_findings(fu: DataFrame, expected: Dict[str, int], names=None, now=None, stuck_hours: float = 12) -> List[dict]

Missing / failed segments and hollow runs at each process's latest date, rows stuck at RUNNING. expected: {pid: number of FilterManager rows}. Pure.

_removal_call

_removal_call(objects) -> str

integrity_findings

integrity_findings(gaps, scope_pids=None, scope_domains=None) -> List[dict]

Findings from :func:tdfs4ds.dependencies.lineage_gaps. Each names its fix; a gap that leaves a dead branch also carries removal, the objects to pass to :func:tdfs4ds.plan_removal. With a scope (processes or data domains), only the gaps touching it are kept. Pure.

dataset_rows_findings

dataset_rows_findings(rows, scope_pids=None, scope_domains=None) -> List[dict]

dataset_feature_no_rows findings from the storage-level check. rows: {"reads": dependencies.dataset_feature_reads(H), "scan": scan_storage_orphans( by_version=True), "completed": {process id: COMPLETED runs} or None}.

A feature read has no rows when the table holds none for its (FEATURE_ID, FEATURE_VERSION) at the valid time the dataset reads (now for a current dataset, any time otherwise). An INNER dataset then returns no row at all (error); a FULL OUTER one reads NULL for the column (warning). The follow-up tells a process that never completed from one whose rows are gone. A table that could not be scanned is skipped (the caller reports it). Pure.

relocation_reverted_findings

relocation_reverted_findings(strays, scope_pids=None, scope_domains=None) -> List[dict]

relocation_reverted findings from the storage-level check. strays: :func:tdfs4ds.feature_store.relocation.read_relocation_strays.

A relocated feature has a placement tag. A client older than 0.3.3.30 does not know it: when it registers the feature again (a type change, or removed and registered anew) it names the untagged table (the catalog row follows) and writes its values there, so the history is split over two tables and the datasets read either. Pure.

network_nodes

network_nodes(processes, datasets)

({node: {"pid", "label"}}, {node: {"label"}}) of the registered process and dataset views, keyed like the persisted lineage graph and labelled like :func:tdfs4ds.dependencies.build_dependency_graph. Pure.

network_scope_findings

network_scope_findings(network, gaps=None, scope_pids=None) -> List[dict]

:func:tdfs4ds.lineage.network_health.network_findings, without the objects the gap check already reported. With a process scope, the findings about other processes are dropped; those about the whole network stay. Pure.

sql_findings

sql_findings(versions: Dict[str, list], drift: Dict[str, dict], fu: DataFrame, names=None) -> List[dict]

versions: {pid: [{"PROCESS_VERSION", "VALID_FROM", "VALID_TO"}]}; drift: {pid: {"since", "changed_steps"}}. Pure.

entity_subsets

entity_subsets(cols) -> List[tuple]

[("ALL", cols), ("-C", cols without C), ...].

entity_count_sql

entity_count_sql(database, table, cols) -> str

Distinct entity tuples of a storage table, for the key and the key minus each column, over the whole history (H:) and now (C:). One request.

The temporal qualifier sits inside each derived table (a derived table refuses CURRENT, and a UNION branch refuses a leading qualifier: Error 9345); the label is CAST because the first branch types the column.

parse_counts

parse_counts(rows) -> Dict[str, Dict[str, int]]

redundancy

redundancy(counts, cols) -> tuple

(determined over the whole history, determined now only). Pure.

reduce_entity_key

reduce_entity_key(run_sql: Callable[[str], list], database, table, cols) -> dict

Greedy reduction: drop a column the others determine over the whole history, re-count, until none is left (at most one round per column). run_sql returns the rows of a statement. Pure apart from run_sql.

entity_scan_findings

entity_scan_findings(results, pi=None) -> List[dict]

Findings from :func:reduce_entity_key results. pi: {(DB, TABLE): [columns]}. Pure.

_pairs_with_results

_pairs_with_results(findings, results)

Each entity-scan finding with the scan result it came from (same object).

entity_superset_findings

entity_superset_findings(features) -> List[dict]

Two entity keys of one domain where one contains the other. Pure.

type_family

type_family(column_type, char_type=None, scale=None) -> str

The family that decides whether two columns join without conversion.

type_mismatch_findings

type_mismatch_findings(columns, entity_cols) -> List[dict]

columns: [(db, table, column, family)]; entity_cols: {(DB, TABLE): {COL}}. Pure.

skew_findings

skew_findings(sizes, threshold: float = 0.25, min_mb: float = 100) -> List[dict]

sizes: {(db, table): [CurrentPerm per AMP]}. Skew = 1 - avg / max. Pure.

_cols_of

_cols_of(text) -> List[str]

source_tables

source_tables(G, node, stop_nodes: set) -> List[str]

Tables a process view reads: walk through plain views, stop at datasets, at other processes' views and at manager / FS objects. Pure.

aligned_pi

aligned_pi(entity_cols, source_node, source_pi, origins) -> Optional[List[str]]

The entity columns that carry the source's primary-index columns, in the source's PI order; None unless every PI column maps. origins: {entity col: {"node", "column"}} from :func:column_lineage. Pure.

_flat

_flat(text) -> str

_steps

_steps(text) -> List[str]

explain_staging_distribution

explain_staging_distribution(text, probe: str = PROBE_TABLE) -> Optional[str]

How the rows reach the probe table of a staging CTAS EXPLAIN: "local" (the spool feeding it is built locally), "redistributed" (by hash code), "duplicated", or None when the text does not say. Read from the steps, never counted. Pure.

table_aliases

table_aliases(texts: Iterable[str], source_node) -> List[str]

Correlation names the view SQL gives source_node: EXPLAIN names a table by them (DB_SOURCE.A in view ..., seen live), not by its name. Pure.

source_read_access

source_read_access(text, source_node, aliases=()) -> Optional[str]

How an EXPLAIN reads source_node ('DB.TABLE'): "partitions" (partition elimination, incl. a single partition), "all_rows" (no elimination), or None when no step names it. aliases: correlation names of the table (see :func:table_aliases). The worst step wins. Pure.

partition_columns

partition_columns(constraint_text, columns) -> List[str]

Columns of a table named in its partitioning expression (FEATURE_ID and the valid-time columns left out). Pure.

time_filter_shape

time_filter_shape(texts: Iterable[str], tm_node: Optional[str]) -> dict

How the view SQL reads its TimeManager: {"scalar_columns": [...], "join": bool}. Scalar = <column> <op> (SELECT ... FROM <manager>), the form the optimizer can substitute and prune on (src.d <= (SELECT BUSINESS_DATE FROM tm), seen live); join = the manager in a FROM/JOIN list. Pure, on the flattened text.

_entity_for_source_column

_entity_for_source_column(r, source_column) -> Optional[str]

The entity column the view passes source_column of the largest source as.

segments_follow_partitioning

segments_follow_partitioning(r) -> bool

A FilterManager column names a partitioning column of the largest source, directly or as the entity column the view passes it as. Pure.

_fix_reregister

_fix_reregister(pids, pi=None, partitioning=None) -> str

distribution_findings

distribution_findings(rows: List[dict], *, align_min_mb: float = 1000, skew_threshold: float = 0.25, names=None) -> List[dict]

rows: one per process (see :func:collect_distribution). Grouped per (domain, entity key): the recommendation is for the entity, never one process. Pure.

_pid_in

_pid_in(obj) -> Optional[str]

The process id a finding's object ends with, as <view> (<process id>).

process_waves

process_waves(edges, pids, absent=()) -> Dict[str, int]

{pid: wave}: the execution waves of the process DAG (wave 0 = runs first), the same layering as :func:tdfs4ds.analyze_parallel_plan. Empty on a cycle; absent processes (unknown to the lineage graph) get no wave. Pure.

dimension_of

dimension_of(f) -> str

place_findings

place_findings(findings, procs, distribution=None) -> None

Give each finding the processes it concerns (pids), for the summary: its own process (object <view> (<pid>)), the processes of its entity (entity=(DOMAIN, KEY)) or those writing its table (table='DB.T'). A store-level finding (an entity column typed differently) keeps none. Pure.

health_model

health_model(procs, findings, unverifiable, checks, waves=None, names=None) -> dict

One row per process, ordered by execution wave, one cell per dimension: the worst severity of its findings, "ok" when the dimension was checked and found nothing, "n/a" when it was not (check not run, or not verifiable for that process). Also the verdict counts and the most frequent issues. Pure.

gap_branches

gap_branches(gaps) -> List[dict]

The dead branch each lineage gap leaves: [{"from", "kind", "branch"}], only the gaps whose branch is not empty. Pure.

_network_html

_network_html(model) -> str

The lineage network section: compilation, connected networks, dead branches. '' when the network check did not run.

_storage_level_note

_storage_level_note(model) -> str

Whether the storage level of the dataset check ran, and when ('' when the check was not part of the assessment).

_lineage_checked_html

_lineage_checked_html(model) -> str

What the lineage integrity check read and looked for ('' when it did not run).

_dataset_rows_summary

_dataset_rows_summary(rows) -> Optional[dict]

What the storage-level check covered (None when it did not run). Pure.

build_workflow_model

build_workflow_model(inputs: dict, *, skew_threshold: float = 0.25, skew_min_mb: float = 100, stuck_hours: float = 12, align_min_mb: float = 1000) -> dict

Findings, counts and what could not be checked. Pure.

_e

_e(v) -> str

render_executive_summary

render_executive_summary(model) -> str

The verdict, the most frequent issues and the health grid: one row per process, grouped by execution wave, one cell per dimension (worst severity; the issues in the tooltip). Scriptless HTML.

render_workflow_html

render_workflow_html(model, *, scan_hint=True) -> str

'Workflow assessment' body: the executive summary, then one group per area (the consistency style).

_md

_md(v) -> str

A Markdown table cell: pipes escaped, one line.

render_workflow_markdown

render_workflow_markdown(model) -> str

The assessment as Markdown: verdict, top issues, the health grid by wave, then the findings per area and what could not be verified.

network_markdown

network_markdown(model) -> List[str]

The lineage network section of the Markdown report ([] when not checked).

render_workflow_page

render_workflow_page(model) -> str

A standalone HTML page (the panel's content, for export): offline, no script.

write_workflow_report

write_workflow_report(model, out_path: Optional[str] = None, fmt: str = 'html') -> str

Write the assessment as html (standalone page), md or csv (findings) and return the path. Default path: DASHBOARD_REPORT_DIR.

findings_csv

findings_csv(model) -> str

_rows

_rows(sql) -> list

_query_df

_query_df(sql) -> pd.DataFrame

_err

_err(exc) -> str

_scope_processes

_scope_processes(proc: DataFrame, processes=None, data_domains=None) -> pd.DataFrame

read_followup

read_followup(process_ids, days: int = 90) -> pd.DataFrame

Follow-up rows of the processes started in the last days days (primary store).

read_dataset_modes

read_dataset_modes(datasets: DataFrame, max_ddl: int = 50) -> Dict[str, dict]

{dataset node: {"mode", "as_of", "tm"}} from the build view, then the view's DDL for a dataset without a build row (at most max_ddl reads).

read_expected_segments

read_expected_segments(proc: DataFrame, max_managers: int = 50) -> tuple

({pid: FilterManager rows}, unverifiable) - one COUNT per manager table.

read_chains

read_chains(proc: DataFrame, max_n: int = 100, emit=None) -> tuple

({pid: {"row", "chain"}}, unverifiable): the live SQL chain of each process (SHOW VIEW of its view and of the views it reads), read once and shared by the SQL drift and distribution checks; at most max_n.

read_versions

read_versions(process_ids, chains: Optional[dict] = None) -> tuple

(versions, drift, unverifiable) from FS_PROCESS_SQL (two reads) and the live chains read by :func:read_chains.

_probe_sql

_probe_sql(view, columns, pi) -> str

The staging CTAS of an ingestion, for EXPLAIN only (never executed).

_explain

_explain(sql) -> str

_segment_plan

_segment_plan(view) -> tuple

(plan text, dynamic): the plan of the process view for the active FilterManager segment, with incremental planning forced (DYNAMIC EXPLAIN, the segment's value known) when the session allows it, else the static plan. A static plan leaves the segment value as a parameter and understates partition elimination (seen live).

plan_access

plan_access(sql: str, source: str, *, dynamic: bool = True) -> Optional[str]

How the plan of the SELECT sql reads the table source ('DB.TABLE'): "partitions" (partition elimination, or a PTI time range), "all_rows", or None when no step reads it. dynamic=True plans with the subquery values known (DYNAMIC EXPLAIN with incremental planning forced, the session's query band restored); False is the static plan, which leaves a scalar subquery's value as a parameter. EXPLAIN only: nothing runs.

collect_distribution

collect_distribution(proc, feat, graph, chains, layout, expected_segments=None, max_explain: int = 30, emit=None, drivers=None) -> tuple

(rows, unverifiable): per denormalized-view process, its storage tables (PI, partitioning), its largest source (size, skew, PI, partitioning), the origin of each entity column, the aligned PI and the EXPLAIN evidence. Reads: the dictionary for the sources (batched) and at most max_explain x 3 EXPLAINs.

read_storage_layout

read_storage_layout(features: DataFrame) -> dict

Primary index, row counts, entity column types and AMP sizes of the storage tables (batched DBC reads). {"ok": False} when DBC is unreadable.

scan_entity_keys

scan_entity_keys(layout: dict, emit=None, run_sql=None, max_entities: Optional[int] = None) -> list

One :func:reduce_entity_key per (domain, entity key) of two columns or more, on its largest storage table (StatsV row count, else any).

read_dataset_rows

read_dataset_rows(reads, existing=None, emit=None, snapshot=None) -> tuple

The storage level of the dataset-feature check: rows per (FEATURE_ID, FEATURE_VERSION) of every storage table the datasets read (one grouped pass per table, now and over the whole history), and the COMPLETED runs of the producing processes. (rows, unverifiable); each failed read is an unverifiable entry, never an empty result read as "all fine".

collect_workflow_inputs

collect_workflow_inputs(processes=None, data_domains=None, checks=None, days: int = 90, max_sql_processes: int = 100, emit=None) -> dict

Everything :func:build_workflow_model needs. Each read is fail-open: a failure becomes an unverifiable entry, never an empty "all clear".

assess_workflow

assess_workflow(processes=None, data_domains=None, checks=None, entity_scan: bool = True, days: int = 90, display: bool = True, skew_threshold: float = 0.25, skew_min_mb: float = 100, stuck_hours: float = 12, align_min_mb: float = 1000, progress=None, dataset_rows: bool = True) -> dict

Assess how the workflow is built: entity keys, run order and time synchronisation, point-in-time inputs, segments, process SQL, storage.

Parameters

processes : list of str, optional Process ids or view names to assess (default: every process). data_domains : str or list, optional Restrict to these data domains. checks : iterable, optional A subset of :data:CHECKS (default: all of them). entity_scan : bool Read the storage tables to test the entity keys (one grouped pass per entity over its largest table). False skips the only expensive read. dataset_rows : bool Count the stored rows behind every dataset feature (one grouped pass per storage table the datasets read). False keeps the dictionary-level checks only. days : int Follow-up window, in days, for the run checks. display : bool Print a summary and the findings. progress : callable, optional Called with one line per step.

Returns

dict findings (area, kind, object, detail, severity, fix), counts, unverifiable (checks that could not run, with the reason), summary, entities (entity scan results), checks.

No language model is used, and nothing is changed: every finding names the command that fixes it.

_print_model

_print_model(model) -> None