Public record
Software health reportschema 0.27.0 · metrics 1.13.0 · 2026-07-30 17:18 UTC

handsomejustin / easy_tdx

免费Python量化SDK:通达信协议直连 A股/港股/美股数据,内置技术指标、缠论分析、回测引擎与 Web 可视化,JSON 输出适配 AI Agent

PythonCustom license★ 636 stars⑂ 194 forkssince May 2026View on GitHub ↗

handsomejustin/easy_tdx holds a health index of 62 out of 100, placing it in the Moderate band. It scores highest on Engineering Quality (69/100) and lowest on Security (43/100). It was last updated 17 days ago. A single contributor accounts for most of its recent work.

62
overall / 100
Moderate

Software health index

Metrics are grouped into weighted categories on one standardized 1–100 scale. Overall starts as their weighted mean; when public evidence triggers the High-Risk Jurisdiction Policy, the rating is adjusted and receives an At risk ceiling of 49. AI Readiness sits outside the overall score.

62
Excellent85-100Exemplary; meets essentially all checked criteria
Good70-84Healthy; minor gaps
Moderate50-69Acceptable with notable gaps; review recommended
At risk30-49Significant weaknesses; adoption warrants caution
Critical1-29Severe problems (abandoned, single-maintainer, no hygiene)
VitalityCommunity &AdoptionSustainability &GovernanceEngineeringQualitySecurityAI Readiness

Score profile

Each axis is a category. The shape matters more than the average — a healthy subject fills the whole shape, while a spike-and-crater profile means strength in one dimension is masking risk in another.

Ownership

毛利哥Personal account
26 followers72 public repossince Jun 201423

This repository is owned by a personal account. A single-owner project carries more continuity risk than an organization-backed one.

Package ecosystems

RegistryPackageVersionDownloads / moVersionsLast publish
PyPIeasy-tdx1.20.415,8048217 days ago

Metrics by category

Vitality

Is the project alive — is code being written and are releases shipping?

68Moderate · 22% of overall
How it's scored
28.8/36Push recency — last push 17 days ago
6.9/36Commit cadence — 10/52 weeks with commits
18/18Commit volume — 289 commits in the last year
0/10OpenSSF Scorecard: Maintained — project was created within the last 90 days. Please review its contents carefully
Inputs used
commits_last_year289
human_commit_share1
days_since_last_push17
active_weeks_last_year10
How it's scored
27/27Ships releases — 65 releases published
36/36Release recency — latest release 17 days ago
27/27Release cadence — a release every ~0.6 days
0/10OpenSSF Scorecard: Signed-Releases — Project has not signed or included provenance with any releases.
Inputs used
releases_count65
latest_release_tagv1.20.4
releases_from_tagsno
days_since_latest_release17
mean_days_between_releases0.6

Community & Adoption

Does the project have users, downloads, attention, and a welcoming setup for contributors?

61Moderate · 18% of overall
How it's scored
45.5/60Stars — 636 stars
19/25Forks — 194 forks
6/15Watchers — 13 watchers
Inputs used
forks194
stars636
watchers13
growth_stateunverified
growth_factor_pct100
growth_unverified_reasonno_history
How it's scored
22.5/22.5README
16.9/22.5License — license file present, not a recognized license
0/18CONTRIBUTING guide
0/13.5Code of conduct
0/7.2Issue template
0/6.3PR template
Inputs used
has_readmeyes
has_licenseyes
has_contributingno
has_issue_templateno
has_code_of_conductno
has_pull_request_templateno
How it's scored
56/80Monthly downloads — 15,804 downloads/month across pypi
0/20Registry dependents — not reported by this ecosystem
Inputs used
packageseasy-tdx
dependents
ecosystemspypi
total_downloads
monthly_downloads15,804
Excluded from scoring (no data or not applicable): Registry dependents. Remaining weights renormalized.

Sustainability & Governance

Will the project survive its people — bus factor, responsiveness, who backs it, and package upkeep?

62Moderate · 24% of overall
How it's scored
9/54Bus factor — 1 contributor(s) cover half of all commits
9.5/22.5Commit distribution — top contributor authored 58% of commits
5.4/13.5Contributor breadth — 4 contributors
6/10OpenSSF Scorecard: Contributors — project has 2 contributing companies or organizations -- score normalized to 6
Inputs used
bus_factor1
contributors_sampled4
top_contributor_share0.58
How it's scored
37.8/46.8Issue resolution — 81% of issues closed
38.2/38.3PR acceptance — 9/9 decided PRs merged
0/15OpenSSF Scorecard: Code-Review — Found 0/22 approved changesets -- score normalized to 0
Inputs used
merged_prs9
open_issues5
closed_issues21
issue_closed_ratio0.808
closed_unmerged_prs0
How it's scored
10/30Ownership backing — personal (user) account
0/20Verified domain — not applicable to user accounts
10.3/25Owner reach — 26 followers of handsomejustin
25/25Track record — 72 public repos, account ~12 yr old
Inputs used
followers26
owner_typeUser
is_verified
owner_loginhandsomejustin
public_repos72
account_age_days4,442
Excluded from scoring (no data or not applicable): Verified domain. Remaining weights renormalized.
How it's scored
25/25Published & resolvable — 1 package(s) on pypi
35/35Publish recency — latest publish 17 days ago
20/20Version history — 82 published versions
20/20Not deprecated — active, not deprecated or yanked
Inputs used
packageseasy-tdx
ecosystemspypi
any_deprecatedno
min_days_since_publish17

Engineering Quality

Are baseline engineering and documentation practices in place?

69Moderate · 20% of overall
How it's scored
24/24CI workflows — 3 workflow(s)
24/24Tests present
0/16Linter config
0/9.6Pre-commit hooks
0/6.4.editorconfig
10/20OpenSSF Scorecard: CI-Tests — 4 out of 7 merged PRs checked by a CI test -- score normalized to 5
Inputs used
has_ciyes
has_testsyes
has_editorconfigno
has_linter_configno
has_precommit_configno

Documentation

85Excellent
How it's scored
30/30README
25/25Documentation directory
0/15Documentation / homepage site
10/10Repository description
10/10Topics — 10 topics
10/10Wiki
Inputs used
topicschinese-stock-market, financial-data, mootdx, pytdx, tdx, technical-analysis, a-share-tdx, stock-analysis, stock-data, stock-trading
has_wikiyes
homepage
has_readmeyes
has_docs_diryes
has_descriptionyes

Security

Are visible security and supply-chain practices strong, without unresolved high-risk jurisdiction exposure?

43At risk · 16% of overall
How it's scored
7.5/7.5Binary-Artifacts — no binaries found in the repo
0/7.5Branch-Protection — branch protection not enabled on development/release branches
1.2/2.5CI-Tests — 4 out of 7 merged PRs checked by a CI test -- score normalized to 5
0/2.5CII-Best-Practices — no effort to earn an OpenSSF best practices badge detected
0/7.5Code-Review — Found 0/22 approved changesets -- score normalized to 0
1.5/2.5Contributors — project has 2 contributing companies or organizations -- score normalized to 6
10/10Dangerous-Workflow — no dangerous workflow patterns detected
0/7.5Dependency-Update-Tool — no update tool detected
0/5Fuzzing — project is not fuzzed
2.2/2.5License — license file detected
0/7.5Maintained — project was created within the last 90 days. Please review its contents carefully
5/5Packaging — packaging workflow detected
1.5/5Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 3
0/5SAST — SAST tool is not run on all commits -- score normalized to 0
0/5Security-Policy — security policy file not detected
0/7.5Signed-Releases — Project has not signed or included provenance with any releases.
0/7.5Token-Permissions — detected GitHub workflow tokens with excessive permissions
1.5/7.5Vulnerabilities — 8 existing vulnerabilities detected
Inputs used
sourceopenssf_scorecard
checks_evaluated18
scorecard_versionv5.5.0
checks_inconclusive0
scorecard_aggregate2.9
How it's scored
35/35Direct dependencies free of known advisories — no direct dependency carries a known advisory
25/25Indirect dependencies free of known advisories — no indirect dependency carries a known advisory
0/40No advisories left outstanding — no advisory carries a publication date
Inputs used
sourceosv
advisories0
affected_packages0
assessed_packages7
unassessed_packages0
affected_by_severitynone
direct_affected_packages0
Excluded from scoring (no data or not applicable): No advisories left outstanding. Remaining weights renormalized. Matched the pypi:easy-tdx@1.20.4 runtime dependency closure — what installing the published package pulls in — 7 packages. Reachability is not analyzed.

AI Readiness

How well is the repo equipped to be developed and maintained with AI coding agents? An independent, experimental badge — weight 0.0, so it is surfaced on its own and does not affect the overall health score.

49At risk · 0% of overall
How it's scored
0/45Agent instructions — no CLAUDE.md / AGENTS.md / editor rules
0/15Machine-readable docs (llms.txt)
40/40Legible commit history — 99 of 100 human commits state their intent (structured subject or explanatory body)
Inputs used
has_llms_txtno
legible_history_share0.99
agent_instruction_files
agent_instruction_max_bytes
How it's scored
0/18One-command bootstrap
22/22Automated tests
0/11Lint / format config
11/11Static type checking — src/easy_tdx/py.typed, web-ui/tsconfig.json
10/10Reproducible environment — lockfile
0/10Demonstrated agent practice — no agent-authored commits among the last 100
0/8Automated maintenance — no automated dependency updates observed
3/10OpenSSF Scorecard: Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 3
Inputs used
has_nixno
has_testsyes
lockfilespackage-lock.json
has_dockerfileno
typed_languageno
bootstrap_files
has_devcontainerno
has_linter_configno
typecheck_configssrc/easy_tdx/py.typed, web-ui/tsconfig.json
agent_commit_share0
toolchain_manifests
dependency_bot_commit_share0
How it's scored
27/45Type-checkable code — Python with type-check config (src/easy_tdx/py.typed, web-ui/tsconfig.json)
54.7/55Manageable file sizes — 2/389 source files over 60KB
Inputs used
primary_languagePython
largest_source_bytes74,718
source_files_sampled389
oversized_source_files2
How it's scored
0/40API schema (OpenAPI/GraphQL/proto)
0/20MCP server
40/40Runnable examples — examples
Inputs used
example_dirsexamples
has_mcp_signalno
api_schema_files

Key facts

636GitHub stars
4contributors
289commits, last 12 months
17days since last push
65releases
1bus factor
5open issues
npm, PyPIpackage ecosystems

Data collection warnings

  • Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token

More detail

Star and fork history 0 ★ / 194 ⇿
0Stars
194Forks
65Releases

When each star and fork was added, collected from GitHub and bucketed by day. Cumulative growth sits directly above the daily additions it is made of, so the two read against each other: steady organic accretion looks nothing like an abrupt, short-lived burst. Where that difference is measurable, it is reported as growth authenticity.

0408012016020019482026-052026-062026-07
Major 0Minor 12Patch 53
OpenSSF Scorecard 2.9 / 10
2.9aggregate

Independent, tool-agnostic security assessment from the open-source OpenSSF Scorecard. Each check rewards a security practice, not a specific vendor's tool. Checks Scorecard could not determine are marked n/a and excluded from the security score (never counted as zero).Scorecard v5.5.0 · 2026-07-30 17:18 UTC

10Binary-Artifactsno binaries found in the repo
0Branch-Protectionbranch protection not enabled on development/release branches
5CI-Tests4 out of 7 merged PRs checked by a CI test -- score normalized to 5
0CII-Best-Practicesno effort to earn an OpenSSF best practices badge detected
0Code-ReviewFound 0/22 approved changesets -- score normalized to 0
6Contributorsproject has 2 contributing companies or organizations -- score normalized to 6
10Dangerous-Workflowno dangerous workflow patterns detected
0Dependency-Update-Toolno update tool detected
0Fuzzingproject is not fuzzed
9Licenselicense file detected
0Maintainedproject was created within the last 90 days. Please review its contents carefully
10Packagingpackaging workflow detected
3Pinned-Dependenciesdependency not pinned by hash detected -- score normalized to 3
0SASTSAST tool is not run on all commits -- score normalized to 0
0Security-Policysecurity policy file not detected
0Signed-ReleasesProject has not signed or included provenance with any releases.
0Token-Permissionsdetected GitHub workflow tokens with excessive permissions
2Vulnerabilities8 existing vulnerabilities detected
Direct dependencies 7
RegistryPackageVersion constraintManifest
PyPIpandas>=2.0,<3pyproject.toml
PyPItzdata>=2024.1pyproject.toml
PyPIclick>=8.0,<9pyproject.toml
npmecharts^6.1.0web-ui/package.json
npmpinia^3.0.4web-ui/package.json
npmvue^3.5.39web-ui/package.json
npmvue-router^4.6.4web-ui/package.json
All dependencies 123

Full resolved dependency set from the GitHub dependency graph: 7 direct and 116 indirect (transitive) packages. The transitive closure is complete when the repository commits a lockfile.

RegistryPackageVersionRelation
npmecharts6.1.0direct
npmpinia3.0.4direct
npmvue3.5.39direct
npmvue-router4.6.4direct
PyPIclickdirect
PyPIpandasdirect
PyPItzdatadirect
npm@babel/helper-string-parser7.29.7indirect
npm@babel/helper-validator-identifier7.29.7indirect
npm@babel/parser7.29.7indirect
npm@babel/types7.29.7indirect
npm@emnapi/core1.11.1indirect
npm@emnapi/runtime1.11.1indirect
npm@emnapi/wasi-threads1.2.2indirect
npm@jridgewell/sourcemap-codec1.5.5indirect
npm@napi-rs/wasm-runtime1.1.6indirect
npm@oxc-project/types0.138.0indirect
npm@rolldown/binding-android-arm641.1.4indirect
npm@rolldown/binding-darwin-arm641.1.4indirect
npm@rolldown/binding-darwin-x641.1.4indirect
npm@rolldown/binding-freebsd-x641.1.4indirect
npm@rolldown/binding-linux-arm-gnueabihf1.1.4indirect
npm@rolldown/binding-linux-arm64-gnu1.1.4indirect
npm@rolldown/binding-linux-arm64-musl1.1.4indirect
npm@rolldown/binding-linux-ppc64-gnu1.1.4indirect
npm@rolldown/binding-linux-s390x-gnu1.1.4indirect
npm@rolldown/binding-linux-x64-gnu1.1.4indirect
npm@rolldown/binding-linux-x64-musl1.1.4indirect
npm@rolldown/binding-openharmony-arm641.1.4indirect
npm@rolldown/binding-wasm32-wasi1.1.4indirect
npm@rolldown/binding-win32-arm64-msvc1.1.4indirect
npm@rolldown/binding-win32-x64-msvc1.1.4indirect
npm@rolldown/pluginutils1.0.1indirect
npm@tybys/wasm-util0.10.3indirect
npm@types/node24.13.2indirect
npm@vitejs/plugin-vue6.0.7indirect
npm@volar/language-core2.4.28indirect
npm@volar/source-map2.4.28indirect
npm@volar/typescript2.4.28indirect
npm@vue/compiler-core3.5.39indirect
npm@vue/compiler-dom3.5.39indirect
npm@vue/compiler-sfc3.5.39indirect
npm@vue/compiler-ssr3.5.39indirect
npm@vue/devtools-api6.6.4indirect
npm@vue/devtools-api7.7.10indirect
npm@vue/devtools-kit7.7.10indirect
npm@vue/devtools-shared7.7.10indirect
npm@vue/language-core3.3.6indirect
npm@vue/reactivity3.5.39indirect
npm@vue/runtime-core3.5.39indirect
npm@vue/runtime-dom3.5.39indirect
npm@vue/server-renderer3.5.39indirect
npm@vue/shared3.5.39indirect
npm@vue/tsconfig0.9.1indirect
npmalien-signals3.2.1indirect
npmbirpc2.9.0indirect
npmcopy-anything4.0.5indirect
npmcsstype3.2.3indirect
npmdetect-libc2.1.2indirect
npmentities7.0.1indirect
npmestree-walker2.0.2indirect
npmfdir6.5.0indirect
npmfsevents2.3.3indirect
npmhookable5.5.3indirect
npmis-what5.5.0indirect
npmlightningcss1.32.0indirect
npmlightningcss-android-arm641.32.0indirect
npmlightningcss-darwin-arm641.32.0indirect
npmlightningcss-darwin-x641.32.0indirect
npmlightningcss-freebsd-x641.32.0indirect
npmlightningcss-linux-arm-gnueabihf1.32.0indirect
npmlightningcss-linux-arm64-gnu1.32.0indirect
npmlightningcss-linux-arm64-musl1.32.0indirect
npmlightningcss-linux-x64-gnu1.32.0indirect
npmlightningcss-linux-x64-musl1.32.0indirect
npmlightningcss-win32-arm64-msvc1.32.0indirect
npmlightningcss-win32-x64-msvc1.32.0indirect
npmmagic-string0.30.21indirect
npmmitt3.0.1indirect
npmmuggle-string0.4.1indirect
npmnanoid3.3.15indirect
npmpath-browserify1.0.1indirect
npmperfect-debounce1.0.0indirect
npmpicocolors1.1.1indirect
npmpicomatch4.0.5indirect
npmpostcss8.5.16indirect
npmrfdc1.4.1indirect
npmrolldown1.1.4indirect
npmsource-map-js1.2.1indirect
npmspeakingurl14.0.1indirect
npmsuperjson2.2.6indirect
npmtinyglobby0.2.17indirect
npmtslib2.3.0indirect
npmtslib2.8.1indirect
npmtypescript6.0.3indirect
npmundici-types7.18.2indirect
npmvite8.1.3indirect
npmvscode-uri3.1.0indirect
npmvue-tsc3.3.6indirect
npmzrender6.1.0indirect
PyPIcoverage7.14.0indirect
PyPIfastapiindirect
PyPIfastapi0.115.11indirect
PyPIhttpxindirect
PyPImypyindirect
PyPImypy2.1.0indirect
PyPImyst-parserindirect
PyPInumpy2.2.3indirect
PyPIpillowindirect
PyPIpystrayindirect
PyPIpytestindirect
PyPIpytest9.0.3indirect
PyPIpytest-asyncioindirect
PyPIpytest-cov7.1.0indirect
PyPIruffindirect
PyPIruff0.11.11indirect
PyPIscipyindirect
PyPIscipy1.15.3indirect
PyPIsphinxindirect
PyPIsphinx-autobuildindirect
PyPIsphinx-rtd-themeindirect
PyPIuvicornindirect
PyPIuvicorn0.34.0indirect
Dependency advisories 0

Installing pypi:easy-tdx@1.20.4 pulls in 7 packages, direct and transitive: 0 carry known advisories, of which 0 are direct dependencies.

No known advisories affect the assessed dependencies.

An advisory means the version recorded in the dependency graph falls inside an advisory’s affected range. Reachability is not analysed, and the graph includes development and test pins — a finding may concern tooling rather than shipped software.

Raw JSON report machine-readable
{
  "data": {
    "repo": {
      "topics": [
        "chinese-stock-market",
        "financial-data",
        "mootdx",
        "pytdx",
        "tdx",
        "technical-analysis",
        "a-share-tdx",
        "stock-analysis",
        "stock-data",
        "stock-trading"
      ],
      "is_fork": false,
      "size_kb": 18248,
      "has_wiki": true,
      "homepage": null,
      "languages": {
        "CSS": 1878,
        "Vue": 161032,
        "HTML": 368,
        "Python": 1915565,
        "JavaScript": 1544,
        "TypeScript": 95589
      },
      "pushed_at": "2026-07-13T08:36:31Z",
      "created_at": "2026-05-21T04:20:17Z",
      "owner_type": "User",
      "updated_at": "2026-07-30T13:51:42Z",
      "description": "免费Python量化SDK:通达信协议直连 A股/港股/美股数据,内置技术指标、缠论分析、回测引擎与 Web 可视化,JSON 输出适配 AI Agent",
      "is_archived": false,
      "is_disabled": false,
      "license_spdx": null,
      "default_branch": "main",
      "license_spdx_raw": "NOASSERTION",
      "primary_language": "Python",
      "significant_languages": [
        "Python"
      ]
    },
    "owner": {
      "blog": null,
      "name": "毛利哥",
      "type": "User",
      "login": "handsomejustin",
      "company": "23",
      "location": null,
      "followers": 26,
      "avatar_url": "https://avatars.githubusercontent.com/u/7761402?v=4",
      "created_at": "2014-06-01T16:13:49Z",
      "is_verified": null,
      "public_repos": 72,
      "account_age_days": 4442
    },
    "license": {
      "state": "custom",
      "spdx_id": null,
      "raw_spdx": "NOASSERTION",
      "file_present": true,
      "scorecard_found": true,
      "profile_has_license": true
    },
    "activity": {
      "releases": [
        {
          "tag": "v1.20.4",
          "kind": "patch",
          "published_at": "2026-07-13T08:39:12Z"
        },
        {
          "tag": "v1.20.3",
          "kind": "patch",
          "published_at": "2026-07-10T07:19:55Z"
        },
        {
          "tag": "v1.20.2",
          "kind": "patch",
          "published_at": "2026-07-09T13:08:50Z"
        },
        {
          "tag": "v1.20.1",
          "kind": "patch",
          "published_at": "2026-07-09T12:20:52Z"
        },
        {
          "tag": "v1.20.0",
          "kind": "minor",
          "published_at": "2026-07-08T10:16:24Z"
        },
        {
          "tag": "v1.19.7",
          "kind": "patch",
          "published_at": "2026-07-07T15:12:04Z"
        },
        {
          "tag": "v1.19.6",
          "kind": "patch",
          "published_at": "2026-07-07T14:26:53Z"
        },
        {
          "tag": "v1.19.5",
          "kind": "patch",
          "published_at": "2026-07-07T12:52:49Z"
        },
        {
          "tag": "v1.19.4",
          "kind": "patch",
          "published_at": "2026-07-07T12:39:38Z"
        },
        {
          "tag": "v1.19.3",
          "kind": "patch",
          "published_at": "2026-07-07T12:16:23Z"
        },
        {
          "tag": "v1.19.2",
          "kind": "patch",
          "published_at": "2026-07-07T07:21:28Z"
        },
        {
          "tag": "v1.19.1",
          "kind": "patch",
          "published_at": "2026-07-06T17:58:36Z"
        },
        {
          "tag": "v1.18.3",
          "kind": "patch",
          "published_at": "2026-07-06T05:24:06Z"
        },
        {
          "tag": "v1.18.2",
          "kind": "patch",
          "published_at": "2026-07-05T18:56:30Z"
        },
        {
          "tag": "v1.18.1",
          "kind": "patch",
          "published_at": "2026-07-05T17:00:39Z"
        },
        {
          "tag": "v1.18.0",
          "kind": "minor",
          "published_at": "2026-07-04T19:02:02Z"
        },
        {
          "tag": "v1.17.15",
          "kind": "patch",
          "published_at": "2026-07-04T18:18:32Z"
        },
        {
          "tag": "v1.17.14",
          "kind": "patch",
          "published_at": "2026-07-04T17:22:14Z"
        },
        {
          "tag": "v1.17.13",
          "kind": "patch",
          "published_at": "2026-07-04T13:08:04Z"
        },
        {
          "tag": "v1.17.12",
          "kind": "patch",
          "published_at": "2026-07-04T12:51:09Z"
        },
        {
          "tag": "v1.17.11",
          "kind": "patch",
          "published_at": "2026-07-04T12:42:07Z"
        },
        {
          "tag": "v1.17.10",
          "kind": "patch",
          "published_at": "2026-07-03T20:18:28Z"
        },
        {
          "tag": "v1.17.8",
          "kind": "patch",
          "published_at": "2026-07-03T18:25:19Z"
        },
        {
          "tag": "v1.17.7",
          "kind": "patch",
          "published_at": "2026-07-03T18:11:58Z"
        },
        {
          "tag": "v1.17.6",
          "kind": "patch",
          "published_at": "2026-07-03T18:04:21Z"
        },
        {
          "tag": "v1.17.5",
          "kind": "patch",
          "published_at": "2026-07-03T17:30:16Z"
        },
        {
          "tag": "v1.17.4",
          "kind": "patch",
          "published_at": "2026-07-03T16:12:27Z"
        },
        {
          "tag": "v1.17.0",
          "kind": "minor",
          "published_at": "2026-07-03T03:56:21Z"
        },
        {
          "tag": "v1.16.3",
          "kind": "patch",
          "published_at": "2026-07-02T04:47:04Z"
        },
        {
          "tag": "v1.16.2",
          "kind": "patch",
          "published_at": "2026-07-01T19:38:52Z"
        },
        {
          "tag": "v1.16.1",
          "kind": "patch",
          "published_at": "2026-07-01T08:24:02Z"
        },
        {
          "tag": "v1.16.0",
          "kind": "minor",
          "published_at": "2026-06-30T07:03:47Z"
        },
        {
          "tag": "v1.15.4",
          "kind": "patch",
          "published_at": "2026-06-29T14:15:53Z"
        },
        {
          "tag": "v1.15.3",
          "kind": "patch",
          "published_at": "2026-06-26T20:38:06Z"
        },
        {
          "tag": "v1.15.2",
          "kind": "patch",
          "published_at": "2026-06-26T20:26:05Z"
        },
        {
          "tag": "v1.15.1",
          "kind": "patch",
          "published_at": "2026-06-26T20:07:08Z"
        },
        {
          "tag": "v1.15.0",
          "kind": "minor",
          "published_at": "2026-06-24T19:34:03Z"
        },
        {
          "tag": "v1.14.5",
          "kind": "patch",
          "published_at": "2026-06-17T10:17:58Z"
        },
        {
          "tag": "v1.14.4",
          "kind": "patch",
          "published_at": "2026-06-16T02:04:13Z"
        },
        {
          "tag": "v1.14.3",
          "kind": "patch",
          "published_at": "2026-06-15T19:04:56Z"
        },
        {
          "tag": "v1.14.2",
          "kind": "patch",
          "published_at": "2026-06-15T18:53:56Z"
        },
        {
          "tag": "v1.14.1",
          "kind": "patch",
          "published_at": "2026-06-15T12:52:14Z"
        },
        {
          "tag": "v1.14.0",
          "kind": "minor",
          "published_at": "2026-06-14T16:35:49Z"
        },
        {
          "tag": "v1.12.0",
          "kind": "minor",
          "published_at": "2026-06-14T10:50:57Z"
        },
        {
          "tag": "v1.11.6",
          "kind": "patch",
          "published_at": "2026-06-13T13:26:44Z"
        },
        {
          "tag": "v1.11.5",
          "kind": "patch",
          "published_at": "2026-06-13T13:11:46Z"
        },
        {
          "tag": "v1.11.1",
          "kind": "patch",
          "published_at": "2026-06-12T14:13:20Z"
        },
        {
          "tag": "v1.10.5",
          "kind": "patch",
          "published_at": "2026-06-12T09:20:31Z"
        },
        {
          "tag": "v1.10.2",
          "kind": "patch",
          "published_at": "2026-06-12T08:47:50Z"
        },
        {
          "tag": "v1.10.0",
          "kind": "minor",
          "published_at": "2026-06-11T19:37:27Z"
        },
        {
          "tag": "v1.9.10",
          "kind": "patch",
          "published_at": "2026-06-11T09:41:48Z"
        },
        {
          "tag": "v1.9.9",
          "kind": "patch",
          "published_at": "2026-06-10T20:24:50Z"
        },
        {
          "tag": "v1.9.8",
          "kind": "patch",
          "published_at": "2026-06-10T20:13:19Z"
        },
        {
          "tag": "v1.9.7",
          "kind": "patch",
          "published_at": "2026-06-10T19:59:32Z"
        },
        {
          "tag": "v1.9.5",
          "kind": "patch",
          "published_at": "2026-06-10T10:53:59Z"
        },
        {
          "tag": "v1.9.4",
          "kind": "patch",
          "published_at": "2026-06-09T19:41:09Z"
        },
        {
          "tag": "v1.9.3",
          "kind": "patch",
          "published_at": "2026-06-09T19:25:42Z"
        },
        {
          "tag": "v1.8.2",
          "kind": "patch",
          "published_at": "2026-06-09T15:20:56Z"
        },
        {
          "tag": "v1.8.0",
          "kind": "minor",
          "published_at": "2026-06-09T12:42:40Z"
        },
        {
          "tag": "v1.7.1",
          "kind": "patch",
          "published_at": "2026-06-07T19:10:24Z"
        },
        {
          "tag": "v1.7.0",
          "kind": "minor",
          "published_at": "2026-06-07T15:33:10Z"
        },
        {
          "tag": "v1.6.1",
          "kind": "patch",
          "published_at": "2026-06-07T13:56:10Z"
        },
        {
          "tag": "v1.6.0",
          "kind": "minor",
          "published_at": "2026-06-07T13:18:08Z"
        },
        {
          "tag": "v1.5.0",
          "kind": "minor",
          "published_at": "2026-06-02T04:33:10Z"
        },
        {
          "tag": "v1.4.2",
          "kind": "patch",
          "published_at": "2026-05-28T10:15:07Z"
        }
      ],
      "recent_commits": [
        {
          "oid": "3b7ce97f2a6942cf9f39e25ee29c4e113bcfc69f",
          "body": "版本包含(PR #37):\n- 新增 _health.py 服务器健康分引擎(降权/冷却/恢复)\n- get_index_bars/get_security_bars 空数据自动逐台换台\n- 8 个 client _execute 统一健康分联动\n- select_best_host_*/find_working_host_* 应用 rank_by_health\n- security_bars 截断日志区分空响应与末尾截断\n- 26 个新增测试,CI 8/8 通过",
          "is_bot": false,
          "headline": "chore(release): v1.20.4 服务器健康分引擎 + K线空数据故障转移",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-13T08:36:11Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0be4277d0392ea4e08816515b672121e68b5d311",
          "body": "…-failover\n\nfeat(reconnect): 服务器健康分引擎 + K线空数据故障转移",
          "is_bot": false,
          "headline": "Merge pull request #37 from handsomejustin/feat/health-score-and-bars…",
          "author_name": "毛利哥",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-13T08:21:55Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "1f040afabf05a20afc2ec0172f1693da8d7947c4",
          "body": "彻底解决通达信服务器\"跳来跳去\"问题:\n\n1. 新增 _health.py 健康分引擎:失败乘性降权(×0.5)、连续失败3次进\n   120s 冷却、成功加性恢复(+0.2)。rank_by_health 按 latency/score\n   (有效延迟)重排,冷却中的剔除。全健康时恒等映射,对既有测试零影响。\n\n2. get_index_bars/get_security_bars 空数据时自动逐台换台(此前直接\n   返回空 DataFrame,是日志\"指数K线响应在第1/800条处被截断\"后用户拿\n   不到数据的根因)。复用泛化后的 _find_host_returning_data\n[…]\n入健康分记录:\n   成功 record_success、连接失败 record_failure。\n\n5. security_bars 截断日志区分\"首条即空(服务器无数据)\"与\"末尾截断\"。\n\n测试:26 个新增(15 health + 7 failover + 4 ex-client 健康分追踪),\n全量 reconnect/failover/decode 回归通过,ruff/mypy 通过。",
          "is_bot": false,
          "headline": "feat(reconnect): 引入服务器健康分引擎 + K线空数据故障转移",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-13T07:59:28Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "5dd1b968185af26fdea977298d03e08957cb3a38",
          "body": "…-feed\n\nfeat(realtime): 新增 RealtimeDataFeed 轮询数据源(issue #34)",
          "is_bot": false,
          "headline": "Merge pull request #36 from handsomejustin/fix/issue-34-realtime-data…",
          "author_name": "毛利哥",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-12T19:58:36Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "afcc100e3539c312cc779edb2226edb3d5450e4f",
          "body": "EventBus 原本是纯发布/订阅管道,库内没有任何代码调用 publish(),导致\nRealtimeStrategy 订阅后无数据可收(issue #34 根因)。\n\n新增 RealtimeDataFeed:轮询 get_stock_quotes 快照 → 封装 MarketEvent →\nbus.publish,让 RealtimeStrategy 拿来就能跑。\n\n- feed.py: async(run_async) + sync(run_sync, executor 线程避免卡 loop)\n  支持 interval/dedup(去重)/sessions(交易时段过滤,默认 9:1\n[…]\nPython API 段落(含 issue #34 三个坑说明),\n  WebSocket 段落标注「当前未联动数据源」现状\n- examples/11_realtime/: 可直接运行的异步示例\n\n通达信协议无服务端推送,本模块是约 3 秒延迟的快照轮询近似,非逐笔 tick。\n\n验证:ruff + mypy(strict) + pytest 全量 956 passed(+20 新增),无回归。",
          "is_bot": false,
          "headline": "feat(realtime): 新增 RealtimeDataFeed 轮询数据源(issue #34)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-12T08:56:41Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "9e7d30f33e83896f0315423197feb91b8cc2238a",
          "body": "fix(client): get_index_bars/get_security_bars 加空数据故障转移",
          "is_bot": false,
          "headline": "Merge pull request #35 from handsomejustin/fix/index-bars-empty-failover",
          "author_name": "毛利哥",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-10T11:58:35Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6b98715a14812f4800abcbc3380174ffa9d389da",
          "body": "指数/板块指数(880xxx 等)并非所有服务器都提供,延迟最低的不一定返回数据。\n实测约 6/47 台可达主机对所有指数返回空 body 且不报错,from_best_host() 按纯\n延迟选主会选中这种\"快但空\"的主机,导致 get_index_bars 返回空 DataFrame。\n\nget_market_stat 早有 _find_host_returning_quotes 兜底,但 K 线查询缺失同类逻辑。\n本提交补齐对称设计:\n\n- 新增 _find_host_returning_bars(sync + async):照搬 _find_host_returning_quotes\n\n[…]\n + async 共 4 处)接入:空数据且\n  auto_reconnect 开启时触发逐台实测,找首台返回数据的 host\n- 新增 TestIndexBarsEmptyFailover(5 个测试):覆盖空数据切 host、全候选空、\n  非空不触发、auto_reconnect=False 不触发、个股 K 线同链路\n\n验证:23 个 failover 测试全绿,941 个单元测试无回归。",
          "is_bot": false,
          "headline": "fix(client): get_index_bars/get_security_bars 加空数据故障转移",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-10T11:37:31Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "a8c035010eb0901434c16b5a500ba3d319a609cd",
          "body": null,
          "is_bot": false,
          "headline": "style(tests): ruff format 修复尾随空行(CI ruff format --check 失败)",
          "author_name": "GitHub",
          "author_login": "actions-user",
          "committed_at": "2026-07-10T07:17:00Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d84a08b7f27623d233e63ef14426a01653d74936",
          "body": "#31: RebalanceEngine 缺失价格导致净值假崩塌\n  已持仓标的当日缺 K 线(停牌/日历错位)时 prices.get(code,0)=0,\n  持仓市值记 0 → 净值单日暴跌(159915 在 20210208 缺一天,持仓 ~93%,\n  净值 1.1M→91,845,全期最大回撤 -92%)。\n  修复:last_known_price forward-fill,缺失日沿用最近已知收盘价。\n  附带:_compute_performance 最大回撤改正值口径(与 BacktestEngine 一致)。\n  验证:真实 ETF 数据 max_drawdown 24.0\n[…]\ntotal_return=0 但 trades 表有 PnL)。\n  修复:改为按\"位置索引\"匹配(归一化 datetime 后查位置),类型无关。\n  验证:int-trade+datetime64-df 修复前净值=100000(恒定),修复后=100289。\n\n测试:新增 4 回归测试(未修复代码上均失败,修复后通过);\n全套 936 passed;mypy 改动文件零错误;ruff 全绿。",
          "is_bot": false,
          "headline": "fix(backtest): v1.20.3 修复回测绩效统计两个 bug(issues #30 #31)",
          "author_name": "GitHub",
          "author_login": "actions-user",
          "committed_at": "2026-07-10T07:12:47Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "06be298c927182492febe9c6be9ab523583be611",
          "body": "GitHub OIDC → sigstore 服务连续 503 overflow(v1.20.2 两次 publish 失败),\n非配置问题(v1.20.1 能发成功证明 trusted publishing 配置正确)。\nattestations 是可选签名,关闭后上传正常进行;待服务稳定后改回 true。",
          "is_bot": false,
          "headline": "ci(publish): 暂时关闭 attestations 规避 OIDC 503 故障",
          "author_name": "GitHub",
          "author_login": "actions-user",
          "committed_at": "2026-07-09T14:12:20Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "471c79d5e21dbe17e66f394f7bb94099aa8bd2a4",
          "body": "fix(types): v1.20.2 修复 v1.20.1 引入的 CI mypy 失败",
          "is_bot": false,
          "headline": "Merge pull request #29 from handsomejustin/fix/v1.20.2-mypy-ci",
          "author_name": "毛利哥",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-09T12:59:58Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "bc0ea064baf512ffa8be017402d5765598370c57",
          "body": null,
          "is_bot": false,
          "headline": "chore: retrigger CI (Actions queue stuck 20min)",
          "author_name": "GitHub",
          "author_login": "actions-user",
          "committed_at": "2026-07-09T12:55:16Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a67e5588d055e4c4ae7e9a87f67b293097fff646",
          "body": "v1.20.1 把 BacktestResult.performance 扩大为 dict[str, float | str]\n(为塞 diagnostic_warning 字符串),破坏 6 处下游消费方类型契约,\nCI mypy job 转红。重构为更干净的设计:\n\n- 诊断信息走独立的 BacktestResult.diagnostic 字段(performance.py\n  PerformanceAnalyzer.diagnostic 属性 → engine 透出 → to_dict 含该字段\n  → CLI 显示),performance 字典回归 dict[str, float](保留别名键)。\n- optimizer.py 补 npt.NDArray 标注,消除 3 个既有 ndarray type-arg 错误。\n\n测试:932 passed;mypy backtest/+portfolio/ 零错误;ruff 全绿。",
          "is_bot": false,
          "headline": "fix(types): v1.20.2 修复 v1.20.1 引入的 CI mypy 失败",
          "author_name": "GitHub",
          "author_login": "actions-user",
          "committed_at": "2026-07-09T12:34:23Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "eb51770608f1c4cfe7e3e62111fc4d79767e88ff",
          "body": "fix(backtest): v1.20.1 修复回测引擎 3 个 bug(issues #22 #23 #25)",
          "is_bot": false,
          "headline": "Merge pull request #27 from handsomejustin/fix/v1.20.1-backtest-bugs",
          "author_name": "毛利哥",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-09T12:17:00Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "00b8c6374a92781f1aee06d75529e5780047e081",
          "body": "排查发现用户反馈的\"回测统计数据缺失/异常\"并非服务器连接问题,\n而是回测引擎与组合优化器自身的代码缺陷:\n\n#23: 首根 bar 访问 close[-1] 崩溃\n  _SeriesAccessor 负向越界改返回 NaN(不抛 IndexError);\n  BacktestEngine 新增 warmup_bars 参数跳过指标预热期。\n\n#25-A: FactorWeightedOptimizer 权重坍缩\n  n_stocks=2 且得分接近时,减最小值把低分标的权重压到 ~6e-8,\n  等于单股满仓、n_stocks 被无视,进而出现持仓1只/-99.98%回撤。\n  新增 _ap\n[…]\n据异常诊断\n  performance dict 新增 sharpe_ratio/start_cash/end_value 别名键;\n  资金曲线异常时返回 diagnostic_warning 而非静默全 0,CLI 显示提示。\n\n文档:README 加入回测手册导航;backtest_usage.md 补充 warmup 说明。\n测试:新增 6 个回归测试,更新 3 个;932 passed。",
          "is_bot": false,
          "headline": "fix(backtest): v1.20.1 修复回测引擎 3 个 bug(issues #22 #23 #25)",
          "author_name": "GitHub",
          "author_login": "actions-user",
          "committed_at": "2026-07-09T12:16:12Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e21c4d595c01e5fd43cc6fc03347ff1445e39855",
          "body": "feat(backtest): v4.2 新增 FSL 分水岭指标策略(单标的/组合/寻优)",
          "is_bot": false,
          "headline": "Merge pull request #26 from handsomejustin/feat/fsl-indicator-strategy",
          "author_name": "毛利哥",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-09T12:10:42Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "052119e6ae3d3a0f67413dbe7ccfbd184c68e430",
          "body": "- MyTT.py: 新增 FSL(CLOSE, VOL, CAPITAL) 分水岭指标\n  - SWL = (EMA(C,5)*7 + EMA(C,10)*3) / 10\n  - SWS = DMA(EMA(C,12), MAX(1, 100*SUM(VOL,5)/(3*CAPITAL)))\n    MIN(A,1.0) 模拟通达信 DMA(X,A) 内部钳制 A<=1 的语义\n- MyTT.pyi: 补 FSL 类型 stub(CI mypy 用 .pyi 校验)\n- strategies/builtin.py: 注册 FslStrategy(SWL/SWS 金叉死叉)\n  capital 作为 Param 暴露流通股本输入\n- strategies/presets.py: fsl 寻优网格(4 档 capital)\n- tests: 注册表断言加 fsl,阈值 18→19",
          "is_bot": false,
          "headline": "feat(backtest): v4.2 新增 FSL 分水岭指标策略(单标的/组合/寻优)",
          "author_name": "GitHub",
          "author_login": "actions-user",
          "committed_at": "2026-07-09T10:42:00Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "8bf4ffb322ab924777176bb109f5b763efa45d10",
          "body": "feat: v1.20.0 服务器失败自动 ping 切换(无需手动 easy-tdx ping)",
          "is_bot": false,
          "headline": "Merge pull request #20 from handsomejustin/feat/auto-failover",
          "author_name": "毛利哥",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-08T10:13:03Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d0d65d64b8c54299d588229897a90195305541d4",
          "body": "服务器连不上或返回空数据时,自动测速切到延迟最低的可用服务器再试,\nPython API / CLI / Web API 三入口全部自动生效。\n\n核心改动:\n- _reconnect.py 新增 select_best_host_sync/async(连接失败 failover)\n  和 find_working_host_sync/async(空数据逐台实测)\n- 8 个 client 的 _execute 注入跨主机故障转移(复用 auto_reconnect 开关)\n- get_market_stat 空数据时按延迟顺序逐台实测找返回数据的服务器\n- 新增 _reconnect/_are\n[…]\nect helper 收敛重建连接副本\n- MacClient failover 用 save_best_mac_host(延续 v1.19.4 不污染 best_host)\n- 顺手修复 test_commands_offline 未使用 import(main CI failure 根因)\n\n测试:925 passed(新增 18 个 failover 测试),ruff/mypy 零新增错误。",
          "is_bot": false,
          "headline": "feat: v1.20.0 服务器失败自动 ping 切换(无需手动 easy-tdx ping)",
          "author_name": "GitHub",
          "author_login": "actions-user",
          "committed_at": "2026-07-08T10:07:37Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "14debdb44699ca46e05737ad5de5418d89a5f232",
          "body": "新增 web UI 第六个页面「服务器设置」,让用户在浏览器上:\n1. 列出全部 50+ 候选服务器\n2. 一键并发测速(显示延迟和可达性)\n3. 点选切换到最快或可用的服务器(热重连,无需重启)\n\n解决不同地区/运营商对通达信各服务器连通性不同的问题。\n\n新增:\n- AsyncTdxClient.reconnect_to(host) 热切换方法(持锁,关旧建新)\n- GET/POST /api/v1/server/hosts|test|switch 三个端点\n- ServerSettingsView.vue 页面(列表+测速+切换)\n- 导航栏第六个入口\n\n设计: 先reconnect成功再save_best_host(避免污染config),\nhost校验只允许候选列表内IP,不自动测速避免首屏卡顿。",
          "is_bot": false,
          "headline": "feat(web-ui): v1.19.7 服务器设置页面(测速+切换通达信服务器)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-07T15:08:32Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c2c3e7ffb4e823d14b9259c71acf1a1b26039b3e",
          "body": "v1.19.5 EXE 只有 11MB,双击报 ModuleNotFoundError: No module named 'pandas'。\n根因:release.yml 先 pip install -e '.[web,packaging]' 再 Build frontend,\n但 force-include 要求 web-ui/dist 在 pip install 时就存在——install 阶段\ndist 不存在导致 editable install 静默降级,PyInstaller 收集不到第三方包。\n\n修复:调换步骤顺序,Build frontend 在 Install Python deps 之前。\nci.yml 上一 commit 已同样修复。",
          "is_bot": false,
          "headline": "fix(release): v1.19.6 release.yml 先构建前端再 pip install(修复EXE丢依赖)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-07T14:23:30Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e8b088bca18850ef7cf15ea6c72c50fe85c65cac",
          "body": "pyproject.toml 的 force-include 要求 web-ui/dist 存在,否则 pip install -e .\n报 FileNotFoundError。ci.yml 的 test 和 mypy job 此前没有构建前端步骤,\n导致全部 CI 失败。\n\n修复:test(6 矩阵)和 mypy job 在 pip install 前加 setup-node + npm ci +\nnpm run build。frontend job 本来就有,不变。\n\n同时清理 pyproject.toml:恢复 force-include(之前误改成 build-hook 但\nhatchling 自定义 hook 需要插件注册,过于复杂)。",
          "is_bot": false,
          "headline": "fix(ci): ci.yml test/mypy job 先构建前端再 pip install",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-07T14:14:03Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9ab093a3c77281bdab634838fc2b7148d7eac7bf",
          "body": "python -m build 先构建 sdist 再从 sdist 构建 wheel。sdist 默认尊重 .gitignore\n不含 web-ui/dist(构建产物),导致 wheel 阶段 force-include 找不到源报\nFileNotFoundError。用 [tool.hatch.build] artifacts 强制 sdist 和 wheel\n都包含 web-ui/dist。\n\n本地验证:sdist 含 4 个 dist 文件,wheel 含 4 个 web/dist 文件。",
          "is_bot": false,
          "headline": "fix(build): hatchling artifacts 让 sdist 也含 web-ui/dist",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-07T12:50:48Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d5309454bcac075c9f7aa388040a2b7fd38de937",
          "body": "根因:PyPI wheel 只有 Python 包,不含前端 dist。_resolve_web_dist_dir\n三级探测(env/_MEIPASS/仓库根)在 PyPI 安装环境下全失败返回 None,\nStaticFiles 不挂载,GET / 返回 404。\n\n修复:\n- pyproject.toml hatchling force-include 把 web-ui/dist 映射到包内\n  easy_tdx/web/dist\n- publish.yml 在 python -m build 前先 npm ci && npm run build\n- _resolve_web_dist_dir 新增第 4 级探测:包内 easy_tdx/web/dist\n\n验证:本地构建 wheel 含 4 个前端文件,模拟 PyPI 安装后第 4 级探测\n成功找到 dist。907 测试全过。",
          "is_bot": false,
          "headline": "fix(packaging): v1.19.5 PyPI wheel 含前端 dist(pip install 后不再 404)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-07T12:46:26Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3bc15e620c66ba662d7d2296aefcbd2b4f1dd9bd",
          "body": "根因:mac/client.py 的 MacClient.from_best_host 和 AsyncMacClient.from_best_host\n调用 save_best_host(best),把选中的 MAC 协议服务器(如 121.36.248.138)\n写进了全局 best_host 字段——但这个字段是标准 TDX 协议用的。之后标准\nAsyncTdxClient 用 get_best_host 读到这个 MAC host,用标准协议请求 MAC\n服务器,返回空 body(偏移 2 剩余 0)。web app lifespan 启动 MAC 客户端\n时触发污染。\n\n修复:\n- c\n[…]\nave_best_mac_host\n- mac/client.py 4 处改用新字段(不再碰 save_best_host / get_best_host)\n- get_best_host 加交叉污染校验:缓存 host 不在标准列表里自动重置\n  (自动修复已被污染的 config.json,用户无需手动操作)\n\n测试:新增 5 个回归测试(污染检测 + MAC host 隔离),907 全过。",
          "is_bot": false,
          "headline": "fix(config): v1.19.4 MAC客户端不再污染标准best_host(取不到数据根因)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-07T12:36:44Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "032f5a40c06c77ee2e44033775f3fba8b79dc67d",
          "body": "两个独立问题(日志证据):\n\n1. K线空body 500:SH600519 等正常股票偶发请求时,通达信返回\n   ret_count>0 但 body 完全为空(偏移2 剩余0)。v1.18.3 的容错有\n   'if bars:' 条件,bars 为空时 raise → 500。移除该条件,始终 return\n   (空列表让前端分页重试比 500 友好)。GetIndexBarsCmd 同改。\n\n2. SPA fallback 缺失:/optimize /portfolio 等前端路由刷新时 404。\n   子类化 StaticFiles 为 SPAStaticFiles,404 时返回 index.html。\n   API 路径不受影响。\n\n测试:更新 test_security_bars_truncated_first_record(原断言 raise,\n现断言返回空列表)+ 新增空 body 回归测试。902 全过。",
          "is_bot": false,
          "headline": "fix(web): v1.19.3 K线空body不再500 + SPA路由刷新404",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-07T12:13:39Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6b514874772aaf4ca41cec8c584a6a1f50f0700a",
          "body": "根因:测试用 max_workers=1 串行执行 5 个任务,断言「前 2 个被淘汰」。\n但 LRU 淘汰发生在 submit() 时,淘汰的是「当时最旧的非 running 任务」。\n快机器上 t0 还在 running(被跳过),实际淘汰 t1/t2;慢机器(CI windows\n3.12)上 t0 已完成,淘汰 t0/t1——取决于提交速度 vs 执行速度的竞态。\n\n修复:不断言特定 task_id 被淘汰,改为验证不变量:\n(1) 最后提交的任务一定存活(LRU 最近)\n(2) 至少淘汰 2 个(5 - max_results 3)\n(3) 存活任务数 ≤ max_results\n\n本地跑 10 次全过,不再 flaky。",
          "is_bot": false,
          "headline": "test: 修复 test_task_runner_lru_eviction 的竞态 flaky",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-07T07:47:13Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "27a8ab1c7630ac19f21233a002e58640cc0a324c",
          "body": "合并 e3e8dd4 时采用远程版本,但其 _log.warning 字符串换行未跑\nruff format,导致 ci.yml 的 format 检查失败。纯格式调整,无逻辑变化。",
          "is_bot": false,
          "headline": "style: ruff format security_bars.py(修复 ci.yml format 检查)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-07T07:27:20Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ecd6e298f51ab25d0b9de5121e80c1845d073c16",
          "body": "根因:干净 Windows(无开发环境)注册表里没有 .js 的 Content Type 映射,\nmimetypes.guess_type('.js') 返回 None,StaticFiles 回退到 text/plain。\n浏览器 <script type=\"module\"> 启用严格 MIME 检查,拒绝执行 text/plain\n的 JS → Vue 不挂载 → 页面纯黑(/docs 不受影响因为它是 API 路由)。\n\n修复:mount 前用 mimetypes.add_type 强制注册 .js/.mjs/.css/.svg 的正确\nMIME,无论机器装没装开发工具都生效。\n\n同时改 easy_tdx.spec:datas 用 pathlib.rglob 逐文件展开,更可靠。",
          "is_bot": false,
          "headline": "fix(packaging): v1.19.2 修复干净 Windows 上 EXE 页面纯黑(MIME 类型)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-07T07:18:53Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "957cb8eeb0237ed23ffa3cc0818cc836d08e1eb9",
          "body": "# Conflicts:\n#\tCHANGELOG.md\n#\tpyproject.toml\n#\tsrc/easy_tdx/commands/security_bars.py",
          "is_bot": false,
          "headline": "Merge commit 'e3e8dd4'",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-06T17:54:20Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0b4ed9af62e78eac0d05a3716576a7d6c8ab3d6d",
          "body": "面向零基础老年用户,easy-tdx 可打包成单一 Windows EXE,双击即用。\n\n新增:\n- 后端同源托管前端 dist(app.py 三级探测:env → _MEIPASS → web-ui/dist)\n- easy-tdx serve 默认 --open-browser,启动后自动开浏览器\n- PyInstaller 打包入口(__main__.py)+ spec 配置(easy_tdx.spec)\n- 系统托盘(tray.py):右下角图标,右键\"打开浏览器/退出\"\n  解决老人不会用任务管理器关闭的问题\n- GitHub Actions release.yml:打 v* tag\n[…]\nimport __main__ → freeze_support + 子进程检测\n  3. 系统托盘需主线程消息泵 → uvicorn 挪到后台线程\n\n文档:\n- README/手册改为三档分流:EXE(零基础)/ Python(一条命令)/ 源码(打包)\n- 删除 npm run dev / 5173 / 两个终端的过时说明\n- 手册补虚拟环境配置 + EXE 打包附录 + EXE 排错 FAQ",
          "is_bot": false,
          "headline": "feat(packaging): v1.19.1 支持 Windows 单 EXE 打包 + 系统托盘 + 自动发版",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-06T17:48:07Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e3e8dd492eea9551923acffd709bf08d7b1b6f00",
          "body": "- security_bars: parse_response 遇末尾残缺记录时丢弃并返回前 N-1 条,\n  避免 TDX 服务端截断响应导致整页 500(如 000408 count=800 日线)\n- OptimizeView: 一键寻优并发默认 8 进程,用户选择持久化到 localStorage\n\nbump version to 1.18.3",
          "is_bot": false,
          "headline": "fix: K线响应截断容错 + 一键寻优并发默认8进程",
          "author_name": "GitHub",
          "author_login": "actions-user",
          "committed_at": "2026-07-06T05:23:37Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c901d198cafaf56581bd97c6039611fb82efe830",
          "body": "记录移除拼音声母搜索的回退发版",
          "is_bot": false,
          "headline": "chore: bump version to 1.18.2 + CHANGELOG",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-05T18:55:58Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c9d80617e815e8f547a8ffb5f1444091ecfcc52a",
          "body": "拼音搜索的底层依赖(首次需爬沪深 A 股 5000 条全名单,几十次 TDX 协议\n往返,慢机器几十秒到超时)太重,反复优化(按需加载/遮罩/单飞/预热)\n都无法兼顾'不阻塞核心行情请求'与'首次可用'。用户决定放弃此功能,\n回到简单稳定的 6 位代码输入。\n\n回退 e2bf29e..2523605 共 6 个 commit 的全部改动:\n- 删除 StockSearchInput / AppInitOverlay / useStockSearch\n- 移除 pypinyin 依赖、/security/search-index 端点、lifespan 预热\n- SymbolPicker / StocksPicker 恢复为纯 6 位代码输入\n- README / CHANGELOG 同步回退\n\n代码状态等同 v1.18.1(一键寻优多进程并发)发布后的干净基线",
          "is_bot": false,
          "headline": "revert: 移除拼音声母搜索,回到 6 位代码输入",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-05T18:50:51Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "25236056f7f9bbbd9e6dd8cebc2030ea606ff877",
          "body": "问题:上一版单飞用 asyncio.Future 共享,lifespan 预热用 wait_for(120s)\n超时会 cancel 整个 Future,导致同时等待的 /security/search-index 端点\n请求收到 CancelledError 返回 500。日志显示预热 TimeoutError + 端点 500。\n\n修复:\n- _build_search_index 改用轮询设计:后台 _do_build task 独立运行\n  (fire-and-forget),调用方通过轮询 _SEARCH_INDEX 每 0.5s 检查结果\n- 调用方被 cancel(预热超时)只退出轮询,不影响后台构建 task\n- lifespan 预热去掉 wait_for,改为纯 best-effort 触发,无权取消构建\n- 冒烟验证:预热被 cancel 后,端点请求仍能正常拿到结果(5206 条)\n\n另外从日志看慢机器爬全名单 >120s,轮询上限提到 300s 兜底",
          "is_bot": false,
          "headline": "fix(web): 单飞改用轮询解耦,预热超时不再波及端点请求",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-05T18:39:02Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "04fd720a05d5ef9d8a4ee9e9c2209ccef91ab44c",
          "body": "问题:另一台机器首次加载报 'signal is aborted without reason'——\n不是取不到数据,是前端 AbortController 15 秒就掐断了请求,但遮罩\n却告诉用户等 30-60 秒,自相矛盾。\n\n修复:\n- 前端 fetchSearchIndex 超时 15s → 120s:遮罩本就是要等的,不能自己掐断\n- 后端 _build_search_index 单飞去重:用 asyncio.Future 让 lifespan 预热\n  与 /security/search-index 端点请求共享同一任务,避免并发各爬一次全名单\n  (两个任务抢同一把 _execute_lock 反而互相拖慢)\n- lifespan 预热改为调 _build_search_index(复用单飞),加 120s 超时保护",
          "is_bot": false,
          "headline": "fix(web-ui): 搜索索引前端超时 15s→120s + 后端单飞去重",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-05T18:31:20Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6b01c0687c6678016c89a5d03ad6def570d31562",
          "body": "新机器首次打开页面时,拼音搜索索引构建要走全量 get_security_list_all\n(几十秒),期间无任何反馈,体验极差。本次双管齐下解决:\n\n前端:\n- 新增 AppInitOverlay 全局遮罩:加载期间全屏灰底 + blur + spinner +\n  诚实文案(预计 30-60 秒)。就绪后 fade-out;失败也消失(不卡死)\n- useStockSearch 改为模块级响应式状态,App 根 setup 时 eagerLoad 触发\n- 方案 A:无跳过按钮强制等待(一次性代价),失败时降级为可手输代码\n\n后端:\n- lifespan 启动钩子加后台预热:asyncio.create_task 触发一次\n  get_security_list_all('all'),不阻塞服务启动,让本地缓存提前建立\n- 用户打开页面时后端缓存大概率已就绪,遮罩可秒消失",
          "is_bot": false,
          "headline": "feat(web-ui): 股票搜索索引加载期间全站遮罩 + 后端启动预热",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-05T18:20:12Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9048dfecaa1275cfba06963481bc108a7b1f013a",
          "body": "问题:新机器首次打开回测/寻优页时,组件挂载即触发 /security/search-index,\n后端要走全量 get_security_list_all(几十次 TDX 协议往返,几十秒)。\n而 AsyncTdxClient 是单连接 + _execute_lock,索引构建期间独占连接,\n把 /bars 行情请求和寻优请求全部堵在后面,表现为点「一键寻优」后\n等好久才出现第一根 bars 请求。\n\n修复(三招组合):\n- useStockSearch: 挂载即拉 → 改为显式 ensureLoaded(),只在首次聚焦\n  输入框时才触发,确保「打开页面直接点取行情/寻优」的核心路径不被拖累\n- fetchSearchIndex: 加 AbortController 15s 超时,避免长时间独占连接\n- 静默失败降级:索引拉取失败只记 loadError 显示小 ⚠,不弹错不阻塞",
          "is_bot": false,
          "headline": "fix(web-ui): 拼音搜索索引改为按需加载,避免首次构建阻塞行情请求",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-05T18:01:47Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "dfbafd5ad44b178c993ada85d4f4ed4f7362bd96",
          "body": "- 前置条件代码块注释加 pypinyin\n- 新增 ⚠️ 升级注意:旧版本升级必须重跑 pip install -e \".[web]\" 否则搜索端点 500\n- 单标的回测取行情描述补上拼音声母搜索能力说明",
          "is_bot": false,
          "headline": "docs(readme): 补充拼音声母搜索的升级提示(v1.19.0 需重装 [web] extra)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-05T17:49:49Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e2bf29e931171a44edbf14441948d41e11d95903",
          "body": "所有股票代码输入框(回测/寻优/组合页)支持代码/中文名/拼音声母三路\n匹配的下拉联想。复用项目里早已有但从未被前端消费的 security_list_all\n数据(沪深 A 股 5206 只完整中文名表)。\n\n后端:\n- 新增 GET /api/v1/security/search-index 端点,返回 [{code,name,initials}]\n- 声母用 pypinyin FIRST_LETTER 预计算,进程内缓存,首次算一次常驻\n- web extra 新增 pypinyin>=0.50 依赖\n\n前端:\n- 新增 useStockSearch composable(模块级缓存 + 三路过滤 + 防抖)\n- 新增 StockSearchInput.vue(下拉 + 键盘导航 + 市场标签)\n- SymbolPicker / StocksPicker 接入新组件,保留直接输 6 位代码的快路径\n\n实测 zjxc→中际旭创、gzmt→贵州茅台、cjxc→楚江新材,5000 条本地过滤 <5ms",
          "is_bot": false,
          "headline": "feat(web-ui): 股票代码输入支持拼音声母搜索(zjxc→中际旭创)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-05T17:43:57Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "044e21064c9ed18843be7bcdc04fd15f0a72cc9e",
          "body": "本次发布两个主题:\n1. 一键寻优所有策略支持多进程并发(串行/4/8/16)\n2. 策略库组合回测结果加组合评级 + 市场前缀纠正",
          "is_bot": false,
          "headline": "chore: bump version to 1.18.1 + CHANGELOG",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-05T16:59:41Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d32c5e17b443e92c1d7adf3aedc266bcbdd18236",
          "body": "回测是 numpy/pandas 的 CPU 密集计算并持有 GIL,多线程无加速,必须用\n多进程。照搬项目里已跑通的 screen/scanner.py 进程池模板。\n\n后端:\n- OptimizeAllBacktestRequest 新增 workers 字段(默认 1=串行,0-32)\n- 抽出模块顶层函数 _optimize_one_strategy(可被 ProcessPoolExecutor\n  pickle),策略类在子进程内构造,避开跨进程 pickle 问题\n- _run_optimize_all 在 workers>=2 时用进程池并行,否则走原串行逻辑\n- 顺带修复 backtest_schemas.py 的 E501 历史遗留\n\n前端:\n- OptimizeView 配置区新增「一键寻优并发」选择器:自动检测 CPU 核数\n  + 串行/4/8/16 进程下拉,默认串行,标注推荐档 min(CPU, 8)\n- types.ts/api.ts 透传 workers 字段\n\n实测 8 进程 vs 串行提速 4-6x;端到端冒烟验证并行与串行结果完全一致",
          "is_bot": false,
          "headline": "feat(web-ui): 一键寻优所有策略支持多进程并发(串行/4/8/16)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-05T16:59:26Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5aec4996c63142e89e5f01b6fe1edde821751acc",
          "body": "- StrategiesView: 组合回测结果区新增「组合评级」详情块,复用\n  gradePortfolio(与 /portfolio 页和单标的回测页同口径的 5 维度评分)\n- StrategiesView: 新增 normalizeSymbol,发请求前用 detectMarket 重算\n  市场前缀,纠正历史保存策略的错配(如 SZ:515030 → SH:515030)\n- BacktestView: fullSymbol 改用 market.ts 的 detectMarket,修复 5 开头\n  沪市基金/ETF 被错判为深市的问题(与 SymbolPicker/StocksPicker 统一)",
          "is_bot": false,
          "headline": "feat(web-ui): 策略库组合回测结果加组合评级 + 市场前缀纠正",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-05T16:59:13Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6f121dc156f05df6db73c0b2f8974b8430df2f27",
          "body": "- OptimizeView「查看」拼 URL 前从输入框实时值取 code/startDate/endDate/category,\n  导致两个问题:(1) 寻优后改输入框代码再点查看,URL 指向没回测过的标的;\n  (2) 切走再回 /optimize,组件 ref 丢失,URL 里 symbol/startDate/endDate 全空。\n- 将标的上下文快照(取行情成功那一刻冻结)下沉到 Pinia store 的 optimizeContext,\n  与 optimizeResult/optimizeAllResult 同生命周期持久化,跨路由保留。\n- buildBacktestQuery 改读 store.optimizeContext,输入框改动与组件卸载都不再污染。",
          "is_bot": false,
          "headline": "fix(web-ui): 参数寻优「查看」用寻优时的标的快照,避免输入框/路由切换污染 URL",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-05T10:03:12Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "173069d5181659336976676d036f0902e1e0b989",
          "body": "get_unusual 的 count 实际被 min(count,600) 截断,写 1000 也只能拿到 600。\n改用 start 翻页 + pd.concat 合并,直到某页不足 600 视为到尾。",
          "is_bot": false,
          "headline": "docs(example): 异动示例改为分页拉取全部(单次 600 协议上限)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-05T10:02:38Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5c520c513d03068bde5338ea99e75bb91e3bb2f4",
          "body": "新增「第十章补充」完整章节,详细写:\n- 如何保存策略组合(💾 保存为组合按钮 + 弹窗)\n- 如何载入组合(↻ 重跑到今天,自动用今天重跑)\n- 持仓三态徽章含义(持有/持有·浮亏/空仓·等买点)\n- 三处警示(过拟合/模型仓位水印/载入确认)\n- 完整日常使用流程(每天收盘后重跑看信号变化)\n- 组合的局限(模型仓位≠真实账户、资金均分、过拟合风险)\n\n补充更新:\n- 第四章 4.4 加投资大师名言轮播说明(100 条/3 秒轮播)\n- 第六章 6.2 加策略库 Tab 分类说明(单标的/组合)\n- 封面 + footer 版本号 v1.17.14 → v1.18.0\n- 目录加第十章补充链接\n\n保持原作者口语化新手教学风格,沿用原文半角标点惯例。",
          "is_bot": false,
          "headline": "docs: 上手手册更新到 v1.18.0(策略组合保存+一键看今日信号+Tab 分类)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-04T19:06:40Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c57cde156b23ea427d78e3306a164394b6ce69ac",
          "body": "v1.18.0\n\n策略库新增 kind='multi' 类型:组合回测结果可保存为组合,\n卡片「↻ 重跑到今天」一键用今天重跑得到截至当天的策略信号\n(持仓/空仓/浮盈/浮亏三态高亮)。策略库拆成「单标的」「组合」\n两个 Tab,避免数量多了混排难找。\n\n核心机制:保存配置 → 载入即重跑到今天 → 看持仓表 = 今日信号。\n复用现有 SQLite 单表(kind 字段本就是 TEXT),零数据库迁移,\n零后端逻辑改动,仅 schema Literal 加 'multi'。\n\n含 10 项代码审计修复:multi-icon 字体误用、lastComboCash 漏赋、\n弹窗 ARIA/ESC/\n[…]\nvue(保存组合+Tab+三态+审计修复)\n- 改 src/easy_tdx/web/backtest_schemas.py(kind Literal 加 'multi')\n- 改 web-ui/src/types.ts(TS 类型同步)\n- 改 CHANGELOG.md(补 v1.17.15 + 新增 v1.18.0)\n- bump pyproject.toml 1.17.15 → 1.18.0",
          "is_bot": false,
          "headline": "feat(web-ui): 策略组合保存 + Tab 分类 + 审计修复 (v1.18.0)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-04T19:01:09Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "15af7a92eeddc151d22b7aa7bd0dd7b69a12130e",
          "body": "v1.17.15\n\n一键寻优所有策略按钮改为暖橙渐变(#f59e0b → #ea580c),比 primary\n蓝更醒目,hover 加亮上移、运行中保留暗橙识别度。\n\n寻优运行中右侧不再空白:展示 100 条全球投资大师名言(巴菲特/芒格/\n格雷厄姆/林奇/索罗斯/利弗莫尔/塔勒布/达利欧等),3 秒随机轮播一条,\nFisher-Yates 洗牌避免短期重复,让等待不枯燥、还能学到东西。\n\n视觉细节:\n- 卡片顶部 3 秒线性进度条(暗示后台在跑)\n- fade + slide-up 350ms 过渡动画\n- 底部脉动橙点 + '后台寻优进行中' 提示\n\n纯前端 TypeScript + 原\n[…]\n零依赖新增。\n\n- 新增 web-ui/src/data/investment-quotes.ts(100 条名言)\n- 新增 web-ui/src/components/QuoteCarousel.vue(轮播组件)\n- 改 web-ui/src/views/OptimizeView.vue(按钮样式 + 引入轮播)\n- bump pyproject.toml 1.17.14 → 1.17.15",
          "is_bot": false,
          "headline": "feat(web-ui): 一键寻优按钮改橙色 + 等待期间投资大师名言轮播",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-04T18:17:22Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "cf843cebaddace8a0a4b8bdb3ec821132c7e470a",
          "body": "纯前端 TypeScript 实现,零后端改动。回测/组合/寻优三处结果顶部\n显示评级徽章,让普通人 1 秒判断「适不适合经常参与」。\n\n评级不看收益率(避免被近期大涨误导),只看风险调整后的持有体验:\n卡玛/最大回撤/胜率/利润因子/夏普/波动率六维加权 + 一票否决。\n京东方「收益 126% 但胜率 35%」案例 = D 档(核心验证点)。\n\n长线低频策略(6年6笔)不会被冤枉:交易<10笔时只降权胜率/利润因子\n维度,不否决整个评级(修复用户实测反馈)。\n\n- 新增 web-ui/src/grading/ 核心模块(engine/thresholds/combinedMetrics/index)\n- 新增 GradeBadge.vue + GradeDetails.vue 组件\n- 接入 BacktestView/PortfolioView/OptimizeView(寻优排名表加评级列)\n- 15 个自检测试(node:test + rolldown 打包),覆盖京东方 D / 长线 B 等场景",
          "is_bot": false,
          "headline": "release: v1.17.14 — Web UI 数据评级系统(S/A/B/C/D 五档,三入口覆盖)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-04T17:21:18Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "66c49d1a8d26bbaf86ef04d9b6ebe0176664a68a",
          "body": "单标的回测、组合回测、参数寻优原默认起始日为「3 年前」(动态滚动),\n统一改为固定日期 2020-01-06,便于跨时间横向对比。结束日仍为今天。\nSymbolPicker 共用组件的 defineModel 默认值同步更新。",
          "is_bot": false,
          "headline": "feat(web-ui): 三处回测入口默认起始日期统一为 2020-01-06",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-04T16:15:00Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "64d08122574dd71facff4730a3d48db064479d51",
          "body": "web-ui 此前只有本地 vue-tsc,CI 不覆盖,类型错误 / 破坏性 import\n只有发布后用户才发觉。新增独立 frontend job 跑 npm ci + npm run build,\n与 Python 版本无关故独立于 test matrix,避免跑 6 遍。\n\n本地 npm run build 验证通过(686 模块,575ms)。",
          "is_bot": false,
          "headline": "ci: 新增 frontend job 防前端构建回归(v1.17.9 数值 bug 教训) (#16)",
          "author_name": "毛利哥",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-04T16:03:14Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2f3826d84d10a2bbc6f0f578f23c1f904b21f201",
          "body": null,
          "is_bot": false,
          "headline": "docs(README): 添加头部宣传图",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-04T15:14:39Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2f4d7bef8e37710c51ec9b4a9cfe5c825f8d3e64",
          "body": null,
          "is_bot": false,
          "headline": "docs(README): 添加 DeepWiki 徽章",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-04T15:03:33Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "239ff46ac872b166a337cfb48f1367d0ea0eb3fa",
          "body": "用户反馈:3 个策略各自最大回撤仅 45.53%/40.16%/16.89%,组合却显示 83.76%。\n根因:_build_combined_equity 的 drawdown_pct 分母误用 initial(固定初始资金),\n净值大涨后(总收益 545%,峰值≈6.45×初始)绝对回撤额除以小初始值被等比放大。\n改为 drawdown / peak(相对当时峰值,0~1),与单标的 PortfolioTracker 一致。\n连带修复卡玛比率(年化/最大回撤)。其余指标经核对均正确。加回归守卫。",
          "is_bot": false,
          "headline": "release: v1.17.13 — 修复多策略组合回测最大回撤虚高(分母误用初始值而非峰值)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-04T13:07:39Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ee628c1b345c5560601888fb3dec5eae230d52e2",
          "body": "DELETE /api/v1/strategies/{id} 原用 status_code=204,较新 FastAPI/Starlette\n在路由注册阶段(add_api_route)就抛 AssertionError: Status code 204 must\nnot have a response body,导致 CI ubuntu 矩阵 21 个 web 测试因 fixture\n导入 router 连带 ERROR。改为返回 200 + {\"deleted\": id} 确认体。",
          "is_bot": false,
          "headline": "release: v1.17.12 — 修复 CI 在新版 FastAPI 上路由注册失败",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-04T12:50:07Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "05dc9a74af499d793e197d931a7f5d4be30eae14",
          "body": "新增两层能力:\n1. 策略库:单标的/组合回测结果可保存到本地 SQLite 单文件\n   (~/.easy_tdx/strategies.db),策略库页可载入回填、重跑、删除。\n2. 多策略组合回测:勾选 N 个单标的策略,各拿 1/N 资金、各跑原标的,\n   净值曲线按日期并集对齐求和,组合结果含 19 项完整绩效指标 + 持仓表。\n\n后端:strategy_store.py(SQLite CRUD) + multi_strategy_engine.py(资金分仓引擎)\n+ routers/strategies.py + /backtest/multi-strategy/run/async。\n前端:StrategiesView.vue + 保存策略按钮 + 复用组合页图表组件。\n\n895 单测全绿(+24 新增),ruff/mypy strict/前端 vue-tsc 全通过。",
          "is_bot": false,
          "headline": "release: v1.17.11 — Web UI 策略库(SQLite 持久化)+ 多策略资金分仓组合回测",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-04T12:40:57Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "4db7104209bdb019aedd875ca89fecf292edea11",
          "body": null,
          "is_bot": false,
          "headline": "docs(README): Web UI 截图改为一行一张",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-04T03:39:44Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "1838995c519703ef4e66f48b908a67edc6db2a33",
          "body": null,
          "is_bot": false,
          "headline": "docs(README): 添加 Web UI 回测可视化截图展示",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-04T03:37:01Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "07205b616698f07c087fcbe9ef4953e6df7d03ba",
          "body": "- SymbolPicker code/category/startDate/endDate 改 defineModel 双向同步\n- OptimizeView 两个「查看」按钮跳转 URL 附带 symbol/startDate/endDate/category\n- BacktestView onMounted 回填标的/周期/日期到 SymbolPicker 表单\n- 向后兼容:老 URL(只有 strategy/params) 仍正常,缺失字段保持默认\n- 前端 vue-tsc / vite build 通过,后端 870 单测全绿(无回归)",
          "is_bot": false,
          "headline": "release: v1.17.10 — 一键寻优「查看」跳转携带完整行情上下文",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-03T20:12:46Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "57cad1f3dbb8fbce982ff97ef796a02aac747e4e",
          "body": "- 交易盈亏指标改为收益率口径(avg_win/loss/max_win/loss = pnl/cost_basis)\n- 平均持仓天数改用真实日历日相减(原 YYYYMMDD 整数差跨月放大)\n- 盈亏比无亏损时记 999.0(原 0.0,与 100% 胜率自相矛盾)\n- 新增 Trade.cost_basis 字段 + engine 填充\n- 3 个回归守卫;870 单测全绿,ruff/mypy strict/前端 vue-tsc 通过",
          "is_bot": false,
          "headline": "release: v1.17.9 — 修复回测交易统计离谱数值(前后端口径错配 + 持仓天数跨月放大)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-03T19:55:05Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f7cb015ec6e69cd439d67a555a034d6074f80eca",
          "body": "在 Python API「扩展市场」章节加 goods_transaction_all 示例(港股当日全部逐笔),\n并补 start 倒序语义说明(start=0 为最新/收盘方向,count=2000 默认只取最近 2000\n笔会集中在尾盘,需全天用 goods_transaction_all)。同步 GitHub Wiki。",
          "is_bot": false,
          "headline": "docs(README): 补充港股 goods_transaction_all + 逐笔倒序语义说明",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-03T19:19:11Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "cd790c9404157226fd1f7ee704307e6589fb86c8",
          "body": "test_task_runner_does_not_evict_running 用 release.wait(timeout=5) 钉住慢任务保持\nrunning,但 CI 慢环境(windows 3.10)下整个测试执行超过 5s 后任务因超时自动完成、\n状态变 done,掩盖了「running 任务被 LRU 错误淘汰」的回归断言。\n\n改为 timeout=30 留足 CI 慢环境余量(release.set() 仍是确定性释放点)。该测试用于\n守护 LRU 淘汰跳过 running 任务的并发正确性,与港股逐笔成交无关,是 v1.17.2 引入的\n预先存在问题。\n\n本地连跑 5 次稳定通过,867 单测全绿。",
          "is_bot": false,
          "headline": "release: v1.17.8 — 修复 windows 3.10 flaky 测试超时",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-03T18:24:48Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "56153eeb971dbf58ecf598553c5a9afd9e954227",
          "body": "1.17.5 引入的 tests/fixtures/ex_history_transaction.json 含中文注释,Windows CI\n默认用 cp1252 解码 UTF-8 文件触发 UnicodeDecodeError: 'charmap' codec can't\ndecode byte 0x8f,导致 3 个 Windows 矩阵的 test_parse_ex_history_transaction_hk\n失败(Linux 矩阵因默认 utf-8 不受影响)。\n\n修复:所有 fixture 读取显式指定 encoding='utf-8':\n- test_hk_transaction.py 的 load_hex/load_json(直接触发点)\n- test_commands_offline.py 的 load_hex(纯 ASCII hex,当前未触发但一并统一)\n- test_decode_errors.py 的 _load_hex(同上)\n\n867 单测全绿,ruff format/check / mypy strict 通过。",
          "is_bot": false,
          "headline": "release: v1.17.7 — 修复 Windows CI fixture 读取编码",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-03T18:11:27Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "94f631a06b778483b397333b6dfce2a29244f3e0",
          "body": "回应 issue #14 后用户反馈:默认 count=2000 取回的成交时间全集中在尾盘。\n根因是通达信逐笔协议(A 股 0x122F 与港股 ex 0x23FC/0x2406 一致)的 start 为\n倒序语义——start=0 指向最新一笔(收盘方向),并非 bug。02715 全天成交 13327\n笔,count=2000 只取最近 2000 笔故集中在尾盘。\n\n新增:\n- goods_transaction_all(MacExClient 同步+异步)—— 港股股票类市场自动按 1800/页\n  翻页取全天全部逐笔成交,安全上限 50 页(90000 条)。返回协议原生倒序,需正序\n[…]\nart 倒序语义说明,引导需全天数据用\n  goods_transaction_all。\n\n修复:\n- 1.17.5 的 test_hk_transaction.py 未过 CI ruff format --check(注释对齐、\n  MacTransaction 单行化、文末空行)。\n\n867 单测全绿(+5 全量取数测试),ruff format/check / mypy strict 通过。",
          "is_bot": false,
          "headline": "release: v1.17.6 — 港股逐笔成交全量取数 + start 倒序语义文档",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-03T18:03:47Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "33239525a962223da17eef851dc24aa5e0dd07c5",
          "body": "CI 的 ruff format --check 报该文件需重格式化:参数化注释前双空格对齐为\n单空格、两处 MacTransaction 单行化、文末多余空行。仅格式调整,无逻辑变更。",
          "is_bot": false,
          "headline": "style(tests): ruff format 修复 test_hk_transaction.py (CI format check)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-03T17:45:35Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "40ae2eba53dbec1b212edcdd7d03ef1e49d1b3a2",
          "body": "MacExClient.goods_transaction 此前对所有扩展市场统一复用 A 股 MAC 协议的\nSymbolTransactionCmd(0x122F),但 0x122F 数据源未接入港股,服务器对港股\nmarket 一律返回 39 字节空响应(count=0)。\n\n港股股票类市场(HK_STOCK_MARKETS = {27,31,48,49,71,98})改走 ex 扩展行情\n协议:query_date=None → GetExTransactionDataCmd(0x23FC 当日),指定日期 →\nGetExHistoryTransactionDataCmd(0x2406\n[…]\n2F 路径不变。\n\n新增 src/easy_tdx/ex/_hk_transaction.py(市场判定 + 字段映射 + 自动分页,\n同步/异步共用);回归测试 24 例(tests/unit/test_hk_transaction.py)+ 真实\nfixture(港股 00700 在 2026-07-03 的 0x2406 响应)。862 单测全绿,ruff /\nmypy strict 通过。",
          "is_bot": false,
          "headline": "release: v1.17.5 — 港股逐笔成交协议路由修复 (issue #14)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-03T17:29:17Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "648c9ccfaf72f7594524e098e9cae05cda5583e9",
          "body": "v1.17.2 引入的 QFQ 重算代码(adjust.py/client.py/test_mac_qfq_integration.py)\n含 magic trailing comma 导致 ruff format 期望多行参数展开,CI 的\n'ruff format --check src/ tests/' 自 v1.17.0 起一直失败。纯格式变更,\n零逻辑改动(20 例 QFQ 测试全过)。",
          "is_bot": false,
          "headline": "style: ruff format 修复预存格式债(CI ruff format --check 转绿)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-03T16:17:06Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "78bbe46d016665352da37392310d444c5bca8626",
          "body": null,
          "is_bot": false,
          "headline": "release: v1.17.4 — Web UI 回测交互重构 + 一键寻优全策略",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-03T16:09:19Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ecd8af167656343845a820bb1503186cb799f005",
          "body": "- 取消单标的/寻优页独立的「取行情」按钮,整合进「开始回测/开始寻测」\n  (SymbolPicker 经 defineExpose 暴露 loadBars,父组件串联取数+回测)\n- 取消市场手动选择(沪/深/北交所下拉),改为 market.ts 按代码段智能\n  识别(17 边界用例验证),代码框旁显示识别结果\n- 成交价下拉精简为中文「开盘价/收盘价」,初始资金默认 100 万\n- 寻优页 ParamGridPicker 切换策略自动填入预设参数网格\n- 寻优页新增「一键寻优所有策略」按钮 + 全局策略排名表\n  (OptimizeView 复用 store.runOptimizeAll,结果区含最佳/排名/合计网格点)\n- types/api/store 新增 OptimizeAll 契约 + submitOptimizeAllTask\n- vue-tsc + vite build 通过",
          "is_bot": false,
          "headline": "feat(web-ui): 取行情整合 + 市场智能识别 + 一键寻优全策略",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-03T16:09:05Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f6ae69845da6895a154ced75bf22ec0fd9bd75fb",
          "body": "- 成交价精简为 next_open/next_close,删除 this_close/worst/best 三种\n  非真实模式(this_close 有未来函数偏差,worst/best 为压力测试边界)\n- 初始资金默认统一为 1,000,000(原 10万/20万)\n- 新增 presets.py:18 策略各配 1-2 参数的预设寻优网格(笛卡尔积≤200)\n- registry.to_schema() 返回 preset_grid 字段供前端自动填充\n- 新增 POST /backtest/optimize-all/run/async:逐策略预设网格寻优 +\n  全局排名(OptimizeAllResult:ranking/best/per_strategy/total_grid_points)\n- 新增 optimize-all 端到端单测 2 例(838 单测全绿)",
          "is_bot": false,
          "headline": "feat(backtest): 成交价精简 + 一键寻优全策略 + 预设网格",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-03T16:08:38Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c15bd8232fb90e165915c0d09308f58f11b81dcf",
          "body": "通达信服务端 QFQ 模式对长期重度除权股票(如 601088)深层历史页\n返回负价格,导致回测总收益 -3087%、回撤 326.85%、年化 nan、\nbollinger 崩溃、10 策略 invalid-value-in-scalar-power、MyTT divide-by-zero。\n\n客户端兜底:检测 QFQ 负价时用 NONE+XDXR 本地重算前复权\n(因子以除权日前一交易日含权收盘价为基准,保证除权日前后连续)。\n同步+异步双路径一致修复,失败降级返回原值。\n\n- 新增 src/easy_tdx/mac/adjust.py(纯函数 compute_forward_factor/\n  apply_forward_adjust/has_bad_prices)\n- MacClient/AsyncMacClient 触发本地重算,XDXR 按 (market,code) 缓存\n- tests: +20 例(16 纯函数 + 4 集成),844 全绿,ruff/mypy 通过",
          "is_bot": false,
          "headline": "release: v1.17.2 — QFQ 深层历史负价修复",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-03T14:42:04Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9245bb2ce9e86bc276acec6b95ad5d9eea238964",
          "body": "test_web_backtest.py:556 的 isinstance(x, (int, float)) 触发 ruff UP038。\nrequires-python>=3.10 支持 PEP 604 的 int | float 运行时语法, 转换安全。\n该报错会导致 GitHub Actions CI 失败。",
          "is_bot": false,
          "headline": "chore(tests): 修复 ruff UP038 (isinstance 元组 -> int | float)",
          "author_name": "GitHub",
          "author_login": "actions-user",
          "committed_at": "2026-07-03T07:42:24Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "cc9c1e264d392cc711d7a01f3b248f902ca4300d",
          "body": "PR #13 修复了 start_date=None 且最新交易日数据不完整时\ntail_offset 越界/读错位的 bug。原 3 个测试构造的都是完整数据\n(total = days*page_size),走 fallback 分支,未覆盖真正的 partial 分支。\n\n新增 test_multi_tick_charts_partial_day: 构造 days=3, page_size=4,\ntotal=6 的 body,验证 tick_counts 分配为 [2,4,0]、尾部元数据在\nactual_total 反推的偏移处正确解析。已反向验证(回退旧 tail_offset\n公式时该测试立即失败复现 '偏移 239 越界'),非空壳测试。",
          "is_bot": false,
          "headline": "test(tick_charts): 补 partial-day 回归测试覆盖 PR #13 修复",
          "author_name": "GitHub",
          "author_login": "actions-user",
          "committed_at": "2026-07-03T07:42:06Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ea1a5c6f4a2d94a5759cdaf2b84e892453a0c6be",
          "body": "修复easy-tdx tick SH 600519 --days 5",
          "is_bot": false,
          "headline": "Merge pull request #13 from shom/main",
          "author_name": "毛利哥",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-03T07:23:44Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "49c892b3172b29331dec2c2010f16a77bf2f4871",
          "body": null,
          "is_bot": false,
          "headline": "docs: 扩充 README Web UI 使用说明(前置条件/启动/四页操作步骤/注意事项)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-03T04:04:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ef1fea6fcd792ab80f9750c667fc738612721d3b",
          "body": null,
          "is_bot": false,
          "headline": "修复easy-tdx tick SH 600519 --days 5",
          "author_name": "shom",
          "author_login": "shom",
          "committed_at": "2026-07-03T03:58:07Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "bd419da83df9f6db11950dfa55093b24f9e0ffb2",
          "body": "回测从命令行升级到浏览器可视化。Vue3 + ECharts 单页应用,零代码完成\n单标的回测、组合回测、参数网格寻优、结果对比四大场景。\n\n后端新增回测 REST API + 策略注册表 + 后台任务执行器,\n内置策略从 5 个扩充到 18 个。823 单测全绿。\n\nREADME 新增 Web UI 章节,CHANGELOG 记录本次全部变更。",
          "is_bot": false,
          "headline": "release: v1.17.0 — 回测可视化 Web UI(单标的/组合/寻优/对比)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-03T03:55:04Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "83232239377961353d10f1c46d6ebb7df2ef7234",
          "body": "4 个独立修复:\n\n1. 寻优跳过参数范围检查:Param.validate 加 skip_bounds 参数,\n   RegisteredStrategy.build 透传,ParamGridOptimizer 传 skip_bounds=True。\n   修复寻优时 fast=250 被 max_value=60 拦截的问题(探索超范围值是寻优目的)。\n\n2. 对比页支持组合回测:CompareView 新增 extractComparable() 统一提取净值+指标,\n   支持单标的(performance/equity_curve)和组合(total_performance/comb\n[…]\n填充参数:BacktestView 加 useRoute(),onMounted 读 query.strategy +\n   query.params,await nextTick 后覆盖(避免 StrategyPicker watch 重置)。\n   修复寻优点「查看」跳转后参数未填充、仍用默认值的问题。\n\n测试:823 passed, mypy 211 files OK, vue-tsc OK",
          "is_bot": false,
          "headline": "fix: 寻优跳过参数范围/对比支持组合/日线x轴年份/寻优跳转填充参数",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-03T03:32:44Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "80d62aec563b356372d7aed07d30124086b2b3b4",
          "body": "图表日期格式(4 个组件):KlineChart/EquityChart/CompareChart/\nPortfolioCompareChart 的 xAxis 加 axisLabel.formatter,强制显示原始\n日期字符串。ECharts 默认把 ISO 日期当时间对象解析,丢失年份只显示\n月-日+00:00。\n\n组合回测取数翻页(_fetch_portfolio_bars):固定 count=800 改为翻页循环\n(与前端 fetchBars 同逻辑),拼接后 sort_values 排序再过滤。修复组合\n回测图表从 800 根最早点(2023-03-14)开始、缺失早期数据的问题。\n\n实测组合回测 2019-01-07 起:净值 1814 点(不再是 800),首日正确。",
          "is_bot": false,
          "headline": "fix: 图表x轴显示完整日期 + 组合回测取数翻页",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-02T20:54:04Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2903d8c80039a2984896037cd7b6cf39861a0875",
          "body": "新增 13 个经典策略,覆盖趋势/通道/震荡/均线四大类:\n\n趋势类:\n- ema_cross  EMA 双线交叉(比 MA 反应更灵敏)\n- triple_ma  三均线系统(多头/空头排列)\n- dmi        DMI 趋向指标(+DI/-DI 交叉)\n- trix       TRIX 三重平滑(过滤短期波动)\n\n通道/突破类:\n- donchian   唐安奇通道突破(海龟交易法)\n- keltner    肯特纳通道(ATR-based)\n- atr_breakout ATR 通道突破(均线±K×ATR)\n\n震荡/反转类:\n- cci        CCI 超卖反弹\n- wr_\n[…]\nal WR 威廉超卖\n- bias_reversal BIAS 乖离反弹\n- emv        EMV 简易波动(量价结合)\n- dpo        DPO 区间震荡\n\n均线类:\n- bbi        BBI 多空指标(4 均线综合)\n\n全部基于 MyTT 现成指标实现,每个策略声明参数 schema 供 Web 表单动态渲染。\n18/18 策略冒烟测试通过(合成数据回测无运行时错误)。",
          "is_bot": false,
          "headline": "feat(strategies): 内置策略从 5 个扩充到 18 个",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-02T20:36:04Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "0cabae4edaf0982b633ae53c55569b5390724365",
          "body": "翻页拼接 concat 后页间时间逆序(page1=最新段,page2=更旧段),\n导致 index 800 处时间从 2026 倒退到 2019。引擎/图表只正确处理前 800 根,\n成交记录在 800 根后出现 2019 年数据。\n\n修复:concat 后加 sort((a,b) => datetime.localeCompare)。\n实测 1600 根排序后整体时间正序,index 799→800 连续递增。",
          "is_bot": false,
          "headline": "fix(web-ui): 翻页拼接后按时间排序,修复>800根数据图表/成交记录错乱",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-02T20:30:45Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "360f8360016ede45b35382d16c58d8e2d6a10c3d",
          "body": "图表格式化:新增 format.ts(fmt2/fmtPct),在 KlineChart/EquityChart/\nCompareChart/PortfolioCompareChart/OptimizeHeatmap 统一应用——tooltip 用\nvalueFormatter、axis 用 axisLabel.formatter,所有数值显示两位小数。\n\n取数翻页:fetchBars 当日期跨度超过 800 根时,循环调用 start=0,800,1600...\n拼接结果,直到覆盖 startDate 或达上限(10 页 ≈ 32 年日线)。翻页后再按\n日期范围闭区间过滤。实测 SZ:000001 翻 2 页拿到 1600 根(2019~2026)。",
          "is_bot": false,
          "headline": "fix(web-ui): ECharts 数字两位小数 + 取数自动翻页",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-02T20:18:23Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ff3476f83a764c2c840506ee58f7673b675e822e",
          "body": "选 2-4 个已完成的回测 task,叠加净值曲线 + 横向指标对比。\n\n后端(最小增量):\n- task_runner.list_recent(limit):返回最近 N 个任务摘要(LRU 倒序)\n- GET /backtest/tasks?limit=20:任务摘要列表端点(不含完整 result)\n- TaskSummary / TaskListResponse schema\n\n前端(/compare 对比页):\n- CompareView:左栏勾选已完成 task,右栏叠加对比\n- CompareChart:多 task 净值叠加图(归一化为初始=1)\n- CompareTable:多 task 指标横向对比表(8 项指标)\n- 按 result 结构判断可对比性(仅单标的 BacktestResult 可对比)\n\n复用现有 task_runner LRU 表,无新增存储。测试:823 passed(+2 列表端点)",
          "is_bot": false,
          "headline": "feat(backtest): 回测结果对比页",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-02T20:11:29Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "87fafe91310623712235c2e0913eb106d9a62331",
          "body": "对单个策略的 1-2 个参数做网格搜索,遍历用户指定的取值列表笛卡尔积,\n每个组合跑一次回测,按 total_return 排序,返回排名表 + 热力图。\n\n后端:\n- ParamGridOptimizer(backtest/optimizer.py):itertools.product 遍历网格,\n  每点 entry.build(params) + BacktestEngine.run(df),复用同一 DataFrame\n- 网格大小上限 200 防组合爆炸,单点失败容错(跳过不中断)\n- 2 参数时生成热力图矩阵(x/y 轴取值 + cell 收益率)\n- POST /backtest\n[…]\n 寻优页):\n- ParamGridPicker:勾选 1-2 个寻优参数,逗号分隔填取值列表\n- OptimizeResultTable:网格点排名表(按收益降序,最优高亮)\n- OptimizeHeatmap:2 参数热力图(ECharts heatmap,绿→红映射收益)\n- 最优点「查看」按钮跳转单标的页用该参数回测\n\n测试:821 passed(+10 寻优器单测 + 3 寻优路由测试)",
          "is_bot": false,
          "headline": "feat(backtest): 参数网格寻优(optimizer + 前端寻优页)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-02T19:55:35Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "6aab7a82b5bfe05b6e1ba7d6fc7a8e632329e5f4",
          "body": "独立前端工程(web-ui/),Vue3 + Vite + TypeScript + Pinia + ECharts。\n通过 vite proxy 对接后端 /api,开发期无跨域。\n\n单标的回测(/):\n- StrategyPicker: 策略下拉 + 按 schema 动态渲染参数表单\n- SymbolPicker: 选标的 + 日期范围取行情(默认3年,结束日=今天)\n- KlineChart: K线主图 + 买卖点 markPoint(按 datetime 对齐)\n- EquityChart: 净值曲线 + 回撤双轴图\n- MetricTable: 19 项绩效指标(收益/风险/交易分\n[…]\nfetchBars 归一化 date/datetime 列名(日线返回 date)\n- K线买卖点按完整 datetime 对齐(分钟线不再折叠)\n- count 上限对齐后端 800\n- ECharts 注册内联 dark 主题\n- 参数表单空输入不打断编辑\n- 网络错误友好提示\n\n技术栈:Vue 3.5 / Vite 8 / TS 6 / Pinia / ECharts(按需引入 725KB)",
          "is_bot": false,
          "headline": "feat(web-ui): Vue3 + ECharts 回测前端(单标的 + 组合回测)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-02T19:35:20Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "38731114b6b016c47128298c6e98c430f61f93cf",
          "body": "后端回测系统完整实现:\n\n策略注册表(backtest/strategies/):\n- Param schema 声明机制,支持动态表单渲染\n- 5 个内置策略:MA交叉/MACD/布林/RSI/KDJ\n- 参数校验(含 NaN/Inf 拦截、范围检查、类型强制转换)\n\nREST API(web/routers/backtest.py):\n- GET  /backtest/strategies  策略枚举 + 参数 schema\n- POST /backtest/run         同步回测(内联 OHLCV)\n- POST /backtest/run/async   后台任务回测(含 s\n[…]\nInf/giant-int(防 DoS)\n- ohlcv max_length=2000(防内存耗尽)\n- LRU 淘汰跳过 running 任务(修复结果丢失竞态)\n- get_runner double-checked locking(修复单例竞态)\n- shutdown 接入 lifespan(修复资源泄漏)\n\n测试:808 passed(含 39 回测路由 + 8 组合引擎 + 安全回归)",
          "is_bot": false,
          "headline": "feat(backtest): 回测 REST API + 策略注册表 + 组合回测引擎",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-02T19:34:34Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "803f2fc3adfd7f2831cb5dba5259699fcd4cbc8b",
          "body": "CVE: pytest <9.0.3 存在临时目录处理安全问题(中等严重程度)。\n本地 Python 3.11 venv + pytest 9.0.3 全量测试通过:761 passed, 2 skipped。\npytest 9.0.3 要求 Python>=3.9,满足 CI 矩阵 3.10/3.12/3.13。",
          "is_bot": false,
          "headline": "fix(deps): bump pytest 8.3.5 -> 9.0.3 修复 tmpdir 安全漏洞 (Dependabot #1)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-02T17:33:11Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "1e7766754e67f56bdef9ea2fadced66270a0cd23",
          "body": "修复 GitHub Actions 中 ruff format --check 的格式化失败。",
          "is_bot": false,
          "headline": "style: ruff format tests/unit/test_a_share_extensions.py",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-02T17:30:40Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "133c208510bfd1b254e18dbc66a5db56b8592c80",
          "body": "通达信统计指数 880005/880006 的计数类字段返回真实家数的 1/10,\nget_market_stat 未做缩放还原,导致 up/down/total/limit_up 等系统性偏小 10 倍。\n对 6 个计数字段统一 round(field * 10),成交额/量/市值不动。\n重写 test_get_market_stat_mapping 覆盖还原逻辑。761 单测全绿。",
          "is_bot": false,
          "headline": "release: v1.16.3 — 修复 market-stat 全市场涨跌统计家数偏小10倍",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-07-02T04:46:27Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2067b80958bd622ddb84b3d8a3833b4d12e9be45",
          "body": "requirements-dev.txt 锁定的 scipy==1.17.0 与 pyproject 声明的\nrequires-python=>=3.10 冲突(scipy 1.16+ 起要求 Python>=3.11),\n导致 CI 矩阵里 3.10 的 job(ubuntu + windows)pip install 失败。\n\n修复:\n- requirements-dev.txt: scipy 1.17.0 → 1.15.3(支持 >=3.10 的最新 scipy)\n- pyproject.toml [dev]/[science]: scipy>=1.10 → >=1.10,<1.16\n  (CI 第一步 pip install -e .[dev] 在 3.10 上也会拉到 1.17,加同上界)\n\n附注:仅影响开发/CI 环境,运行时用户(pip install easy-tdx)不受影响。\n本地 761 单测 + ruff + coverage(62%) 全过。requirements-dev.txt\n头部加注释说明 3.10 兼容约束,防日后再次踩坑。",
          "is_bot": false,
          "headline": "fix(ci): scipy 1.17 要求 Python>=3.11,CI 跑 3.10 装不上",
          "author_name": "GitHub",
          "author_login": "actions-user",
          "committed_at": "2026-07-01T19:53:25Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "155328df8b9848e568061496a50c5f7300aff2fd",
          "body": "经三轮代码审计后的综合质量加固版本,覆盖协议核心层、数据正确性、\n错误处理、测试真实度与可维护性。761 单测全绿(+58),ruff/mypy 全过。\n\n主要修复:\n- 离线 .day 写入原子化(fsync + _repair_tail + 读取校验,CQS 守住)\n- 回测止损前视偏差(延迟下一根开盘 + 跳空保护)\n- VWAP 权重索引 / bar_time fail-fast / 绩效除零保护\n- 闭包绑定 / 路径穿越 / naive datetime 跨时区 / ruff UP038\n\n重构:\n- 抽 AsyncHeartbeatMixin 收敛 4 处心跳副本(12→1)\n- 统一 _RETRY_DELAYS 退避序列 / scanner 失败可观测性\n\n新增 5 个测试文件 + 公共 API 类型契约,CI 加 Windows 矩阵 +\ntrusted publishing 签名 + 锁文件。\n\n详见 CHANGELOG.md",
          "is_bot": false,
          "headline": "release: v1.16.2 — 三轮审计质量加固(B6.9→A7.9)",
          "author_name": "GitHub",
          "author_login": "actions-user",
          "committed_at": "2026-07-01T19:37:37Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "bc83ffa4ac6086c47c7aac3e85fbff4c445124b1",
          "body": null,
          "is_bot": false,
          "headline": "fix(tick_charts): 多日分时图 minutes>=1440 报 ValueError (Issue #10)",
          "author_name": "GitHub",
          "author_login": "actions-user",
          "committed_at": "2026-07-01T08:22:57Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "615994ad7238bbb95dbad7fa4e5574cde8a251a2",
          "body": "通达信协议用bar开始时间打时间戳(5min线上午最后一根标11:25、下午第一根标13:00;午休11:30-13:00无bar),而Tushare/同花顺/聚宽用bar结束时间(标11:30/13:05)。新增bar_time参数让用户一键切换,避免自行+5分钟偏移。\n\n- 全部3条K线路径覆盖:A股get_security_bars/get_index_bars、扩展行情get_instrument_bars、MAC get_stock_kline(含同步+异步、get_stock_kline_with_indicators)\n\n- CLI kline新增--bar-time {star\n[…]\n界\n\n- 协议解码层零改动,偏移作为纯展示语义在client层后处理,单一工具函数_apply_bar_time_align_df/_apply_bar_time_align_bars复用于全部路径\n\n- 新增27个单元测试(test_codec_datetime.py偏移逻辑 + test_kline_bar_time.py三路径覆盖),全量700单测通过\n\n- bump 版本号至 1.16.0",
          "is_bot": false,
          "headline": "feat(kline): 分钟级K线时间戳可选bar_time对齐Tushare (Discussion #7)",
          "author_name": "GitHub",
          "author_login": "actions-user",
          "committed_at": "2026-06-30T07:02:27Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "db83e7505d3b04e05974ba8ae5c8cec60b29ab80",
          "body": null,
          "is_bot": false,
          "headline": "fix(security_quotes): 修复 ETF/指数实时行情价格被放大10倍 (Issue #8)",
          "author_name": "GitHub",
          "author_login": "actions-user",
          "committed_at": "2026-06-29T14:15:28Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3945800728ee63ec9ed0e67569d4a487f585a77a",
          "body": "修复体验问题:此前传板块名仍需用户关心 --offset/--length,太笨拙。\n现在传板块名即自动按目录 length 分块循环读取整个板块(单次上限 30720 字节,\n大板块如「公司大事」77万字节也能一次读全),--offset/--length 仅传文件名时生效。\n\n- _resolve_filename 返回板块 length,_run_content 分块循环读取完整内容\n- 修复分块 offset 推进 bug:原按解码字符串 GBK 重编码计字节,遇 U+FFFD 崩溃;\n  改为按请求字节数推进(服务器按字节偏移工作)\n- 修复多服务器目录版本不一致:传板块名未命中时自动重试多个服务器(最多4次)\n- bump 版本号至 1.15.3",
          "is_bot": false,
          "headline": "feat(cli): company-info 传板块名自动读完整正文(分块循环 + 多服务器重试)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-06-26T20:37:44Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "297a479928012a04c8f6622ea33e53ed66a5a189",
          "body": "修正上一个 commit:CLAUDE.md 应从版本控制移除(本地配置,不递交 GitHub),\n但被误重新加入。本次 git rm --cached 真正移除跟踪,本地文件保留继续使用。",
          "is_bot": false,
          "headline": "chore: 从 git 跟踪移除 CLAUDE.md(本地文件保留)",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-06-26T20:23:46Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "202415dd19eae87b31ee21f0589666423ccd13f3",
          "body": "CLAUDE.md 含本地开发指引(venv 使用要求等),属于本地配置。\ngit rm --cached 移除跟踪(本地文件保留),加入 .gitignore 防止误加回。",
          "is_bot": false,
          "headline": "chore: CLAUDE.md 改为本地文件,不再递交到 GitHub",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-06-26T20:22:50Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "781ee6c48af5ede1ba78f670d00669a2d2fa1782",
          "body": "合并 company-info(列目录)与 company-info-content(读正文)为一个命令,\n板块名改为可选位置参数:无参数列目录,有参数读正文。消除两个相似命令名\n导致的混淆(用户曾误用 company-info SH 601088 \"公司概况\" 报错)。\n\n- company-info:name_or_filename 可选,无则列 F10 板块目录,有则读正文\n- company-info-content:保留为隐藏别名(hidden=True),向后兼容 v1.15.1\n- 提取 _run_category/_run_content 模块级函数复用逻辑\n- 新增 examples/06_finance/{README.md,company_cli.sh,company_web_api.py}\n  覆盖 CLI / Python API / Web API 三种调用方式,含 16 个 F10 板块完整列表\n- 更新 company_info.py 板块名为实测的 16 板块\n- bump 版本号至 1.15.2",
          "is_bot": false,
          "headline": "feat(cli): company-info 命令合并 + examples/06_finance 文档完善",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-06-26T20:22:43Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3df1ea607fa2f51a3ff295d014647d1201de6ca6",
          "body": "同步版本号至 1.15.1:pyproject.toml + CHANGELOG。",
          "is_bot": false,
          "headline": "chore(release): v1.15.1",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-06-26T20:05:57Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "29b6ca33aa27ac3fe42db189ef0ff0a778199e9b",
          "body": "把 TdxClient 上已封装但未暴露给 CLI 的三个 F10/财务方法做成命令,\n数据源走通达信原生协议(与 Web 层 /finance /company/* 同源),\n覆盖 f10(新浪三表)之外的 F10 全文板块。\n\n- finance-info: 最新财务快照(37 字段单期指标),与 f10 多期三表互补\n- company-info: F10 板块目录(最新提示/公司概况/财务分析/... 等 16 板块)\n- company-info-content: 读 F10 正文,支持板块名(自动解析)或文件名\n  --offset 语义随入参而定:板块名=板块内相对偏移,文件名=绝对偏移\n- conn.py: 新增 get_tdx_client() 上下文管理器\n\n补 README 命令表与 CHANGELOG。",
          "is_bot": false,
          "headline": "feat(cli): 新增 finance-info / company-info / company-info-content 命令",
          "author_name": "Justin Gu",
          "author_login": "handsomejustin",
          "committed_at": "2026-06-26T20:02:09Z",
          "body_truncated": false,
          "is_coding_agent": false
        }
      ],
      "releases_count": 65,
      "commits_last_year": 289,
      "latest_release_at": "2026-07-13T08:39:12Z",
      "latest_release_tag": "v1.20.4",
      "releases_from_tags": false,
      "days_since_last_push": 17,
      "active_weeks_last_year": 10,
      "days_since_latest_release": 17,
      "mean_days_between_releases": 0.6
    },
    "community": {
      "has_readme": true,
      "has_license": true,
      "has_description": true,
      "has_contributing": false,
      "health_percentage": 42,
      "has_issue_template": false,
      "has_code_of_conduct": false,
      "has_pull_request_template": false
    },
    "ecosystem": {
      "packages": [
        {
          "name": "easy-tdx",
          "exists": true,
          "license": null,
          "keywords": [],
          "ecosystem": "pypi",
          "matches_repo": null,
          "registry_url": "https://pypi.org/project/easy-tdx/",
          "is_deprecated": false,
          "latest_version": "1.20.4",
          "repository_url": null,
          "versions_count": 82,
          "total_downloads": null,
          "dependents_count": null,
          "deprecation_note": null,
          "maintainers_count": null,
          "monthly_downloads": 15804,
          "first_published_at": "2026-05-22T04:34:46.425850Z",
          "latest_published_at": "2026-07-13T08:37:25.245278Z",
          "latest_version_yanked": null,
          "days_since_latest_publish": 17
        }
      ]
    },
    "popularity": {
      "forks": 194,
      "stars": 636,
      "watchers": 13,
      "fork_history": {
        "days": [
          {
            "date": "2026-05-23",
            "count": 1
          },
          {
            "date": "2026-05-24",
            "count": 2
          },
          {
            "date": "2026-05-27",
            "count": 1
          },
          {
            "date": "2026-05-28",
            "count": 3
          },
          {
            "date": "2026-05-29",
            "count": 4
          },
          {
            "date": "2026-05-30",
            "count": 2
          },
          {
            "date": "2026-06-01",
            "count": 2
          },
          {
            "date": "2026-06-02",
            "count": 2
          },
          {
            "date": "2026-06-03",
            "count": 3
          },
          {
            "date": "2026-06-04",
            "count": 2
          },
          {
            "date": "2026-06-05",
            "count": 1
          },
          {
            "date": "2026-06-07",
            "count": 1
          },
          {
            "date": "2026-06-08",
            "count": 2
          },
          {
            "date": "2026-06-09",
            "count": 7
          },
          {
            "date": "2026-06-10",
            "count": 5
          },
          {
            "date": "2026-06-11",
            "count": 4
          },
          {
            "date": "2026-06-12",
            "count": 3
          },
          {
            "date": "2026-06-13",
            "count": 2
          },
          {
            "date": "2026-06-14",
            "count": 3
          },
          {
            "date": "2026-06-15",
            "count": 2
          },
          {
            "date": "2026-06-16",
            "count": 3
          },
          {
            "date": "2026-06-17",
            "count": 3
          },
          {
            "date": "2026-06-18",
            "count": 3
          },
          {
            "date": "2026-06-19",
            "count": 1
          },
          {
            "date": "2026-06-20",
            "count": 5
          },
          {
            "date": "2026-06-21",
            "count": 1
          },
          {
            "date": "2026-06-22",
            "count": 2
          },
          {
            "date": "2026-06-23",
            "count": 2
          },
          {
            "date": "2026-06-24",
            "count": 2
          },
          {
            "date": "2026-06-25",
            "count": 6
          },
          {
            "date": "2026-06-26",
            "count": 4
          },
          {
            "date": "2026-06-27",
            "count": 3
          },
          {
            "date": "2026-06-29",
            "count": 1
          },
          {
            "date": "2026-06-30",
            "count": 7
          },
          {
            "date": "2026-07-01",
            "count": 2
          },
          {
            "date": "2026-07-03",
            "count": 4
          },
          {
            "date": "2026-07-04",
            "count": 4
          },
          {
            "date": "2026-07-05",
            "count": 5
          },
          {
            "date": "2026-07-06",
            "count": 4
          },
          {
            "date": "2026-07-07",
            "count": 7
          },
          {
            "date": "2026-07-08",
            "count": 8
          },
          {
            "date": "2026-07-09",
            "count": 7
          },
          {
            "date": "2026-07-10",
            "count": 3
          },
          {
            "date": "2026-07-11",
            "count": 4
          },
          {
            "date": "2026-07-12",
            "count": 4
          },
          {
            "date": "2026-07-13",
            "count": 8
          },
          {
            "date": "2026-07-14",
            "count": 4
          },
          {
            "date": "2026-07-15",
            "count": 4
          },
          {
            "date": "2026-07-16",
            "count": 3
          },
          {
            "date": "2026-07-17",
            "count": 1
          },
          {
            "date": "2026-07-18",
            "count": 2
          },
          {
            "date": "2026-07-20",
            "count": 5
          },
          {
            "date": "2026-07-21",
            "count": 1
          },
          {
            "date": "2026-07-23",
            "count": 1
          },
          {
            "date": "2026-07-24",
            "count": 3
          },
          {
            "date": "2026-07-25",
            "count": 1
          },
          {
            "date": "2026-07-26",
            "count": 2
          },
          {
            "date": "2026-07-27",
            "count": 4
          },
          {
            "date": "2026-07-28",
            "count": 1
          },
          {
            "date": "2026-07-29",
            "count": 5
          },
          {
            "date": "2026-07-30",
            "count": 2
          }
        ],
        "complete": true,
        "collected": 194,
        "total_forks": 194
      },
      "star_history": null,
      "open_issues_and_prs": 5
    },
    "ai_readiness": {
      "has_nix": false,
      "example_dirs": [
        "examples"
      ],
      "has_llms_txt": false,
      "has_dockerfile": false,
      "has_mcp_signal": false,
      "bootstrap_files": [],
      "api_schema_files": [],
      "has_devcontainer": false,
      "typecheck_configs": [
        "src/easy_tdx/py.typed",
        "web-ui/tsconfig.json"
      ],
      "toolchain_manifests": [],
      "largest_source_bytes": 74718,
      "source_files_sampled": 389,
      "oversized_source_files": 2,
      "agent_instruction_files": [],
      "agent_instruction_max_bytes": null
    },
    "dependencies": {
      "manifests": [
        "docs/requirements.txt",
        "pyproject.toml",
        "requirements-dev.txt",
        "web-ui/package.json"
      ],
      "advisories": {
        "error": null,
        "scope": "published_package",
        "source": "osv",
        "findings": [],
        "collected": true,
        "malicious": [],
        "truncated": false,
        "by_severity": {},
        "advisory_count": 0,
        "affected_count": 0,
        "assessed_count": 7,
        "malicious_count": 0,
        "assessed_package": "pypi:easy-tdx@1.20.4",
        "unassessed_count": 0,
        "direct_affected_count": 0
      },
      "ecosystems": [
        "npm",
        "pypi"
      ],
      "dependencies": [
        {
          "name": "pandas",
          "manifest": "pyproject.toml",
          "ecosystem": "pypi",
          "version_constraint": ">=2.0,<3"
        },
        {
          "name": "tzdata",
          "manifest": "pyproject.toml",
          "ecosystem": "pypi",
          "version_constraint": ">=2024.1"
        },
        {
          "name": "click",
          "manifest": "pyproject.toml",
          "ecosystem": "pypi",
          "version_constraint": ">=8.0,<9"
        },
        {
          "name": "echarts",
          "manifest": "web-ui/package.json",
          "ecosystem": "npm",
          "version_constraint": "^6.1.0"
        },
        {
          "name": "pinia",
          "manifest": "web-ui/package.json",
          "ecosystem": "npm",
          "version_constraint": "^3.0.4"
        },
        {
          "name": "vue",
          "manifest": "web-ui/package.json",
          "ecosystem": "npm",
          "version_constraint": "^3.5.39"
        },
        {
          "name": "vue-router",
          "manifest": "web-ui/package.json",
          "ecosystem": "npm",
          "version_constraint": "^4.6.4"
        }
      ],
      "all_dependencies": {
        "error": null,
        "source": "github-sbom",
        "packages": [
          {
            "name": "echarts",
            "direct": true,
            "version": "6.1.0",
            "ecosystem": "npm"
          },
          {
            "name": "pinia",
            "direct": true,
            "version": "3.0.4",
            "ecosystem": "npm"
          },
          {
            "name": "vue",
            "direct": true,
            "version": "3.5.39",
            "ecosystem": "npm"
          },
          {
            "name": "vue-router",
            "direct": true,
            "version": "4.6.4",
            "ecosystem": "npm"
          },
          {
            "name": "click",
            "direct": true,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "pandas",
            "direct": true,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "tzdata",
            "direct": true,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "@babel/helper-string-parser",
            "direct": false,
            "version": "7.29.7",
            "ecosystem": "npm"
          },
          {
            "name": "@babel/helper-validator-identifier",
            "direct": false,
            "version": "7.29.7",
            "ecosystem": "npm"
          },
          {
            "name": "@babel/parser",
            "direct": false,
            "version": "7.29.7",
            "ecosystem": "npm"
          },
          {
            "name": "@babel/types",
            "direct": false,
            "version": "7.29.7",
            "ecosystem": "npm"
          },
          {
            "name": "@emnapi/core",
            "direct": false,
            "version": "1.11.1",
            "ecosystem": "npm"
          },
          {
            "name": "@emnapi/runtime",
            "direct": false,
            "version": "1.11.1",
            "ecosystem": "npm"
          },
          {
            "name": "@emnapi/wasi-threads",
            "direct": false,
            "version": "1.2.2",
            "ecosystem": "npm"
          },
          {
            "name": "@jridgewell/sourcemap-codec",
            "direct": false,
            "version": "1.5.5",
            "ecosystem": "npm"
          },
          {
            "name": "@napi-rs/wasm-runtime",
            "direct": false,
            "version": "1.1.6",
            "ecosystem": "npm"
          },
          {
            "name": "@oxc-project/types",
            "direct": false,
            "version": "0.138.0",
            "ecosystem": "npm"
          },
          {
            "name": "@rolldown/binding-android-arm64",
            "direct": false,
            "version": "1.1.4",
            "ecosystem": "npm"
          },
          {
            "name": "@rolldown/binding-darwin-arm64",
            "direct": false,
            "version": "1.1.4",
            "ecosystem": "npm"
          },
          {
            "name": "@rolldown/binding-darwin-x64",
            "direct": false,
            "version": "1.1.4",
            "ecosystem": "npm"
          },
          {
            "name": "@rolldown/binding-freebsd-x64",
            "direct": false,
            "version": "1.1.4",
            "ecosystem": "npm"
          },
          {
            "name": "@rolldown/binding-linux-arm-gnueabihf",
            "direct": false,
            "version": "1.1.4",
            "ecosystem": "npm"
          },
          {
            "name": "@rolldown/binding-linux-arm64-gnu",
            "direct": false,
            "version": "1.1.4",
            "ecosystem": "npm"
          },
          {
            "name": "@rolldown/binding-linux-arm64-musl",
            "direct": false,
            "version": "1.1.4",
            "ecosystem": "npm"
          },
          {
            "name": "@rolldown/binding-linux-ppc64-gnu",
            "direct": false,
            "version": "1.1.4",
            "ecosystem": "npm"
          },
          {
            "name": "@rolldown/binding-linux-s390x-gnu",
            "direct": false,
            "version": "1.1.4",
            "ecosystem": "npm"
          },
          {
            "name": "@rolldown/binding-linux-x64-gnu",
            "direct": false,
            "version": "1.1.4",
            "ecosystem": "npm"
          },
          {
            "name": "@rolldown/binding-linux-x64-musl",
            "direct": false,
            "version": "1.1.4",
            "ecosystem": "npm"
          },
          {
            "name": "@rolldown/binding-openharmony-arm64",
            "direct": false,
            "version": "1.1.4",
            "ecosystem": "npm"
          },
          {
            "name": "@rolldown/binding-wasm32-wasi",
            "direct": false,
            "version": "1.1.4",
            "ecosystem": "npm"
          },
          {
            "name": "@rolldown/binding-win32-arm64-msvc",
            "direct": false,
            "version": "1.1.4",
            "ecosystem": "npm"
          },
          {
            "name": "@rolldown/binding-win32-x64-msvc",
            "direct": false,
            "version": "1.1.4",
            "ecosystem": "npm"
          },
          {
            "name": "@rolldown/pluginutils",
            "direct": false,
            "version": "1.0.1",
            "ecosystem": "npm"
          },
          {
            "name": "@tybys/wasm-util",
            "direct": false,
            "version": "0.10.3",
            "ecosystem": "npm"
          },
          {
            "name": "@types/node",
            "direct": false,
            "version": "24.13.2",
            "ecosystem": "npm"
          },
          {
            "name": "@vitejs/plugin-vue",
            "direct": false,
            "version": "6.0.7",
            "ecosystem": "npm"
          },
          {
            "name": "@volar/language-core",
            "direct": false,
            "version": "2.4.28",
            "ecosystem": "npm"
          },
          {
            "name": "@volar/source-map",
            "direct": false,
            "version": "2.4.28",
            "ecosystem": "npm"
          },
          {
            "name": "@volar/typescript",
            "direct": false,
            "version": "2.4.28",
            "ecosystem": "npm"
          },
          {
            "name": "@vue/compiler-core",
            "direct": false,
            "version": "3.5.39",
            "ecosystem": "npm"
          },
          {
            "name": "@vue/compiler-dom",
            "direct": false,
            "version": "3.5.39",
            "ecosystem": "npm"
          },
          {
            "name": "@vue/compiler-sfc",
            "direct": false,
            "version": "3.5.39",
            "ecosystem": "npm"
          },
          {
            "name": "@vue/compiler-ssr",
            "direct": false,
            "version": "3.5.39",
            "ecosystem": "npm"
          },
          {
            "name": "@vue/devtools-api",
            "direct": false,
            "version": "6.6.4",
            "ecosystem": "npm"
          },
          {
            "name": "@vue/devtools-api",
            "direct": false,
            "version": "7.7.10",
            "ecosystem": "npm"
          },
          {
            "name": "@vue/devtools-kit",
            "direct": false,
            "version": "7.7.10",
            "ecosystem": "npm"
          },
          {
            "name": "@vue/devtools-shared",
            "direct": false,
            "version": "7.7.10",
            "ecosystem": "npm"
          },
          {
            "name": "@vue/language-core",
            "direct": false,
            "version": "3.3.6",
            "ecosystem": "npm"
          },
          {
            "name": "@vue/reactivity",
            "direct": false,
            "version": "3.5.39",
            "ecosystem": "npm"
          },
          {
            "name": "@vue/runtime-core",
            "direct": false,
            "version": "3.5.39",
            "ecosystem": "npm"
          },
          {
            "name": "@vue/runtime-dom",
            "direct": false,
            "version": "3.5.39",
            "ecosystem": "npm"
          },
          {
            "name": "@vue/server-renderer",
            "direct": false,
            "version": "3.5.39",
            "ecosystem": "npm"
          },
          {
            "name": "@vue/shared",
            "direct": false,
            "version": "3.5.39",
            "ecosystem": "npm"
          },
          {
            "name": "@vue/tsconfig",
            "direct": false,
            "version": "0.9.1",
            "ecosystem": "npm"
          },
          {
            "name": "alien-signals",
            "direct": false,
            "version": "3.2.1",
            "ecosystem": "npm"
          },
          {
            "name": "birpc",
            "direct": false,
            "version": "2.9.0",
            "ecosystem": "npm"
          },
          {
            "name": "copy-anything",
            "direct": false,
            "version": "4.0.5",
            "ecosystem": "npm"
          },
          {
            "name": "csstype",
            "direct": false,
            "version": "3.2.3",
            "ecosystem": "npm"
          },
          {
            "name": "detect-libc",
            "direct": false,
            "version": "2.1.2",
            "ecosystem": "npm"
          },
          {
            "name": "entities",
            "direct": false,
            "version": "7.0.1",
            "ecosystem": "npm"
          },
          {
            "name": "estree-walker",
            "direct": false,
            "version": "2.0.2",
            "ecosystem": "npm"
          },
          {
            "name": "fdir",
            "direct": false,
            "version": "6.5.0",
            "ecosystem": "npm"
          },
          {
            "name": "fsevents",
            "direct": false,
            "version": "2.3.3",
            "ecosystem": "npm"
          },
          {
            "name": "hookable",
            "direct": false,
            "version": "5.5.3",
            "ecosystem": "npm"
          },
          {
            "name": "is-what",
            "direct": false,
            "version": "5.5.0",
            "ecosystem": "npm"
          },
          {
            "name": "lightningcss",
            "direct": false,
            "version": "1.32.0",
            "ecosystem": "npm"
          },
          {
            "name": "lightningcss-android-arm64",
            "direct": false,
            "version": "1.32.0",
            "ecosystem": "npm"
          },
          {
            "name": "lightningcss-darwin-arm64",
            "direct": false,
            "version": "1.32.0",
            "ecosystem": "npm"
          },
          {
            "name": "lightningcss-darwin-x64",
            "direct": false,
            "version": "1.32.0",
            "ecosystem": "npm"
          },
          {
            "name": "lightningcss-freebsd-x64",
            "direct": false,
            "version": "1.32.0",
            "ecosystem": "npm"
          },
          {
            "name": "lightningcss-linux-arm-gnueabihf",
            "direct": false,
            "version": "1.32.0",
            "ecosystem": "npm"
          },
          {
            "name": "lightningcss-linux-arm64-gnu",
            "direct": false,
            "version": "1.32.0",
            "ecosystem": "npm"
          },
          {
            "name": "lightningcss-linux-arm64-musl",
            "direct": false,
            "version": "1.32.0",
            "ecosystem": "npm"
          },
          {
            "name": "lightningcss-linux-x64-gnu",
            "direct": false,
            "version": "1.32.0",
            "ecosystem": "npm"
          },
          {
            "name": "lightningcss-linux-x64-musl",
            "direct": false,
            "version": "1.32.0",
            "ecosystem": "npm"
          },
          {
            "name": "lightningcss-win32-arm64-msvc",
            "direct": false,
            "version": "1.32.0",
            "ecosystem": "npm"
          },
          {
            "name": "lightningcss-win32-x64-msvc",
            "direct": false,
            "version": "1.32.0",
            "ecosystem": "npm"
          },
          {
            "name": "magic-string",
            "direct": false,
            "version": "0.30.21",
            "ecosystem": "npm"
          },
          {
            "name": "mitt",
            "direct": false,
            "version": "3.0.1",
            "ecosystem": "npm"
          },
          {
            "name": "muggle-string",
            "direct": false,
            "version": "0.4.1",
            "ecosystem": "npm"
          },
          {
            "name": "nanoid",
            "direct": false,
            "version": "3.3.15",
            "ecosystem": "npm"
          },
          {
            "name": "path-browserify",
            "direct": false,
            "version": "1.0.1",
            "ecosystem": "npm"
          },
          {
            "name": "perfect-debounce",
            "direct": false,
            "version": "1.0.0",
            "ecosystem": "npm"
          },
          {
            "name": "picocolors",
            "direct": false,
            "version": "1.1.1",
            "ecosystem": "npm"
          },
          {
            "name": "picomatch",
            "direct": false,
            "version": "4.0.5",
            "ecosystem": "npm"
          },
          {
            "name": "postcss",
            "direct": false,
            "version": "8.5.16",
            "ecosystem": "npm"
          },
          {
            "name": "rfdc",
            "direct": false,
            "version": "1.4.1",
            "ecosystem": "npm"
          },
          {
            "name": "rolldown",
            "direct": false,
            "version": "1.1.4",
            "ecosystem": "npm"
          },
          {
            "name": "source-map-js",
            "direct": false,
            "version": "1.2.1",
            "ecosystem": "npm"
          },
          {
            "name": "speakingurl",
            "direct": false,
            "version": "14.0.1",
            "ecosystem": "npm"
          },
          {
            "name": "superjson",
            "direct": false,
            "version": "2.2.6",
            "ecosystem": "npm"
          },
          {
            "name": "tinyglobby",
            "direct": false,
            "version": "0.2.17",
            "ecosystem": "npm"
          },
          {
            "name": "tslib",
            "direct": false,
            "version": "2.3.0",
            "ecosystem": "npm"
          },
          {
            "name": "tslib",
            "direct": false,
            "version": "2.8.1",
            "ecosystem": "npm"
          },
          {
            "name": "typescript",
            "direct": false,
            "version": "6.0.3",
            "ecosystem": "npm"
          },
          {
            "name": "undici-types",
            "direct": false,
            "version": "7.18.2",
            "ecosystem": "npm"
          },
          {
            "name": "vite",
            "direct": false,
            "version": "8.1.3",
            "ecosystem": "npm"
          },
          {
            "name": "vscode-uri",
            "direct": false,
            "version": "3.1.0",
            "ecosystem": "npm"
          },
          {
            "name": "vue-tsc",
            "direct": false,
            "version": "3.3.6",
            "ecosystem": "npm"
          },
          {
            "name": "zrender",
            "direct": false,
            "version": "6.1.0",
            "ecosystem": "npm"
          },
          {
            "name": "coverage",
            "direct": false,
            "version": "7.14.0",
            "ecosystem": "pypi"
          },
          {
            "name": "fastapi",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "fastapi",
            "direct": false,
            "version": "0.115.11",
            "ecosystem": "pypi"
          },
          {
            "name": "httpx",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "mypy",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "mypy",
            "direct": false,
            "version": "2.1.0",
            "ecosystem": "pypi"
          },
          {
            "name": "myst-parser",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "numpy",
            "direct": false,
            "version": "2.2.3",
            "ecosystem": "pypi"
          },
          {
            "name": "pillow",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "pystray",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "pytest",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "pytest",
            "direct": false,
            "version": "9.0.3",
            "ecosystem": "pypi"
          },
          {
            "name": "pytest-asyncio",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "pytest-cov",
            "direct": false,
            "version": "7.1.0",
            "ecosystem": "pypi"
          },
          {
            "name": "ruff",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "ruff",
            "direct": false,
            "version": "0.11.11",
            "ecosystem": "pypi"
          },
          {
            "name": "scipy",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "scipy",
            "direct": false,
            "version": "1.15.3",
            "ecosystem": "pypi"
          },
          {
            "name": "sphinx",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "sphinx-autobuild",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "sphinx-rtd-theme",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "uvicorn",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "uvicorn",
            "direct": false,
            "version": "0.34.0",
            "ecosystem": "pypi"
          }
        ],
        "collected": true,
        "truncated": false,
        "total_count": 123,
        "direct_count": 7,
        "indirect_count": 116
      }
    },
    "maintainership": {
      "issues": {
        "open_prs": 0,
        "merged_prs": 9,
        "open_issues": 5,
        "closed_ratio": 0.808,
        "closed_issues": 21,
        "closed_unmerged_prs": 0
      },
      "bus_factor": 1,
      "bot_contributors": 0,
      "top_contributors": [
        {
          "type": "User",
          "login": "handsomejustin",
          "commits": 160,
          "avatar_url": "https://avatars.githubusercontent.com/u/7761402?v=4"
        },
        {
          "type": "User",
          "login": "actions-user",
          "commits": 105,
          "avatar_url": "https://avatars.githubusercontent.com/u/65916846?v=4"
        },
        {
          "type": "User",
          "login": "minionszyw",
          "commits": 10,
          "avatar_url": "https://avatars.githubusercontent.com/u/174980299?v=4"
        },
        {
          "type": "User",
          "login": "shom",
          "commits": 1,
          "avatar_url": "https://avatars.githubusercontent.com/u/144610?v=4"
        }
      ],
      "contributors_sampled": 4,
      "top_contributor_share": 0.58
    },
    "quality_signals": {
      "has_ci": true,
      "has_tests": true,
      "ci_workflows": [
        "ci.yml",
        "publish.yml",
        "release.yml"
      ],
      "has_docs_dir": true,
      "linter_configs": [],
      "has_editorconfig": false,
      "has_linter_config": false,
      "has_precommit_config": false
    },
    "security_signals": {
      "lockfiles": [
        "package-lock.json"
      ],
      "scorecard": {
        "checks": [
          {
            "name": "Binary-Artifacts",
            "score": 10,
            "reason": "no binaries found in the repo",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#binary-artifacts"
          },
          {
            "name": "Branch-Protection",
            "score": 0,
            "reason": "branch protection not enabled on development/release branches",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection"
          },
          {
            "name": "CI-Tests",
            "score": 5,
            "reason": "4 out of 7 merged PRs checked by a CI test -- score normalized to 5",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#ci-tests"
          },
          {
            "name": "CII-Best-Practices",
            "score": 0,
            "reason": "no effort to earn an OpenSSF best practices badge detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#cii-best-practices"
          },
          {
            "name": "Code-Review",
            "score": 0,
            "reason": "Found 0/22 approved changesets -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
          },
          {
            "name": "Contributors",
            "score": 6,
            "reason": "project has 2 contributing companies or organizations -- score normalized to 6",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors"
          },
          {
            "name": "Dangerous-Workflow",
            "score": 10,
            "reason": "no dangerous workflow patterns detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dangerous-workflow"
          },
          {
            "name": "Dependency-Update-Tool",
            "score": 0,
            "reason": "no update tool detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dependency-update-tool"
          },
          {
            "name": "Fuzzing",
            "score": 0,
            "reason": "project is not fuzzed",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#fuzzing"
          },
          {
            "name": "License",
            "score": 9,
            "reason": "license file detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
          },
          {
            "name": "Maintained",
            "score": 0,
            "reason": "project was created within the last 90 days. Please review its contents carefully",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained"
          },
          {
            "name": "Packaging",
            "score": 10,
            "reason": "packaging workflow detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
          },
          {
            "name": "Pinned-Dependencies",
            "score": 3,
            "reason": "dependency not pinned by hash detected -- score normalized to 3",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies"
          },
          {
            "name": "SAST",
            "score": 0,
            "reason": "SAST tool is not run on all commits -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast"
          },
          {
            "name": "Security-Policy",
            "score": 0,
            "reason": "security policy file not detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#security-policy"
          },
          {
            "name": "Signed-Releases",
            "score": 0,
            "reason": "Project has not signed or included provenance with any releases.",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases"
          },
          {
            "name": "Token-Permissions",
            "score": 0,
            "reason": "detected GitHub workflow tokens with excessive permissions",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
          },
          {
            "name": "Vulnerabilities",
            "score": 2,
            "reason": "8 existing vulnerabilities detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
          }
        ],
        "commit": "3b7ce97f2a6942cf9f39e25ee29c4e113bcfc69f",
        "ran_at": "2026-07-30T17:18:23Z",
        "aggregate_score": 2.9,
        "scorecard_version": "v5.5.0"
      },
      "has_codeql_workflow": false,
      "has_security_policy": false,
      "has_dependabot_config": false
    },
    "contribution_flow": {
      "collected": true,
      "ci_last_run_at": "2026-07-13T08:37:31Z",
      "oldest_open_prs": [],
      "last_merged_pr_at": "2026-07-13T08:21:55Z",
      "ci_last_conclusion": "SUCCESS",
      "oldest_open_issues": [
        {
          "number": 39,
          "created_at": "2026-07-17T11:32:16Z",
          "last_comment_at": "2026-07-18T19:06:24Z",
          "last_comment_author": "handsomejustin"
        },
        {
          "number": 40,
          "created_at": "2026-07-19T11:11:38Z",
          "last_comment_at": null,
          "last_comment_author": null
        },
        {
          "number": 41,
          "created_at": "2026-07-23T06:23:06Z",
          "last_comment_at": null,
          "last_comment_author": null
        },
        {
          "number": 42,
          "created_at": "2026-07-24T06:18:25Z",
          "last_comment_at": null,
          "last_comment_author": null
        },
        {
          "number": 43,
          "created_at": "2026-07-28T02:35:41Z",
          "last_comment_at": null,
          "last_comment_author": null
        }
      ]
    }
  },
  "config": {
    "disabled_metrics": [],
    "disabled_categories": [],
    "disabled_components": {}
  },
  "source": {
    "url": "https://github.com/handsomejustin/easy_tdx",
    "host": "github.com",
    "name": "easy_tdx",
    "owner": "handsomejustin"
  },
  "metrics": {
    "overall": {
      "key": "overall",
      "band": "moderate",
      "name": "Overall health",
      "note": null,
      "notes": [],
      "value": 62,
      "inputs": {
        "security": 43,
        "vitality": 68,
        "community": 61,
        "governance": 62,
        "engineering": 69
      },
      "components": []
    },
    "categories": [
      {
        "key": "vitality",
        "band": "moderate",
        "name": "Vitality",
        "value": 68,
        "weight": 0.22,
        "metrics": [
          {
            "key": "development_activity",
            "band": "moderate",
            "name": "Development activity",
            "note": null,
            "notes": [],
            "value": 54,
            "inputs": {
              "commits_last_year": 289,
              "human_commit_share": 1,
              "days_since_last_push": 17,
              "active_weeks_last_year": 10
            },
            "components": [
              {
                "key": "push_recency",
                "name": "Push recency",
                "detail": "last push 17 days ago",
                "points": 28.8,
                "status": "partial",
                "details": [
                  {
                    "code": "push_recency",
                    "params": {
                      "days": 17
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_cadence",
                "name": "Commit cadence",
                "detail": "10/52 weeks with commits",
                "points": 6.9,
                "status": "partial",
                "details": [
                  {
                    "code": "commit_cadence_weeks",
                    "params": {
                      "weeks": 10
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_volume",
                "name": "Commit volume",
                "detail": "289 commits in the last year",
                "points": 18,
                "status": "met",
                "details": [
                  {
                    "code": "commits_last_year",
                    "params": {
                      "count": 289
                    }
                  }
                ],
                "max_points": 18
              },
              {
                "key": "openssf_scorecard_maintained",
                "name": "OpenSSF Scorecard: Maintained",
                "detail": "project was created within the last 90 days. Please review its contents carefully",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "release_discipline",
            "band": "excellent",
            "name": "Release discipline",
            "note": null,
            "notes": [],
            "value": 90,
            "inputs": {
              "releases_count": 65,
              "latest_release_tag": "v1.20.4",
              "releases_from_tags": false,
              "days_since_latest_release": 17,
              "mean_days_between_releases": 0.6
            },
            "components": [
              {
                "key": "ships_releases",
                "name": "Ships releases",
                "detail": "65 releases published",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "releases_published",
                    "params": {
                      "count": 65
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "release_recency",
                "name": "Release recency",
                "detail": "latest release 17 days ago",
                "points": 36,
                "status": "met",
                "details": [
                  {
                    "code": "release_recency",
                    "params": {
                      "days": 17
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "release_cadence",
                "name": "Release cadence",
                "detail": "a release every ~0.6 days",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "release_cadence",
                    "params": {
                      "gap": 0.6
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "openssf_scorecard_signed_releases",
                "name": "OpenSSF Scorecard: Signed-Releases",
                "detail": "Project has not signed or included provenance with any releases.",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "abandonment",
            "band": "excellent",
            "name": "Abandonment",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "cap": null,
              "state": "unverified",
              "guards": [],
              "signals": [],
              "red_flag": false,
              "multiplier_pct": 100,
              "declared_reason": null,
              "unverified_reason": "repository_too_young",
              "unanswered_open_prs": null,
              "unanswered_open_issues": null,
              "days_since_last_merged_pr": null,
              "days_since_last_human_commit": null,
              "days_since_last_human_commit_is_floor": false
            },
            "components": [
              {
                "key": "project_is_still_maintained",
                "name": "Project is still maintained",
                "detail": "maintenance record not established from the collected data",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "abandonment_unverified",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Is the project alive — is code being written and are releases shipping?"
      },
      {
        "key": "community",
        "band": "moderate",
        "name": "Community & Adoption",
        "value": 61,
        "weight": 0.18,
        "metrics": [
          {
            "key": "popularity",
            "band": "good",
            "name": "Popularity & adoption",
            "note": null,
            "notes": [],
            "value": 70,
            "inputs": {
              "forks": 194,
              "stars": 636,
              "watchers": 13,
              "growth_state": "unverified",
              "growth_factor_pct": 100,
              "growth_unverified_reason": "no_history"
            },
            "components": [
              {
                "key": "stars",
                "name": "Stars",
                "detail": "636 stars",
                "points": 45.5,
                "status": "partial",
                "details": [
                  {
                    "code": "stars",
                    "params": {
                      "count": 636
                    }
                  }
                ],
                "max_points": 60
              },
              {
                "key": "forks",
                "name": "Forks",
                "detail": "194 forks",
                "points": 19,
                "status": "partial",
                "details": [
                  {
                    "code": "forks",
                    "params": {
                      "count": 194
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "watchers",
                "name": "Watchers",
                "detail": "13 watchers",
                "points": 6,
                "status": "partial",
                "details": [
                  {
                    "code": "watchers",
                    "params": {
                      "count": 13
                    }
                  }
                ],
                "max_points": 15
              }
            ]
          },
          {
            "key": "community_health",
            "band": "at_risk",
            "name": "Community health",
            "note": null,
            "notes": [],
            "value": 44,
            "inputs": {
              "has_readme": true,
              "has_license": true,
              "has_contributing": false,
              "has_issue_template": false,
              "has_code_of_conduct": false,
              "has_pull_request_template": false
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 22.5,
                "status": "met",
                "details": [],
                "max_points": 22.5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "license file present, not a recognized license",
                "points": 16.9,
                "status": "partial",
                "details": [
                  {
                    "code": "license_custom",
                    "params": {}
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributing_guide",
                "name": "CONTRIBUTING guide",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 18
              },
              {
                "key": "code_of_conduct",
                "name": "Code of conduct",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 13.5
              },
              {
                "key": "issue_template",
                "name": "Issue template",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.2
              },
              {
                "key": "pr_template",
                "name": "PR template",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 6.3
              }
            ]
          },
          {
            "key": "ecosystem_adoption",
            "band": "good",
            "name": "Ecosystem adoption (downloads)",
            "note": "Excluded from scoring (no data or not applicable): Registry dependents. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "registry_dependents"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 70,
            "inputs": {
              "packages": [
                "easy-tdx"
              ],
              "dependents": null,
              "ecosystems": "pypi",
              "total_downloads": null,
              "monthly_downloads": 15804
            },
            "components": [
              {
                "key": "monthly_downloads",
                "name": "Monthly downloads",
                "detail": "15,804 downloads/month across pypi",
                "points": 56,
                "status": "partial",
                "details": [
                  {
                    "code": "downloads_monthly",
                    "params": {
                      "count": 15804,
                      "ecosystems": "pypi"
                    }
                  }
                ],
                "max_points": 80
              },
              {
                "key": "registry_dependents",
                "name": "Registry dependents",
                "detail": "not reported by this ecosystem",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "not_reported_by_this_ecosystem",
                    "params": {}
                  }
                ],
                "max_points": 20
              }
            ]
          }
        ],
        "description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?"
      },
      {
        "key": "governance",
        "band": "moderate",
        "name": "Sustainability & Governance",
        "value": 62,
        "weight": 0.24,
        "metrics": [
          {
            "key": "maintainer_resilience",
            "band": "at_risk",
            "name": "Maintainer resilience (bus factor)",
            "note": null,
            "notes": [],
            "value": 30,
            "inputs": {
              "bus_factor": 1,
              "contributors_sampled": 4,
              "top_contributor_share": 0.58
            },
            "components": [
              {
                "key": "bus_factor",
                "name": "Bus factor",
                "detail": "1 contributor(s) cover half of all commits",
                "points": 9,
                "status": "partial",
                "details": [
                  {
                    "code": "bus_factor",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 54
              },
              {
                "key": "commit_distribution",
                "name": "Commit distribution",
                "detail": "top contributor authored 58% of commits",
                "points": 9.5,
                "status": "partial",
                "details": [
                  {
                    "code": "top_contributor_share",
                    "params": {
                      "share": 58
                    }
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributor_breadth",
                "name": "Contributor breadth",
                "detail": "4 contributors",
                "points": 5.4,
                "status": "partial",
                "details": [
                  {
                    "code": "contributors_sampled",
                    "params": {
                      "count": 4
                    }
                  }
                ],
                "max_points": 13.5
              },
              {
                "key": "openssf_scorecard_contributors",
                "name": "OpenSSF Scorecard: Contributors",
                "detail": "project has 2 contributing companies or organizations -- score normalized to 6",
                "points": 6,
                "status": "partial",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "responsiveness",
            "band": "good",
            "name": "Issue & PR responsiveness",
            "note": null,
            "notes": [],
            "value": 76,
            "inputs": {
              "merged_prs": 9,
              "open_issues": 5,
              "closed_issues": 21,
              "issue_closed_ratio": 0.808,
              "closed_unmerged_prs": 0
            },
            "components": [
              {
                "key": "issue_resolution",
                "name": "Issue resolution",
                "detail": "81% of issues closed",
                "points": 37.8,
                "status": "partial",
                "details": [
                  {
                    "code": "issues_closed_share",
                    "params": {
                      "share": 81
                    }
                  }
                ],
                "max_points": 46.75
              },
              {
                "key": "pr_acceptance",
                "name": "PR acceptance",
                "detail": "9/9 decided PRs merged",
                "points": 38.2,
                "status": "met",
                "details": [
                  {
                    "code": "decided_prs_merged",
                    "params": {
                      "merged": 9,
                      "decided": 9
                    }
                  }
                ],
                "max_points": 38.25
              },
              {
                "key": "openssf_scorecard_code_review",
                "name": "OpenSSF Scorecard: Code-Review",
                "detail": "Found 0/22 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              }
            ]
          },
          {
            "key": "stewardship",
            "band": "moderate",
            "name": "Ownership & stewardship",
            "note": "Excluded from scoring (no data or not applicable): Verified domain. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "verified_domain"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 57,
            "inputs": {
              "followers": 26,
              "owner_type": "User",
              "is_verified": null,
              "owner_login": "handsomejustin",
              "public_repos": 72,
              "account_age_days": 4442
            },
            "components": [
              {
                "key": "ownership_backing",
                "name": "Ownership backing",
                "detail": "personal (user) account",
                "points": 10,
                "status": "partial",
                "details": [
                  {
                    "code": "owner_personal",
                    "params": {}
                  }
                ],
                "max_points": 30
              },
              {
                "key": "verified_domain",
                "name": "Verified domain",
                "detail": "not applicable to user accounts",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "not_applicable_to_user_accounts",
                    "params": {}
                  }
                ],
                "max_points": 20
              },
              {
                "key": "owner_reach",
                "name": "Owner reach",
                "detail": "26 followers of handsomejustin",
                "points": 10.3,
                "status": "partial",
                "details": [
                  {
                    "code": "owner_followers",
                    "params": {
                      "count": 26,
                      "login": "handsomejustin"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "track_record",
                "name": "Track record",
                "detail": "72 public repos, account ~12 yr old",
                "points": 25,
                "status": "met",
                "details": [
                  {
                    "code": "public_repos",
                    "params": {
                      "count": 72
                    }
                  },
                  {
                    "code": "account_age_years",
                    "params": {
                      "years": 12
                    }
                  }
                ],
                "max_points": 25
              }
            ]
          },
          {
            "key": "package_maintenance",
            "band": "excellent",
            "name": "Package maintenance",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "packages": [
                "easy-tdx"
              ],
              "ecosystems": "pypi",
              "any_deprecated": false,
              "min_days_since_publish": 17
            },
            "components": [
              {
                "key": "published_resolvable",
                "name": "Published & resolvable",
                "detail": "1 package(s) on pypi",
                "points": 25,
                "status": "met",
                "details": [
                  {
                    "code": "packages_published",
                    "params": {
                      "count": 1,
                      "ecosystems": "pypi"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "publish_recency",
                "name": "Publish recency",
                "detail": "latest publish 17 days ago",
                "points": 35,
                "status": "met",
                "details": [
                  {
                    "code": "publish_recency",
                    "params": {
                      "days": 17
                    }
                  }
                ],
                "max_points": 35
              },
              {
                "key": "version_history",
                "name": "Version history",
                "detail": "82 published versions",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "published_versions",
                    "params": {
                      "count": 82
                    }
                  }
                ],
                "max_points": 20
              },
              {
                "key": "not_deprecated",
                "name": "Not deprecated",
                "detail": "active, not deprecated or yanked",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "package_not_deprecated",
                    "params": {}
                  }
                ],
                "max_points": 20
              }
            ]
          }
        ],
        "description": "Will the project survive its people — bus factor, responsiveness, who backs it, and package upkeep?"
      },
      {
        "key": "engineering",
        "band": "moderate",
        "name": "Engineering Quality",
        "value": 69,
        "weight": 0.2,
        "metrics": [
          {
            "key": "engineering_practices",
            "band": "moderate",
            "name": "Engineering practices",
            "note": null,
            "notes": [],
            "value": 58,
            "inputs": {
              "has_ci": true,
              "has_tests": true,
              "has_editorconfig": false,
              "has_linter_config": false,
              "has_precommit_config": false
            },
            "components": [
              {
                "key": "ci_workflows",
                "name": "CI workflows",
                "detail": "3 workflow(s)",
                "points": 24,
                "status": "met",
                "details": [
                  {
                    "code": "ci_workflows",
                    "params": {
                      "count": 3
                    }
                  }
                ],
                "max_points": 24
              },
              {
                "key": "tests_present",
                "name": "Tests present",
                "detail": null,
                "points": 24,
                "status": "met",
                "details": [],
                "max_points": 24
              },
              {
                "key": "linter_config",
                "name": "Linter config",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 16
              },
              {
                "key": "pre_commit_hooks",
                "name": "Pre-commit hooks",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 9.6
              },
              {
                "key": "editorconfig",
                "name": ".editorconfig",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 6.4
              },
              {
                "key": "openssf_scorecard_ci_tests",
                "name": "OpenSSF Scorecard: CI-Tests",
                "detail": "4 out of 7 merged PRs checked by a CI test -- score normalized to 5",
                "points": 10,
                "status": "partial",
                "details": [],
                "max_points": 20
              }
            ]
          },
          {
            "key": "documentation",
            "band": "excellent",
            "name": "Documentation",
            "note": null,
            "notes": [],
            "value": 85,
            "inputs": {
              "topics": [
                "chinese-stock-market",
                "financial-data",
                "mootdx",
                "pytdx",
                "tdx",
                "technical-analysis",
                "a-share-tdx",
                "stock-analysis",
                "stock-data",
                "stock-trading"
              ],
              "has_wiki": true,
              "homepage": null,
              "has_readme": true,
              "has_docs_dir": true,
              "has_description": true
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 30,
                "status": "met",
                "details": [],
                "max_points": 30
              },
              {
                "key": "documentation_directory",
                "name": "Documentation directory",
                "detail": null,
                "points": 25,
                "status": "met",
                "details": [],
                "max_points": 25
              },
              {
                "key": "documentation_homepage_site",
                "name": "Documentation / homepage site",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              },
              {
                "key": "repository_description",
                "name": "Repository description",
                "detail": null,
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              },
              {
                "key": "topics",
                "name": "Topics",
                "detail": "10 topics",
                "points": 10,
                "status": "met",
                "details": [
                  {
                    "code": "topics_count",
                    "params": {
                      "count": 10
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "wiki",
                "name": "Wiki",
                "detail": null,
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              }
            ]
          }
        ],
        "description": "Are baseline engineering and documentation practices in place?"
      },
      {
        "key": "security",
        "band": "at_risk",
        "name": "Security",
        "value": 43,
        "weight": 0.16,
        "metrics": [
          {
            "key": "security_posture",
            "band": "critical",
            "name": "Security posture",
            "note": null,
            "notes": [],
            "value": 29,
            "inputs": {
              "source": "openssf_scorecard",
              "checks_evaluated": 18,
              "scorecard_version": "v5.5.0",
              "checks_inconclusive": 0,
              "scorecard_aggregate": 2.9
            },
            "components": [
              {
                "key": "binary_artifacts",
                "name": "Binary-Artifacts",
                "detail": "no binaries found in the repo",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "branch_protection",
                "name": "Branch-Protection",
                "detail": "branch protection not enabled on development/release branches",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "ci_tests",
                "name": "CI-Tests",
                "detail": "4 out of 7 merged PRs checked by a CI test -- score normalized to 5",
                "points": 1.2,
                "status": "partial",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "cii_best_practices",
                "name": "CII-Best-Practices",
                "detail": "no effort to earn an OpenSSF best practices badge detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "code_review",
                "name": "Code-Review",
                "detail": "Found 0/22 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "contributors",
                "name": "Contributors",
                "detail": "project has 2 contributing companies or organizations -- score normalized to 6",
                "points": 1.5,
                "status": "partial",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "dangerous_workflow",
                "name": "Dangerous-Workflow",
                "detail": "no dangerous workflow patterns detected",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              },
              {
                "key": "dependency_update_tool",
                "name": "Dependency-Update-Tool",
                "detail": "no update tool detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "fuzzing",
                "name": "Fuzzing",
                "detail": "project is not fuzzed",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "license file detected",
                "points": 2.2,
                "status": "partial",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "maintained",
                "name": "Maintained",
                "detail": "project was created within the last 90 days. Please review its contents carefully",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "packaging",
                "name": "Packaging",
                "detail": "packaging workflow detected",
                "points": 5,
                "status": "met",
                "details": [],
                "max_points": 5
              },
              {
                "key": "pinned_dependencies",
                "name": "Pinned-Dependencies",
                "detail": "dependency not pinned by hash detected -- score normalized to 3",
                "points": 1.5,
                "status": "partial",
                "details": [],
                "max_points": 5
              },
              {
                "key": "sast",
                "name": "SAST",
                "detail": "SAST tool is not run on all commits -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "security_policy",
                "name": "Security-Policy",
                "detail": "security policy file not detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "signed_releases",
                "name": "Signed-Releases",
                "detail": "Project has not signed or included provenance with any releases.",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "token_permissions",
                "name": "Token-Permissions",
                "detail": "detected GitHub workflow tokens with excessive permissions",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "vulnerabilities",
                "name": "Vulnerabilities",
                "detail": "8 existing vulnerabilities detected",
                "points": 1.5,
                "status": "partial",
                "details": [],
                "max_points": 7.5
              }
            ]
          },
          {
            "key": "dependency_advisories",
            "band": "excellent",
            "name": "Dependency advisories",
            "note": "Excluded from scoring (no data or not applicable): No advisories left outstanding. Remaining weights renormalized. Matched the pypi:easy-tdx@1.20.4 runtime dependency closure — what installing the published package pulls in — 7 packages. Reachability is not analyzed.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "no_advisories_left_outstanding"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              },
              {
                "code": "advisories_scope_published",
                "params": {
                  "package": "pypi:easy-tdx@1.20.4",
                  "assessed": 7
                }
              },
              {
                "code": "advisories_reachability",
                "params": {}
              }
            ],
            "value": 100,
            "inputs": {
              "source": "osv",
              "advisories": 0,
              "affected_packages": 0,
              "assessed_packages": 7,
              "unassessed_packages": 0,
              "affected_by_severity": "none",
              "direct_affected_packages": 0
            },
            "components": [
              {
                "key": "direct_dependencies_free_of_known_advisories",
                "name": "Direct dependencies free of known advisories",
                "detail": "no direct dependency carries a known advisory",
                "points": 35,
                "status": "met",
                "details": [
                  {
                    "code": "no_direct_advisories",
                    "params": {}
                  }
                ],
                "max_points": 35
              },
              {
                "key": "indirect_dependencies_free_of_known_advisories",
                "name": "Indirect dependencies free of known advisories",
                "detail": "no indirect dependency carries a known advisory",
                "points": 25,
                "status": "met",
                "details": [
                  {
                    "code": "no_indirect_advisories",
                    "params": {}
                  }
                ],
                "max_points": 25
              },
              {
                "key": "no_advisories_left_outstanding",
                "name": "No advisories left outstanding",
                "detail": "no advisory carries a publication date",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "advisories_no_publication_date",
                    "params": {}
                  }
                ],
                "max_points": 40
              }
            ]
          },
          {
            "key": "malicious_dependencies",
            "band": "excellent",
            "name": "Malicious dependencies",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "source": "osv",
              "meaning": "reported as a malicious package by the OpenSSF corpus; the remedy is removal or moving off the compromised name, never an upgrade of the same artifact. Versions the registry has since pulled are listed but not scored",
              "packages": [],
              "red_flag": false,
              "assessed_packages": 7,
              "malicious_packages": 0,
              "direct_malicious_packages": 0,
              "withdrawn_malicious_packages": 0,
              "installable_malicious_packages": 0
            },
            "components": [
              {
                "key": "no_dependency_reported_as_a_malicious_package",
                "name": "No dependency reported as a malicious package",
                "detail": "no dependency is reported as a malicious package",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "no_malicious_dependencies",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?"
      },
      {
        "key": "ai_readiness",
        "band": "at_risk",
        "name": "AI Readiness",
        "value": 49,
        "weight": 0,
        "metrics": [
          {
            "key": "ai_agent_context",
            "band": "at_risk",
            "name": "Agent context & guidance",
            "note": null,
            "notes": [],
            "value": 40,
            "inputs": {
              "has_llms_txt": false,
              "legible_history_share": 0.99,
              "agent_instruction_files": [],
              "agent_instruction_max_bytes": null
            },
            "components": [
              {
                "key": "agent_instructions",
                "name": "Agent instructions",
                "detail": "no CLAUDE.md / AGENTS.md / editor rules",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_agent_instructions",
                    "params": {}
                  }
                ],
                "max_points": 45
              },
              {
                "key": "machine_readable_docs_llms_txt",
                "name": "Machine-readable docs (llms.txt)",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              },
              {
                "key": "legible_commit_history",
                "name": "Legible commit history",
                "detail": "99 of 100 human commits state their intent (structured subject or explanatory body)",
                "points": 40,
                "status": "met",
                "details": [
                  {
                    "code": "legible_history",
                    "params": {
                      "legible": 99,
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          },
          {
            "key": "ai_verify_loop",
            "band": "at_risk",
            "name": "Verify loop (build / test / typecheck)",
            "note": null,
            "notes": [],
            "value": 46,
            "inputs": {
              "has_nix": false,
              "has_tests": true,
              "lockfiles": [
                "package-lock.json"
              ],
              "has_dockerfile": false,
              "typed_language": false,
              "bootstrap_files": [],
              "has_devcontainer": false,
              "has_linter_config": false,
              "typecheck_configs": [
                "src/easy_tdx/py.typed",
                "web-ui/tsconfig.json"
              ],
              "agent_commit_share": 0,
              "toolchain_manifests": [],
              "dependency_bot_commit_share": 0
            },
            "components": [
              {
                "key": "one_command_bootstrap",
                "name": "One-command bootstrap",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 18
              },
              {
                "key": "automated_tests",
                "name": "Automated tests",
                "detail": null,
                "points": 22,
                "status": "met",
                "details": [],
                "max_points": 22
              },
              {
                "key": "lint_format_config",
                "name": "Lint / format config",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 11
              },
              {
                "key": "static_type_checking",
                "name": "Static type checking",
                "detail": "src/easy_tdx/py.typed, web-ui/tsconfig.json",
                "points": 11,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "src/easy_tdx/py.typed, web-ui/tsconfig.json"
                    }
                  }
                ],
                "max_points": 11
              },
              {
                "key": "reproducible_environment",
                "name": "Reproducible environment",
                "detail": "lockfile",
                "points": 10,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "lockfile"
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "demonstrated_agent_practice",
                "name": "Demonstrated agent practice",
                "detail": "no agent-authored commits among the last 100",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_agent_authored_commits",
                    "params": {
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "automated_maintenance",
                "name": "Automated maintenance",
                "detail": "no automated dependency updates observed",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_dependency_automation",
                    "params": {}
                  }
                ],
                "max_points": 8
              },
              {
                "key": "openssf_scorecard_pinned_dependencies",
                "name": "OpenSSF Scorecard: Pinned-Dependencies",
                "detail": "dependency not pinned by hash detected -- score normalized to 3",
                "points": 3,
                "status": "partial",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "ai_code_legibility",
            "band": "good",
            "name": "Code legibility for models",
            "note": null,
            "notes": [],
            "value": 82,
            "inputs": {
              "primary_language": "Python",
              "largest_source_bytes": 74718,
              "source_files_sampled": 389,
              "oversized_source_files": 2
            },
            "components": [
              {
                "key": "type_checkable_code",
                "name": "Type-checkable code",
                "detail": "Python with type-check config (src/easy_tdx/py.typed, web-ui/tsconfig.json)",
                "points": 27,
                "status": "partial",
                "details": [
                  {
                    "code": "typecheck_config_language",
                    "params": {
                      "files": "src/easy_tdx/py.typed, web-ui/tsconfig.json",
                      "language": "Python"
                    }
                  }
                ],
                "max_points": 45
              },
              {
                "key": "manageable_file_sizes",
                "name": "Manageable file sizes",
                "detail": "2/389 source files over 60KB",
                "points": 54.7,
                "status": "partial",
                "details": [
                  {
                    "code": "oversized_source_files",
                    "params": {
                      "kb": 60,
                      "sampled": 389,
                      "oversized": 2
                    }
                  }
                ],
                "max_points": 55
              }
            ]
          },
          {
            "key": "ai_interfaces",
            "band": "at_risk",
            "name": "Machine-readable interfaces",
            "note": null,
            "notes": [],
            "value": 40,
            "inputs": {
              "example_dirs": [
                "examples"
              ],
              "has_mcp_signal": false,
              "api_schema_files": []
            },
            "components": [
              {
                "key": "api_schema_openapi_graphql_proto",
                "name": "API schema (OpenAPI/GraphQL/proto)",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 40
              },
              {
                "key": "mcp_server",
                "name": "MCP server",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 20
              },
              {
                "key": "runnable_examples",
                "name": "Runnable examples",
                "detail": "examples",
                "points": 40,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "examples"
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          }
        ],
        "description": "How well is the repo equipped to be developed and maintained with AI coding agents? An independent, experimental badge — weight 0.0, so it is surfaced on its own and does not affect the overall health score."
      }
    ],
    "metrics_version": "1.13.0"
  },
  "warnings": [
    "Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token"
  ],
  "report_type": "repository",
  "generated_at": "2026-07-30T17:18:38.616816Z",
  "schema_version": "0.27.0",
  "badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/h/handsomejustin/easy_tdx.svg",
  "full_name": "handsomejustin/easy_tdx",
  "license_state": "custom",
  "license_spdx": null
}

Scores are signals, not warranties. They reflect publicly visible practices on GitHub — not a code audit, and not a security guarantee.

Missing data is excluded and weights renormalized, never scored as zero. Methodology is versioned and open: metrics v1.13.0, schema v0.27.0 — full methodology · metrics wiki.

How one result sits in the wider record: aggregate statisticsPyPI.