@petercjl/taobao-ai-ops 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +30 -22
  2. package/package.json +2 -7
  3. package/requirements.txt +1 -0
  4. package/skills/taobao-ai-operations/SKILL.md +8 -1
  5. package/src/cli.mjs +16 -6
  6. package/src/components.mjs +73 -0
  7. package/src/core.mjs +5 -1
  8. package/src/runtime.mjs +18 -16
  9. package/src/skill-manager.mjs +4 -4
  10. package/src/update.mjs +5 -5
  11. package/src/workflow.mjs +3 -3
  12. package/suite-manifest.json +186 -10
  13. package/skills/taobao-search-product-form/SKILL.md +0 -74
  14. package/skills/taobao-search-product-form/adapters/codex.json +0 -139
  15. package/skills/taobao-search-product-form/adapters/sealseek.json +0 -24
  16. package/skills/taobao-search-product-form/agents/openai.yaml +0 -3
  17. package/skills/taobao-search-product-form/capabilities.json +0 -148
  18. package/skills/taobao-search-product-form/references/SCHEMA.md +0 -3
  19. package/skills/taobao-search-product-form/references/index.md +0 -10
  20. package/skills/taobao-search-product-form/references/log.md +0 -35
  21. package/skills/taobao-search-product-form/references/queries/evaluate-white-label-forms.md +0 -3
  22. package/skills/taobao-search-product-form/references/queries/identify-advertisers.md +0 -5
  23. package/skills/taobao-search-product-form/references/queries/identify-geography.md +0 -5
  24. package/skills/taobao-search-product-form/references/queries/identify-price-bands.md +0 -3
  25. package/skills/taobao-search-product-form/references/queries/identify-product-forms.md +0 -3
  26. package/skills/taobao-search-product-form/references/queries/identify-title-roots.md +0 -5
  27. package/skills/taobao-search-product-form/references/report-output.md +0 -23
  28. package/skills/taobao-search-product-form/references/source-manifest.md +0 -37
  29. package/skills/taobao-search-product-form/references/title-lexicon.json +0 -55
  30. package/skills/taobao-search-product-form/references/topics/advertiser-analysis-method.md +0 -43
  31. package/skills/taobao-search-product-form/references/topics/geography-analysis-method.md +0 -23
  32. package/skills/taobao-search-product-form/references/topics/price-band-analysis-method.md +0 -27
  33. package/skills/taobao-search-product-form/references/topics/title-root-analysis-method.md +0 -37
  34. package/skills/taobao-search-product-form/references/topics/visual-form-method.md +0 -31
  35. package/skills/taobao-search-product-form/references/topics/white-label-evidence.md +0 -38
  36. package/skills/taobao-search-product-form/references/workflow-ledger.md +0 -28
  37. package/skills/taobao-search-product-form/report_runtime/VERSION +0 -1
  38. package/skills/taobao-search-product-form/report_runtime/renderer.py +0 -485
  39. package/skills/taobao-search-product-form/report_runtime/templates/workbench.html +0 -67
  40. package/skills/taobao-search-product-form/report_runtime/validator.py +0 -117
  41. package/skills/taobao-search-product-form/scripts/analyze.py +0 -119
  42. package/skills/taobao-search-product-form/scripts/analyze_advertisers.py +0 -352
  43. package/skills/taobao-search-product-form/scripts/analyze_geography.py +0 -316
  44. package/skills/taobao-search-product-form/scripts/analyze_price_bands.py +0 -257
  45. package/skills/taobao-search-product-form/scripts/analyze_title_roots.py +0 -327
  46. package/skills/taobao-search-product-form/scripts/assemble_opportunity_evidence.py +0 -322
  47. package/skills/taobao-search-product-form/scripts/build_report_viewmodel.py +0 -822
  48. package/skills/taobao-search-product-form/scripts/build_visual_review_viewmodel.py +0 -177
  49. package/skills/taobao-search-product-form/scripts/clean_search_export.mjs +0 -61
  50. package/skills/taobao-search-product-form/scripts/cluster_visual_forms.py +0 -193
  51. package/skills/taobao-search-product-form/scripts/compile_style_prototypes.py +0 -115
  52. package/skills/taobao-search-product-form/scripts/compile_visual_review.py +0 -60
  53. package/skills/taobao-search-product-form/scripts/compile_visual_selection.py +0 -148
  54. package/skills/taobao-search-product-form/scripts/prepare.py +0 -180
  55. package/skills/taobao-search-product-form/scripts/render_report.py +0 -31
  56. package/skills/taobao-search-product-form/scripts/validate_opportunity_cards.py +0 -84
  57. package/skills/taobao-search-product-form/scripts/validate_report_viewmodel.py +0 -152
  58. package/skills/taobao-search-product-form/scripts/visual_form_pipeline.py +0 -247
  59. package/skills/taobao-search-product-form/scripts/visual_observation_batches.py +0 -117
  60. package/skills/taobao-search-product-form/scripts/workflow_ledger.py +0 -177
  61. package/skills/yuce-category-opportunity-report/SKILL.md +0 -151
  62. package/skills/yuce-category-opportunity-report/adapters/codex.json +0 -113
  63. package/skills/yuce-category-opportunity-report/adapters/sealseek.json +0 -39
  64. package/skills/yuce-category-opportunity-report/agents/openai.yaml +0 -3
  65. package/skills/yuce-category-opportunity-report/assets/chart-views.js +0 -80
  66. package/skills/yuce-category-opportunity-report/assets/package.json +0 -1
  67. package/skills/yuce-category-opportunity-report/assets/report-template.html +0 -62
  68. package/skills/yuce-category-opportunity-report/capabilities.json +0 -48
  69. package/skills/yuce-category-opportunity-report/references/SCHEMA.md +0 -5
  70. package/skills/yuce-category-opportunity-report/references/data-contract.md +0 -40
  71. package/skills/yuce-category-opportunity-report/references/index.md +0 -6
  72. package/skills/yuce-category-opportunity-report/references/log.md +0 -29
  73. package/skills/yuce-category-opportunity-report/references/queries/assess-category-opportunity.md +0 -7
  74. package/skills/yuce-category-opportunity-report/references/source-manifest.md +0 -14
  75. package/skills/yuce-category-opportunity-report/references/topics/category-opportunity-method.md +0 -39
  76. package/skills/yuce-category-opportunity-report/scripts/build_report.py +0 -779
  77. package/skills/yuce-category-opportunity-report/scripts/market_routes.py +0 -139
@@ -1,151 +0,0 @@
1
- ---
2
- name: yuce-category-opportunity-report
3
- description: Analyze one Yuce 一级类目 from supplied monthly Excel workbooks or NAS/yccli data, prepare a local Excel cache for the rolling 24-month first-/second-/third-category market, and create a self-contained HTML research-priority report. Use for 预策一级类目机会研究、类目月度补数与白牌选品方向初筛; not for product/shop ranking deep dives or stocking decisions.
4
- ---
5
-
6
- # Yuce Category Opportunity Report
7
-
8
- Produce a research-priority report for one 一级类目. The Skill has a portable core and a bundled HTML template; ordinary execution does not require `compact-commerce-ui` or any other HTML Skill. Its creation-time visual system follows the compact business-report design, while `assets/report-template.html` and `scripts/build_report.py` own reproducible runtime output.
9
-
10
- ## I → S → O
11
-
12
- - **Input:** one exact Yuce 一级类目 name or source Excel workbooks from which it can be identified uniquely; optional output directory and ending month. Two input routes are supported: supplied `.xlsx` monthly files, or the authorized NAS/yccli collection flow. The analysis window is 24 consecutive completed calendar months, ending at the latest available complete source month unless the user specifies an earlier end.
13
- - **Strategy:** choose the input route → validate and merge raw first-/second-/third-level monthly rows → create one local data-only Excel cache → read that cache for the two-year comparison → diagnose the current category's capacity distribution → create relative large/medium/small-capacity bands → output all three independent research queues with route-specific metrics → preserve a scale benchmark and watch/defer counterexamples → present a concise reading-first judgment page before the detailed evidence views → render and inspect the full report. Only the NAS route checks warehouse coverage, collects gaps, and attempts additive import before cache creation.
14
- - **Output:** one new local Excel cache, preliminary/final analysis JSON, a self-contained HTML report, and a concise receipt recording source coverage, quality findings, and limitations. The NAS route may also create yccli JSON files and a `tbcli db yuce export` snapshot.
15
- - **Authority:** use only the current user's authorized Yuce session and warehouse role. Database writes are limited to the user's requested category-month incremental import through `tbcli`; no ranking data, schema rewrite, or existing-value replacement is implied.
16
-
17
- ## Runtime Entry And Dependencies
18
-
19
- The canonical Skill is bundled in Taobao AI operations. Resolve it with `taobao-ai-ops skill source --name yuce-category-opportunity-report --json`, probe local dependencies with `taobao-ai-ops doctor --node category-research --mode excel --json`, and execute its builder through `taobao-ai-ops script category-research build_report.py ...`. Suite-managed platform commands use `taobao-ai-ops tool run TOOL ...`, preserving the command arguments below. The NAS route still requires its live feature, access and consent checks; Excel remains independently usable.
20
-
21
- The local cache builder and report reader require Python 3 and `openpyxl>=3.1`, installed in a stable runtime. Check the actual interpreter/import before running. For the NAS route only, resolve `yccli` and `tbcli` from the executing environment and run their live `--version` and `capabilities --json` before relying on commands. Require `yccli` monthly first/second/third collection and `tbcli db yuce coverage|export`, `yuce categories validate|import`, and database access/write checks. The portable capability contract is in `capabilities.json`; load the current platform adapter. A missing required feature returns `CAPABILITY_UNAVAILABLE`, not an improvised browser/API/database implementation. The supplied-Excel route does not require either CLI, NAS, VPN, or a browser session.
22
-
23
- Use raw values from Yuce Excel, yccli JSON, or the tbcli snapshot as source evidence. `scripts/build_report.py` converts the chosen sources to one `.xlsx` cache with only `类目月度缓存` as a data sheet; all analysis calls then use `--cache-xlsx` and never query NAS. Source filenames and SHA-256 hashes travel in cache row columns, not a `元数据` sheet. The Skill itself contains no credentials, company database address, author-machine path, or category-specific facts.
24
-
25
- ## Main Line
26
-
27
- ### 1. Resolve Scope And Preflight
28
-
29
- Record the exact 一级类目, source mode, 24-month inclusive window, output directory, and new file names. Check existence and metadata before each write. Never overwrite an existing report, cache, source JSON, or database export. If the user supplies Excel workbooks, use those files as the explicit input route: read their data sheets, identify the unique 一级类目, choose the latest complete 24-month source window at or before the previous calendar month, and continue directly to Step 4A. If multiple candidate sets overlap, select the complete 24-month set by actual sheet contents and report which files were selected; never rely on a filename alone. Mixed 一级类目, ambiguous windows, or incomplete first-level data require clarification or a scoped failure. Do not contact NAS or yccli merely to refresh a user-supplied Excel analysis.
30
-
31
- If the user supplies no Excel source, continue through the NAS path below. The latest completed calendar month is its proposed end; if Yuce does not offer it, report the available period and ask before shifting the window.
32
-
33
- For the NAS path, check `tbcli db network --json`, then `tbcli db status --json` and `tbcli db access-check --json`. If the route or connection fails, perform at most one bounded retry after inspecting the reported network state. Put a finite timeout on the preflight and coverage/export commands; a successful status probe does not establish that a later read completed. Do not change credentials, network configuration, or database roles as part of this Skill.
34
-
35
- **Mandatory stop:** if the database still cannot be read, tell the user that the database connection failed and that the next step would collect the full latest 24 months from Yuce. Ask whether they agree. Do not call `yccli` for collection until the user explicitly agrees in this run. If they decline or do not answer, return `DATABASE_UNAVAILABLE_AWAITING_CONSENT` and stop. After consent, enter Step 3 with an empty database snapshot.
36
-
37
- ### 2. Map Database Coverage (NAS Route)
38
-
39
- When the database is readable, run:
40
-
41
- ```text
42
- tbcli db yuce coverage --category '<一级类目>' --start-month YYYY-MM --end-month YYYY-MM --json
43
- tbcli db yuce export --category '<一级类目>' --start-month YYYY-MM --end-month YYYY-MM --out '<new-snapshot.json>' --json
44
- ```
45
-
46
- Use full category paths, not only the database's overall min/max month. Record first-level gaps, the union of second-level gaps, and third-level gaps under each known second-level parent. If the 一级类目 is absent, its initial 24-month window is entirely missing. Database coverage cannot prove that Yuce has no newly introduced category; newly collected paths must be compared with the snapshot.
47
-
48
- If coverage or export loses the database connection before collection, apply the same bounded retry and mandatory consent stop from Step 1. A successful status probe alone is not sufficient authorization to continue gathering data after the warehouse becomes unreadable.
49
-
50
- ### 3. Collect Missing Months With yccli (NAS Route)
51
-
52
- Run `yccli auth status --json` and let the user complete visible login if needed. Read `references/data-contract.md` at this collection node; it defines command handoffs, contiguous-range grouping, and completeness checks. Use the stable commands only:
53
-
54
- ```text
55
- yccli industry first-category-monthly --category '<一级类目>' --start-month YYYY-MM --end-month YYYY-MM --out '<new.json>' --json
56
- yccli industry second-category-monthly --category '<一级类目>' --start-month YYYY-MM --end-month YYYY-MM --out '<new.json>' --json
57
- yccli industry third-category-monthly --category '<一级类目>' --second-category '<二级类目>' --start-month YYYY-MM --end-month YYYY-MM --out '<new.json>' --json
58
- ```
59
-
60
- Collect first-level missing ranges, then second-level missing ranges (one request covers all second categories in that period). Discover the second-level parents from database plus new source rows. For each parent with third-level children, collect the union of known third-child missing months; if that parent has no third-level history, collect its full 24-month range. Keep each CLI call to a contiguous range and preserve every successful file. Do not fetch product or shop rankings in this Skill.
61
-
62
- If Yuce reports an unavailable period, verification challenge, category mismatch, or changed report contract, follow yccli's stop behavior. Never bypass login, request pacing, or platform validation. A partial collection may be retained as evidence but cannot be reported as a complete 24-month market.
63
-
64
- ### 4. Attempt Incremental Database Import (NAS Route)
65
-
66
- When database access and maintainer writes are available, run `tbcli db write-check --json`; require the rolled-back probe and zero residue. Initialize the Yuce schema only if it is absent and the maintainer check passed. Validate each new yccli JSON with `tbcli yuce categories validate`, then import in hierarchy order: first, second, each third. The importer inserts missing rows, preserves identical overlaps, and rejects changed existing values. Do not use `upsert`, `replace-range`, or generic `db import` as an automatic repair. After successful import, rerun coverage and export a fresh snapshot for analysis.
67
-
68
- If database write access fails or any import fails, record the affected file, reason, and inserted/unchanged counts. Continue the current research from the read snapshot plus verified yccli files; database write success is not a report prerequisite. If the database was unavailable with user consent, skip this step and analyze the full downloaded set.
69
-
70
- ### 4A. Prepare The Local Excel Cache (Both Routes)
71
-
72
- Read `references/data-contract.md` for the exact source and cache columns. For supplied Excel, pass every selected original `.xlsx` file with `--excel`. For the NAS route, pass the latest local tbcli snapshot plus each newly collected yccli JSON; if the post-import snapshot is complete, it may stand alone. Create a new cache file:
73
-
74
- ```text
75
- python3 <skill-root>/scripts/build_report.py --category '<一级类目>' \
76
- --start-month YYYY-MM --end-month YYYY-MM \
77
- [--excel '<source.xlsx>' ...] [--db-snapshot '<snapshot.json>'] [--yc-json '<source.json>' ...] \
78
- --cache-out '<new-local-cache.xlsx>'
79
- ```
80
-
81
- Check the cache has exactly one data sheet, 24 first-level months, the expected full category paths at levels 2–3, no conflicting category-month values, and source identity columns. Preserve the original files. Missing lower-level months must be explicit in the next analysis; never fill them with zero. The cache is the only input to Step 5, regardless of how its source data arrived.
82
-
83
- ### 5. Analyze Market Signals
84
-
85
- At this first judgment-heavy node, read `references/SCHEMA.md`, `references/index.md`, recent `references/log.md`, then `references/queries/assess-category-opportunity.md` and only its linked topic page. The method separates user-confirmed research intent, traceable domain knowledge, and provisional ranking heuristics.
86
-
87
- First compute a preliminary analysis sidecar without rendering:
88
-
89
- ```text
90
- python3 <skill-root>/scripts/build_report.py --category '<一级类目>' \
91
- --start-month YYYY-MM --end-month YYYY-MM --cache-xlsx '<local-cache.xlsx>' \
92
- --analysis-out '<new-preliminary-analysis.json>' --analysis-only
93
- ```
94
-
95
- The script reconciles duplicate category-month keys, rejects conflicting amounts, requires 24 complete first-level months, computes two comparable 12-month periods, month-matched year-over-year growth, recent-six-month year-over-year growth, all 24 monthly amounts for every path, and parent/child differences. It surfaces incomplete child histories. Research queues are quantitative signals, not judgments that a white-label seller can profitably enter. The initial run produces relative capacity bands and all three routes for review against this category's distribution.
96
-
97
- Inspect the full analysis JSON and its `marketRoutes`. Use the actual distribution of complete, positive-GMV 三级 paths within this 一级类目: count, median, p10/p90, top-decile GMV share, ties, missingness, and ±5-percentage-point boundary sensitivity. Capacity is relative to this population, never a fixed currency cutoff or copied kitchen threshold. Default P40/P70/P90 is a provisional starting point: below P40 is the small-capacity watch area; P40–P70 is medium-small; P70–P90 is medium; P90 and above is large. Quantile boundaries use linear interpolation, ties stay together and boundary values enter the higher band. Sparse/tied populations may produce empty routes. Diagnose whether taxonomy breadth, skew or the user's sales ambition calls for different quantile positions; record the evidence and review rather than claiming automatic economically optimal bands. A statistical middle market is not necessarily commercially sufficient.
98
-
99
- Write a new `--strategy-config` JSON with only these fields (all except `why` may retain defaults):
100
-
101
- ```json
102
- {
103
- "capacityQuantiles": [0.4, 0.7, 0.9],
104
- "minYoy": 0.2,
105
- "minSixMonthYoy": 0.2,
106
- "minPositiveMonths": 10,
107
- "shortlistLimit": 6,
108
- "why": "Explain the observed distribution, sensitivity, user objective and why these relative boundaries are suitable."
109
- }
110
- ```
111
-
112
- No absolute-money capacity override is accepted. Small numeric boundary changes must be justified, not tuned to include a favorite product. The growth defaults remain provisional, not universal. Rerun using the same cache, `--strategy-config '<new-policy.json>'`, `--analysis-out '<new-final.json>'`, and `--html-out '<new-report.html>'`. Old `--decisions` single-queue files require explicit migration; never reuse a prior absolute floor. Preliminary and final files are separate. Do not hand-edit calculated metrics.
113
-
114
- Always output all three routes, even if one is empty:
115
- - **Large-market increment:** capacity in the relative top band; rank by `100 × max(annual increment,0) / root annual GMV × positive-month count / 12`.
116
- - **Medium-capacity sustained growth:** capacity in the middle band; rank by `100 × min(annual YoY, recent-six-month YoY) × positive-month count / 12`.
117
- - **Medium-small-capacity growth:** capacity in the lower selected band; same growth-strength metric, ranked independently from medium markets.
118
-
119
- All routes require complete 24 months, positive annual GMV, calculable base-period growth, both reviewed growth floors and positive-month coverage. Mixed “其他/其它” buckets stay in the all-category table but require decomposition before shortlist selection. Rank with full precision, tie-break by positive-month count and full path; retain all pool paths and take up to N per route. Never mix unlike metric values into a global winner list. The flattened candidatePaths is a route-ordered union for rendering, not an overall ranking. Keep the largest complete class as a scale benchmark, plus measured watch/defer counterexamples.
120
-
121
- For each leaf calculate capacity band, midpoint-tie percentile, independent route rank, both indices, first-half and last-half same-period YoY, their percentage-point difference, and largest-three-month positive-increment share. These are descriptions, not causal predictions. Annual and six-month comparisons overlap; they are not independent confirmations. Concentrated increments may reflect normal seasonality. Missing/zero-base comparisons remain unknown, not fabricated zeroes.
122
-
123
- Category monthly totals support research prioritization only. Keep brand competition, white-label access, differentiation and profitability unverified. Before costly product deep-dives recommend a lightweight, separately authorized product/shop-ranking check, with sample coverage and brand classification uncertainty. Smaller markets are not presumed to have weaker brands.
124
-
125
- ### 6. Audit And Deliver
126
-
127
- Every table region must provide independent **Table / Chart** views, defaulting to Table. This includes opening route previews, full route shortlists, market structure, monthly series, matched-month comparisons, watch/defer and the full category table. Render charts from embedded full-precision source arrays, never by parsing rounded display strings. Monthly comparisons use lines with explicit month labels and gaps for missing values; categorical comparisons use grouped bars with a zero baseline, including negative values. Separate metric/unit groups via a selector, with series visibility controls; unlike ranking indices remain separate even if both use points. Provide axis units, legend and exact-value point/bar tooltips. Missing values remain missing; empty filters and hidden series have explicit messages.
128
-
129
- Charts track the same table rows, filters and sort order. Paginate long category charts with visible range/total and previous/next controls; never silently chart only top rows. Preserve the full table and its exact-number access, navigation links and existing filtering/sorting. Charts are self-contained SVG with no remote runtime/assets. On narrow screens chart canvases scroll within their own region; keyboard controls have labels and visible focus. Print all tables and any actively selected chart with its visible-page range. Test every table has a valid chart spec and identical numeric evidence, negative/zero/missing cases, group/series/view controls, pagination and filter/order synchronization. If browser access is blocked, report visual/real-browser interaction checks as unverified, independently of deterministic tests.
130
-
131
- The report starts with three plain-language market judgments and **all three route summaries**; each route shows its capacity range derived from this dataset, population → growth pool → shortlist counts, metric, and linked candidate comparisons. Keep formulas/distribution detail expandable. Different users can compare/select markets without changing data or silently hiding another route.
132
-
133
- Preserve market-structure and 24-month first/second-level tables; a complete shortlist for each route; one submenu/detail page per shortlisted category plus benchmark with numbered interpretations and 12 matched-month rows; measured watch/defer cases; and one unified sortable/searchable table of every third-level path. The all-category table supports second-parent, coverage, and capacity-route filters and shows both metrics, route rank, relative percentile, trend change, concentration, and exclusion reasons. Incomplete and below-boundary paths remain visible. Empty routes explicitly state why. Amounts are displayed for interpretation but never define reusable segmentation constants.
134
-
135
- Verify each selected path is the route's actual top N with no manual insertion. Check a scaled copy of the data has the same assignments/rankings; validate ties, small populations, empty/declining/zero-base cases, missing months, malformed policy and absolute-cutoff rejection. Confirm all three route summaries, unique all-category rows, exact pool membership, linked detail pages, numeric sorting, search, mobile navigation and print visibility. Do not call a main run a clean regression.
136
-
137
- Deliver HTML, cache link, analysis JSON and reviewed policy; report coverage, all-route counts, distribution rationale, sensitivity and missing competition evidence. NAS receipts retain the existing collection/import boundary. Clean-context regression requires an explicit user request.
138
-
139
- ## Branches And Return Points
140
-
141
- - **Database connection fails before collection:** stop and ask for consent as in Step 1. Consent returns to Step 3; refusal ends the run.
142
- - **Supplied Excel:** enter Step 4A directly and return to Step 5 after cache QA. Database/yccli errors and consent gates do not apply because this route performs no live collection or import.
143
- - **Database readable, target empty:** collect the full 24 months and return to Step 4.
144
- - **Database write fails:** preserve local JSON, mark `IMPORT_DEFERRED`, return to Step 5.
145
- - **Overlapping source conflict:** preserve both sources, mark `SOURCE_CONFLICT`, stop report synthesis until the category-month value is resolved. Never silently replace warehouse history.
146
- - **Incomplete source or parent/child anomaly:** identify exact paths and months; withhold unsupported growth comparisons. A complete first-level window is mandatory. Return to Step 3 if a supported missing range can be collected; otherwise report partial evidence and stop.
147
- - **HTML template or renderer fails:** preserve data and analysis, report `OUTPUT_CONTRACT_FAILED`; do not hand-edit the generated HTML.
148
-
149
- ## QA And Evolution
150
-
151
- The bundled `scripts/market_routes.py`, `scripts/build_report.py`, `assets/report-template.html` and `assets/chart-views.js` implement the calculation/rendering contract `yuce-category-opportunity@2.0`. Keep the local-cache and source-conflict safeguards. Report changes belong in these reusable resources rather than hand-edited output. Development tests verify relative segmentation, policy provenance, route ranking, evidence completeness and UI interactions. Run the Python tests plus `node tests/test_navigation.cjs` and `node tests/test_chart_views.cjs` from the Skill directory; Node is a development-test dependency, while delivered HTML needs no external library or service. Synthetic DOM checks do not establish actual browser layout quality. Capacity/growth thresholds remain reviewable hypotheses. Agent/platform runtime compatibility and clean-regression evidence must be reported separately.
@@ -1,113 +0,0 @@
1
- {
2
- "schema": "portable-skill-adapter",
3
- "schema_version": "1.0.0",
4
- "platform": "codex",
5
- "mappings": [
6
- {
7
- "capability_id": "category.monthly.collect",
8
- "status": "implemented",
9
- "implementation": {
10
- "kind": "stable-cli",
11
- "discovery": "Resolve yccli from PATH, then inspect --version and capabilities --json before invoking its industry monthly JSON commands."
12
- },
13
- "features": [
14
- "yccli>=0.5.0",
15
- "first_category_monthly",
16
- "second_category_monthly",
17
- "third_category_monthly",
18
- "json_output",
19
- "login_and_verification_stop"
20
- ],
21
- "permissions": [
22
- "read authorized Yuce account",
23
- "create new task-scoped JSON files"
24
- ],
25
- "normalization": "Return CLI receipt, actual months, category path, new JSON path and errors; never expose browser authentication material."
26
- },
27
- {
28
- "capability_id": "category.warehouse.sync",
29
- "status": "implemented",
30
- "implementation": {
31
- "kind": "stable-cli",
32
- "discovery": "Resolve tbcli from PATH; inspect capabilities for db yuce coverage/export and yuce categories validate/import."
33
- },
34
- "features": [
35
- "tbcli_db_yuce_coverage",
36
- "tbcli_db_yuce_export",
37
- "tbcli_yuce_categories_validate",
38
- "tbcli_yuce_categories_import",
39
- "read_role_check",
40
- "rolled_back_write_probe",
41
- "additive_transaction"
42
- ],
43
- "permissions": [
44
- "read authorized NAS warehouse tables",
45
- "insert missing requested category-month rows only when maintainer role is verified"
46
- ],
47
- "normalization": "Return coverage, new snapshot path and per-file import receipts; distinguish unavailable connection from write failure."
48
- },
49
- {
50
- "capability_id": "category.excel.cache",
51
- "status": "tested",
52
- "implementation": {
53
- "kind": "bundled-script",
54
- "path": "scripts/build_report.py",
55
- "runtime": "Python 3 with openpyxl>=3.1"
56
- },
57
- "features": [
58
- "xlsx_input",
59
- "first_second_third_monthly",
60
- "full_category_paths",
61
- "source_hashes",
62
- "conflict_stop",
63
- "new_output_only",
64
- "data_only_cache"
65
- ],
66
- "permissions": [
67
- "read selected local Excel or JSON files",
68
- "create one new local Excel cache"
69
- ],
70
- "normalization": "Return new cache path and data-quality counts; Excel-input mode does not contact NAS or Yuce.",
71
- "tested_date": "2026-09-25",
72
- "evidence": [
73
- "Codex/macOS prepared one 3852-row, single-sheet cache from six existing first-/second-/third-category kitchen workbooks; 24-month root coverage and source identities were checked."
74
- ]
75
- },
76
- {
77
- "capability_id": "report.html.render",
78
- "status": "tested",
79
- "implementation": {
80
- "kind": "bundled-script",
81
- "path": "scripts/build_report.py",
82
- "template": "assets/report-template.html",
83
- "runtime": "Python 3 with openpyxl>=3.1"
84
- },
85
- "features": [
86
- "yuce-category-opportunity@2.0",
87
- "relative_capacity_bands",
88
- "three_independent_routes",
89
- "table_chart_dual_views",
90
- "raw_chart_evidence",
91
- "filter_sort_chart_sync",
92
- "local_xlsx_cache_input",
93
- "bundled_template",
94
- "responsive_views",
95
- "print_all_views",
96
- "escaped_text",
97
- "new_output_only",
98
- "analysis_sidecar"
99
- ],
100
- "permissions": [
101
- "read selected JSON evidence",
102
- "write new report and analysis files"
103
- ],
104
- "normalization": "Return paths, contract, category and reconciliation counts from the JSON receipt.",
105
- "tested_date": "2026-10-04",
106
- "evidence": [
107
- "Codex/macOS synthetic 24-month first/second/third source test passed; output files, shortlist, self-contained view structure, source-conflict stop and non-overwrite were checked.",
108
- "Codex/macOS v2 local-cache main run: three relative-capacity routes; scale-invariance, tie, sparse, missing, zero-base, invalid-policy and source safety tests passed. Clean-context regression not run.",
109
- "Self-contained SVG table/chart views and synthetic-DOM event tests passed; actual browser layout/interaction inspection remains unverified due local URL policy."
110
- ]
111
- }
112
- ]
113
- }
@@ -1,39 +0,0 @@
1
- {
2
- "schema": "portable-skill-adapter",
3
- "schema_version": "1.0.0",
4
- "platform": "sealseek",
5
- "mappings": [
6
- {
7
- "capability_id": "category.monthly.collect",
8
- "status": "implemented",
9
- "implementation": {"kind": "stable-cli", "discovery": "Resolve yccli from the SealSeek host PATH; check --version, capabilities --json and the current account session before collecting monthly JSON."},
10
- "features": ["yccli>=0.5.0", "first_category_monthly", "second_category_monthly", "third_category_monthly", "json_output", "login_and_verification_stop"],
11
- "permissions": ["read authorized Yuce account", "create new task-scoped JSON files"],
12
- "normalization": "Return CLI receipt, actual months, category path and new JSON path. The CLI commands were inspected on the shared macOS host; a clean SealSeek task run has not been tested."
13
- },
14
- {
15
- "capability_id": "category.warehouse.sync",
16
- "status": "implemented",
17
- "implementation": {"kind": "stable-cli", "discovery": "Resolve tbcli from the SealSeek host PATH; check capabilities for db yuce coverage/export and yuce categories validate/import at runtime."},
18
- "features": ["tbcli_db_yuce_coverage", "tbcli_db_yuce_export", "tbcli_yuce_categories_validate", "tbcli_yuce_categories_import", "read_role_check", "rolled_back_write_probe", "additive_transaction"],
19
- "permissions": ["read authorized NAS warehouse tables", "insert only missing requested category-month rows after maintainer verification"],
20
- "normalization": "Return coverage, snapshot and import receipts. CLI source tests passed on the shared macOS host; a clean SealSeek task run and live coverage query have not been verified."
21
- },
22
- {
23
- "capability_id": "category.excel.cache",
24
- "status": "implemented",
25
- "implementation": {"kind": "bundled-script", "path": "scripts/build_report.py", "runtime": "Python 3 with openpyxl>=3.1"},
26
- "features": ["xlsx_input", "first_second_third_monthly", "full_category_paths", "source_hashes", "conflict_stop", "new_output_only", "data_only_cache"],
27
- "permissions": ["read selected local Excel or JSON files", "create one new local Excel cache"],
28
- "normalization": "Same CLI and data contract as Codex; clean SealSeek execution has not been tested."
29
- },
30
- {
31
- "capability_id": "report.html.render",
32
- "status": "implemented",
33
- "implementation": {"kind": "bundled-script", "path": "scripts/build_report.py", "template": "assets/report-template.html", "runtime": "Python 3 standard library"},
34
- "features": ["yuce-category-opportunity@2.0", "relative_capacity_bands", "three_independent_routes", "table_chart_dual_views", "raw_chart_evidence", "filter_sort_chart_sync", "local_xlsx_cache_input", "bundled_template", "responsive_views", "print_all_views", "escaped_text", "new_output_only", "analysis_sidecar"],
35
- "permissions": ["read selected JSON evidence", "write new report and analysis files"],
36
- "normalization": "Use the same CLI arguments and output JSON; runtime and browser QA have not been verified on SealSeek."
37
- }
38
- ]
39
- }
@@ -1,3 +0,0 @@
1
- interface:
2
- display_name: "预策一级类目机会研究"
3
- short_description: "用预策Excel或NAS数据生成24个月类目研究报告"
@@ -1,80 +0,0 @@
1
- (function(global) {
2
- 'use strict';
3
- const colors=['#315bd6','#15936f','#cf7225','#8653c3','#bd466b','#378a9d'];
4
- const escape=value=>String(value).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
5
- const finite=value=>typeof value==='number'&&Number.isFinite(value);
6
- function bounds(values){const good=values.filter(finite);if(!good.length)return null;let min=Math.min(0,...good),max=Math.max(0,...good);if(min===max)max=1;return {min,max};}
7
- function project(spec,indices,groupIndex,enabled,page=0){
8
- const group=spec.groups[groupIndex],size=spec.kind==='line'?Math.max(indices.length,1):20;
9
- const pages=Math.max(1,Math.ceil(indices.length/size)),actual=Math.max(0,Math.min(page,pages-1));
10
- const selected=indices.slice(actual*size,(actual+1)*size);
11
- return {group,indices:selected,labels:selected.map(i=>spec.labels[i]),page:actual,pages,total:indices.length,
12
- start:indices.length?actual*size+1:0,end:Math.min((actual+1)*size,indices.length),
13
- series:group.series.map((s,i)=>({name:s.name,color:colors[i%colors.length],values:selected.map(row=>s.values[row])})).filter((_,i)=>enabled[i])};
14
- }
15
- function exact(value,unit){return finite(value)?value.toLocaleString('zh-CN',{maximumFractionDigits:20})+(unit==='%'?'(比率原值;乘100为百分数)':unit):'缺失';}
16
- function svgChart(kind,data){
17
- const {group,labels,series}=data,values=series.flatMap(s=>s.values),domain=bounds(values);
18
- if(!labels.length)return '<p class="dual-empty">当前筛选没有数据。</p>';
19
- if(!series.length)return '<p class="dual-empty">请至少勾选一个数据系列。</p>';
20
- if(!domain)return '<p class="dual-empty">所选指标在这些行中没有可用数值;缺失值未按零绘制。</p>';
21
- const magnitude=Math.max(Math.abs(domain.min),Math.abs(domain.max));
22
- const factor=group.unit==='元'?(magnitude>=1e8?1e8:magnitude>=1e4?1e4:1):group.unit==='%'?.01:1;
23
- const axisUnit=group.unit==='元'?(factor===1e8?'亿元':factor===1e4?'万元':'元'):group.unit;
24
- const tick=v=>(v/factor).toLocaleString('zh-CN',{maximumFractionDigits:2});
25
- const tip=(label,s,value)=>`${label} · ${s.name}:${group.unit==='%'&&finite(value)?(value*100).toLocaleString('zh-CN',{maximumFractionDigits:12})+'%;原始比率 '+value:exact(value,group.unit)}`;
26
- const width=kind==='line'?Math.max(800,labels.length*48):1020;
27
- const left=kind==='line'?85:250,right=35,top=42,bottom=kind==='line'?82:50;
28
- const rowHeight=Math.max(40,series.length*17+12),height=kind==='line'?380:top+bottom+labels.length*rowHeight;
29
- const plotW=width-left-right,plotH=height-top-bottom,span=domain.max-domain.min;
30
- let out=`<svg xmlns="http://www.w3.org/2000/svg" role="img" aria-label="${escape(group.label)}${kind==='line'?'折线图':'柱状图'}" viewBox="0 0 ${width} ${height}" style="min-width:${kind==='line'?720:800}px;width:100%"><title>${escape(group.label)},单位${escape(axisUnit)};第${data.start}至${data.end}行,共${data.total}行</title><text x="12" y="22" class="dual-axis">${escape(group.label)}(${escape(axisUnit)})</text>`;
31
- if(kind==='line'){
32
- const x=i=>left+(labels.length===1?plotW/2:i*plotW/(labels.length-1));
33
- const y=v=>top+(domain.max-v)/span*plotH;
34
- for(let k=0;k<=4;k++){const v=domain.min+span*k/4,py=y(v);out+=`<line x1="${left}" y1="${py}" x2="${width-right}" y2="${py}" class="dual-grid"/><text x="${left-9}" y="${py+4}" text-anchor="end" class="dual-axis">${tick(v)}</text>`;}
35
- labels.forEach((label,i)=>{out+=`<text x="${x(i)}" y="${height-bottom+22}" transform="rotate(40 ${x(i)} ${height-bottom+22})" class="dual-axis"><title>${escape(label)}</title>${escape(label)}</text>`;});
36
- for(const s of series){let points=[];const flush=()=>{if(points.length>1)out+=`<polyline fill="none" stroke="${s.color}" stroke-width="2.5" points="${points.join(' ')}"/>`;points=[];};
37
- s.values.forEach((v,i)=>{if(!finite(v)){flush();return;}points.push(`${x(i)},${y(v)}`);});flush();
38
- s.values.forEach((v,i)=>{if(finite(v))out+=`<circle tabindex="0" role="img" aria-label="${escape(tip(labels[i],s,v))}" cx="${x(i)}" cy="${y(v)}" r="4" fill="${s.color}"><title>${escape(tip(labels[i],s,v))}</title></circle>`;});}
39
- }else{
40
- const x=v=>left+(v-domain.min)/span*plotW,zero=x(0);
41
- for(let k=0;k<=4;k++){const v=domain.min+span*k/4,px=x(v);out+=`<line x1="${px}" y1="${top}" x2="${px}" y2="${height-bottom}" class="dual-grid"/><text x="${px}" y="${height-19}" text-anchor="middle" class="dual-axis">${tick(v)}</text>`;}
42
- out+=`<line x1="${zero}" y1="${top}" x2="${zero}" y2="${height-bottom}" stroke="#52627b"/>`;
43
- labels.forEach((label,i)=>{const cy=top+i*rowHeight+rowHeight/2,short=String(label).split(' > ').at(-1);out+=`<text x="12" y="${cy+4}" class="dual-axis"><title>${escape(label)}</title>${escape(short.length>23?short.slice(0,22)+'…':short)}</text>`;
44
- series.forEach((s,j)=>{const v=s.values[i],py=cy-series.length*8.5+j*17;if(!finite(v)){out+=`<text x="${zero+4}" y="${py+11}" class="dual-axis">缺失</text>`;return;}
45
- out+=`<rect tabindex="0" role="img" aria-label="${escape(tip(label,s,v))}" x="${Math.min(zero,x(v))}" y="${py}" width="${Math.max(Math.abs(x(v)-zero),1)}" height="13" rx="2" fill="${s.color}"><title>${escape(tip(label,s,v))}</title></rect>`;});});
46
- }
47
- return out+'</svg>';
48
- }
49
- const api={project,bounds,svgChart};
50
- if(typeof module!=='undefined'&&module.exports){module.exports=api;return;}
51
- const document=global.document,controllers=[];
52
- function node(tag,cls,text){const e=document.createElement(tag);if(cls)e.className=cls;if(text!==undefined)e.textContent=text;return e;}
53
- document.querySelectorAll('table[data-chart-spec]').forEach((table,number)=>{
54
- const spec=JSON.parse(table.dataset.chartSpec),originalRows=[...table.tBodies[0].rows];
55
- if(originalRows.length!==spec.labels.length)throw new Error('图表行数与表格不匹配');
56
- const rowIndex=new Map(originalRows.map((row,i)=>[row,i])),wrap=table.parentElement,root=node('div','dual-view'),toolbar=node('div','dual-toolbar');
57
- wrap.parentElement.insertBefore(root,wrap);root.append(toolbar,wrap);wrap.classList.add('dual-table');wrap.id=`dual-table-${number}`;
58
- const chart=node('div','dual-chart');chart.id=`dual-chart-${number}`;chart.hidden=true;root.append(chart);
59
- const tableButton=node('button','','表格'),chartButton=node('button','','图表');
60
- for(const [b,target] of [[tableButton,wrap],[chartButton,chart]]){b.type='button';b.setAttribute('aria-controls',target.id);toolbar.append(b);}
61
- tableButton.setAttribute('aria-pressed','true');chartButton.setAttribute('aria-pressed','false');
62
- const controls=node('div','dual-chart-controls'),label=node('label','','指标 '),select=node('select');select.setAttribute('aria-label','图表指标');
63
- spec.groups.forEach((g,i)=>{const option=node('option','',`${g.label}(${g.unit})`);option.value=String(i);select.append(option);});label.append(select);controls.append(label);chart.append(controls);
64
- const legend=node('div','dual-legend'),note=node('p','dual-note'),canvas=node('div','dual-canvas'),pager=node('div','dual-pager');note.setAttribute('aria-live','polite');chart.append(legend,note,canvas,pager);
65
- const prev=node('button','','上一页'),next=node('button','','下一页');prev.type=next.type='button';pager.append(prev,next);
66
- let groupIndex=0,enabled=spec.groups[0].series.map(()=>true),page=0,signature='';
67
- function draw(){const indices=[...table.tBodies[0].rows].filter(row=>!row.hidden).map(row=>rowIndex.get(row));const sig=indices.join(',');if(sig!==signature){page=0;signature=sig;}
68
- const data=project(spec,indices,groupIndex,enabled,page);page=data.page;
69
- canvas.innerHTML=svgChart(spec.kind,data);note.textContent=`显示第 ${data.start}—${data.end} 行,共 ${data.total} 行;与表格筛选及顺序一致。悬停或聚焦图形查看精确数值,缺失值不作零。`;
70
- pager.hidden=data.pages===1;prev.disabled=page===0;next.disabled=page>=data.pages-1;
71
- }
72
- function buildLegend(){legend.replaceChildren();spec.groups[groupIndex].series.forEach((s,i)=>{const l=node('label'),input=node('input'),swatch=node('span','dual-swatch');input.type='checkbox';input.checked=enabled[i];swatch.style.background=colors[i%colors.length];l.append(input,swatch,document.createTextNode(s.name));legend.append(l);input.addEventListener('change',()=>{enabled[i]=input.checked;draw();});});}
73
- function setView(isChart){wrap.hidden=isChart;chart.hidden=!isChart;tableButton.setAttribute('aria-pressed',String(!isChart));chartButton.setAttribute('aria-pressed',String(isChart));if(isChart)draw();}
74
- tableButton.addEventListener('click',()=>setView(false));chartButton.addEventListener('click',()=>setView(true));
75
- select.addEventListener('change',()=>{groupIndex=Number(select.value);enabled=spec.groups[groupIndex].series.map(()=>true);page=0;buildLegend();draw();});
76
- prev.addEventListener('click',()=>{page--;draw();});next.addEventListener('click',()=>{page++;draw();});buildLegend();
77
- controllers.push({refresh:()=>{if(!chart.hidden)draw();},table,chart,setView});
78
- });
79
- global.yuceCharts={...api,refresh:()=>controllers.forEach(c=>c.refresh()),count:controllers.length};
80
- })(typeof window!=='undefined'?window:globalThis);
@@ -1 +0,0 @@
1
- {"type":"commonjs"}
@@ -1,62 +0,0 @@
1
- <!doctype html>
2
- <html lang="zh-CN">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width,initial-scale=1">
6
- <meta name="color-scheme" content="light">
7
- <meta name="report-contract" content="yuce-category-opportunity@2.0">
8
- <link rel="icon" href="data:,">
9
- <title>__TITLE__</title>
10
- <style>
11
- :root{--primary:#315bd6;--primary-soft:#eaf0ff;--page:#f3f5f7;--panel:#fff;--text:#20242c;--muted:#697386;--line:#dfe4ea;--positive:#16845b;--positive-soft:#eaf8f2;--warning:#9a6500;--warning-soft:#fff7df;--danger:#bd3e3e;--danger-soft:#fff0f0;--sidebar:236px;font-family:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;font-size:15px;line-height:1.58;color:var(--text);background:var(--page);font-variant-numeric:tabular-nums}
12
- *{box-sizing:border-box}body{margin:0;background:var(--page)}button,input{font:inherit}button{cursor:pointer}[hidden]{display:none!important}:focus-visible{outline:2px solid var(--primary);outline-offset:2px}
13
- .sidebar{position:fixed;inset:0 auto 0 0;width:var(--sidebar);background:#fafbfc;border-right:1px solid var(--line);display:flex;flex-direction:column;z-index:20}.brand{min-height:72px;padding:14px 17px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:10px}.brandmark{width:36px;height:36px;flex:none;border-radius:9px;background:var(--primary);color:#fff;display:grid;place-items:center;font-weight:800}.brand b,.brand small{display:block}.brand b{font-size:15px}.brand small{font-size:12px;color:var(--muted)}.nav{padding:16px 10px;display:grid;gap:5px}.nav button{width:100%;min-height:40px;border:0;border-radius:7px;padding:8px 10px;display:flex;align-items:center;gap:10px;background:transparent;color:#596274;text-align:left;font-size:13px}.nav button:hover{background:#eef2f7}.nav button.active{background:var(--primary-soft);color:var(--primary);font-weight:750}.nav-dot{width:7px;height:7px;flex:none;border-radius:2px;background:currentColor}.side-note{margin:auto 13px 17px;padding-top:12px;border-top:1px solid var(--line);color:var(--muted);font-size:12px}
14
- .app{margin-left:var(--sidebar);min-height:100vh}.topbar{position:sticky;top:0;z-index:10;min-height:72px;padding:13px 23px;display:flex;align-items:center;justify-content:space-between;gap:14px;background:rgba(255,255,255,.97);border-bottom:1px solid var(--line);backdrop-filter:blur(8px)}.identity b,.identity span{display:block}.identity b{font-size:15px}.identity span{font-size:12px;color:var(--muted)}.tools{display:flex;align-items:center;gap:8px}.search{height:38px;min-width:220px;padding:0 11px;border:1px solid var(--line);border-radius:8px;background:#fafbfc}.date-chip{padding:8px 11px;border:1px solid var(--line);border-radius:8px;color:#596274;font-size:12px;white-space:nowrap}.menu-toggle{display:none;width:38px;height:38px;border:1px solid var(--line);border-radius:8px;background:#fff}.reading-path{position:sticky;top:72px;z-index:9;padding:9px 22px;display:flex;gap:10px;overflow:auto;white-space:nowrap;background:rgba(250,251,252,.98);border-bottom:1px solid var(--line)}.reading-path span{font-size:12px;color:#596274}.reading-path b{color:var(--primary)}
15
- main{max-width:1450px;margin:0 auto;padding:22px}.view{outline:none}.page-heading{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:14px}.eyebrow{font-size:12px;font-weight:750;color:var(--primary)}.page-heading h1{margin:3px 0 0;font-size:25px;line-height:1.26}.page-heading p{margin:6px 0 0;color:var(--muted);font-size:13px}.tag{display:inline-flex;align-items:center;padding:4px 9px;border-radius:999px;background:var(--primary-soft);color:var(--primary);font-size:12px;font-weight:700;white-space:nowrap}.card{min-width:0;margin-bottom:10px;background:var(--panel);border:1px solid var(--line);border-radius:9px;box-shadow:0 1px 2px rgba(25,34,49,.03)}.card-head{min-height:48px;padding:10px 14px;border-bottom:1px solid #edf0f3;display:flex;align-items:center;justify-content:space-between;gap:10px}.card-head h2{margin:0;font-size:15px}.card-body{padding:14px}.muted{color:var(--muted)}
16
- .conclusion-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:10px;padding:13px}.conclusion-item{padding:14px;min-width:0;border:1px solid var(--line);border-top:3px solid var(--primary);border-radius:8px;overflow-wrap:anywhere}.conclusion-item.positive{border-top-color:var(--positive)}.conclusion-item.warning{border-top-color:var(--warning)}.conclusion-item h3{margin:0 0 8px;color:var(--primary);font-size:13px;font-weight:800}.conclusion-item.positive h3{color:var(--positive)}.conclusion-item.warning h3{color:var(--warning)}.conclusion-item p{margin:0;font-size:16px;line-height:1.52}.conclusion-item small{display:block;margin-top:8px;color:var(--muted);font-size:12px}.strong{font-weight:800}.italic{font-style:italic}.positive-text{color:var(--positive)}.warning-text{color:var(--warning)}.info-text{color:var(--primary)}
17
- .metrics{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin-bottom:10px}.metric{padding:14px}.metric span{display:block;color:var(--muted);font-size:12px}.metric b{display:block;margin:6px 0 3px;font-size:25px;line-height:1.2}.metric small{font-size:12px;color:var(--muted)}.detail-metrics{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;margin:12px 0}.detail-metrics div{padding:10px;border-radius:7px;background:#f7f9fc}.detail-metrics span,.detail-metrics small{display:block;font-size:11px;color:var(--muted)}.detail-metrics b{display:block;font-size:19px;line-height:1.3}.candidate-detail h3{font-size:13px;margin:13px 0 5px}.candidate-detail .card{margin:0;border-left:0;border-right:0;border-bottom:0;border-radius:0;box-shadow:none}.two-col{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.prose{margin:0;font-size:13px;color:#4f596a}.prose+.prose{margin-top:8px}.list{margin:0;padding-left:20px;font-size:13px;color:#4f596a}.list li+li{margin-top:7px}.table-wrap{overflow:auto}.data-table{width:100%;border-collapse:collapse;font-size:12px;white-space:nowrap}.data-table th{position:sticky;top:0;background:#f7f8fa;color:#596274;text-align:left;font-weight:750}.data-table th,.data-table td{padding:9px 11px;border-bottom:1px solid #edf0f3}.data-table tbody tr:nth-child(even){background:#fbfcfd}.data-table td.num{text-align:right;font-variant-numeric:tabular-nums}.data-table .em{font-weight:750;color:var(--primary)}.footer{padding:12px 2px 20px;color:var(--muted);font-size:12px}.notice{padding:12px 14px;background:var(--warning-soft);border-left:3px solid var(--warning);border-radius:0 7px 7px 0;font-size:13px;color:#72531a}
18
- @media(max-width:1100px){.metrics,.detail-metrics{grid-template-columns:repeat(2,minmax(0,1fr))}.two-col{grid-template-columns:1fr}}@media(max-width:760px){.sidebar{transform:translateX(-100%);transition:transform .2s}.sidebar.open{transform:none;box-shadow:0 0 0 100vmax rgba(0,0,0,.22)}.app{margin-left:0}.topbar{padding:11px 13px}.menu-toggle{display:block}.search{min-width:0;width:145px}.date-chip{display:none}.reading-path{top:64px;padding:8px 13px}main{padding:15px}.page-heading h1{font-size:22px}.conclusion-grid{grid-template-columns:1fr}.metrics,.detail-metrics{grid-template-columns:repeat(2,minmax(0,1fr))}.metric b{font-size:21px}}@media(max-width:400px){.search{width:110px}.metrics,.detail-metrics{grid-template-columns:1fr 1fr}}
19
- @media print{.sidebar,.topbar,.reading-path{display:none!important}.app{margin:0}.view{display:block!important;break-before:page}.view:first-child{break-before:auto}.card,.metric,.conclusion-item{break-inside:avoid}main{max-width:none;padding:0}.data-table{white-space:normal;font-size:10px}.data-table th{position:static}body{background:#fff}}
20
- .nav-group{min-width:0}.nav-chevron{margin-left:auto;font-size:17px;transition:transform .2s}.nav-group.expanded .nav-chevron{transform:rotate(180deg)}.nav-submenu{padding:2px 0 7px 17px;border-left:1px solid #dce5f5;margin-left:13px}.nav .nav-child{min-height:34px;padding:6px 10px 6px 15px;font-size:12px;line-height:1.35;overflow-wrap:anywhere}.nav .nav-child::before{content:"";width:4px;height:4px;border-radius:50%;background:currentColor;flex:none}.nav .nav-child.active{font-weight:800}
21
- .insight-list{list-style:none;margin:0;padding:14px;display:grid;gap:10px}.insight-list li{display:flex;gap:12px;align-items:flex-start;padding:13px 14px;border:1px solid var(--line);border-left:3px solid var(--primary);border-radius:8px;background:#fbfcff;font-size:13px;line-height:1.7}.insight-list li:nth-child(2){border-left-color:var(--positive);background:#f8fcfa}.insight-list li:nth-child(3){border-left-color:var(--warning);background:#fffdf8}.insight-number{flex:none;display:grid;place-items:center;width:24px;height:24px;border-radius:50%;background:var(--primary-soft);color:var(--primary);font-weight:800}.insight-content{min-width:0;overflow-wrap:anywhere}.insight-content strong{font-weight:800}.candidate-detail>.insight-card{margin:0;border-left:0;border-right:0;border-radius:0;box-shadow:none}.candidate-detail>.insight-card .card-head{padding-left:14px}.candidate-detail .verification{border-top:1px solid var(--line)}.candidate-detail .verification h3{margin-top:0}
22
- .table-controls{padding:11px 14px;display:flex;gap:10px;flex-wrap:wrap;align-items:end;border-bottom:1px solid var(--line)}.table-controls label{display:grid;gap:3px;color:var(--muted);font-size:12px}.table-controls select,.table-controls input{height:34px;min-width:150px;padding:0 8px;border:1px solid var(--line);border-radius:6px;background:#fff;color:var(--text)}.sortable-table th button{border:0;background:transparent;color:inherit;font:inherit;font-weight:750;white-space:nowrap;padding:0;cursor:pointer}.sortable-table th button:hover,.sortable-table th[aria-sort] button{color:var(--primary)}.sortable-table th button span{color:#8c98aa}.sortable-table tbody tr[hidden]{display:none}.sortable-table td:nth-child(9){font-weight:750;color:var(--primary)}
23
- @media(max-width:760px){.insight-list{padding:10px}.insight-list li{padding:10px}.table-controls label{flex:1 1 145px}.table-controls select,.table-controls input{width:100%;min-width:0}}
24
- @media print{.nav-submenu{display:none}.table-controls{display:none}.sortable-table tbody tr[hidden]{display:table-row!important}}
25
- .read-hero{padding:28px 30px;background:#10284b;color:#fff;border-radius:14px;margin-bottom:14px}.read-overline{color:#a7c9ff;font-size:12px;font-weight:800;letter-spacing:.06em}.read-hero h1{font-size:34px;line-height:1.22;margin:9px 0 10px}.read-hero p{max-width:790px;color:#d3dff0;font-size:14px;margin:0}.read-hero-actions{display:flex;flex-wrap:wrap;gap:9px;margin-top:21px}.read-hero-actions button{padding:8px 13px;border:1px solid #6e9fd0;border-radius:7px;background:#f5faff;color:#15345b;font-weight:750}.read-hero-actions button+button{background:transparent;color:#e5efff}
26
- .read-kpi-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin-bottom:28px}.read-kpi{background:#fff;border:1px solid #dfe6ee;border-radius:10px;padding:15px 17px}.read-kpi span,.read-kpi small{display:block;color:#667588;font-size:12px}.read-kpi strong{display:block;font-size:27px;line-height:1.2;margin:5px 0;color:#183b68}.read-kpi small{font-size:12px}
27
- .read-section{margin:0 0 29px}.read-section-heading{margin:0 0 13px}.read-section-heading span{display:block;color:#2c5dab;font-size:12px;font-weight:800}.read-section-heading h2{font-size:22px;margin:3px 0;line-height:1.3}.read-section-heading p{font-size:13px;color:#657287;margin:0}.read-findings{display:grid;gap:10px}.read-findings article{display:flex;gap:15px;padding:17px 19px;background:#fff;border:1px solid #dfe6ee;border-left:4px solid #2780c0;border-radius:10px}.read-findings article:nth-child(2){border-left-color:#15936f}.read-findings article:nth-child(3){border-left-color:#c5861d}.read-number{width:28px;height:28px;border-radius:50%;background:#e9f2ff;color:#245cac;flex:none;display:grid;place-items:center;font-weight:800}.read-findings h3{margin:0 0 5px;font-size:16px}.read-findings p{margin:0;color:#506077;font-size:13px;line-height:1.65}.read-findings p strong{color:#163d73}
28
- .read-table-wrap{overflow:auto;border:1px solid #dfe6ee;border-radius:10px;background:#fff}.read-compare{width:100%;border-collapse:collapse;min-width:940px;font-size:12px}.read-compare th{padding:11px 10px;text-align:left;color:#607088;background:#f3f6fa;white-space:nowrap}.read-compare td{padding:11px 10px;border-top:1px solid #edf0f3;white-space:nowrap}.read-compare tbody tr:hover{background:#f8fbff}.read-rank{font-weight:800;color:#7486a1}.read-compare td strong{color:#235cc5}.read-row-link{border:0;background:none;padding:0;color:#204f9d;font-size:12px;font-weight:800;text-align:left}.read-row-link span{padding-left:4px}.read-row-note{white-space:normal!important;min-width:265px;color:#59687c}.read-caption{font-size:12px;color:#68788c;margin:10px 2px 0}
29
- .read-method,.read-next{background:#fff;border:1px solid #dfe6ee;border-radius:10px;padding:17px 19px}.read-steps{display:flex;align-items:center;gap:15px;margin:13px 0}.read-steps>div{display:grid;gap:1px;flex:1;background:#f2f6fb;border-radius:8px;padding:11px 13px}.read-steps b{font-size:24px;color:#205ea8}.read-steps span{font-size:12px;color:#627087}.read-step-arrow{flex:none!important;font-size:20px!important;color:#8295ad!important}.read-method p{font-size:13px;color:#506077;line-height:1.65}.read-method details{padding:11px 13px;border-radius:7px;background:#f8fafc;font-size:12px}.read-method summary{cursor:pointer;font-weight:750;color:#2c5dab}.read-method details p{margin:8px 0 0;font-size:12px}.read-next-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.read-next-grid article{padding:13px;border:1px solid #e1e7ee;border-radius:8px}.read-next-grid b{color:#234f91}.read-next-grid p{font-size:12px;color:#5d6c80;margin:6px 0 0;line-height:1.6}.read-endnote{background:#edf7f3;border-left:3px solid #15936f;border-radius:5px;padding:11px 13px;margin-top:13px;font-size:13px;color:#255b4a}.read-endnote strong{color:#126446}
30
- @media(max-width:800px){.read-kpi-grid,.read-next-grid{grid-template-columns:1fr}.read-hero{padding:22px}.read-hero h1{font-size:27px}.read-steps{gap:5px}.read-steps>div{padding:8px}.read-steps b{font-size:20px}.read-findings article{padding:13px}}@media print{.read-hero{background:#10284b!important;-webkit-print-color-adjust:exact;print-color-adjust:exact}.read-kpi-grid,.read-findings article,.read-method,.read-next{break-inside:avoid}.read-method details>p{display:block!important}}
31
- .sidebar{height:100dvh;min-height:0;overflow:hidden}.brand,.side-note{flex-shrink:0}.nav{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;align-content:start;overscroll-behavior:contain;scrollbar-gutter:stable}.side-note{margin:0 13px 17px}.nav-group-header{display:flex;align-items:center;min-width:0}.nav-group-header>[data-nav-parent]{flex:1;min-width:0}.nav .nav-disclosure{width:32px;min-width:32px;flex:none;justify-content:center;padding:8px 4px}.nav-disclosure .nav-chevron{margin:0}.nav button:focus-visible{outline:2px solid var(--primary);outline-offset:-2px}.menu-toggle{display:block}.sidebar-hidden .sidebar{transform:translateX(-100%)}.sidebar-hidden .app{margin-left:0}
32
- .dual-view{min-width:0}.dual-toolbar,.dual-chart-controls,.dual-legend,.dual-pager{display:flex;flex-wrap:wrap;align-items:center;gap:10px;padding:10px 12px}.dual-toolbar{border-bottom:1px solid var(--line);background:#f8fafc}.dual-toolbar button,.dual-pager button{border:1px solid var(--line);background:white;border-radius:6px;padding:6px 14px;color:var(--text)}.dual-toolbar button[aria-pressed="true"]{background:var(--primary);color:white;border-color:var(--primary);font-weight:750}.dual-pager button:disabled{opacity:.4;cursor:default}.dual-chart-controls select{max-width:100%;padding:6px;border:1px solid var(--line);border-radius:6px;background:white}.dual-chart-controls label{min-width:0;font-size:13px}.dual-legend{padding-top:0;font-size:12px}.dual-legend label{display:flex;align-items:center;gap:5px;cursor:pointer}.dual-swatch{width:12px;height:12px;border-radius:3px;display:inline-block}.dual-note{margin:0;padding:0 12px 10px;color:var(--muted);font-size:12px}.dual-canvas{max-width:100%;overflow:auto;padding:6px 10px;background:white}.dual-axis{font-size:12px;fill:#536177}.dual-grid{stroke:#e3e8ef;stroke-width:1}.dual-empty{padding:25px;color:var(--muted)}.dual-chart svg{display:block}.dual-chart svg [tabindex]:focus{outline:2px solid #111;outline-offset:2px}
33
- @media print{.dual-toolbar,.dual-chart-controls,.dual-legend input,.dual-pager{display:none!important}.dual-table[hidden]{display:block!important}.dual-canvas{overflow:visible}.dual-chart svg{min-width:0!important;max-width:100%}.dual-view{break-inside:auto}}
34
- </style>
35
- </head>
36
- <body>
37
- <aside class="sidebar" id="sidebar"><div class="brand"><div class="brandmark">研</div><div><b>类目机会研究</b><small>预策月度数据 · 一级至三级</small></div></div><nav class="nav" aria-label="报告目录">__NAV__</nav><div class="side-note">__SIDE_NOTE__</div></aside>
38
- <div class="app"><header class="topbar"><div class="tools"><button class="menu-toggle" id="menuToggle" aria-label="打开目录">☰</button><div class="identity"><b>__CATEGORY__</b><span>类目研究优先级报告</span></div></div><div class="tools"><input class="search" id="search" placeholder="搜索当前页" aria-label="搜索当前页"><div class="date-chip">__PERIOD__</div></div></header><div class="reading-path"><b>阅读顺序</b><span>先看判断 → 首批方向 → 选择方法 → 逐月与全量证据</span></div><main>__CONTENT__<div class="footer">__FOOTER__</div></main></div>
39
- <script>
40
- const views=[...document.querySelectorAll('[data-view]')],buttons=[...document.querySelectorAll('[data-view-target]')];
41
- const group=document.getElementById('candidateNavGroup'),submenu=document.getElementById('candidateSubmenu');
42
- let submenuOpened=false;
43
- const disclosure=document.getElementById('candidateToggle'),menuToggle=document.getElementById('menuToggle'),sidebar=document.getElementById('sidebar');
44
- function setSubmenu(open){submenuOpened=open;submenu.hidden=!open;group.classList.toggle('expanded',open);disclosure.setAttribute('aria-expanded',String(open));disclosure.setAttribute('aria-label',open?'收起研究路线目录':'展开研究路线目录')}
45
- disclosure.addEventListener('click',()=>setSubmenu(!submenuOpened));
46
- function syncMenu(){const visible=window.matchMedia('(max-width:760px)').matches?sidebar.classList.contains('open'):!document.body.classList.contains('sidebar-hidden');menuToggle.setAttribute('aria-expanded',String(visible));menuToggle.setAttribute('aria-controls','sidebar');menuToggle.setAttribute('aria-label',visible?'收起目录':'展开目录')}
47
- function showView(id,{writeHash=true}={}){const target=views.some(v=>v.dataset.view===id)?id:views[0].dataset.view;views.forEach(v=>{v.hidden=v.dataset.view!==target});buttons.forEach(b=>{const active=b.dataset.viewTarget===target;b.classList.toggle('active',active);b.setAttribute('aria-current',active?'page':'false')});if(target==='candidates'||target.startsWith('candidate-'))setSubmenu(true);else if(submenuOpened)setSubmenu(false);if(writeHash&&location.hash.slice(1)!==target)history.pushState(null,'','#'+target);document.getElementById('sidebar').classList.remove('open');syncMenu();window.scrollTo(0,0)}
48
- buttons.forEach(b=>b.addEventListener('click',()=>showView(b.dataset.viewTarget)));window.addEventListener('popstate',()=>showView(location.hash.slice(1),{writeHash:false}));showView(location.hash.slice(1),{writeHash:false});
49
- menuToggle.addEventListener('click',()=>{if(window.matchMedia('(max-width:760px)').matches){document.body.classList.remove('sidebar-hidden');sidebar.classList.toggle('open')}else{document.body.classList.toggle('sidebar-hidden');sidebar.classList.remove('open')}syncMenu()});window.addEventListener('resize',syncMenu);document.addEventListener('keydown',event=>{if(event.key==='Escape'){sidebar.classList.remove('open');syncMenu()}});syncMenu();
50
- const allTable=document.getElementById('allCategoryTable'),allRows=[...allTable.tBodies[0].rows];
51
- const secondFilter=document.getElementById('allSecondFilter'),statusFilter=document.getElementById('allStatusFilter'),textFilter=document.getElementById('allTextFilter'),routeFilter=document.getElementById('allRouteFilter');
52
- function filterAll(){const global=document.getElementById('search').value.trim().toLowerCase(),local=textFilter.value.trim().toLowerCase();let count=0;allRows.forEach(row=>{const visible=(!secondFilter.value||row.dataset.second===secondFilter.value)&&(!statusFilter.value||row.dataset.status===statusFilter.value)&&(!routeFilter.value||row.dataset.route===routeFilter.value)&&(!local||row.textContent.toLowerCase().includes(local))&&(!global||row.textContent.toLowerCase().includes(global));row.hidden=!visible;if(visible)count++});document.getElementById('allTableCount').textContent=count+' / '+allRows.length+' 行';window.yuceCharts?.refresh()}
53
- [secondFilter,statusFilter,textFilter,routeFilter].forEach(input=>input.addEventListener('input',filterAll));
54
- let sortColumn=8,sortDirection=-1;
55
- function sortAll(column){const isText=[0,1,9,10,16].includes(column);sortDirection=column===sortColumn?-sortDirection:(isText||column===13?1:-1);sortColumn=column;allRows.sort((a,b)=>{const av=a.cells[column].dataset.sort,bv=b.cells[column].dataset.sort;if(av===''||bv==='')return av===''&&bv===''?0:av===''?1:-1;const result=isText?av.localeCompare(bv,'zh-CN'):Number(av)-Number(bv);return result*sortDirection});allRows.forEach(row=>allTable.tBodies[0].appendChild(row));allTable.querySelectorAll('th').forEach((th,index)=>{if(index===column)th.setAttribute('aria-sort',sortDirection===1?'ascending':'descending');else th.removeAttribute('aria-sort')});filterAll()}
56
- allTable.querySelectorAll('[data-sort-column]').forEach(button=>button.addEventListener('click',()=>sortAll(Number(button.dataset.sortColumn))));
57
- document.getElementById('search').addEventListener('input',e=>{const term=e.target.value.trim().toLowerCase();document.querySelectorAll('[data-view]:not([hidden]) .searchable').forEach(n=>n.hidden=!!term&&!n.textContent.toLowerCase().includes(term));filterAll()});
58
- window.yuceReport={contract:'yuce-category-opportunity@2.0',showView,visibleViews:()=>views.filter(v=>!v.hidden).map(v=>v.dataset.view),filterAll,sortAll};
59
- </script>
60
- <script>__CHART_SCRIPT__</script>
61
- </body>
62
- </html>
@@ -1,48 +0,0 @@
1
- {
2
- "schema": "portable-skill-capabilities",
3
- "schema_version": "1.0.0",
4
- "skill": "yuce-category-opportunity-report",
5
- "target_platforms": ["codex", "sealseek"],
6
- "capabilities": [
7
- {
8
- "id": "category.monthly.collect",
9
- "purpose": "Use the installed yccli to collect authorized first-, second-, and third-category monthly JSON for explicit periods.",
10
- "required": false,
11
- "required_features": ["yccli>=0.5.0", "first_category_monthly", "second_category_monthly", "third_category_monthly", "json_output", "login_and_verification_stop"],
12
- "input_fields": ["category", "second_category_optional", "start_month", "end_month", "new_output_path"],
13
- "output_fields": ["json_path", "actual_months", "category_path", "receipt"],
14
- "side_effects": {"external_mutation": true, "may_cost_money": false, "authorization": "Read data visible to the current Yuce account; create only new local JSON. If initial NAS read fails, obtain explicit user consent before collection."},
15
- "errors": ["CAPABILITY_UNAVAILABLE", "AUTH_REQUIRED", "PERIOD_UNAVAILABLE", "PROVIDER_FAILURE", "OUTPUT_CONTRACT_FAILED"]
16
- },
17
- {
18
- "id": "category.warehouse.sync",
19
- "purpose": "Use tbcli for path-level coverage, read-only monthly export, and authorized additive import.",
20
- "required": false,
21
- "required_features": ["tbcli_db_yuce_coverage", "tbcli_db_yuce_export", "tbcli_yuce_categories_validate", "tbcli_yuce_categories_import", "read_role_check", "rolled_back_write_probe", "additive_transaction"],
22
- "input_fields": ["category", "start_month", "end_month", "yccli_json_files", "new_snapshot_path"],
23
- "output_fields": ["coverage", "snapshot_path", "import_receipts", "warnings"],
24
- "side_effects": {"external_mutation": true, "may_cost_money": false, "authorization": "Warehouse read through existing role; default write scope is only missing rows for the requested category-months, after maintainer checks. Connection or write failure never authorizes credential or network changes."},
25
- "errors": ["CAPABILITY_UNAVAILABLE", "DATABASE_UNAVAILABLE_AWAITING_CONSENT", "PERMISSION_REQUIRED", "SOURCE_CONFLICT", "PROVIDER_FAILURE"]
26
- },
27
- {
28
- "id": "category.excel.cache",
29
- "purpose": "Normalize supplied Yuce monthly Excel or locally exported NAS/yccli data into one data-only, 24-month Excel cache before analysis.",
30
- "required": true,
31
- "required_features": ["xlsx_input", "first_second_third_monthly", "full_category_paths", "source_hashes", "conflict_stop", "new_output_only", "data_only_cache"],
32
- "input_fields": ["category", "start_month", "end_month", "excel_files_or_local_json", "new_cache_path"],
33
- "output_fields": ["cache_xlsx", "row_count", "category_count", "source_count", "quality_findings"],
34
- "side_effects": {"external_mutation": false, "may_cost_money": false, "authorization": "Read only selected local source files and create one new local Excel cache; Excel-input mode makes no database or platform calls."},
35
- "errors": ["CAPABILITY_UNAVAILABLE", "SOURCE_CONFLICT", "OUTPUT_CONTRACT_FAILED"]
36
- },
37
- {
38
- "id": "report.html.render",
39
- "purpose": "Analyze one local Excel cache and render a self-contained 24-month category-priority HTML report without a runtime HTML Skill dependency.",
40
- "required": true,
41
- "required_features": ["yuce-category-opportunity@2.0", "relative_capacity_bands", "three_independent_routes", "table_chart_dual_views", "raw_chart_evidence", "filter_sort_chart_sync", "local_xlsx_cache_input", "bundled_template", "responsive_views", "print_all_views", "escaped_text", "new_output_only", "analysis_sidecar"],
42
- "input_fields": ["category", "window", "local_cache_xlsx", "strategy_config_optional", "output_paths"],
43
- "output_fields": ["analysis_json", "html_path", "contract", "validation"],
44
- "side_effects": {"external_mutation": true, "may_cost_money": false, "authorization": "Read task evidence and create two new files in the selected output directory."},
45
- "errors": ["CAPABILITY_UNAVAILABLE", "SOURCE_CONFLICT", "OUTPUT_CONTRACT_FAILED"]
46
- }
47
- ]
48
- }
@@ -1,5 +0,0 @@
1
- # Knowledge Layer Schema
2
-
3
- This is a small, routed knowledge layer for the Step 5 category-opportunity judgment. At Step 5, read this file, `index.md`, recent `log.md`, the matching query entry, and its required topic page. Do not load this layer during database checks or collection. `source-manifest.md` is maintenance provenance and is not an ordinary runtime read.
4
-
5
- Source classes: `U` user-confirmed method, `W` compiled traceable domain knowledge, `E` verified execution evidence, `A` provisional Agent hypothesis.