JSON output (--json)
Pretty-printed JSON, stable field order, additive schema:
{
"schema_version": 1,
"target": "samples/AdventureWorks Sales.SemanticModel",
"reports": ["samples/AdventureWorks Sales.Report"],
"summary": {
"findings": 64,
"objects": 130,
"reachable": 74,
"roots": 51,
"unused": 56,
"unused_total": 56,
"ignored": 0,
"kept": 0,
"queried": 0,
"broken": 2,
"broken_total": 2,
"broken_artifacts": 1,
"broken_artifacts_total": 1,
"auto_date_time": {
"hidden_tables": 6,
"date_columns": 5,
"member_findings": 41,
"in_use": 1,
"unused_by_reports": 0,
"dead": 5
}
},
"unused": [
{
"type": "column",
"id": "'Sales'[Order Quantity (base)]",
"table": "'Sales'",
"used_by": [
{
"id": "'Sales'[Order Quantity]",
"provenance": "measure expression",
"also_unused": true
}
],
"named_in_power_query": []
}
],
"broken": [
{
"target": "'Sales'[Color]",
"reason": "field_not_found",
"bound_artifact": null,
"bound_artifact_report": null,
"provenance": "field well 'Values' — visual 'V2' on page 'P1'",
"location": "field well 'Values' — card visual 'Colors sold' (id 'V2') on page 'Overview' (id 'P1')"
},
{
"target": "'Sales'[Broken Total]",
"reason": "bound_artifact_broken",
"bound_artifact": "'Sales'[Broken Total]",
"bound_artifact_report": null,
"provenance": "field well 'Values' — visual 'V3' on page 'P1'",
"location": "field well 'Values' — card visual 'V3' on page 'Overview' (id 'P1')"
}
],
"broken_artifacts": [
{
"id": "'Sales'[Broken Total]",
"type": "measure",
"report": null,
"unresolved_references": ["'Sales'[Nope]"]
}
],
"auto_date_time": [
{
"verdict": "in_use",
"id": "table 'LocalDateTable_9e0bbdfc-9803-41d0-b204-481ce398f228'",
"source_column": "'Opportunity Calendar'[Date]",
"finding": null
}
],
"skips": {
"count": 0,
"notices": []
}
}
summary.findingscounts every reported finding:unused,broken,broken_artifacts, and theauto_date_timerows whose verdict is notin_use(adeadrow counts once, its nested finding with it). It equals the number of unsuppressed SARIF results. Under--compare-rootit counts only the new findings, since existing ones are left out of every array.summary.unusedis the length ofunused— after[scan].ignoreand the type flags.summary.unused_totalcounts every unused object in the graph before any suppression, filter, or section move, soreachable = objects − unused_totalalways holds and a consumer can tell a filtered-away finding from an absent one.summary.ignoredcounts findings suppressed by[scan].ignore— unused objects, broken artifacts, and broken bindings alike.summary.keptcounts the objects aripbi_keepmodel annotation keeps (a kept table counts once) — roots, so never inunused(see Keeping objects on purpose).summary.queriedcounts the objects--queries-fromlogged queries name directly — roots too, and0without a log (see Logged queries). On a model with auto date/time machinery, the remaining gap betweenunusedandunused_totalis the machinery:summary.auto_date_time.member_findingscounts its unused members and thedeadverdict count its nested own findings.summary.brokenis the length ofbroken— after[scan].ignore, the unknown-object suppression, and the type selection.summary.broken_totalcounts every broken binding detected, before any of those, so a consumer can tell a suppressed or filtered-away binding from an absent one (issue #60).summary.broken_artifactsis the length ofbroken_artifactsafter ignore,unknown_objectsuppression, and type selection.broken_artifacts_totalcounts all detected artifact owners before those steps. Each owner counts once, even when several expressions or references are broken.summary.auto_date_timecounts the section's rows by verdict, plushidden_tables(every row, all verdicts together),date_columns(the distinct user date columns the machinery serves — the shared template serves none), andmember_findings(the machinery's unused members covered by the rows, absent fromunusedindividually).- Type selection filters the
unusedarray andsummary.unused;summary.unused_totalstays model-wide. Both breakage arrays and their reported summary counts follow the same rule: a--typeselection without--brokenempties them, while--brokenretains them. Theauto_date_timearray andsummary.auto_date_timecounts are present in full when no type selection is passed or--type tableis selected; otherwise they are empty and zero. broken(issue #60) carries one row per reported broken visual binding:targetis the written field reference;reasonis one oftable_not_found,field_not_found,measure_not_found,hierarchy_not_found,level_not_found, orbound_artifact_broken;bound_artifactnames the broken artifact the binding lands on (present exactly when the reason isbound_artifact_broken);bound_artifact_reportis the report path for a report-level measure and null for model artifacts or other reasons;provenanceis the same binding-site phrase the unused findings'used_byentries carry,mobile layout …prefixed for phone-layout bindings — IDs only, so it stays a stable key (baselines compare on it) when a visual is retitled;locationis the same site with the visual's type and title and the page's display name, for people. Bindings suppressed by the clean-ingest bar or by[scan].ignoreare absent entirely; the totals above keep the arithmetic.broken_artifactscarries one row per DAX owner:id, objecttype, nullable report path, and sortedunresolved_references. Report paths distinguish equally named report measures in different reports.auto_date_timecarries one row perLocalDateTable_*/DateTableTemplate_*table:verdictisin_use,unused_by_reports, ordead;source_columnnames the varied user column the machinery serves (nullwhen none resolves, e.g. the template);findingis the table's own unused finding — with itsused_bychain — present exactly whenverdictis"dead"(the row moved here fromunused). Rows suppressed by[scan].ignoreare absent entirely. The machinery's other unused members are in no array at all: the row covers them (issue #47), andsummary.auto_date_time.member_findingscounts them.typeis one oftable,column,measure,hierarchy,partition,relationship,role,calculation_item,expression,function,report_measure,bookmark.tableis the finding's model table, quoted ('Sales') — a relationship reports its "from" side. It isnullfor the kinds with no model table (role,expression,function,report_measure,bookmark).--plaindeliberately omits it: its records keep the type and id as their first two fields.provenanceis the human phrase for how the use is made (e.g.measure expression,field well 'Y' — visual 'V' on page 'P' in report 'R',hierarchy level). A stale bookmark edge usesstale bookmarkand always hasalso_unused: true. A binding from the phone layout prefixesmobile layout(mobile layout field well 'Y' — visual 'V' on page 'P' in report 'R'), so an audit names the right surface.named_in_power_querylists the M expressions (partitions by their table, shared expressions by name) that mention the column — supply-chain context, never a consumer. Empty for every non-column finding and for columns no M step names.- Storage fields (issue #122; see Storage sizes) appear only for
PBIX and
.abfmodels, or other models with a storage source attached, and are omitted — notnull— everywhere else, so the output of other inputs is unchanged. An unused entry (or a dead auto date/time row'sfinding) that is a table, column, or relationship carriesbytes,size_basis("files", exact;"lower_bound", files missing from the backup log; or"engine", in-memory sizes from a.vpax, issue #108),rows, and — columns only —cardinality(distinct values).summary.unused_bytestotalsunused, each file once (a table covers its reported columns and relationships);summary.unused_bytes_lower_boundistruewhen any contributing size is a lower bound, and absent otherwise;summary.model_bytesis every data file in the model (in the storage source, when one is attached);summary.stats_sourceis that source's path (issue #129), absent for a PBIX or.abfmodel's own catalog. compare(issue #141) is present only under--compare-root:{"root", "baseline", "baseline_detail"?, "existing", "unverified", "fixed", "fixed_uncertain"}.rootis the other checkout as given;baselineisscanned,skipped(the model had no connected reports there), orunavailable(the scan could not run there), withbaseline_detailsaying why when it is notscanned;unverifiedlists, under askippedbaseline, the unused findings on objects that already existed there, as{type, id}(absent from every array above and from the exit code; empty otherwise);existingcounts the reportable findings that already existed there, which are absent from every array above and from the summary counts that mirror them;fixedlists the other checkout's findings this scan no longer detects, as{type, id}plusreason/provenance(broken bindings),report(report-level broken artifacts), orverdict(auto date/time tables) where they apply;fixed_uncertainistruewhenfixedis non-empty and this scan recorded anunknown_objectskip notice (see Comparing against another checkout). Without the flag the key is omitted, so other scans' output is unchanged.skips.noticescarries{path, location, kind, detail}per parser skip;kindis one ofunknown_object,unknown_property,malformed_value,unresolved_alias,stale_state(also: a--queries-fromlog that does not identify the model),opaque_source(an M partition callsValue.NativeQueryorOdbc.Query; SQL text is not analyzed),incomplete_report_set(the model was fetched byripbi fetchand its.ripbi/source.jsonlists downstream reports that could not be downloaded; see fetch), and — when reports are discovered by walking search folders —unresolved_dataset_reference(a report item under a search folder with no usabledatasetReference) andmalformed_report_item(an anchor-less.Reportfolder the search walk pruned). Under--strict,count > 0corresponds to exit code2. Each affected partition contributes oneopaque_sourcenotice; exact duplicate notices are collapsed before rendering. In--plainand--summary, these notices use the same stderr block as every other skip kind.