Subcategory: Overall architecture
Current score: 8/10 → Target: 10/10
The architecture is otherwise clean (hub-and-spoke accessor design, private _-prefixed
module split, Protocol-based decoupling). Two concrete, low-risk items hold it back:
1. Avoidable function-local imports in _portfolio_cost.py
src/jquantstats/_portfolio_cost.py:184-186 imports inside a method body:
import numpy as np
from ._stats._core import _std_is_negligible
numpy is already a top-level runtime dependency (declared in pyproject.toml), so
there is no lazy-loading benefit.
_std_is_negligible lives in src/jquantstats/_stats/_core.py, which does not
import _portfolio_cost, so hoisting it creates no import cycle. _stats/_performance.py:13
already imports it at module top the normal way.
These belong at module top with the existing import math / import polars as pl block
(lines 5-10). Function-local imports here just obscure the module's real dependency surface.
2. Document the accessor import cycle
data.py and the _stats / _plots / _reports subpackages form a genuine circular
dependency that is broken with lazy imports inside the .stats / .plots / .report /
.utils accessor properties (src/jquantstats/data.py:523-559) and TYPE_CHECKING
DataLike protocol imports. This is a legitimate pandas-style pattern, but it is currently
undocumented, so a future contributor may "helpfully" hoist one of these imports and
reintroduce a hard cycle. Add a short comment at each lazy-import site (or a note in
CLAUDE.md Architecture) stating the imports are intentionally deferred to break the
data <-> accessors cycle.
Done when
_portfolio_cost.py performs numpy and _std_is_negligible imports at module top and
make typecheck + make test still pass.
- Each remaining intentional lazy import in
data.py accessors carries a one-line comment
explaining the cycle it breaks (or the pattern is documented in CLAUDE.md).
Subcategory: Overall architecture
Current score: 8/10 → Target: 10/10
The architecture is otherwise clean (hub-and-spoke accessor design, private
_-prefixedmodule split,
Protocol-based decoupling). Two concrete, low-risk items hold it back:1. Avoidable function-local imports in
_portfolio_cost.pysrc/jquantstats/_portfolio_cost.py:184-186imports inside a method body:numpyis already a top-level runtime dependency (declared inpyproject.toml), sothere is no lazy-loading benefit.
_std_is_negligiblelives insrc/jquantstats/_stats/_core.py, which does notimport
_portfolio_cost, so hoisting it creates no import cycle._stats/_performance.py:13already imports it at module top the normal way.
These belong at module top with the existing
import math/import polars as plblock(lines 5-10). Function-local imports here just obscure the module's real dependency surface.
2. Document the accessor import cycle
data.pyand the_stats/_plots/_reportssubpackages form a genuine circulardependency that is broken with lazy imports inside the
.stats/.plots/.report/.utilsaccessor properties (src/jquantstats/data.py:523-559) andTYPE_CHECKINGDataLikeprotocol imports. This is a legitimate pandas-style pattern, but it is currentlyundocumented, so a future contributor may "helpfully" hoist one of these imports and
reintroduce a hard cycle. Add a short comment at each lazy-import site (or a note in
CLAUDE.mdArchitecture) stating the imports are intentionally deferred to break thedata <-> accessorscycle.Done when
_portfolio_cost.pyperformsnumpyand_std_is_negligibleimports at module top andmake typecheck+make teststill pass.data.pyaccessors carries a one-line commentexplaining the cycle it breaks (or the pattern is documented in CLAUDE.md).