fix: resolve all mypy type errors for strict type checking (#48)

## Summary

Fixed 30 mypy type errors across 8 files to enable strict type checking.

## Changes by Category

### no-any-return errors (fixed with explicit type casts)
- alerter/history.py: Cast Redis zcount/zremrangebyscore returns to int
- ingestor/clob_client.py: Cast API response values appropriately
- ingestor/publisher.py: Cast Redis xack, xlen, xtrim returns to int
- ingestor/metadata_sync.py: Cast Redis delete return to int
- detector/scorer.py: Cast Redis delete return to int

### operator errors (fixed with null checks)
- alerter/formatter.py: Added `if age_hours is not None:` guards

### External library typing issues (fixed with type: ignore)
- ingestor/publisher.py: redis-py typing issue with xadd dict parameter
- profiler/funding.py: web3 typing issue with block parameters
- storage/repos.py: SQLAlchemy Result.rowcount typing issue

### assignment errors (fixed by renaming)
- storage/repos.py: Renamed SQLite insert stmt to avoid type conflict

Closes #48

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Patrick Selamy
2026-01-04 19:15:32 -05:00
co-authored by Claude Opus 4.5
parent f57e0243dd
commit 5c5440d776
8 changed files with 60 additions and 45 deletions
+20 -17
View File
@@ -208,20 +208,20 @@ class WalletRepository:
await self.session.execute(stmt)
except Exception:
# Fall back to SQLite upsert for testing
stmt = sqlite_insert(WalletProfileModel).values(**values, created_at=now)
stmt = stmt.on_conflict_do_update(
sqlite_stmt = sqlite_insert(WalletProfileModel).values(**values, created_at=now)
sqlite_stmt = sqlite_stmt.on_conflict_do_update(
index_elements=["address"],
set_={
"nonce": stmt.excluded.nonce,
"first_seen_at": stmt.excluded.first_seen_at,
"is_fresh": stmt.excluded.is_fresh,
"matic_balance": stmt.excluded.matic_balance,
"usdc_balance": stmt.excluded.usdc_balance,
"analyzed_at": stmt.excluded.analyzed_at,
"updated_at": stmt.excluded.updated_at,
"nonce": sqlite_stmt.excluded.nonce,
"first_seen_at": sqlite_stmt.excluded.first_seen_at,
"is_fresh": sqlite_stmt.excluded.is_fresh,
"matic_balance": sqlite_stmt.excluded.matic_balance,
"usdc_balance": sqlite_stmt.excluded.usdc_balance,
"analyzed_at": sqlite_stmt.excluded.analyzed_at,
"updated_at": sqlite_stmt.excluded.updated_at,
},
)
await self.session.execute(stmt)
await self.session.execute(sqlite_stmt)
await self.session.flush()
return dto
@@ -238,7 +238,8 @@ class WalletRepository:
result = await self.session.execute(
delete(WalletProfileModel).where(WalletProfileModel.address == address.lower())
)
return result.rowcount > 0
# SQLAlchemy Result does have rowcount but typing doesn't reflect it
return (result.rowcount or 0) > 0 # type: ignore[attr-defined]
async def mark_stale(self, address: str) -> bool:
"""Mark a wallet profile as stale (soft delete).
@@ -257,7 +258,8 @@ class WalletRepository:
.where(WalletProfileModel.address == address.lower())
.values(analyzed_at=stale_time, updated_at=datetime.now(UTC))
)
return result.rowcount > 0
# SQLAlchemy Result does have rowcount but typing doesn't reflect it
return (result.rowcount or 0) > 0 # type: ignore[attr-defined]
class FundingRepository:
@@ -478,12 +480,12 @@ class RelationshipRepository:
await self.session.execute(stmt)
except Exception:
# Fall back to SQLite upsert for testing
stmt = sqlite_insert(WalletRelationshipModel).values(**values)
stmt = stmt.on_conflict_do_update(
sqlite_stmt = sqlite_insert(WalletRelationshipModel).values(**values)
sqlite_stmt = sqlite_stmt.on_conflict_do_update(
index_elements=["wallet_a", "wallet_b", "relationship_type"],
set_={"confidence": stmt.excluded.confidence},
set_={"confidence": sqlite_stmt.excluded.confidence},
)
await self.session.execute(stmt)
await self.session.execute(sqlite_stmt)
await self.session.flush()
return dto
@@ -506,4 +508,5 @@ class RelationshipRepository:
WalletRelationshipModel.relationship_type == relationship_type,
)
)
return result.rowcount > 0
# SQLAlchemy Result does have rowcount but typing doesn't reflect it
return (result.rowcount or 0) > 0 # type: ignore[attr-defined]