fs-document¶
LLM-powered process documentation + EXPLAIN plan quality score (1–5).
name: "Feature Store: Document" description: "Document a tdfs4ds feature store — LLM-generated (one process, a dataset, or the whole store from the lineage graph with rebuild_documentation), loaded from a data dictionary, or written by hand with provenance (including deep/historian tables), and keep the vector index in step. Also covers the EXPLAIN plan quality score (1-5). Invoke when the user wants to document, review or fill documentation gaps." argument-hint: "[process-id][llm-provider: openai|bedrock|vllm|azure]" allowed-tools: "Read, Edit, Write, Bash, Glob, Grep"
Generate Python code to produce and persist LLM-powered documentation for a registered tdfs4ds process, including an EXPLAIN plan quality score.
User context: $ARGUMENTS
Related skills¶
| Step | Skill | Why |
|---|---|---|
| Before | fs-upload |
The process must be registered before documentation can run. |
| Sibling | fs-lineage |
Pure dependency graph without the LLM cost. |
| If low score | fs-analyze |
Full scalability analysis + FilterManager rewrite proposal. |
| If raw EXPLAIN | td-explain |
Visual flow diagram of any Teradata EXPLAIN output. |
| After | fs-vector-store |
The vector index is built from what this skill writes; it must be refreshed to search new text. |
| Downstream | fs-agent |
The consumer agent reads the business dictionary populated here. |
Demos: demos/notebooks/G1 - Documentation and query optimisation.ipynb, T3 - What the documentation knows.ipynb (deep / historian tables).
Which way to document¶
| Situation | Call | Index refreshed? |
|---|---|---|
| At registration | upload_features(..., document=True, vector_indexing=True) (cascade_redocumentation=True also re-documents what depends on it) |
with vector_indexing=True |
| A dataset when it is built | build_dataset(..., document=True, vector_indexing=True) |
with vector_indexing=True |
| One process, now | document_process(process_id) (always rewrites) or document_process_incremental(process_id, force_update=False) (skips a documented one, documents upstream views first) |
no: call build_vector_index() |
| One dataset | document_dataset_incremental(dataset_id) |
no |
| A plain view that is not a process | document_view_incremental('<DATABASE>', '<VIEW_NAME>') |
no |
| The whole store (first time, or to fill gaps) | tdfs4ds.rebuild_documentation(force_update=False): walks the persisted lineage graph once, upstream first, each object documented once |
yes (rebuild_vector_indexing=True, default) |
| After changing some views | tdfs4ds.rebuild_documentation(objects=['DB.VIEW']): the objects and everything downstream, forced |
yes |
| By hand, no LLM | set_feature_documentation(...) and the other set_* below |
yes (refresh_index=True, default) |
rebuild_documentationrefreshes the lineage graph first (rebuild_lineage=True); a store documented by hand only needsrebuild_lineage=False.extra_views=[...]adds standalone views that no process or dataset reaches.force_update=Falseskips what is already documented: re-running after a stop (an expired token, a time-out) resumes where it stopped.- What is missing: the admin app's Documentation › Coverage panel lists every process, feature, dataset, intermediate view and source table as documented / partial / missing, with its index status, and downloads the gaps as CSV. Documentation › Objects shows one object, its provenance and history, and edits it.
Ask for any missing values before generating code:
- Process ID or view name — Either the UUID, or the qualified view name (e.g. "demo_user"."FEAT_ENG_CUST"). Prefer the view name when the user provides it — use get_process_id to resolve it (see pattern below).
- LLM provider — 'openai', 'azure', 'bedrock', or 'vllm'. Credentials loaded from env vars.
document_process produces and uploads:
- Business logic description of the SQL query
- Entity description
- Per-feature column descriptions
- EXPLAIN plan analysis with a quality score 1–5 (5 = optimal use of PI and partitioning)
Emit the following pattern, substituting real values:
import tdfs4ds # reads the TDFS4DS_* settings of .env
tdfs4ds.reconnect_teradata(raise_error=True) # teradataml context from TDFS4DS_TD_HOST / _USERNAME / _PASSWORD
tdfs4ds.connect(database='<DATABASE>', create_if_missing=True)
import teradataml as tdml
tdfs4ds.select_data_domain('<DATA_DOMAIN>') # or set TDFS4DS_DATA_DOMAIN in .env
# ---------------------------------------------------------------------------
# LLM configuration — choose one provider block.
# Preferred: set TDFS4DS_INSTRUCT_MODEL_* in .env — auto-loaded at import.
# The explicit assignments below override any file/env settings if needed.
# ---------------------------------------------------------------------------
# Option A: OpenAI-compatible (openai, vllm, azure)
tdfs4ds.INSTRUCT_MODEL_PROVIDER = '<PROVIDER>' # 'openai', 'vllm', or 'azure'
tdfs4ds.INSTRUCT_MODEL_URL = '<MODEL_URL>' # None for default OpenAI endpoint
# API key: keep it in .env as TDFS4DS_INSTRUCT_MODEL_API_KEY (never in code)
tdfs4ds.INSTRUCT_MODEL_MODEL = '<MODEL_NAME>'
# Option B: AWS Bedrock
# tdfs4ds.INSTRUCT_MODEL_PROVIDER = 'bedrock'
# tdfs4ds.INSTRUCT_MODEL_MODEL = 'anthropic.claude-3-sonnet-20240229-v1:0'
# (uses boto3 default credentials — no API key needed)
# ---------------------------------------------------------------------------
# 1. Inspect available processes
# ---------------------------------------------------------------------------
tdfs4ds.process_catalog()
# ---------------------------------------------------------------------------
# 2. Resolve process ID — two options
# ---------------------------------------------------------------------------
# Option A: you already have the UUID
process_id = '<PROCESS_ID>'
# Option B: resolve from a qualified view name (preferred when user supplies a view name)
from tdfs4ds.process_store.process_query_administration import get_process_id
process_id = get_process_id('"<DATABASE>"."<VIEW_NAME>"')
# get_process_id strips quotes, handles "db"."view" or plain view names,
# and falls back to tdfs4ds.SCHEMA when no database prefix is given.
# ---------------------------------------------------------------------------
# 3. Document the process
# - Calls the LLM to generate business logic, entity, and feature docs
# - Runs EXPLAIN on the process SQL and scores it 1–5
# - Persists everything to the feature store documentation tables
# ---------------------------------------------------------------------------
from tdfs4ds.genai import document_process
process_info = document_process(
process_id=process_id,
show_sql_query=True, # print the process SQL alongside the docs
show_explain_plan=True, # print the raw EXPLAIN plan
)
# The returned dict contains all generated documentation:
# process_info['DOCUMENTED_SQL'] — business logic + feature descriptions
# process_info['EXPLAIN_ANALYSIS'] — plain-text EXPLAIN plan analysis
# process_info['USER_SCORE'] — int 1–5: SQL quality (what you control)
# process_info['OPTIMIZATION_SCORE'] — int 1–5: overall execution quality (infra included)
# process_info['EXPLAIN_WARNINGS'] — list[str]: flagged issues
# process_info['EXPLAIN_RECOMMENDATIONS'] — list[str]: split [You] / [Data Engineer / DBA]
# ---------------------------------------------------------------------------
# 4. Document all processes in the current data domain (batch)
# ---------------------------------------------------------------------------
# process_ids = (
# tdfs4ds.process_catalog()[['PROCESS_ID']].to_pandas().PROCESS_ID.values
# )
# for pid in process_ids:
# document_process(process_id=pid)
# ---------------------------------------------------------------------------
# 4b. Document a non-catalog view (base/staging views not registered as processes)
# Use document_view_incremental when SHOW VIEW is available but the view is
# not in the process catalog.
# ---------------------------------------------------------------------------
# from tdfs4ds.genai import document_view_incremental
# document_view_incremental('<DATABASE>', '<VIEW_NAME>') # e.g. 'DB_SOURCE', 'FEAT_BASE_CUSTOMER'
# ---------------------------------------------------------------------------
# 4c. Make the new text searchable by the consumer agent (document_process does
# not touch the vector index; rebuild_documentation and the set_* functions do)
# ---------------------------------------------------------------------------
# from tdfs4ds.agent import build_vector_index
# build_vector_index(force_rebuild=False) # only new or changed rows (see fs-vector-store)
# ---------------------------------------------------------------------------
# 5. Retrieve previously stored documentation (no LLM call)
# ---------------------------------------------------------------------------
from tdfs4ds.process_store.process_store_catalog_management import get_process_info
stored = get_process_info(process_id)
print(stored.get('DOCUMENTED_SQL'))
print(stored.get('EXPLAIN_ANALYSIS'))
Processes that call a model or a known operator (0.3.5.15+)¶
A process view that scores a BYOM model (ONNXPredict, PMMLPredict, H2OPredict, ...) or compresses series
with td-ts-compressor (ts_compress_to) is documented with a fixed block written by code: for BYOM, the model
table's metadata rows (non-LOB columns, the row the view's WHERE selects first) and the selection rule; for
ts_compress_to, the parameters, the mode and the meaning of the output columns. Nothing to call: it happens in
upload_features(document=True), document_process_incremental and the agent's EXPLAIN. To make it useful,
keep the metadata in the model table (save_byom(..., additional_columns={'trained_as_of': ..., 'model_comment':
...})); preview the block with from tdfs4ds.genai.operator_context import operator_context; print(operator_context(view_sql)).
EXPLAIN score interpretation¶
| Score | Meaning |
|---|---|
| 5/5 | Query fully leverages primary index and/or partitioning — optimal |
| 3–4/5 | Partial optimisation — some full-AMP or full-partition scans remain |
| 1–2/5 | Full-table scans, product joins, or spool-heavy operations — consider rewriting or adding a FilterManager |
Formula consistency validation (v0.3.1.35+)¶
After LLM documentation runs, the engine cross-checks each generated column description against SQL signals extracted from the view DDL (ABS, STDDEV, PERCENTILE_CONT, SIGN, aggregations). Four additional columns are written to FS_BUSINESS_DICTIONARY_COLUMNS:
| Column | Type | Meaning |
|---|---|---|
MONOTONIC_DIRECTION |
VARCHAR | 'increasing', 'decreasing', or NULL — direction implied by the formula |
VALUE_DOMAIN |
VARCHAR | Expected value range or domain (e.g. '[0, 1]', 'non-negative') |
IS_ABSOLUTE_VALUE |
CHAR(1) | 'Y' when the formula applies ABS — description must not mention sign |
FORMULA_CONSISTENCY_WARNING |
VARCHAR | Plain-language note when the description contradicts the SQL signals |
Migrating an existing install: run once after upgrading to v0.3.1.35+ to add these columns to an existing table (idempotent):
from tdfs4ds.genai.documentation import migrate_documentation_columns_schema
migrate_documentation_columns_schema()
Temporary credentials (v0.3.1.215+)¶
When the instruct model uses a short-lived token (for example a Bedrock bearer token in INSTRUCT_MODEL_API_KEY), a long rebuild_documentation() can outlive it. On expiry tdfs4ds asks for a renewed token with hidden input, in a terminal or a notebook, and retries the failed call. It asks up to tdfs4ds.CREDENTIAL_RENEWAL_ATTEMPTS times (default 3).
- A blank answer stops the run cleanly. The summary carries
stopped_on_credentials=True. - To resume, set a valid token and re-run
rebuild_documentation(force_update=False). Objects that are already documented are skipped. - Without an interactive session (a server or papermill), nothing is prompted and the expiry error propagates.
- Set
tdfs4ds.CREDENTIAL_RENEWAL_ATTEMPTS = 0to turn prompting off.
Business dictionary¶
Two temporal tables store business-oriented descriptions for database objects and their columns, independently of the process documentation workflow. They are provisioned automatically by tdfs4ds.connect(create_if_missing=True).
| Table | Key columns | Purpose |
|---|---|---|
FS_BUSINESS_DICTIONARY_OBJECTS |
DATABASE_NAME, OBJECT_NAME |
One row per table or view |
FS_BUSINESS_DICTIONARY_COLUMNS |
DATABASE_NAME, TABLE_NAME, COLUMN_NAME |
One row per column |
Both tables are VALIDTIME temporal — every write uses CURRENT VALIDTIME MERGE, so the full history of descriptions is preserved.
Export the documentation (0.3.3.16)¶
tdfs4ds.export_documentation('documentation.xlsx') # About / Objects / Columns / Sections
tdfs4ds.export_documentation('documentation.html') # one self-contained page
tdfs4ds.export_documentation('sources.xlsx', scope='outside') # source tables and other views only
tdfs4ds.export_documentation('fs.html', scope='feature_store', databases='DB_LAB')
Every documented table and view with its description, documented columns and sections, each row tagged in the
feature store (process view, dataset, Time/FilterManager) or outside the feature store (source tables and
other views, with the feature processes that read them, from lineage). The same rule drives the consumer agent's
answers (tdfs4ds.documentation_export.object_scope). Admin app: Documentation › Coverage › Export.
What is not documented yet is listed too (STATUS='missing', empty text, yellow in the XLSX): process
views, datasets and lineage tables/views without a description, their undocumented columns (from DBC, in table
order) and a dataset's missing standard sections. include_missing=False for documented rows only.
Round trip — fill the yellow cells, then:
tdfs4ds.import_documentation('documentation.xlsx') # dry run
tdfs4ds.import_documentation('documentation.xlsx', dry_run=False, author='Jane Doe')
Only new or changed texts are written, through set_process_documentation / set_dataset_documentation /
set_object_documentation (MANUAL provenance, kept by LLM runs; a feature's text also reaches the process
documentation and the datasets). An emptied cell deletes nothing. Read-only stores, managers, internal objects and
process-view sections are skipped with a reason. One index refresh at the end.
Upload object descriptions¶
import pandas as pd
from tdfs4ds.genai import upload_business_dictionary_objects
objects_df = pd.DataFrame([
{
'DATABASE_NAME' : 'MY_DB',
'OBJECT_NAME' : 'CUSTOMER',
'OBJECT_TYPE' : 'T', # 'T' = table, 'V' = view
'BUSINESS_DESCRIPTION': 'Core customer table. Each row represents a unique customer enrolled in the loyalty programme.',
},
{
'DATABASE_NAME' : 'MY_DB',
'OBJECT_NAME' : 'V_CUSTOMER_ORDERS',
'OBJECT_TYPE' : 'V',
'BUSINESS_DESCRIPTION': 'Business view joining customers with their order history for reporting.',
},
])
upload_business_dictionary_objects(objects_df)
Upload column descriptions¶
from tdfs4ds.genai import upload_business_dictionary_columns
columns_df = pd.DataFrame([
{
'DATABASE_NAME' : 'MY_DB',
'TABLE_NAME' : 'CUSTOMER',
'COLUMN_NAME' : 'CUSTOMER_ID',
'BUSINESS_DESCRIPTION': 'Unique customer identifier assigned at enrolment.',
},
{
'DATABASE_NAME' : 'MY_DB',
'TABLE_NAME' : 'CUSTOMER',
'COLUMN_NAME' : 'BIRTH_DATE',
'BUSINESS_DESCRIPTION': 'Customer date of birth, used for age-band segmentation.',
},
])
upload_business_dictionary_columns(columns_df)
Both functions validate that all required columns are present and raise ValueError with the list of missing columns if not.
Since 0.3.2.1 the rows they load are tagged IMPORTED (pass author='...'). That is the right tag for source-data
dictionaries, and LLM runs keep those rows. Pass source=None to record nothing.
Writing documentation by hand (0.3.2.1)¶
Use these when a user asks how to save a comment or description on a feature, a process or a dataset. No LLM is needed. The text is tagged MANUAL and kept by every later LLM run.
tdfs4ds.set_feature_documentation('nb_days_since_last_transactions', 'Days since the last purchase.',
entity='CustomerID', author='Jane Doe') # both places + dataset copies
tdfs4ds.set_process_documentation(pid, business_logic='...', features={'sum_x': '...'}, author='Jane Doe')
tdfs4ds.set_dataset_documentation('DS_CUSTOMER', overview='...', sections={'BUSINESS_QUESTIONS': '...'})
tdfs4ds.set_object_documentation('DB_SOURCE', 'transactions', columns={'amount': '...'}) # source table / view
tdfs4ds.propagate_documentation('DS_CUSTOMER') # dataset columns from feature docs, no LLM
tdfs4ds.link_feature_documentation(pid) # feature rows of a process from its view's column docs (repair, no LLM)
tdfs4ds.get_documentation('DS_CUSTOMER') # each part: label (imported/manual/propagated/...), editor, since
tdfs4ds.mark_documentation_manual(target, parts) # protect legacy hand-written docs
tdfs4ds.release_manual_documentation(target, parts) # let the LLM rewrite again
tdfs4ds.documentation_log(target='DB.OBJ').to_pandas() # who did what, when, from where
# a deep (historian) table: layout once, then each tag as if it were a column of the pivoted table
tdfs4ds.set_deep_table_documentation('DB', 'HISTORIAN', id_columns=['MACHINE_NR'], tag_column='SENSOR_NAME',
payload_column='PAYLOAD', timestamp_column='TD_TIMECODE',
tags=tag_df, tag_name_column='SENSOR_NAME', tag_text_column='DESCRIPTION',
unit_column='UNIT', source='IMPORTED')
tdfs4ds.deep_table_tags('DB', 'HISTORIAN', since='2026-09-01', documented=False) # reads the table: give a window
-
Deep tables. The layout is the reserved section
DEEP_LAYOUT; a tag is a column row whose name is not a physical column. The LLM gets only the tags the documented SQL names (its literals), whatever the SQL shape. A table without a layout behaves exactly as before. -
Tables read through a view (0.3.4.6). When users read a table through a view (
LOCKING ROW FOR ACCESS, often a row restriction on the user or a join to an entitlement table), document the table: a view whose every column is a column of that table (renames allowed, row filters allowed) takes its documentation, layout and tags included, marked PROPAGATED, with no right on the table needed. It happens by itself in the documentation walk (EXPOSED_VIEW_AUTO_ATTACH, default True);tdfs4ds.suggest_exposed_views(databases=[...])lists candidates,attach_exposed_view('DB.VIEW')does it now,refresh_exposed_views()after an import. Edits on the table throughset_object_documentation/set_deep_table_documentationreach its views by themselves. -
Soft clones of a TimeManager (0.3.6.0). With a domain clock every process reads its own clone (
<process view>_TM). Document the clock once (set_object_documentation('<DB>.<CLOCK>', description=..., columns={'BUSINESS_DATE': ...})): the documentation walk copies it onto each clone (PROPAGATED), with no LLM call per clone. A clock without documentation leaves its clones to the usual path. -
Attach a view or table (0.3.5.2). A view that joins a dataset with a source table, read by no process, is outside the feature store until it is attached.
attach_objectsrecords its lineage and documents it: pass-through columns take the description of their source column (PROPAGATED), computed columns and the object description are written by the LLM (skipped without an instruct model). A table is documented by its owner (set_object_documentation). IMPORTED and MANUAL parts are never overwritten.
tdfs4ds.suggest_attachments(databases=['<DB>']) # read only: views reading a dataset or process
tdfs4ds.attach_objects(['<DB>.<VIEW>'], data_domain='<DOMAIN>', dry_run=True) # report first
tdfs4ds.attach_objects(['<DB>.<VIEW>'], data_domain='<DOMAIN>')
tdfs4ds.attached_objects() # DATABASE_NAME, OBJECT_NAME, KIND, DATA_DOMAIN, DOCUMENTED_AT
tdfs4ds.refresh_attached_objects(dry_run=True) # unchanged / sources_changed / changed / broken / disappeared
tdfs4ds.refresh_attached_objects(dry_run=False) # also run by rebuild_documentation
tdfs4ds.detach_objects(['<DB>.<VIEW>'])
A process view, a dataset, an FS_* object or a name not in the database is refused. refresh_attached_objects
re-documents only what changed (the LLM only for a computed column or changed sources); a broken object is
reported and nothing is written; a disappeared one is detached, its documentation ended (readable at a past date).
- Check before writing.
dry_run=Truereturns the report without writing:written,propagated,skipped_manual,stale(the LLM text derived from the old text) andindex. - Regenerating dependents.
regenerate_dependents=Trueregenerates the stale LLM documentation, only when an instruct model is configured. - Ambiguous names. A feature name shared by several entities or domains raises with the candidates. Pass
entity=/data_domain=(the default domain istdfs4ds.DATA_DOMAIN). - Overwriting human text. The LLM functions take
overwrite_manual=Trueto replace human text. - Where it is stored. Provenance lives in
FS_DOCUMENTATION_PROVENANCEand the log inFS_DOCUMENTATION_LOG. Both are optional tables created byconnect(); the six documentation tables are never altered, andFS_FOLLOW_UPis never used for documentation. - In the admin app: Documentation › Objects › Edit documentation (admin view). It requires a name and shows a preview before saving.
Rules¶
- When the user provides a view name instead of a UUID, always use
get_process_idfromtdfs4ds.process_store.process_query_administrationto resolve it — never ask the user to look up the UUID manually. document_processboth generates and persists documentation — it is safe to re-run; it overwrites previous LLM-written docs for the sameprocess_id(imported / manual parts are kept unlessoverwrite_manual=True).- LLM config can be set via
TDFS4DS_INSTRUCT_MODEL_*in.envortdfs4ds.json(auto-loaded atimport tdfs4ds). Explicit programmatic assignment always wins and must appear before callingdocument_process. - For
'bedrock'provider, noINSTRUCT_MODEL_URLorINSTRUCT_MODEL_API_KEYis needed — it uses boto3 credentials. - A low EXPLAIN score (1–2) is a signal to run
fs-analyzeto check for partition/PI opportunities and a possible FilterManager rewrite. upload_business_dictionary_objects/upload_business_dictionary_columnsdo not require an LLM — descriptions are provided by the user as plain text in a pandas DataFrame.- To document a whole store, use
rebuild_documentation(), not a loop overdocument_process: the walk documents upstream views before the processes that read them (the LLM sees their meaning), each object once, and refreshes the index at the end. - After any documentation that does not refresh the index itself (table above), the consumer agent's search keeps the old text until
build_vector_index()runs. - Order: sources first, then processes, then datasets. A process documented before its source tables were described is documented from the column names alone and is not regenerated by a plain re-run (
force_update=Falseskips documented objects): describe the sources, then re-document withforce_update=True. build_dataset(document=True)/document_dataset_incrementalalso documents the upstream processes and views that have no documentation yet: one LLM pass each. On a new store, document the processes first (or callrebuild_documentation()once) rather than paying for it inside each dataset build.