name: zsxq-recommend
description: Recommend zsxq reports to read by scanning the most-recent rows of db/zsxq.db (titles + summaries — no PDF parsing). Default: latest 50 reports, focus on AI / robotics. User may override with a count ("latest 100") and/or a subject ("focus on semiconductors", "anything on EVs"). When the user has no clue, group the recent feed into themes and surface a handful of standout reads. Also persists any sell-side price-target (PT) calls found in the same summaries into db/stock_price_target.db (idempotent via UNIQUE(ticker, broker, file_id)), surfaced in the /pt viewer. Pair: hand a returned file_id to /zsxq-analyze for a deep read.
Recommend zsxq PDF
The user wants a curated pointer into the recent zsxq report feed —
not a deep read of any single PDF. Work only from
db/zsxq.db.pdf_files's metadata columns (title, summary, tags, etc.).
Do not extract or open PDFs. (If they want a deep dive on a specific
report, the zsxq-analyze skill handles that.)
When to use vs the siblings
/zsxq-recommend(this) — reading-list triage from titles + summaries; no PDF is opened./zsxq-ideas— "what should I buy" / "pitch me something" / "scan my feed for ideas" → an idea shortlist./zsxq-expert— an in-depth question or comparison grounded in the PDFs ("what do my reports say about X")./zsxq-analyze— deep read of one named PDF (file_id / filename).
Interpreter: run all project scripts with /opt/anaconda3/bin/python3
(per feedback_anaconda_python_db_scripts — bare python3 has failed
read-only DB opens and lacks deps like yfinance in some shells).
Workflow
1. Parse the request
Pull out three optional knobs from the user's prompt:
- Count — "latest 50" (default), "latest 100", "last week", etc.
Map to
--limit Nor--since YYYY-MM-DD. - Subject — explicit topic ("semiconductors", "EVs", "中东"), or none. If none, default focus = AI + robotics.
- Vibe — does the user know what they want, or are they fishing? Wording like "summarize for me", "anything interesting", "what should I read" → fishing mode (theme-cluster + 3-5 picks).
2. Pull recent rows
# Latest 50 (default)
python3 .claude/skills/zsxq-recommend/scripts/list_recent.py
# Latest 100
python3 .claude/skills/zsxq-recommend/scripts/list_recent.py --limit 100
# Coarse subject filter before Claude ranks (only when the user named one)
python3 .claude/skills/zsxq-recommend/scripts/list_recent.py \
--limit 100 --subject "semiconductor"
# Recency window
python3 .claude/skills/zsxq-recommend/scripts/list_recent.py \
--since 2026-05-01
Flags:
--limit N(default 50)--subject TEXT— case-insensitive LIKE on name/topic_title/summary/tags/comment. Only pass this when the user gave an explicit subject. Default AI/robotics focus is done by Claude in step 3, not by SQL — the booleanai_robotics_related/ai_related/robotics_relatedcolumns are sparsely populated, so don't rely on them as a hard filter.--since YYYY-MM-DD— only rows newer than this.--summary-chars N— truncate each summary (default 1500). Bump to 0 if the user wants very detailed picks, drop to ~500 for 100+ rows.
Output: JSON {count, generated_at, filters, rows:[…]}. Each row has
file_id, name, topic_title, summary, create_time, page_count, tickers, tags, comment, bank, ai_robotics_related, ai_related, robotics_related, semiconductor_related, energy_related, claude_rating, user_rating.
3. Rank and recommend (Claude does this in-context)
Read every row's topic_title + summary. Then:
If the user named a subject — pick the 5-10 most relevant reports and explain why each one fits. Ignore the rest. If only 1-2 truly match, say so honestly rather than padding.
If no subject (default = AI/robotics) — score each row on AI / robotics / adjacent (semis, infra, data, autonomy). Surface 5-10 top picks across these subthemes. Down-weight pure macro / general strategy unless tightly AI-linked.
If the user is fishing ("summarize for me", "what's interesting") — first cluster the recent feed into 3-6 themes ("AI capex / inference economics", "robotics + autonomy", "energy & power", "China consumer slowdown", "geopolitics", …) with a one-line gist each. Then pick 2-3 standout reads under each theme.
Ranking rubric (after relevance filtering, all three modes). Order
picks by: (1) bank-quality tier — GS / MS / JPM / UBS / Bernstein /
Nomura > other global > regional > unknown / #代找; (2) substance —
page_count 12–80 is the sweet spot; down-rank 1–3-page flash notes
unless the user asked for quick takes; (3) recency (create_time);
(4) claude_rating / user_rating when populated. A 4-page
regional-broker flyer must not outrank an 80-page GS deep-dive on
topical match alone. Name the tier in each "why read this" line.
4. Persist any PT calls into stock_price_target.db (free side-effect)
While reading the same summaries in step 3, the agent is already
inspecting the broker-call language. Whenever a row's summary contains
an explicit price-target call — patterns like 目标价 1300美元,
TP $450, 12个月目标价116港元, target price ¥7100,
reiterate Buy/Outperform/Overweight/Neutral/Underweight/Sell —
extract one record per (ticker × broker) pair into a JSON list and
pipe it to the shared persistence helper (this script is also
used by /zsxq-analyze):
python3 scripts/persist_pts.py <<'JSON'
[
{"ticker":"1109.HK","company_name":"China Resources Land",
"broker":"Goldman Sachs","rating":"Buy","pt":36.6,"ccy":"HKD",
"catalyst":"Tier-1 housing recovery; mall ops alpha",
"file_id":184152128158222},
{"ticker":"LLY","company_name":"Eli Lilly",
"broker":"Bernstein","rating":"Outperform","pt":1300,"ccy":"USD",
"catalyst":"LIBRETTO-432 Selpercatinib RET+ HR=0.17",
"file_id":184152151455852}
]
JSON
Full schema, rating/currency vocabulary, what to emit vs skip, and
idempotency rules are documented in
reference/pt_extraction.md —
read it once if you're unsure what counts as a PT call.
Recommend-specific notes (the bits that don't live in the shared doc):
- No
--replaceflag here — this skill works from the summary text, which is less reliable than the full-PDF deep read. If/zsxq-analyzelater writes a higher-fidelity row for the same (ticker, broker, file_id), it should win. DefaultINSERT OR IGNOREmeans "first wins";/zsxq-analyzepasses--replacelater to overwrite the summary-derived row. - The script's stdout is a JSON summary
{considered, inserted, duplicate, skipped, errored, total_in_db, rows}— surfaceinsertedandtotal_in_dbin the final reply, and userowsto honour the surfacing rule below. - Show the report-date price next to every PT you mention (mandatory).
A bare "GS Costco Buy, TP $1,159" is not actionable — the user needs
the price the stock traded at on the report's date and the implied
upside that price fixes.
persist_pts.pyreturns these per row in itsrowsarray (report_date_price,price_currency,upside_pct), so quote them:GS Costco Buy, TP $1,159 vs $1,030 @ 2026-05-28 → +12.5%. Never substitute today's spot for the report-date price; ifreport_date_priceis null, writereport-date price n/a. Seereference/pt_extraction.md§ "Surfacing rule". - Revision arrows & disagreement flags — the summary-level case of the
project's "Sell-side view evolution (卖方观点演变)" convention. Before
composing the reply, SELECT prior rows for each PT'd ticker — STRICTLY
read-only:
sqlite3.connect('file:db/stock_price_target.db?mode=ro', uri=True), tableprice_targets. If the same institute already has a different PT on record for the name, annotate that PT line in step 6 with a revision arrow —PT ↑ from 120 (2026-04). If the scanned feed (or the live rows) carries contradictory calls on the same name — opposite ratings, PTs >20% apart — flag the disagreement explicitly in the read list (one line per side, each dated with its institute); never average them into a fake consensus. - If the summary contains zero PT calls (pure macro, strategy notes, data dashboards), skip step 4 entirely — no harm, no pipe.
5. Show research-coverage gap
After step 4 finishes (even if it inserted nothing), run the
missing-coverage helper and append its markdown table to the reply.
This surfaces every PT-mentioned ticker that does not yet have a
reports/company/<…>/ folder — sorted by market cap descending — so
the user can decide where to spend their next /company-research slot:
python3 scripts/missing_coverage.py --markdown --limit 25
Flags:
--limit N— cap to top N by market cap (default 0 = no cap; the skill calls it with--limit 25to keep the chat tail readable).--markdown— emit a github-markdown table (drop the flag for the shell-friendly text table).--regions US,HK,CN— comma list of regions to include. Default isUS,HK,CNwhich matches the user's typical next-research priority; widen toUS,HK,CN,TW,JP,KR,IN,EUwhen the user explicitly says "all regions" / "include Japan" / etc.
The script's matching rule (so the model doesn't second-guess its
output): a ticker is considered covered if (a) any folder under
reports/company/ ends with <EXCH><CODE> or <EXCH>_<CODE> for
the ticker's code (e.g. Meituan_美团_HKEX3690 covers 3690.HK),
OR (b) any folder starts with the ticker's company_name first
significant word (≥3 chars), followed by _/- — this catches
cross-listings (folder BYD_SZSE002594 for the A-share counts as
coverage for a PT call on H-share 1211.HK).
If the helper prints "✓ Every PT-mentioned ticker … already has a report", just echo that line — no table to show.
6. Output format
For each recommendation give:
file_id(so the user can hand it to/zsxq-analyze)- Bank / publisher if known (
bankcolumn, or extract from name) - A ≤2-sentence "why read this" — anchored in the actual summary, not generic.
- Page count + create_time when useful for triage.
Markdown table when listing >3 picks. Keep the whole reply tight — the user is choosing what to read next, not consuming the reports here.
End the reply with two compact one-liners (only when non-empty):
📈 PT inserts: <inserted> new, <total_in_db> total in /pt— from step 4's stdout summary. When you list the actual PT calls (not just the count), one line per row using the step-4rowsdata:<broker> <ticker> <rating>, TP <ccy><pt> vs <ccy><report_date_price> @ <report_date> → <±upside_pct>%— the report-date price is mandatory (surfacing rule);report-date price n/aif it's null. Append the revision arrow when the step-4 read-only check found a prior same-institute PT (PT ↑ from 120 (2026-04)).🟡 <N> PT-mentioned tickers without a reports/company/ entry— followed by the markdown table from step 5.
AI / Robotics / Semiconductor — detailed-narrative rule (MANDATORY)
AI / robotics is already this skill's default focus — so the bar is higher than a title gloss. Every surfaced AI / robotics / semiconductor pick gets 3–5 sentences of real narrative drawn from the summary text: the broker's actual thesis (mechanism, not topic), the key numbers present in the summary (PT, TAM, pricing, units), where the name sits in the supply chain, and why the report matters now (cycle position, catalyst, estimate revision). Theme groupings of AI/robotics/semi reports get a short narrative paragraph per theme — what the cluster of brokers collectively believes and where they disagree — not just a list of titles.
Notes
- DB is read-only here. Never write to
db/zsxq.dbfrom this skill. scripts/list_recent.pyresolves the DB viadb_paths.db_path()(honoursFINAGENT_DB_DIR); any new script added underscripts/must copy that pattern in the same commit (CLAUDE.md DB-safety rule).- Do not open the PDF files. Title + summary is the contract.
- If
count == 0(empty filter result), tell the user the filter and suggest relaxing it (drop the subject, widen--since). - The
tickers/claude_rating/user_ratingcolumns are sparse but valuable when present — mention them in the "why" line if a recommended row has them populated. - This skill pairs with
zsxq-analyze: recommend here → user picks afile_id→/zsxq-analyze <question> file_id <N>for the deep read.