mirror of
https://github.com/ZhuLinsen/daily_stock_analysis.git
synced 2026-10-06 15:13:28 +08:00
* feat(#455): Markdown-to-image for dashboard, m2f engine, prefetch controls - Dashboard report supports markdown-to-image (Telegram/WeChat/Custom/Email) - MD2IMG_ENGINE=markdown-to-file for better emoji support - PREFETCH_REALTIME_QUOTES to disable full-market quote prefetch - Stock name prefetch with allow_realtime=False to reduce network overhead - Email groups and Custom webhook image routing - Enhanced failure hint (wkhtmltopdf or m2f) - Docs: README, full-guide, CHANGELOG, .env.example - Tests: prefetch_stock_names, pipeline notification routing, prefetch dry_run Fixes #455 * chore: trigger CI and AI review refresh * feat: intelligent import P1 - name resolver, image extract, CSV/Excel import, preview UI - Extract STOCK_NAME_MAP to src/data/stock_mapping.py - Add name_to_code_resolver (local map, pinyin, AkShare, fuzzy match) - Add stock_code_utils for shared is_code_like/normalize_code - Enhance image_stock_extractor: code+name+confidence prompt, multi-key, retry - Add import_parser for CSV/Excel/clipboard with parse-import API - Add IntelligentImport component (image+file+paste, dedup, confidence-based check) - Fix single-column code import, preserve name when code dirty - Deduplicate codes in parse-import response - Add unit tests for resolver and import_parser * fix: resolve TS2339 Property 'id' does not exist on type 'never' In mergeItems when !existing, use fallback id directly instead of existing?.id to avoid TypeScript narrowing existing to never in that branch. * chore: address PR review - deps doc, file size check, json_repair, logging, tests - docs/full-guide: document pypinyin and openpyxl for 智能导入 - api: add file size check before reading in parse-import - image_stock_extractor: use json_repair fallback for malformed JSON - import_parser/name_to_code_resolver: add debug logging - CHANGELOG: add extract-from-image items field compatibility note - tests: VISION_MODEL priority, json_repair when JSON invalid * fix(image-extract): markdown strip bug, fake codes filter, EXTRACT_PROMPT doc, parse_import errors - Fix markdown strip: only remove opening fence to avoid wiping JSON - Add JSON to _FAKE_CODES; filter field names in fallback - Add docs/image-extract-prompt.md; PR template EXTRACT_PROMPT block - Refine parse_import errors: Excel/CSV actionable hints * chore(parse_import): add detailed error logs, document AkShare cache - Log file type, size, error on parse_import failures (JSON, file read, parse) - Add AkShare name resolver 1h TTL cache note to docs/full-guide.md * fix(import): handle space-separated pairs and confidence upgrade * fix(import): address PR #546 review - Excel no-header, fuzzy cutoff, code prefix - fix(import_parser): use header=None for read_excel; detect header row the same way as the CSV path to avoid silently consuming first data row as column names - fix(name_to_code_resolver): raise difflib cutoff 0.6->0.8 and skip fuzzy matching for inputs of length <=2 to prevent false-positive short matches (e.g. '中国' matching arbitrary stocks in a 5000+ name pool) - fix(stock_code_utils): normalize_code and is_code_like now recognise exchange-prefix formats SH600519, SZ000001, HK00700 (case-insensitive), aligning with data_provider/base.py normalize_stock_code behaviour - tests: add test_parses_xlsx_without_header to TestParseImportFromBytesExcel; add tests/test_stock_code_utils.py covering prefix/suffix/plain/edge cases --------- Co-authored-by: mumu <42829555+ZhuLinsen@users.noreply.github.com>
118 lines
3.2 KiB
Python
118 lines
3.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
Tests for src/services/stock_code_utils.py
|
|
Covers: is_code_like, normalize_code - including exchange prefix handling.
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from src.services.stock_code_utils import is_code_like, normalize_code
|
|
|
|
|
|
class TestIsCodeLike:
|
|
# --- Plain digit codes ---
|
|
def test_plain_6_digit(self):
|
|
assert is_code_like("600519") is True
|
|
|
|
def test_plain_5_digit(self):
|
|
assert is_code_like("00700") is True
|
|
|
|
def test_4_digit_rejected(self):
|
|
assert is_code_like("6001") is False
|
|
|
|
# --- Suffix format ---
|
|
def test_suffix_sh(self):
|
|
assert is_code_like("600519.SH") is True
|
|
|
|
def test_suffix_sz(self):
|
|
assert is_code_like("000001.SZ") is True
|
|
|
|
def test_suffix_lowercase(self):
|
|
assert is_code_like("600519.sh") is True
|
|
|
|
# --- Exchange prefix format (Issue #6 fix) ---
|
|
def test_prefix_sh_upper(self):
|
|
assert is_code_like("SH600519") is True
|
|
|
|
def test_prefix_sh_lower(self):
|
|
assert is_code_like("sh600519") is True
|
|
|
|
def test_prefix_sz(self):
|
|
assert is_code_like("SZ000001") is True
|
|
|
|
def test_prefix_hk(self):
|
|
assert is_code_like("HK00700") is True
|
|
|
|
def test_prefix_hk_lower(self):
|
|
assert is_code_like("hk00700") is True
|
|
|
|
# --- US tickers ---
|
|
def test_us_ticker(self):
|
|
assert is_code_like("AAPL") is True
|
|
|
|
def test_us_ticker_with_exchange(self):
|
|
assert is_code_like("TSLA.O") is True
|
|
|
|
# --- Negative cases ---
|
|
def test_plain_text(self):
|
|
assert is_code_like("贵州茅台") is False
|
|
|
|
def test_empty(self):
|
|
assert is_code_like("") is False
|
|
|
|
def test_mixed_invalid(self):
|
|
assert is_code_like("abc123") is False
|
|
|
|
|
|
class TestNormalizeCode:
|
|
# --- Plain digit codes ---
|
|
def test_plain_6_digit(self):
|
|
assert normalize_code("600519") == "600519"
|
|
|
|
def test_plain_5_digit(self):
|
|
assert normalize_code("00700") == "00700"
|
|
|
|
def test_whitespace_stripped(self):
|
|
assert normalize_code(" 600519 ") == "600519"
|
|
|
|
# --- Suffix format ---
|
|
def test_suffix_sh_strips(self):
|
|
assert normalize_code("600519.SH") == "600519"
|
|
|
|
def test_suffix_sz_strips(self):
|
|
assert normalize_code("000001.SZ") == "000001"
|
|
|
|
def test_suffix_ss_strips(self):
|
|
assert normalize_code("600000.SS") == "600000"
|
|
|
|
# --- Exchange prefix format (Issue #6 fix) ---
|
|
def test_prefix_sh_upper(self):
|
|
assert normalize_code("SH600519") == "600519"
|
|
|
|
def test_prefix_sh_lower(self):
|
|
assert normalize_code("sh600519") == "600519"
|
|
|
|
def test_prefix_sz(self):
|
|
assert normalize_code("SZ000001") == "000001"
|
|
|
|
def test_prefix_hk(self):
|
|
assert normalize_code("HK00700") == "00700"
|
|
|
|
def test_prefix_hk_lower(self):
|
|
assert normalize_code("hk00700") == "00700"
|
|
|
|
# --- US tickers ---
|
|
def test_us_ticker(self):
|
|
assert normalize_code("AAPL") == "AAPL"
|
|
|
|
# --- Invalid inputs ---
|
|
def test_empty_returns_none(self):
|
|
assert normalize_code("") is None
|
|
|
|
def test_plain_text_returns_none(self):
|
|
assert normalize_code("贵州茅台") is None
|
|
|
|
def test_partial_prefix_no_digits_returns_none(self):
|
|
# SH followed by wrong digit count
|
|
assert normalize_code("SH6005") is None
|