chore(release): prepare v0.3.1

This commit is contained in:
2026-09-13 15:32:27 +09:00
parent 5b443f718e
commit 1010d257ad
18 changed files with 176 additions and 42 deletions
+3 -3
View File
@@ -58,8 +58,8 @@ def test_release_version_and_migration_contract_has_current_head_0031():
for path in (REPO / "app/db/alembic/versions").glob("*.py")
if path.name != "__init__.py"
)
assert 'version = "0.3.0"' in project
assert 'app_version: str = "0.3.0"' in config
assert 'version = "0.3.1"' in project
assert 'app_version: str = "0.3.1"' in config
assert len(migrations) == 31
assert migrations[-1].startswith("20260913_0031_")
@@ -85,7 +85,7 @@ def test_operations_separate_publication_deployment_migration_and_db_rollback():
"Image rollback is **not** database rollback",
"read-only root filesystem",
"10001:10001",
"PortfolioHub v0.3.0 container contract",
"PortfolioHub v0.3.1 container contract",
"v0.2.1 includes migration `0030`",
"trex-web v0.2.3 displays `strategy_nick`",
):
+24
View File
@@ -0,0 +1,24 @@
from scripts.ruff_ratchet import BASELINE_BY_RULE, BASELINE_TOTAL, baseline_regressions
def _diagnostics(**counts: int) -> list[dict[str, str]]:
return [{"code": code} for code, count in counts.items() for _ in range(count)]
def test_exact_baseline_passes() -> None:
diagnostics = _diagnostics(**BASELINE_BY_RULE)
assert len(diagnostics) == BASELINE_TOTAL
assert baseline_regressions(diagnostics) == []
def test_new_rule_fails_from_zero_baseline() -> None:
assert baseline_regressions([{"code": "E501"}]) == ["E501: 1 > 0"]
def test_existing_rule_increase_fails() -> None:
diagnostics = _diagnostics(I001=BASELINE_BY_RULE["I001"] + 1)
assert baseline_regressions(diagnostics) == ["I001: 91 > 90"]
def test_reduced_debt_passes() -> None:
assert baseline_regressions(_diagnostics(I001=1, F401=1)) == []
+5 -2
View File
@@ -1,15 +1,18 @@
from decimal import Decimal
from app.domain.corporate_actions import calculate_stock_consolidation
def test_stock_consolidation_preserves_cost_and_scales_wac():
result = calculate_stock_consolidation(Decimal("1452"), Decimal("726"), Decimal("1086.23"))
result = calculate_stock_consolidation(Decimal(1452), Decimal(726), Decimal("1086.23"))
assert result.average_price_after == Decimal("2172.46")
assert result.total_cost_basis == Decimal("1577205.96000000")
assert result.ratio == Decimal("0.5")
def test_stock_consolidation_rejects_fractional_result():
import pytest
from app.domain.corporate_actions import CorporateActionError
with pytest.raises(CorporateActionError) as exc:
calculate_stock_consolidation(Decimal("3"), Decimal("1.5"), Decimal("10"))
calculate_stock_consolidation(Decimal(3), Decimal("1.5"), Decimal(10))
assert exc.value.code == "FRACTIONAL_RESULT_UNSUPPORTED"
+6 -6
View File
@@ -1,4 +1,4 @@
from datetime import date, datetime, timezone
from datetime import UTC, date, datetime
from decimal import Decimal
from pathlib import Path
@@ -9,9 +9,9 @@ from app.schemas.corporate_actions import CorporateActionRequest
def test_buy_consolidation_sell_replays_wac_and_realized_pnl():
rows = [
{"side": "buy", "quantity": 10, "price": 100, "fee_amount": 0, "tax_amount": 0, "executed_at": datetime(2026, 1, 1, tzinfo=timezone.utc)},
{"side": "stock_consolidation", "quantity": 0, "price": 0, "quantity_before": 10, "quantity_after": 5, "average_price_after": 200, "executed_at": datetime(2026, 1, 2, tzinfo=timezone.utc)},
{"side": "sell", "quantity": 2, "price": 250, "fee_amount": 0, "tax_amount": 0, "executed_at": datetime(2026, 1, 3, tzinfo=timezone.utc)},
{"side": "buy", "quantity": 10, "price": 100, "fee_amount": 0, "tax_amount": 0, "executed_at": datetime(2026, 1, 1, tzinfo=UTC)},
{"side": "stock_consolidation", "quantity": 0, "price": 0, "quantity_before": 10, "quantity_after": 5, "average_price_after": 200, "executed_at": datetime(2026, 1, 2, tzinfo=UTC)},
{"side": "sell", "quantity": 2, "price": 250, "fee_amount": 0, "tax_amount": 0, "executed_at": datetime(2026, 1, 3, tzinfo=UTC)},
]
assert calculate_current_wac(rows).current_wac == 200
result = calculate_realized_pnl(rows)
@@ -20,8 +20,8 @@ def test_buy_consolidation_sell_replays_wac_and_realized_pnl():
def test_canonical_hash_same_payload_and_changed_payload_differ():
a = CorporateActionRequest(effective_date=date(2026, 1, 2), quantity_before=Decimal("10"), quantity_after=Decimal("5"), evidence_ref="ev")
b = CorporateActionRequest(effective_date=date(2026, 1, 2), quantity_before=Decimal("10"), quantity_after=Decimal("4"), evidence_ref="ev")
a = CorporateActionRequest(effective_date=date(2026, 1, 2), quantity_before=Decimal(10), quantity_after=Decimal(5), evidence_ref="ev")
b = CorporateActionRequest(effective_date=date(2026, 1, 2), quantity_before=Decimal(10), quantity_after=Decimal(4), evidence_ref="ev")
assert canonical_payload_hash(a) == canonical_payload_hash(a.model_copy())
assert canonical_payload_hash(a) != canonical_payload_hash(b)
+3 -3
View File
@@ -7,7 +7,7 @@ import pytest
from fastapi.testclient import TestClient
from app.api import v1_portfolios
from app.domain.models import CorporateAction, Portfolio
from app.domain.models import CorporateAction
from app.main import create_app
from app.repositories.postgres import PostgresPortfolioRepository
@@ -36,7 +36,7 @@ def test_create_corporate_action_maps_postgres_missing_portfolio_to_404(monkeypa
def _action(asset_id="asset-1"):
return CorporateAction("ca-1", "pf-1", asset_id, "stock_consolidation", date(2026, 1, 1), Decimal("0"), Decimal("1"), Decimal("0"), Decimal("0"), Decimal("0"), Decimal("10"), Decimal("0"), "ev", "idem", "hash", 0)
return CorporateAction("ca-1", "pf-1", asset_id, "stock_consolidation", date(2026, 1, 1), Decimal(0), Decimal(1), Decimal(0), Decimal(0), Decimal(0), Decimal(10), Decimal(0), "ev", "idem", "hash", 0)
def _repo_for_row(asset_id):
@@ -47,7 +47,7 @@ def _repo_for_row(asset_id):
conn.__enter__.return_value = conn
conn.cursor.return_value.__enter__.return_value = cur
repo._connect = MagicMock(return_value=conn)
cur.fetchone.side_effect = [(Decimal("0"), Decimal("10"), Decimal("0"), asset_id, "enabled"), None, None, None, (7, date(2026, 1, 1))]
cur.fetchone.side_effect = [(Decimal(0), Decimal(10), Decimal(0), asset_id, "enabled"), None, None, None, (7, date(2026, 1, 1))]
cur.fetchall.return_value = []
return repo