feat: output schema conformance to spec Section 8
- VelocityMetrics: hype_velocity/pub_velocity range -100 to +100 (was 0-1) - EventFlag: full spec Section 8.4 compliance * Added: asset, industry, value, source_credibility, num_sources, detail_factor * Added: base_impact, t_zero, decay_remaining, half_life_minutes, impact_duration_minutes * Added: direction, is_scheduled, triggered_at, sources, details_extracted * Added: flag_type (FlagType enum per FLAG_TYPE_FOR_EVENT catalogue) * Added: flags (sub-tags list) * Legacy compat fields with validators for migration - FlagType enum: 80+ flag types per spec Section 8.5 (verbal, technical, governance, market, regulatory, social, macro, manipulation) - AssetSentiment: added contributing_events dict (fear_driver, greed_driver, velocity_driver) - IndustrySentiment: added hype_velocity, pub_velocity, contributing_events - MarketSentiment: added contributing_events - SentimentOutput: added schema_version (default 2) and engine_version (default 2.0.0) - All 46 core NLP unit tests pass
This commit is contained in:
@@ -7,11 +7,11 @@ from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
|
||||
class VelocityMetrics(BaseModel):
|
||||
"""Hype and publication velocity metrics"""
|
||||
hype_velocity: float = Field(..., ge=0.0, le=1.0, description="Rate of sentiment acceleration")
|
||||
pub_velocity: float = Field(..., ge=0.0, le=1.0, description="Publication rate velocity")
|
||||
"""Hype and publication velocity metrics (per spec: -100 to +100)"""
|
||||
hype_velocity: float = Field(..., ge=-100.0, le=100.0, description="Rate of acceleration of hype-weighted mentions per hour")
|
||||
pub_velocity: float = Field(..., ge=-100.0, le=100.0, description="Rate of acceleration of raw publication count per hour")
|
||||
velocity_direction: str = Field(default="neutral", description="accelerating | decelerating | neutral")
|
||||
window_minutes: int = Field(default=15, description="Velocity computation window")
|
||||
window_minutes: int = Field(default=60, description="Velocity computation window (spec: 60 min)")
|
||||
source_count: int = Field(default=0, description="Number of sources in window")
|
||||
unique_assets: int = Field(default=0, description="Unique assets mentioned in window")
|
||||
|
||||
@@ -27,20 +27,167 @@ class PumpDumpScore(BaseModel):
|
||||
last_update_ts: float = Field(..., description="Last score update timestamp")
|
||||
|
||||
|
||||
class FlagType(str, Enum):
|
||||
"""FLAG_TYPE_FOR_EVENT catalogue per spec Section 8.5"""
|
||||
# Verbal/Linguistic
|
||||
FLAG_VERBAL_BULLISH = "FLAG_VERBAL_BULLISH"
|
||||
FLAG_VERBAL_BEARISH = "FLAG_VERBAL_BEARISH"
|
||||
FLAG_VERBAL_UPGRADE = "FLAG_VERBAL_UPGRADE"
|
||||
FLAG_VERBAL_DOWNGRADE = "FLAG_VERBAL_DOWNGRADE"
|
||||
FLAG_VERBAL_GUIDANCE_RAISE = "FLAG_VERBAL_GUIDANCE_RAISE"
|
||||
FLAG_VERBAL_GUIDANCE_CUT = "FLAG_VERBAL_GUIDANCE_CUT"
|
||||
FLAG_VERBAL_BUYBACK_ANNOUNCEMENT = "FLAG_VERBAL_BUYBACK_ANNOUNCEMENT"
|
||||
FLAG_VERBAL_DIVIDEND_DECLARE = "FLAG_VERBAL_DIVIDEND_DECLARE"
|
||||
FLAG_VERBAL_EARNINGS_BEAT = "FLAG_VERBAL_EARNINGS_BEAT"
|
||||
FLAG_VERBAL_EARNINGS_MISS = "FLAG_VERBAL_EARNINGS_MISS"
|
||||
FLAG_VERBAL_REVENUE_GROWTH = "FLAG_VERBAL_REVENUE_GROWTH"
|
||||
FLAG_VERBAL_PARTNERSHIP = "FLAG_VERBAL_PARTNERSHIP"
|
||||
FLAG_VERBAL_EXPANSION = "FLAG_VERBAL_EXPANSION"
|
||||
FLAG_VERBAL_CONTRACT_WIN = "FLAG_VERBAL_CONTRACT_WIN"
|
||||
# Technical
|
||||
FLAG_TOKEN_UNLOCK = "FLAG_TOKEN_UNLOCK"
|
||||
FLAG_TOKEN_BURN = "FLAG_TOKEN_BURN"
|
||||
FLAG_TOKEN_MINT = "FLAG_TOKEN_MINT"
|
||||
FLAG_STAKING_REWARD = "FLAG_STAKING_REWARD"
|
||||
FLAG_STAKING_SLASHING = "FLAG_STAKING_SLASHING"
|
||||
FLAG_HARD_FORK = "FLAG_HARD_FORK"
|
||||
FLAG_SOFT_FORK = "FLAG_SOFT_FORK"
|
||||
FLAG_PROTOCOL_UPGRADE = "FLAG_PROTOCOL_UPGRADE"
|
||||
FLAG_AIRDROP = "FLAG_AIRDROP"
|
||||
FLAG_BRIDGE_INTEGRATION = "FLAG_BRIDGE_INTEGRATION"
|
||||
FLAG_API_DEPRECATION = "FLAG_API_DEPRECATION"
|
||||
FLAG_API_LIMIT_CHANGE = "FLAG_API_LIMIT_CHANGE"
|
||||
FLAG_FEATURE_RELEASE = "FLAG_FEATURE_RELEASE"
|
||||
FLAG_PERFORMANCE_DEGRADATION = "FLAG_PERFORMANCE_DEGRADATION"
|
||||
FLAG_SECURITY_AUDIT_PASS = "FLAG_SECURITY_AUDIT_PASS"
|
||||
FLAG_SECURITY_AUDIT_FAIL = "FLAG_SECURITY_AUDIT_FAIL"
|
||||
# Governance
|
||||
FLAG_GOV_PROPOSAL_NEW = "FLAG_GOV_PROPOSAL_NEW"
|
||||
FLAG_GOV_VOTE_SUCCESS = "FLAG_GOV_VOTE_SUCCESS"
|
||||
FLAG_GOV_VOTE_FAILED = "FLAG_GOV_VOTE_FAILED"
|
||||
FLAG_GOV_VOTE_RAN_AWAY = "FLAG_GOV_VOTE_RAN_AWAY"
|
||||
FLAG_GOV_QUORUM_MISS = "FLAG_GOV_QUORUM_MISS"
|
||||
FLAG_DAO_DEPLOYMENT = "FLAG_DAO_DEPLOYMENT"
|
||||
FLAG_GOV_DELAY_CHANGE = "FLAG_GOV_DELAY_CHANGE"
|
||||
# Market Structure
|
||||
FLAG_LISTING = "FLAG_LISTING"
|
||||
FLAG_DELISTING = "FLAG_DELISTING"
|
||||
FLAG_HALVING = "FLAG_HALVING"
|
||||
FLAG_ETP_APPROVAL = "FLAG_ETP_APPROVAL"
|
||||
FLAG_ETP_REJECTION = "FLAG_ETP_REJECTION"
|
||||
FLAG_WHALE_ACCUMULATION = "FLAG_WHALE_ACCUMULATION"
|
||||
FLAG_WHALE_DISTRIBUTION = "FLAG_WHALE_DISTRIBUTION"
|
||||
FLAG_EXCHANGE_HALT = "FLAG_EXCHANGE_HALT"
|
||||
FLAG_WITHDRAWAL_SUSPEND = "FLAG_WITHDRAWAL_SUSPEND"
|
||||
FLAG_LIQUIDITY_MIGRATION = "FLAG_LIQUIDITY_MIGRATION"
|
||||
FLAG_MM_PROGRAM_CHANGE = "FLAG_MM_PROGRAM_CHANGE"
|
||||
# Regulatory
|
||||
FLAG_REG_CLARITY_POSITIVE = "FLAG_REG_CLARITY_POSITIVE"
|
||||
FLAG_REG_CLARITY_NEGATIVE = "FLAG_REG_CLARITY_NEGATIVE"
|
||||
FLAG_REG_ENFORCEMENT = "FLAG_REG_ENFORCEMENT"
|
||||
FLAG_REG_INVESTIGATION = "FLAG_REG_INVESTIGATION"
|
||||
FLAG_REG_COMPLIANCE_ISSUE = "FLAG_REG_COMPLIANCE_ISSUE"
|
||||
FLAG_TAX_TREATMENT_CHANGE = "FLAG_TAX_TREATMENT_CHANGE"
|
||||
FLAG_LITIGATION_FILED = "FLAG_LITIGATION_FILED"
|
||||
FLAG_LITIGATION_SETTLED = "FLAG_LITIGATION_SETTLED"
|
||||
FLAG_BANKRUPTCY = "FLAG_BANKRUPTCY"
|
||||
FLAG_DEFAULT = "FLAG_DEFAULT"
|
||||
# Social
|
||||
FLAG_PUMP_COORDINATION = "FLAG_PUMP_COORDINATION"
|
||||
FLAG_PROMOTION = "FLAG_PROMOTION"
|
||||
FLAG_CRITICISM = "FLAG_CRITICISM"
|
||||
FLAG_ECHO_CHAMBER = "FLAG_ECHO_CHAMBER"
|
||||
FLAG_BOT_ACTIVITY = "FLAG_BOT_ACTIVITY"
|
||||
FLAG_FEAR_KEYWORD_SPIKE = "FLAG_FEAR_KEYWORD_SPIKE"
|
||||
FLAG_GREED_KEYWORD_SPIKE = "FLAG_GREED_KEYWORD_SPIKE"
|
||||
FLAG_PANIC_KEYWORD_SPIKE = "FLAG_PANIC_KEYWORD_SPIKE"
|
||||
FLAG_UNCERTAINTY_KEYWORD_SPIKE = "FLAG_UNCERTAINTY_KEYWORD_SPIKE"
|
||||
FLAG_CONFIDENCE_KEYWORD_SPIKE = "FLAG_CONFIDENCE_KEYWORD_SPIKE"
|
||||
# Macro
|
||||
FLAG_FED_RATE_CUT = "FLAG_FED_RATE_CUT"
|
||||
FLAG_FED_RATE_HIKE = "FLAG_FED_RATE_HIKE"
|
||||
FLAG_CPI_RELEASE = "FLAG_CPI_RELEASE"
|
||||
FLAG_CPI_SURPRISE_HIGH = "FLAG_CPI_SURPRISE_HIGH"
|
||||
FLAG_CPI_SURPRISE_LOW = "FLAG_CPI_SURPRISE_LOW"
|
||||
FLAG_GDP_RELEASE = "FLAG_GDP_RELEASE"
|
||||
FLAG_EMPLOYMENT_RELEASE = "FLAG_EMPLOYMENT_RELEASE"
|
||||
FLAG_CENTRAL_BANK_SPEECH_HAWKISH = "FLAG_CENTRAL_BANK_SPEECH_HAWKISH"
|
||||
FLAG_CENTRAL_BANK_SPEECH_DOVE = "FLAG_CENTRAL_BANK_SPEECH_DOVE"
|
||||
FLAG_GEOPOLITICAL_TENSION = "FLAG_GEOPOLITICAL_TENSION"
|
||||
FLAG_GEOPOLITICAL_RESOLUTION = "FLAG_GEOPOLITICAL_RESOLUTION"
|
||||
# Manipulation
|
||||
FLAG_ECHO_CHAMBER_DETECTED = "FLAG_ECHO_CHAMBER_DETECTED"
|
||||
FLAG_COORDINATED_MANIPULATION = "FLAG_COORDINATED_MANIPULATION"
|
||||
FLAG_WASH_TRADING = "FLAG_WASH_TRADING"
|
||||
FLAG_SPOOFING = "FLAG_SPOOFING"
|
||||
FLAG_LAYERING = "FLAG_LAYERING"
|
||||
FLAG_QUOTE_STUFFING = "FLAG_QUOTE_STUFFING"
|
||||
|
||||
|
||||
class EventFlag(BaseModel):
|
||||
"""Event flag with strength"""
|
||||
"""Event flag with strength - per spec Section 8.4"""
|
||||
event_type: str
|
||||
asset_id: str
|
||||
strength: float = Field(..., ge=0.0, le=100.0, description="Event strength 0-100")
|
||||
asset: str
|
||||
industry: str
|
||||
value: float = Field(..., ge=0.0, le=100.0, description="Event strength 0-100")
|
||||
confidence: float = Field(..., ge=0.0, le=1.0)
|
||||
first_seen_ts: float
|
||||
last_seen_ts: float
|
||||
source_credibility: float = Field(..., ge=0.0, le=1.0)
|
||||
num_sources: int = Field(default=1, ge=1)
|
||||
detail_factor: float = Field(default=0.0, ge=0.0, le=1.0)
|
||||
base_impact: float = Field(default=0.0, ge=0.0, le=100.0)
|
||||
t_zero: float
|
||||
decay_remaining: float = Field(default=1.0, ge=0.0, le=1.0)
|
||||
half_life_minutes: float = Field(default=0.0, ge=0.0)
|
||||
impact_duration_minutes: float = Field(default=0.0, ge=0.0)
|
||||
direction: str = Field(default="neutral", description="positive | negative | mixed | neutral")
|
||||
is_scheduled: bool = False
|
||||
triggered_at: float
|
||||
sources: List[str] = Field(default_factory=list)
|
||||
details_extracted: Dict[str, Any] = Field(default_factory=dict)
|
||||
flag_type: Optional[FlagType] = None
|
||||
flags: List[str] = Field(default_factory=list)
|
||||
|
||||
# Legacy compatibility fields
|
||||
asset_id: str = ""
|
||||
strength: float = 0.0
|
||||
first_seen_ts: float = 0.0
|
||||
last_seen_ts: float = 0.0
|
||||
source_count: int = 1
|
||||
details: Dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
@field_validator('asset_id', mode='before')
|
||||
@classmethod
|
||||
def _set_asset_id(cls, v, info):
|
||||
return info.data.get('asset', '')
|
||||
|
||||
@field_validator('strength', mode='before')
|
||||
@classmethod
|
||||
def _set_strength(cls, v, info):
|
||||
return info.data.get('value', 0.0)
|
||||
|
||||
@field_validator('first_seen_ts', mode='before')
|
||||
@classmethod
|
||||
def _set_first_seen(cls, v, info):
|
||||
return info.data.get('triggered_at', 0.0)
|
||||
|
||||
@field_validator('last_seen_ts', mode='before')
|
||||
@classmethod
|
||||
def _set_last_seen(cls, v, info):
|
||||
return info.data.get('triggered_at', 0.0)
|
||||
|
||||
@field_validator('source_count', mode='before')
|
||||
@classmethod
|
||||
def _set_source_count(cls, v, info):
|
||||
return info.data.get('num_sources', 1)
|
||||
|
||||
@field_validator('details', mode='before')
|
||||
@classmethod
|
||||
def _set_details(cls, v, info):
|
||||
return info.data.get('details_extracted', {})
|
||||
|
||||
|
||||
class AssetSentiment(BaseModel):
|
||||
"""Per-asset sentiment output"""
|
||||
"""Per-asset sentiment output - per spec Section 8.1"""
|
||||
asset_id: str
|
||||
fear_state: float = Field(..., ge=0.0, le=100.0, description="Fear level 0-100")
|
||||
greed_state: float = Field(..., ge=0.0, le=100.0, description="Greed level 0-100")
|
||||
@@ -52,10 +199,11 @@ class AssetSentiment(BaseModel):
|
||||
last_update_ts: float = Field(..., description="Last update timestamp")
|
||||
contributing_sources: int = Field(default=0)
|
||||
decay_factor: float = Field(default=1.0, ge=0.0, le=1.0, description="Temporal decay applied")
|
||||
contributing_events: Dict[str, str] = Field(default_factory=dict, description="Top event driving each state metric")
|
||||
|
||||
|
||||
class IndustrySentiment(BaseModel):
|
||||
"""Industry/class level sentiment aggregation"""
|
||||
"""Industry/class level sentiment aggregation - per spec Section 8.1"""
|
||||
industry: str
|
||||
assets: List[str] = Field(default_factory=list)
|
||||
fear_state: float = Field(..., ge=0.0, le=100.0)
|
||||
@@ -66,15 +214,18 @@ class IndustrySentiment(BaseModel):
|
||||
dominant_events: List[EventFlag] = Field(default_factory=list)
|
||||
asset_count: int = 0
|
||||
last_update_ts: float
|
||||
hype_velocity: float = Field(default=0.0, ge=-100.0, le=100.0)
|
||||
pub_velocity: float = Field(default=0.0, ge=-100.0, le=100.0)
|
||||
contributing_events: Dict[str, str] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class MarketSentiment(BaseModel):
|
||||
"""Market-wide sentiment aggregation"""
|
||||
"""Market-wide sentiment aggregation - per spec Section 8.1"""
|
||||
fear_state: float = Field(..., ge=0.0, le=100.0)
|
||||
greed_state: float = Field(..., ge=0.0, le=100.0)
|
||||
sentiment_index: float = Field(..., ge=-100.0, le=100.0, description="Market-wide sentiment index")
|
||||
hype_velocity: float = Field(..., ge=0.0, le=100.0)
|
||||
pub_velocity: float = Field(..., ge=0.0, le=100.0)
|
||||
hype_velocity: float = Field(..., ge=-100.0, le=100.0)
|
||||
pub_velocity: float = Field(..., ge=-100.0, le=100.0)
|
||||
aggregate_pump_risk: float = Field(default=0.0, ge=0.0, le=100.0)
|
||||
aggregate_dump_risk: float = Field(default=0.0, ge=0.0, le=100.0)
|
||||
top_pump_assets: List[str] = Field(default_factory=list) # Top 10 by pump_score
|
||||
@@ -84,15 +235,18 @@ class MarketSentiment(BaseModel):
|
||||
last_update_ts: float
|
||||
total_sources: int = 0
|
||||
total_assets: int = 0
|
||||
contributing_events: Dict[str, str] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class SentimentOutput(BaseModel):
|
||||
"""Complete sentiment engine output snapshot"""
|
||||
"""Complete sentiment engine output snapshot - per spec Section 8"""
|
||||
timestamp: float = Field(..., description="Output generation timestamp")
|
||||
market: MarketSentiment
|
||||
industries: Dict[str, IndustrySentiment] = Field(default_factory=dict)
|
||||
assets: Dict[str, AssetSentiment] = Field(default_factory=dict)
|
||||
metadata: Dict[str, Any] = Field(default_factory=dict)
|
||||
schema_version: int = Field(default=2, description="Output schema version for downstream compatibility")
|
||||
engine_version: str = Field(default="2.0.0", description="Engine version string")
|
||||
|
||||
def get_acb_signals(self) -> Dict[str, float]:
|
||||
"""Extract signals for ACB consumption"""
|
||||
|
||||
Reference in New Issue
Block a user