Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
S
skills
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
allen.wang
skills
Commits
594861cb
Commit
594861cb
authored
Apr 10, 2026
by
allen.wang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat:init
parent
574904c8
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
158 additions
and
30 deletions
+158
-30
README.md
README.md
+9
-0
SKILL.md
vip-report/SKILL.md
+1
-0
vip-report-inventory-monthly-sync.ps1
vip-report/bin/vip-report-inventory-monthly-sync.ps1
+5
-1
SKILL.md
vip-report/campaign-s11/SKILL.md
+5
-2
render_template_com.ps1
vip-report/scripts/render_template_com.ps1
+57
-1
sync_inventory_monthly_assets.py
vip-report/scripts/sync_inventory_monthly_assets.py
+81
-26
No files found.
README.md
View file @
594861cb
# skills
http://erp.charleskeith.cn/metaData/api/flow/getOutboundDetails?pageNumber=1&pageSize=20&sortName=daily&sortOrder=-1&sku=8887049481029&type=%E5%85%B1%E4%BA%AB%E4%BB%93%E5%BA%93%E5%AD%98%E5%8F%98%E5%8A%A8%E8%AE%B0%E5%BD%95&range_createddate_begin=2026-04-09%2008%3A49%3A47&range_createddate_end=2026-04-10%2008%3A49%3A47
http://erp.charleskeith.cn/metaData/api/flow/getOutboundDetails?pageNumber=1&pageSize=20&sortName=daily&sortOrder=-1&sku=8887049481029&type=%E7%89%A9%E7%90%86%E4%BB%93%E5%BA%93%E5%AD%98%E5%8F%98%E5%8A%A8%E8%AE%B0%E5%BD%95&range_createddate_begin=2026-04-09%2008%3A50%3A20&range_createddate_end=2026-04-10%2008%3A50%3A20
http://erp.charleskeith.cn/metaData/api/flow/getOutboundDetails?pageNumber=1&pageSize=20&sortName=daily&sortOrder=-1&sku=8887049481029&type=%E6%9B%B4%E6%96%B0%E5%BA%97%E9%93%BA%E5%BA%93%E5%AD%98%E8%AE%B0%E5%BD%95&range_createddate_begin=2026-04-09%2008%3A50%3A35&range_createddate_end=2026-04-10%2008%3A50%3A35
http://erp.charleskeith.cn/metaData/api/flow/getOutboundDetails?pageNumber=1&pageSize=20&sortName=daily&sortOrder=-1&sku=8887049481029&type=%E7%B3%BB%E7%BB%9F%E6%8E%A8%E5%8D%95%E8%AE%B0%E5%BD%95&range_createddate_begin=2026-04-09%2008%3A50%3A43&range_createddate_end=2026-04-10%2008%3A50%3A43
http://erp.charleskeith.cn/metaData/api/flow/getOutboundDetailsNum?sku=8887049481029&type=%E4%BB%93%E5%BA%93%E6%96%87%E4%BB%B6%E5%BA%93%E5%AD%98&range_createddate_begin=2026-04-09%2008%3A50%3A55&range_createddate_end=2026-04-10%2008%3A50%3A55&summarizingFields=qty
vip-report/SKILL.md
View file @
594861cb
...
...
@@ -15,6 +15,7 @@ description: 协调 VIP 月报所有页的来源与渲染,通过固定配置
-
依赖 Tableau (
`tableau.charleskeith.cn`
) 时统一走
`config.yaml`
里的账号(目前是
`ec_user01`
),优先尝试下载/导出,若必须截屏则按照脚本裁切。
-
某些页面会引用
`vip.com`
,凭据不驻留仓库;调用
`config.yaml`
中的
`vip.login_endpoint`
让第三方系统自动处理登录。
-
需要数据库的页面使用测试库
`ckc_cep_db_test`
(
`config.yaml`
的
`mysql`
段),会参考
`s11-source-validation.md`
、
`s11-sql-hypothesis.sql`
等文件保持口径。
-
S11 的活动口径已切换到
`oms_shop_report`
:
`memo`
维护活动/非活动,
`single='Y'`
维护独家。
## 关键路径
...
...
vip-report/bin/vip-report-inventory-monthly-sync.ps1
View file @
594861cb
...
...
@@ -3,7 +3,8 @@ param(
[
string
]
$Slides
=
"S04,S05,S06,S07,S08"
,
[
string
]
$ReportMonth
=
""
,
[
int
]
$ReportYear
=
0,
[
int
]
$CompareYear
=
0
[
int
]
$CompareYear
=
0,
[
switch
]
$DisableTemplateLock
)
$ErrorActionPreference
=
"Stop"
...
...
@@ -24,6 +25,9 @@ if ($ReportYear -gt 0) {
if
(
$CompareYear
-gt 0
)
{
$pythonArgs
+
=
@
(
"--compare-year"
,
"
$CompareYear
"
)
}
if
(
$DisableTemplateLock
.IsPresent
)
{
$pythonArgs
+
=
"--disable-template-lock"
}
python @pythonArgs
Write-Output
$opsPath
vip-report/campaign-s11/SKILL.md
View file @
594861cb
...
...
@@ -13,6 +13,7 @@ description: Use when preparing VIP report slide S11 from MySQL daily report tab
-
`oms_daily_report`
-
`oms_daily_report_detail`
-
`oms_shop_report`
(活动款/非活动款/ESS/独家标记)
-
`ceprepeat`
for
`Repeat / ESS`
supporting evidence
## Validation References
...
...
@@ -23,5 +24,7 @@ description: Use when preparing VIP report slide S11 from MySQL daily report tab
## Guardrails
-
`活动款 / 独家款 / 非活动款`
当前仍视为未完全确认
-
不要把弱线索写成正式标签源
-
活动口径优先使用
`oms_shop_report.memo`
:
-
`活动款`
、
`活动折扣款`
-> 活动款
-
`非活动款`
、
`ESS`
-> 非活动款
-
独家口径使用
`oms_shop_report.single='Y'`
vip-report/scripts/render_template_com.ps1
View file @
594861cb
...
...
@@ -114,6 +114,8 @@ if (-not (Test-Path -LiteralPath $TemplatePath)) {
$ops
=
[
pscustomobject]@
{
replace_text
=
@
()
replace_tables
=
@
()
replace_charts
=
@
()
replace_images
=
@
()
}
...
...
@@ -125,10 +127,18 @@ if ($OperationsPath) {
}
$replaceText
=
@
()
$replaceTables
=
@
()
$replaceCharts
=
@
()
$replaceImages
=
@
()
if
(
$ops
.PSObject.Properties.Name -contains
"replace_text"
)
{
$replaceText
=
@
(
$ops
.replace_text
)
}
if
(
$ops
.PSObject.Properties.Name -contains
"replace_tables"
)
{
$replaceTables
=
@
(
$ops
.replace_tables
)
}
if
(
$ops
.PSObject.Properties.Name -contains
"replace_charts"
)
{
$replaceCharts
=
@
(
$ops
.replace_charts
)
}
if
(
$ops
.PSObject.Properties.Name -contains
"replace_images"
)
{
$replaceImages
=
@
(
$ops
.replace_images
)
}
...
...
@@ -140,7 +150,7 @@ if ($outDir -and -not (Test-Path -LiteralPath $outDir)) {
Copy-Item
-LiteralPath
$TemplatePath
-Destination
$OutputPath
-Force
if
(
$replaceText
.Count -eq 0 -and
$replaceImages
.Count -eq 0
)
{
if
(
$replaceText
.Count -eq 0 -and
$replace
Tables
.Count -eq 0 -and
$replaceCharts
.Count -eq 0 -and
$replace
Images
.Count -eq 0
)
{
# 无替换操作时直接复制模板,避免 PowerPoint 重新保存造成包结构差异。
Write-Output
$OutputPath
return
...
...
@@ -171,6 +181,52 @@ try {
Replace-PictureShape -Slide
$slide
-Shape
$shape
-ImagePath
([
string
]
$item
.image_path
)
}
foreach
(
$item
in
$replaceTables
)
{
$slide
=
$pres
.Slides.Item
([
int
]
$item
.slide
)
$shape
=
Find-Shape -Slide
$slide
-ShapeId
$item
.shape_id -ShapeName
$item
.shape_name -ExactText
$item
.exact_text -ContainsText
$item
.contains_text
if
(
$null
-eq
$shape
)
{
throw
"Table target not found on slide
$(
$item
.slide
)
"
}
if
(
$shape
.HasTable -ne -1
)
{
throw
"Shape '
$(
$shape
.Name
)
' is not a table."
}
foreach
(
$cell
in
@
(
$item
.cells
))
{
$row
=
[
int
]
$cell
.row
$col
=
[
int
]
$cell
.col
$newText
=
[
string
]
$cell
.new_text
$shape
.Table.Cell
(
$row
,
$col
)
.Shape.TextFrame.TextRange.Text
=
$newText
}
}
foreach
(
$item
in
$replaceCharts
)
{
$slide
=
$pres
.Slides.Item
([
int
]
$item
.slide
)
$shape
=
Find-Shape -Slide
$slide
-ShapeId
$item
.shape_id -ShapeName
$item
.shape_name -ExactText
$item
.exact_text -ContainsText
$item
.contains_text
if
(
$null
-eq
$shape
)
{
throw
"Chart target not found on slide
$(
$item
.slide
)
"
}
if
(
$shape
.HasChart -ne -1
)
{
throw
"Shape '
$(
$shape
.Name
)
' is not a chart."
}
$chart
=
$shape
.Chart
foreach
(
$series
in
@
(
$item
.series
))
{
$index
=
[
int
]
$series
.index
$chartSeries
=
$chart
.SeriesCollection
(
$index
)
if
(
$series
.PSObject.Properties.Name -contains
"name"
)
{
try
{
$chartSeries
.Name
=
[
string
]
$series
.name
}
catch
{}
}
if
(
$series
.PSObject.Properties.Name -contains
"x_values"
)
{
try
{
$chartSeries
.XValues
=
@
(
$series
.x_values
)
}
catch
{}
}
if
(
$series
.PSObject.Properties.Name -contains
"values"
)
{
try
{
$chartSeries
.Values
=
@
(
$series
.values
)
}
catch
{}
}
}
try
{
$chart
.Refresh
()
| Out-Null
}
catch
{}
}
$pres
.Save
()
Write-Output
$OutputPath
}
finally
{
...
...
vip-report/scripts/sync_inventory_monthly_assets.py
View file @
594861cb
...
...
@@ -77,20 +77,54 @@ def resolve_report_period(args: argparse.Namespace, config: dict[str, Any]) -> t
return
report_month
,
report_year
,
compare_year
def
build_filters
(
report_month
:
str
,
report_year
:
int
)
->
list
[
dict
[
str
,
Any
]]:
def
resolve_month_index
(
report_month
:
str
)
->
int
:
"""将月份标签转换成 1-12;无法识别时默认返回 1。"""
month_index_map
=
{
"一月"
:
1
,
"二月"
:
2
,
"三月"
:
3
,
"四月"
:
4
,
"五月"
:
5
,
"六月"
:
6
,
"七月"
:
7
,
"八月"
:
8
,
"九月"
:
9
,
"十月"
:
10
,
"十一月"
:
11
,
"十二月"
:
12
,
}
normalized
=
normalize_month_label
(
report_month
)
if
normalized
in
month_index_map
:
return
month_index_map
[
normalized
]
match
=
re
.
fullmatch
(
r"0?([1-9]|1[0-2])"
,
str
(
report_month
)
.
strip
())
if
match
:
return
int
(
match
.
group
(
1
))
return
1
def
build_filters
(
report_month
:
str
,
report_year
:
int
,
compare_year
:
int
|
None
=
None
)
->
list
[
dict
[
str
,
Any
]]:
"""构造尽可能多的 caption 过滤字段,失败会在 JS 侧被吞掉,避免抛错。"""
# billdate 年筛选按“报告年 + 上一年”动态计算,避免硬编码固定年份。
resolved_compare_year
=
report_year
-
1
if
compare_year
is
None
else
compare_year
year_values
=
[
str
(
resolved_compare_year
),
str
(
report_year
)]
# month 按累计区间筛选:例如三月 => 1,2,3。
month_index
=
resolve_month_index
(
report_month
)
month_values_numeric_str
=
[
str
(
idx
)
for
idx
in
range
(
1
,
month_index
+
1
)]
return
[
{
"field"
:
"Storename (组)"
,
"values"
:
[
"CKC-VIP"
]},
{
"field"
:
"storename (组)"
,
"values"
:
[
"CKC-VIP"
]},
{
"field"
:
"Storename"
,
"values"
:
[
"CKC-VIP"
]},
{
"field"
:
"storename"
,
"values"
:
[
"CKC-VIP"
]},
{
"field"
:
"Month"
,
"values"
:
[
report_month
]},
{
"field"
:
"月(billdate)"
,
"values"
:
[
report_month
]},
{
"field"
:
"billdate 月"
,
"values"
:
[
report_month
]},
{
"field"
:
"Year"
,
"values"
:
[
str
(
report_year
)]},
{
"field"
:
"year"
,
"values"
:
[
str
(
report_year
)]},
{
"field"
:
"年(billdate)"
,
"values"
:
[
str
(
report_year
)]},
{
"field"
:
"billdate 年"
,
"values"
:
[
str
(
report_year
)]},
{
"field"
:
"Storename (组)"
,
"values"
:
[
"ckc-vip"
]},
{
"field"
:
"storename (组)"
,
"values"
:
[
"ckc-vip"
]},
{
"field"
:
"Storename"
,
"values"
:
[
"ckc-vip"
]},
{
"field"
:
"storename"
,
"values"
:
[
"ckc-vip"
]},
{
"field"
:
"Month"
,
"values"
:
month_values_numeric_str
},
{
"field"
:
"month"
,
"values"
:
month_values_numeric_str
},
{
"field"
:
"月(billdate)"
,
"values"
:
month_values_numeric_str
},
{
"field"
:
"billdate 月"
,
"values"
:
month_values_numeric_str
},
{
"field"
:
"月"
,
"values"
:
month_values_numeric_str
},
{
"field"
:
"Year"
,
"values"
:
year_values
},
{
"field"
:
"year"
,
"values"
:
year_values
},
{
"field"
:
"年(billdate)"
,
"values"
:
year_values
},
{
"field"
:
"billdate 年"
,
"values"
:
year_values
},
{
"field"
:
"Brand"
,
"values"
:
[
"CK"
]},
{
"field"
:
"brand"
,
"values"
:
[
"CK"
]},
]
...
...
@@ -98,7 +132,7 @@ def build_filters(report_month: str, report_year: int) -> list[dict[str, Any]]:
def
build_specs
(
report_month
:
str
,
report_year
:
int
,
compare_year
:
int
)
->
dict
[
str
,
Any
]:
"""返回 S04-S08 需要的 capture/asset 描述,包含裁切信息与说明。"""
filters
=
build_filters
(
report_month
,
report_year
)
filters
=
build_filters
(
report_month
,
report_year
,
compare_year
)
capture_template
=
[
{
"capture_id"
:
"overall"
,
...
...
@@ -151,7 +185,10 @@ def build_specs(report_month: str, report_year: int, compare_year: int) -> dict[
"inner_frame_fragment"
:
info
[
"inner_frame_fragment"
],
"activate_sheet"
:
info
[
"activate_sheet"
],
"filters"
:
filters
,
"params"
:
{},
"params"
:
{
"year2"
:
compare_year
,
"Year2"
:
compare_year
,
},
"raw_screenshot_name"
:
info
[
"raw_screenshot_name"
],
"note"
:
info
[
"note"
],
}
...
...
@@ -165,10 +202,10 @@ def build_specs(report_month: str, report_year: int, compare_year: int) -> dict[
"shape_id"
:
2
,
"asset_name"
:
"s04_category_overall_top"
,
"capture_id"
:
"overall"
,
"crop"
:
{
"left"
:
0
,
"top"
:
1
20
,
"width"
:
1400
,
"height"
:
46
0
},
"crop"
:
{
"left"
:
0
,
"top"
:
1
10
,
"width"
:
1400
,
"height"
:
24
0
},
"resize_to"
:
{
"width"
:
948
,
"height"
:
208
},
"source_view"
:
"Overall"
,
"note"
:
"S04
上半部分,保留 Overall 主要图表
。"
,
"note"
:
"S04
第一行:Sales LFL 与 YTM
。"
,
},
{
"slide_code"
:
"S04"
,
...
...
@@ -177,10 +214,10 @@ def build_specs(report_month: str, report_year: int, compare_year: int) -> dict[
"shape_id"
:
4
,
"asset_name"
:
"s04_category_overall_mid"
,
"capture_id"
:
"overall"
,
"crop"
:
{
"left"
:
0
,
"top"
:
580
,
"width"
:
1400
,
"height"
:
42
0
},
"crop"
:
{
"left"
:
0
,
"top"
:
610
,
"width"
:
1400
,
"height"
:
25
0
},
"resize_to"
:
{
"width"
:
948
,
"height"
:
207
},
"source_view"
:
"Overall"
,
"note"
:
"S04
中段图表,用于突出 category 列表
。"
,
"note"
:
"S04
第二行:Category Contributions
。"
,
},
{
"slide_code"
:
"S04"
,
...
...
@@ -189,10 +226,10 @@ def build_specs(report_month: str, report_year: int, compare_year: int) -> dict[
"shape_id"
:
5
,
"asset_name"
:
"s04_category_overall_bottom"
,
"capture_id"
:
"overall"
,
"crop"
:
{
"left"
:
0
,
"top"
:
1
010
,
"width"
:
1400
,
"height"
:
51
0
},
"crop"
:
{
"left"
:
0
,
"top"
:
1
590
,
"width"
:
1400
,
"height"
:
40
0
},
"resize_to"
:
{
"width"
:
948
,
"height"
:
310
},
"source_view"
:
"Overall"
,
"note"
:
"S04
下段,用于强调趋势或列表
。"
,
"note"
:
"S04
第三/四行:ASP LFL/GP Difference + PD Difference/Regular Qty
%
Difference
。"
,
},
{
"slide_code"
:
"S05"
,
...
...
@@ -201,10 +238,10 @@ def build_specs(report_month: str, report_year: int, compare_year: int) -> dict[
"shape_id"
:
2
,
"asset_name"
:
"s05_bags_top"
,
"capture_id"
:
"bags"
,
"crop"
:
{
"left"
:
0
,
"top"
:
1
30
,
"width"
:
1400
,
"height"
:
36
0
},
"crop"
:
{
"left"
:
0
,
"top"
:
1
75
,
"width"
:
1400
,
"height"
:
25
0
},
"resize_to"
:
{
"width"
:
1093
,
"height"
:
237
},
"source_view"
:
"Bags"
,
"note"
:
"S05
上部图表对齐模板宽度
。"
,
"note"
:
"S05
第一行:Sales LFL 与 YTM
。"
,
},
{
"slide_code"
:
"S05"
,
...
...
@@ -213,10 +250,10 @@ def build_specs(report_month: str, report_year: int, compare_year: int) -> dict[
"shape_id"
:
3
,
"asset_name"
:
"s05_bags_bottom"
,
"capture_id"
:
"bags"
,
"crop"
:
{
"left"
:
0
,
"top"
:
520
,
"width"
:
1400
,
"height"
:
50
0
},
"crop"
:
{
"left"
:
0
,
"top"
:
1400
,
"width"
:
1400
,
"height"
:
42
0
},
"resize_to"
:
{
"width"
:
1093
,
"height"
:
376
},
"source_view"
:
"Bags"
,
"note"
:
"S05
下段清晰呈现 bags 分类细节
。"
,
"note"
:
"S05
第二/三行:Bags Qty
%
/ASP 与 Bags GP/PD
。"
,
},
{
"slide_code"
:
"S06"
,
...
...
@@ -372,7 +409,16 @@ def build_configure_view_js(spec: dict[str, Any]) -> str:
if (activeSheet && typeof activeSheet.getWorksheets === 'function') {{
worksheets = activeSheet.getWorksheets();
}}
const targets = worksheets.length ? worksheets : [activeSheet];
// 先对 activeSheet(Dashboard)应用筛选,再回退到内部 worksheet。
const targets = [];
if (activeSheet) {{
targets.push(activeSheet);
}}
for (const worksheet of worksheets) {{
if (!targets.includes(worksheet)) {{
targets.push(worksheet);
}}
}}
const updateType = window.tableau.FilterUpdateType.REPLACE;
const filterApply = [];
...
...
@@ -566,6 +612,11 @@ def parse_args() -> argparse.Namespace:
default
=
0
,
help
=
"需要传给 Tableau 年份参数 year2"
,
)
parser
.
add_argument
(
"--disable-template-lock"
,
action
=
"store_true"
,
help
=
"禁用基线模板锁定,强制走 Tableau 实时抓取。"
,
)
return
parser
.
parse_args
()
...
...
@@ -666,7 +717,11 @@ def main() -> None:
captures_by_id
=
{
item
[
"capture_id"
]:
item
for
item
in
specs
[
"captures"
]}
required_capture_ids
=
collect_required_capture_ids
(
filtered_assets
)
use_template_lock
=
should_use_template_lock
(
report_month
,
report_year
,
compare_year
)
use_template_lock
=
(
False
if
args
.
disable_template_lock
else
should_use_template_lock
(
report_month
,
report_year
,
compare_year
)
)
raw_screenshots
:
dict
[
str
,
Path
]
=
{}
if
not
use_template_lock
:
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment