fix: preserve unmanaged first-seen timestamps
Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
co-authored by
genichin
parent
be2e73a2d6
commit
f9284d1de8
@@ -99,3 +99,30 @@ class TestCRUDRoundTrip:
|
|||||||
rows_b = pg_unmanaged_store.list_states(instance_key="inst-B", lifecycle_state="all")
|
rows_b = pg_unmanaged_store.list_states(instance_key="inst-B", lifecycle_state="all")
|
||||||
assert len(rows_a) == 1 and rows_a[0]["lifecycle_state"] == "resolved"
|
assert len(rows_a) == 1 and rows_a[0]["lifecycle_state"] == "resolved"
|
||||||
assert len(rows_b) == 1 and rows_b[0]["quantity"] == 7
|
assert len(rows_b) == 1 and rows_b[0]["quantity"] == 7
|
||||||
|
|
||||||
|
def test_reactivated_state_preserves_original_first_seen(self, pg_unmanaged_store):
|
||||||
|
t1 = datetime(2026, 9, 7, 0, 0, tzinfo=UTC)
|
||||||
|
t2 = t1 + timedelta(minutes=1)
|
||||||
|
t3 = t2 + timedelta(minutes=1)
|
||||||
|
|
||||||
|
pg_unmanaged_store.observe(
|
||||||
|
instance_key="inst-A",
|
||||||
|
symbol="008110",
|
||||||
|
quantity=3,
|
||||||
|
observed_at=t1,
|
||||||
|
)
|
||||||
|
pg_unmanaged_store.resolve_absent(
|
||||||
|
instance_key="inst-A",
|
||||||
|
observed_symbols=set(),
|
||||||
|
observed_at=t2,
|
||||||
|
)
|
||||||
|
reopened = pg_unmanaged_store.observe(
|
||||||
|
instance_key="inst-A",
|
||||||
|
symbol="008110",
|
||||||
|
quantity=5,
|
||||||
|
observed_at=t3,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert reopened["transition"] == "detected"
|
||||||
|
assert reopened["state"]["first_seen_at"] == t1
|
||||||
|
assert reopened["state"]["last_seen_at"] == t3
|
||||||
|
|||||||
@@ -109,6 +109,22 @@ def test_in_memory_store_isolates_same_symbol_by_instance():
|
|||||||
assert state_b["quantity"] == 7
|
assert state_b["quantity"] == 7
|
||||||
|
|
||||||
|
|
||||||
|
def test_in_memory_store_preserves_first_seen_when_resolved_symbol_reappears():
|
||||||
|
store = InMemoryUnmanagedPositionStore()
|
||||||
|
t1 = datetime(2026, 9, 7, 0, 0, tzinfo=UTC)
|
||||||
|
t2 = t1 + timedelta(minutes=1)
|
||||||
|
t3 = t2 + timedelta(minutes=1)
|
||||||
|
|
||||||
|
store.observe(instance_key=INSTANCE, symbol="008110", quantity=3, observed_at=t1)
|
||||||
|
store.resolve_absent(instance_key=INSTANCE, observed_symbols=set(), observed_at=t2)
|
||||||
|
reopened = store.observe(instance_key=INSTANCE, symbol="008110", quantity=5, observed_at=t3)
|
||||||
|
|
||||||
|
assert reopened["transition"] == "detected"
|
||||||
|
assert reopened["state"]["first_seen_at"] == t1
|
||||||
|
assert reopened["state"]["last_seen_at"] == t3
|
||||||
|
assert reopened["state"]["quantity_changed_at"] == t3
|
||||||
|
|
||||||
|
|
||||||
def test_reconcile_core_holdings_suppresses_repeated_unmanaged_alarm_spam():
|
def test_reconcile_core_holdings_suppresses_repeated_unmanaged_alarm_spam():
|
||||||
repo, svc = _wire()
|
repo, svc = _wire()
|
||||||
emitter, transport = _make_emitter()
|
emitter, transport = _make_emitter()
|
||||||
|
|||||||
@@ -58,11 +58,11 @@ class PostgreSQLUnmanagedPositionStore:
|
|||||||
cur.execute(
|
cur.execute(
|
||||||
"UPDATE unmanaged_position_state "
|
"UPDATE unmanaged_position_state "
|
||||||
"SET quantity = %s, lifecycle_state = 'active', "
|
"SET quantity = %s, lifecycle_state = 'active', "
|
||||||
" first_seen_at = %s, last_seen_at = %s, quantity_changed_at = %s, "
|
" last_seen_at = %s, quantity_changed_at = %s, resolved_at = NULL, "
|
||||||
" resolved_at = NULL, updated_at = %s "
|
" updated_at = %s "
|
||||||
"WHERE instance_key = %s AND symbol = %s "
|
"WHERE instance_key = %s AND symbol = %s "
|
||||||
f"RETURNING {_STATE_COLUMNS}",
|
f"RETURNING {_STATE_COLUMNS}",
|
||||||
(quantity, now, now, now, now, instance_key, symbol),
|
(quantity, now, now, now, instance_key, symbol),
|
||||||
)
|
)
|
||||||
elif int(previous["quantity"]) != int(quantity):
|
elif int(previous["quantity"]) != int(quantity):
|
||||||
transition = "quantity_changed"
|
transition = "quantity_changed"
|
||||||
|
|||||||
@@ -629,7 +629,9 @@ class InMemoryUnmanagedPositionStore:
|
|||||||
"symbol": symbol,
|
"symbol": symbol,
|
||||||
"quantity": quantity,
|
"quantity": quantity,
|
||||||
"lifecycle_state": "active",
|
"lifecycle_state": "active",
|
||||||
"first_seen_at": now,
|
"first_seen_at": (
|
||||||
|
existing.get("first_seen_at", now) if existing is not None else now
|
||||||
|
),
|
||||||
"last_seen_at": now,
|
"last_seen_at": now,
|
||||||
"quantity_changed_at": now,
|
"quantity_changed_at": now,
|
||||||
"resolved_at": None,
|
"resolved_at": None,
|
||||||
|
|||||||
Reference in New Issue
Block a user