mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-11 16:34:57 +03:00
Add trace mode for field origin analysis
New mode traces a field from title/name to its full origin: dataset fields, calculated expression with operands, resource formulas. Searches by dataPath, exact title, or title substring. Collapses 5-7 manual calls into one. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: skd-info
|
||||
description: Анализ структуры схемы компоновки данных 1С (СКД) — наборы, поля, параметры, варианты
|
||||
argument-hint: <TemplatePath> [-Mode overview|query|fields|links|totals|params|variant] [-Name <dataset|variant>]
|
||||
argument-hint: <TemplatePath> [-Mode overview|query|fields|links|totals|params|variant|trace] [-Name <dataset|variant|field>]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
@@ -24,7 +24,7 @@ allowed-tools:
|
||||
| Параметр | Обязательный | По умолчанию | Описание |
|
||||
|--------------|:------------:|--------------|---------------------------------------------------|
|
||||
| TemplatePath | да | — | Путь к Template.xml или каталогу макета |
|
||||
| Mode | нет | `overview` | Режим: `overview`, `query`, `fields`, `links`, `totals`, `params`, `variant` |
|
||||
| Mode | нет | `overview` | Режим: `overview`, `query`, `fields`, `links`, `totals`, `params`, `variant`, `trace` |
|
||||
| Name | нет | — | Имя набора (query/fields), поля (totals) или варианта (variant) |
|
||||
| Batch | нет | `0` | Номер пакета запроса (0 = все). Только для query |
|
||||
| Limit | нет | `150` | Макс. строк вывода (защита от переполнения) |
|
||||
@@ -45,7 +45,10 @@ powershell.exe -NoProfile -File .claude\skills\skd-info\scripts\skd-info.ps1 -Te
|
||||
... -Mode fields -Name НаборДанных1
|
||||
... -Mode links
|
||||
... -Mode totals
|
||||
... -Mode totals -Name СуммаНалога
|
||||
... -Mode params
|
||||
... -Mode trace -Name КоэффициентКи
|
||||
... -Mode trace -Name "Коэффициент Ки"
|
||||
... -Mode variant -Name Основной
|
||||
... -Mode variant -Name 1
|
||||
```
|
||||
@@ -210,6 +213,27 @@ DataParams: КлючВарианта="НоменклатураИЦены"
|
||||
Output: style=ЧерноБелый groups=Separately totalsH=None totalsV=None
|
||||
```
|
||||
|
||||
### trace — трассировка поля от заголовка до запроса
|
||||
|
||||
Ищет поле по dataPath ИЛИ заголовку (включая подстроку) и показывает полную цепочку происхождения за один вызов:
|
||||
|
||||
```
|
||||
=== Trace: КоэффициентКи "Коэффициент Ки" ===
|
||||
|
||||
Dataset: (schema-level only, not in dataset fields)
|
||||
|
||||
Calculated:
|
||||
ВЫБОР КОГДА ... ТОГДА 0 ИНАЧЕ ... КОНЕЦ
|
||||
Operands:
|
||||
КоличествоМесяцевИспользования -> РасчетНалогаНаИмущество [Query]
|
||||
КоличествоМесяцевВладения -> РасчетНалогаНаИмущество [Query]
|
||||
|
||||
Resource:
|
||||
[ОсновноеСредство] Сумма(КоэффициентКи)
|
||||
```
|
||||
|
||||
Типичный сценарий: пользователь видит колонку "Коэффициент Ки" в отчёте и спрашивает как она считается. Один вызов `trace` показывает: формулу вычисления, откуда берутся операнды, как агрегируется в ресурс.
|
||||
|
||||
## Разрешение пути
|
||||
|
||||
- Прямой путь: `path/to/Template.xml`
|
||||
@@ -232,6 +256,7 @@ Output: style=ЧерноБелый groups=Separately totalsH=None totalsV=Non
|
||||
- **Формулы и ресурсы**: totals для вычисляемых полей и ресурсов
|
||||
- **Программный вызов**: params для списка параметров
|
||||
- **Изменение вывода**: variant для структуры группировок и фильтров
|
||||
- **Как считается колонка?**: trace для полной цепочки от заголовка до запроса
|
||||
|
||||
## Защита от переполнения
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$TemplatePath,
|
||||
[ValidateSet("overview", "query", "fields", "links", "totals", "params", "variant")]
|
||||
[ValidateSet("overview", "query", "fields", "links", "totals", "params", "variant", "trace")]
|
||||
[string]$Mode = "overview",
|
||||
[string]$Name,
|
||||
[int]$Batch = 0,
|
||||
@@ -516,6 +516,7 @@ if ($Mode -eq "overview") {
|
||||
} elseif ($variants.Count -gt 1) {
|
||||
$hints += "-Mode variant -Name <N> variant structure (1..$($variants.Count))"
|
||||
}
|
||||
$hints += "-Mode trace -Name <f> trace field origin (by name or title)"
|
||||
$lines.Add("Next:")
|
||||
foreach ($h in $hints) { $lines.Add(" $h") }
|
||||
}
|
||||
@@ -1138,6 +1139,199 @@ elseif ($Mode -eq "variant") {
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# MODE: trace
|
||||
# ============================================================
|
||||
elseif ($Mode -eq "trace") {
|
||||
|
||||
if (-not $Name) {
|
||||
Write-Error "Trace mode requires -Name <field_name_or_title>"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Build field index ---
|
||||
|
||||
$dsFields = @{} # dataPath -> @{ datasets=@(); title="" }
|
||||
$calcFields = @{} # dataPath -> @{ expression=""; title="" }
|
||||
$resFields = @{} # dataPath -> @(@{ expression=""; group="" })
|
||||
$titleMap = @{} # title -> dataPath
|
||||
|
||||
# Scan dataset fields (including nested Union items)
|
||||
$dataSets = $root.SelectNodes("s:dataSet", $ns)
|
||||
foreach ($ds in $dataSets) {
|
||||
$dsName = $ds.SelectSingleNode("s:name", $ns).InnerText
|
||||
$dsType = Get-DataSetType $ds
|
||||
|
||||
foreach ($f in $ds.SelectNodes("s:field", $ns)) {
|
||||
$dp = $f.SelectSingleNode("s:dataPath", $ns)
|
||||
if (-not $dp) { continue }
|
||||
$dpStr = $dp.InnerText
|
||||
if (-not $dsFields.ContainsKey($dpStr)) {
|
||||
$dsFields[$dpStr] = @{ datasets = @(); title = "" }
|
||||
}
|
||||
$dsFields[$dpStr].datasets += "$dsName [$dsType]"
|
||||
$titleNode = $f.SelectSingleNode("s:title", $ns)
|
||||
if ($titleNode) {
|
||||
$t = Get-MLText $titleNode
|
||||
if ($t) {
|
||||
if (-not $dsFields[$dpStr].title) { $dsFields[$dpStr].title = $t }
|
||||
if (-not $titleMap.ContainsKey($t)) { $titleMap[$t] = $dpStr }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($dsType -eq "Union") {
|
||||
foreach ($subDs in $ds.SelectNodes("s:item", $ns)) {
|
||||
$subName = $subDs.SelectSingleNode("s:name", $ns).InnerText
|
||||
$subType = Get-DataSetType $subDs
|
||||
foreach ($f in $subDs.SelectNodes("s:field", $ns)) {
|
||||
$dp = $f.SelectSingleNode("s:dataPath", $ns)
|
||||
if (-not $dp) { continue }
|
||||
$dpStr = $dp.InnerText
|
||||
if (-not $dsFields.ContainsKey($dpStr)) {
|
||||
$dsFields[$dpStr] = @{ datasets = @(); title = "" }
|
||||
}
|
||||
$dsFields[$dpStr].datasets += "$subName [$subType]"
|
||||
$titleNode = $f.SelectSingleNode("s:title", $ns)
|
||||
if ($titleNode) {
|
||||
$t = Get-MLText $titleNode
|
||||
if ($t) {
|
||||
if (-not $dsFields[$dpStr].title) { $dsFields[$dpStr].title = $t }
|
||||
if (-not $titleMap.ContainsKey($t)) { $titleMap[$t] = $dpStr }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Scan calculated fields
|
||||
foreach ($cf in $root.SelectNodes("s:calculatedField", $ns)) {
|
||||
$dpStr = $cf.SelectSingleNode("s:dataPath", $ns).InnerText
|
||||
$expr = $cf.SelectSingleNode("s:expression", $ns).InnerText
|
||||
$cfTitle = $cf.SelectSingleNode("s:title", $ns)
|
||||
$t = ""
|
||||
if ($cfTitle) { $t = Get-MLText $cfTitle }
|
||||
$calcFields[$dpStr] = @{ expression = $expr; title = $t }
|
||||
if ($t -and -not $titleMap.ContainsKey($t)) { $titleMap[$t] = $dpStr }
|
||||
}
|
||||
|
||||
# Scan resources
|
||||
foreach ($tf in $root.SelectNodes("s:totalField", $ns)) {
|
||||
$dpStr = $tf.SelectSingleNode("s:dataPath", $ns).InnerText
|
||||
$expr = $tf.SelectSingleNode("s:expression", $ns).InnerText
|
||||
$grp = $tf.SelectSingleNode("s:group", $ns)
|
||||
$groupStr = "(overall)"
|
||||
if ($grp) { $groupStr = $grp.InnerText }
|
||||
if (-not $resFields.ContainsKey($dpStr)) { $resFields[$dpStr] = @() }
|
||||
$resFields[$dpStr] += @{ expression = $expr; group = $groupStr }
|
||||
}
|
||||
|
||||
# --- Resolve name: try dataPath, then exact title, then substring title ---
|
||||
$targetPath = $Name
|
||||
$knownPaths = @()
|
||||
$knownPaths += $dsFields.Keys
|
||||
$knownPaths += $calcFields.Keys
|
||||
$knownPaths += $resFields.Keys
|
||||
$isKnown = $knownPaths -contains $Name
|
||||
|
||||
if (-not $isKnown) {
|
||||
if ($titleMap.ContainsKey($Name)) {
|
||||
$targetPath = $titleMap[$Name]
|
||||
} else {
|
||||
# Substring match in titles
|
||||
$matchedTitle = $null
|
||||
foreach ($key in $titleMap.Keys) {
|
||||
if ($key -like "*$Name*") {
|
||||
$matchedTitle = $key
|
||||
break
|
||||
}
|
||||
}
|
||||
if ($matchedTitle) {
|
||||
$targetPath = $titleMap[$matchedTitle]
|
||||
} else {
|
||||
Write-Error "Field '$Name' not found by dataPath or title"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- Build output ---
|
||||
$title = ""
|
||||
if ($calcFields.ContainsKey($targetPath) -and $calcFields[$targetPath].title) {
|
||||
$title = $calcFields[$targetPath].title
|
||||
} elseif ($dsFields.ContainsKey($targetPath) -and $dsFields[$targetPath].title) {
|
||||
$title = $dsFields[$targetPath].title
|
||||
}
|
||||
$titleStr = if ($title) { " `"$title`"" } else { "" }
|
||||
|
||||
$lines.Add("=== Trace: $targetPath$titleStr ===")
|
||||
$lines.Add("")
|
||||
|
||||
# Dataset origin
|
||||
if ($dsFields.ContainsKey($targetPath)) {
|
||||
$uniqueDs = $dsFields[$targetPath].datasets | Select-Object -Unique
|
||||
$lines.Add("Dataset: $($uniqueDs -join ', ')")
|
||||
} else {
|
||||
$lines.Add("Dataset: (schema-level only, not in dataset fields)")
|
||||
}
|
||||
|
||||
# Calculated field
|
||||
if ($calcFields.ContainsKey($targetPath)) {
|
||||
$cf = $calcFields[$targetPath]
|
||||
$lines.Add("")
|
||||
$lines.Add("Calculated:")
|
||||
foreach ($el in ($cf.expression -split "`n")) { $lines.Add(" $($el.TrimEnd())") }
|
||||
|
||||
# Extract operands: find known field names in expression
|
||||
$operands = @()
|
||||
$allKnown = @()
|
||||
$allKnown += $dsFields.Keys
|
||||
$allKnown += $calcFields.Keys
|
||||
$allKnown = $allKnown | Select-Object -Unique | Where-Object { $_ -ne $targetPath }
|
||||
# Sort by length descending to match longer names first
|
||||
$allKnown = $allKnown | Sort-Object -Property Length -Descending
|
||||
|
||||
foreach ($fieldName in $allKnown) {
|
||||
$escaped = [regex]::Escape($fieldName)
|
||||
if ($cf.expression -match "(?<![а-яА-ЯёЁa-zA-Z0-9_.])$escaped(?![а-яА-ЯёЁa-zA-Z0-9_.])") {
|
||||
$operands += $fieldName
|
||||
}
|
||||
}
|
||||
|
||||
if ($operands.Count -gt 0) {
|
||||
$lines.Add(" Operands:")
|
||||
foreach ($op in $operands) {
|
||||
if ($calcFields.ContainsKey($op)) {
|
||||
$lines.Add(" $op -> calculated")
|
||||
} elseif ($dsFields.ContainsKey($op)) {
|
||||
$opDs = ($dsFields[$op].datasets | Select-Object -Unique) -join ", "
|
||||
$lines.Add(" $op -> $opDs")
|
||||
} else {
|
||||
$lines.Add(" $op")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Resource
|
||||
if ($resFields.ContainsKey($targetPath)) {
|
||||
$lines.Add("")
|
||||
$lines.Add("Resource:")
|
||||
foreach ($r in $resFields[$targetPath]) {
|
||||
$lines.Add(" [$($r.group)] $($r.expression)")
|
||||
}
|
||||
}
|
||||
|
||||
# Simple dataset field, no calc/resource
|
||||
if (-not $calcFields.ContainsKey($targetPath) -and -not $resFields.ContainsKey($targetPath)) {
|
||||
if ($dsFields.ContainsKey($targetPath)) {
|
||||
$lines.Add("")
|
||||
$lines.Add("(direct dataset field, no calculated expression or resource)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- Output ---
|
||||
|
||||
$result = $lines.ToArray()
|
||||
|
||||
Reference in New Issue
Block a user