Skip to content

Commit 3bb4fbf

Browse files
yjwyjw
authored andcommitted
Polish report routing and package defaults for 0.2.4
1 parent 638355f commit 3bb4fbf

12 files changed

Lines changed: 136 additions & 26 deletions

File tree

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.2.4] - 2026-06-19
9+
10+
### Added
11+
- Added persistent long-term chat memory with dedicated `young memory show|clear|reset` management commands.
12+
- Added `young init` to initialize local state and check report/LLM readiness in one step.
13+
14+
### Fixed
15+
- Fixed `young report` to prefer identity-based Markdown/PDF artifacts over legacy `replay.*` files and to sanitize legacy reports before export.
16+
- Fixed report/session routing so `young report` defaults to the current calendar date and historical report exports stay tagged as `盘后`.
17+
- Fixed exported Markdown/HTML/PDF to remove the fixed “资深A股交易员”前言 and `Kami-compatible editorial layout` residue.
18+
- Fixed `young send` to deliver the identity-matched PDF instead of looking only for `report.pdf`.
19+
- Fixed installation guidance so the default package path includes PDF support and the readiness/error messages point to the same install flow.
20+
821
## [0.2.3] - 2026-06-18
922

1023
### Added

README.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Recommended for CLI isolation:
2121

2222
```bash
2323
uv tool install young-stock-cli
24+
young init
2425
```
2526

2627
Or install into the active Python environment:
@@ -45,6 +46,10 @@ If you installed `young` with `uv tool`, upgrade that tool-managed environment i
4546
uv tool install --upgrade young-stock-cli
4647
```
4748

49+
`young init` creates the local home/profile files, verifies whether PDF rendering is available in the current
50+
environment, and prints the recommended next steps. You only need to install the tool once per environment; you do
51+
not need to reinstall it before every report.
52+
4853
If `python3 -m pip install --upgrade young-stock-cli` succeeds but `young --version` still shows an older release,
4954
you are probably running a different executable entrypoint than the interpreter you just upgraded. A quick check:
5055

@@ -79,6 +84,7 @@ young daily --format summary # concise personalized daily report
7984
young daily --format key-points # short report with trend/risk points
8085
young daily --format full # full personalized daily report
8186
young daily --llm # evidence-driven deep replay with your configured LLM
87+
young init # initialize local state and verify report/LLM readiness
8288
young replay # deep M1-M6 market replay
8389
young analyze 600519 # deep single-stock analysis
8490
young chat # Rich chat mode with slash commands
@@ -257,16 +263,16 @@ is retained.
257263

258264
### Professional PDF reports
259265

260-
Install the optional renderer:
266+
The standard install already includes PDF support. If you upgraded from an older environment, refresh the tool once:
261267

262268
```bash
263-
uv tool install --force 'young-stock-cli[pdf]'
269+
uv tool install --force 'young-stock-cli'
264270
```
265271

266272
If `young` was installed into the active Python environment instead:
267273

268274
```bash
269-
python3 -m pip install "young-stock-cli[pdf]"
275+
python3 -m pip install --upgrade young-stock-cli
270276
```
271277

272278
Then export the latest report:
@@ -277,8 +283,8 @@ young report --date 20260618
277283
```
278284

279285
If no Markdown report exists for the selected date, `young report` first reuses a saved diary entry when available,
280-
otherwise it automatically generates a deterministic full daily report. It keeps `daily.md`/`replay.md`,
281-
`report.html`, and `report.pdf` together.
286+
otherwise it automatically generates a deterministic full daily report. Run `young init` first if you want a quick
287+
readiness check for PDF rendering, config, and local storage paths.
282288

283289
The bundled Equity Report layout follows the
284290
[`tw93/Kami`](https://github.com/tw93/Kami) editorial language: parchment `#f5f4ed`, ink blue `#1B365D`,

pyproject.toml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "young-stock-cli"
7-
version = "0.2.3"
7+
version = "0.2.4"
88
description = "A-share (China stock market) after-hours CLI — no login, no scraping tricks, just data."
99
readme = "README.md"
1010
requires-python = ">=3.9"
@@ -29,6 +29,7 @@ dependencies = [
2929
"requests>=2.31",
3030
"rich>=13.0",
3131
"click>=8.1",
32+
"weasyprint>=62",
3233
]
3334

3435
[project.urls]
@@ -41,9 +42,7 @@ Changelog = "https://github.com/AdvancingTitans/young-stock-cli/blob/main/CHANGE
4142
young = "young_stock.cli:cli"
4243

4344
[project.optional-dependencies]
44-
pdf = [
45-
"weasyprint>=62",
46-
]
45+
pdf = []
4746
dev = [
4847
"pytest>=7",
4948
"pytest-cov>=4",

src/young_stock/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""young-stock-cli: A-share after-hours CLI."""
22

3-
__version__ = "0.2.3"
3+
__version__ = "0.2.4"

src/young_stock/artifacts.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ def market_session(now: datetime | None = None) -> str:
2424
return "盘后"
2525

2626

27+
def report_session(trade_date: str, now: datetime | None = None) -> str:
28+
now = now or datetime.now()
29+
if trade_date != now.strftime("%Y%m%d"):
30+
return "盘后"
31+
return market_session(now)
32+
33+
2734
@dataclass(frozen=True)
2835
class ReportIdentity:
2936
trade_date: str

src/young_stock/channels/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@
1313
def send_report(trade_date: str | None, *, channel_name: str | None = None) -> list[DeliveryResult]:
1414
if not trade_date:
1515
raise ValueError("没有报告日期;请先运行 `young report`。")
16-
artifacts = ReportArtifacts(trade_date)
1716
markdown = ReportArtifacts.latest_markdown(trade_date)
18-
pdf = artifacts.path("report", "pdf")
17+
pdf = markdown.with_suffix(".pdf") if markdown else None
1918
if markdown is None or not pdf.exists():
2019
raise ValueError(f"{trade_date} 缺少 Markdown/PDF;请先运行 `young report --date {trade_date}`。")
2120
configs = load_config(strict=False).get("channels", {}).get("feishu", {})

src/young_stock/cli.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@
1010
import click
1111

1212
from . import __version__, _core
13-
from .artifacts import ReportArtifacts, ReportIdentity, market_session
13+
from .artifacts import ReportArtifacts, ReportIdentity, report_session
1414
from .config import (
1515
add_feishu_channel,
1616
config_path,
1717
load_config,
1818
mask_config,
1919
remove_feishu_channel,
20+
save_config,
2021
update_llm_config,
2122
)
2223
from .evidence import build_daily_evidence, build_stock_evidence
@@ -32,6 +33,7 @@
3233
load_profile,
3334
profile_path,
3435
remove_profile_item,
36+
save_profile,
3537
)
3638
from .reports import generate_llm_daily_report
3739

@@ -283,7 +285,7 @@ def _run_llm_replay(date_str: str, kind: str = "replay", symbol: str | None = No
283285
else build_daily_evidence(_core, date_str, profile)
284286
)
285287
artifacts = ReportArtifacts(date_str)
286-
session = market_session()
288+
session = report_session(date_str)
287289
topic = f"{symbol}深度分析" if symbol else "A股深度复盘"
288290
identity = ReportIdentity(date_str, session, topic)
289291
artifacts.write_json(f"{identity.prefix}-evidence", evidence.to_dict())
@@ -764,6 +766,37 @@ def guide() -> None:
764766
click.echo("4. young profile list / young diagnose")
765767

766768

769+
@cli.command(help="Initialize local state, verify installed capabilities, and print next steps.")
770+
def init() -> None:
771+
from .pdf import _load_weasyprint
772+
773+
home = young_home()
774+
home.mkdir(parents=True, exist_ok=True)
775+
(home / "reports").mkdir(parents=True, exist_ok=True)
776+
save_config(load_config(strict=False))
777+
save_profile(load_profile())
778+
for name, default in (
779+
("notes", []),
780+
("alerts", []),
781+
("diaries", {}),
782+
("portfolios", {}),
783+
):
784+
save_store(name, load_store(name, default))
785+
786+
pdf_ready = _load_weasyprint() is not None
787+
click.echo("初始化完成。")
788+
click.echo(f"Home: {home}")
789+
click.echo(f"Profile: {profile_path()}")
790+
click.echo(f"Config: {config_path()}")
791+
click.echo(f"PDF: {'已就绪' if pdf_ready else '当前环境未检测到 PDF 渲染能力'}")
792+
if not pdf_ready:
793+
click.echo("若当前环境缺少 PDF 渲染能力,请重新执行 `uv tool install --force 'young-stock-cli'`。")
794+
click.echo("下一步:")
795+
click.echo("1. young daily --format summary")
796+
click.echo("2. young replay")
797+
click.echo("3. young config llm --help")
798+
799+
767800
@cli.command(help="Show common examples.")
768801
def example() -> None:
769802
click.echo("young daily --format summary --quick")

src/young_stock/pdf.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,19 @@
33
from __future__ import annotations
44

55
import contextlib
6-
from datetime import datetime
76
import html
87
import io
98
import json
109
import os
1110
import re
1211
import sys
1312
import tempfile
13+
from datetime import datetime
1414
from importlib import resources
1515
from pathlib import Path
1616
from typing import Any, Callable
1717

18-
from .artifacts import ReportArtifacts, ReportIdentity, market_session
18+
from .artifacts import ReportArtifacts, ReportIdentity, report_session
1919
from .local_store import load_store
2020
from .research_style import sanitize_public_report
2121

@@ -144,7 +144,7 @@ def _canonicalize_markdown(
144144
identity = _parse_identity_from_path(markdown_path)
145145
evidence = _load_related_evidence(markdown_path)
146146
cleaned = sanitize_public_report(markdown, evidence)
147-
session = identity.session if identity else market_session(now)
147+
session = identity.session if identity else report_session(trade_date, now)
148148
topic = identity.topic if identity else _report_topic(cleaned)
149149
canonical = artifacts.write_report_markdown(ReportIdentity(trade_date, session, topic), cleaned)
150150
return canonical, cleaned
@@ -177,8 +177,8 @@ def _default_render(html_path: Path, pdf_path: Path) -> None:
177177
if renderer is None:
178178
raise PDFDependencyError(
179179
"当前环境未检测到 PDF 渲染能力。请先运行 `young init` 检查安装状态;"
180-
"若仍缺少依赖,uv tool 用户请执行 `uv tool install --force 'young-stock-cli[pdf]'`,"
181-
"普通 Python 环境请执行 `python3 -m pip install \"young-stock-cli[pdf]\"`。"
180+
"若仍缺少依赖,uv tool 用户请执行 `uv tool install --force 'young-stock-cli'`,"
181+
"普通 Python 环境请执行 `python3 -m pip install --upgrade young-stock-cli`。"
182182
)
183183
renderer(filename=str(html_path), base_url=str(html_path.parent)).write_pdf(str(pdf_path))
184184

@@ -221,7 +221,7 @@ def export_report_pdf(
221221
else:
222222
raise ValueError("没有可用报告;请先运行 `young daily` 或提供日报生成器。")
223223
markdown_path = artifacts.write_report_markdown(
224-
ReportIdentity(trade_date, market_session(now), "A股投资日报"),
224+
ReportIdentity(trade_date, report_session(trade_date, now), "A股投资日报"),
225225
sanitize_public_report(markdown),
226226
)
227227
markdown = markdown_path.read_text(encoding="utf-8")

tests/test_artifacts.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import json
22
from datetime import datetime
33

4-
from young_stock.artifacts import ReportArtifacts, ReportIdentity, market_session
4+
from young_stock.artifacts import ReportArtifacts, ReportIdentity, market_session, report_session
55
from young_stock.reports import generate_llm_daily_report
66

77

@@ -54,6 +54,11 @@ def test_market_session_labels():
5454
assert market_session(datetime(2026, 6, 18, 15, 10)) == "盘后"
5555

5656

57+
def test_report_session_uses_after_close_for_historical_trade_date():
58+
assert report_session("20260618", datetime(2026, 6, 19, 9, 41)) == "盘后"
59+
assert report_session("20260619", datetime(2026, 6, 19, 9, 41)) == "早盘"
60+
61+
5762
def test_report_identity_uses_date_session_and_topic():
5863
identity = ReportIdentity("20260618", "盘后", "A股深度复盘")
5964
assert identity.prefix == "20260618-盘后-A股深度复盘"

tests/test_channels.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import pytest
44

5+
from young_stock.artifacts import ReportArtifacts, ReportIdentity
56
from young_stock.channels import send_report
67
from young_stock.channels.feishu import FeishuChannel
78

@@ -68,6 +69,33 @@ def test_send_report_requires_artifacts(monkeypatch, tmp_path):
6869
send_report("20260618")
6970

7071

72+
def test_send_report_uses_identity_named_pdf(monkeypatch, tmp_path):
73+
monkeypatch.setenv("YOUNG_STOCK_HOME", str(tmp_path))
74+
artifacts = ReportArtifacts("20260618")
75+
identity = ReportIdentity("20260618", "盘后", "A股深度复盘")
76+
markdown = artifacts.write_report_markdown(identity, "# 复盘\n\n正文")
77+
pdf = artifacts.path(identity.prefix, "pdf")
78+
pdf.write_bytes(b"%PDF")
79+
sent = []
80+
81+
class DummyChannel:
82+
def __init__(self, name, config):
83+
self.name = name
84+
self.config = config
85+
86+
def send(self, markdown_path, pdf_path):
87+
sent.append((markdown_path, pdf_path))
88+
return SimpleNamespace(ok=True, channel=self.name, target="x", detail="ok")
89+
90+
monkeypatch.setattr("young_stock.channels.load_config", lambda strict=False: {"channels": {"feishu": {"work": {"webhook": "x"}}}})
91+
monkeypatch.setattr("young_stock.channels.FeishuChannel", DummyChannel)
92+
93+
results = send_report("20260618")
94+
95+
assert results[0].ok is True
96+
assert sent == [(markdown, pdf)]
97+
98+
7199
def test_feishu_retries_transient_http_failure(monkeypatch, tmp_path):
72100
markdown = tmp_path / "report.md"
73101
pdf = tmp_path / "report.pdf"

0 commit comments

Comments
 (0)