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):
|
class VelocityMetrics(BaseModel):
|
||||||
"""Hype and publication velocity metrics"""
|
"""Hype and publication velocity metrics (per spec: -100 to +100)"""
|
||||||
hype_velocity: float = Field(..., ge=0.0, le=1.0, description="Rate of sentiment acceleration")
|
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=0.0, le=1.0, description="Publication rate velocity")
|
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")
|
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")
|
source_count: int = Field(default=0, description="Number of sources in window")
|
||||||
unique_assets: int = Field(default=0, description="Unique assets mentioned 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")
|
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):
|
class EventFlag(BaseModel):
|
||||||
"""Event flag with strength"""
|
"""Event flag with strength - per spec Section 8.4"""
|
||||||
event_type: str
|
event_type: str
|
||||||
asset_id: str
|
asset: str
|
||||||
strength: float = Field(..., ge=0.0, le=100.0, description="Event strength 0-100")
|
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)
|
confidence: float = Field(..., ge=0.0, le=1.0)
|
||||||
first_seen_ts: float
|
source_credibility: float = Field(..., ge=0.0, le=1.0)
|
||||||
last_seen_ts: float
|
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
|
source_count: int = 1
|
||||||
details: Dict[str, Any] = Field(default_factory=dict)
|
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):
|
class AssetSentiment(BaseModel):
|
||||||
"""Per-asset sentiment output"""
|
"""Per-asset sentiment output - per spec Section 8.1"""
|
||||||
asset_id: str
|
asset_id: str
|
||||||
fear_state: float = Field(..., ge=0.0, le=100.0, description="Fear level 0-100")
|
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")
|
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")
|
last_update_ts: float = Field(..., description="Last update timestamp")
|
||||||
contributing_sources: int = Field(default=0)
|
contributing_sources: int = Field(default=0)
|
||||||
decay_factor: float = Field(default=1.0, ge=0.0, le=1.0, description="Temporal decay applied")
|
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):
|
class IndustrySentiment(BaseModel):
|
||||||
"""Industry/class level sentiment aggregation"""
|
"""Industry/class level sentiment aggregation - per spec Section 8.1"""
|
||||||
industry: str
|
industry: str
|
||||||
assets: List[str] = Field(default_factory=list)
|
assets: List[str] = Field(default_factory=list)
|
||||||
fear_state: float = Field(..., ge=0.0, le=100.0)
|
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)
|
dominant_events: List[EventFlag] = Field(default_factory=list)
|
||||||
asset_count: int = 0
|
asset_count: int = 0
|
||||||
last_update_ts: float
|
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):
|
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)
|
fear_state: float = Field(..., ge=0.0, le=100.0)
|
||||||
greed_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")
|
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)
|
hype_velocity: float = Field(..., ge=-100.0, le=100.0)
|
||||||
pub_velocity: float = Field(..., ge=0.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_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)
|
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
|
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
|
last_update_ts: float
|
||||||
total_sources: int = 0
|
total_sources: int = 0
|
||||||
total_assets: int = 0
|
total_assets: int = 0
|
||||||
|
contributing_events: Dict[str, str] = Field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
class SentimentOutput(BaseModel):
|
class SentimentOutput(BaseModel):
|
||||||
"""Complete sentiment engine output snapshot"""
|
"""Complete sentiment engine output snapshot - per spec Section 8"""
|
||||||
timestamp: float = Field(..., description="Output generation timestamp")
|
timestamp: float = Field(..., description="Output generation timestamp")
|
||||||
market: MarketSentiment
|
market: MarketSentiment
|
||||||
industries: Dict[str, IndustrySentiment] = Field(default_factory=dict)
|
industries: Dict[str, IndustrySentiment] = Field(default_factory=dict)
|
||||||
assets: Dict[str, AssetSentiment] = Field(default_factory=dict)
|
assets: Dict[str, AssetSentiment] = Field(default_factory=dict)
|
||||||
metadata: Dict[str, Any] = 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]:
|
def get_acb_signals(self) -> Dict[str, float]:
|
||||||
"""Extract signals for ACB consumption"""
|
"""Extract signals for ACB consumption"""
|
||||||
|
|||||||
Reference in New Issue
Block a user