tradeval / src /services /computation.py
SoumyajitSen94298's picture
Upload 5 files
778c071 verified
Raw
History Blame Contribute Delete
20.9 kB
"""
Trademark Valuation Computation Logic
Heuristic-based approach for estimating trademark value based on multiple factors
"""
import re
from typing import Dict, Any
from datetime import datetime
import math
import random
# =========================================
# Constants
# =========================================
REGISTERED = {700, 701, 702, 703, 704, 705, 706, 707, 708, 739, 717, 800}
ITU = {688, 718, 719, 720, 721, 722, 724, 725, 730, 731, 732, 733, 734}
POU = {
806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820,
821, 822, 823, 824, 825, 632, 638, 640, 641, 642, 643, 644, 645, 646, 647,
648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660
}
STATUS_BOOST = {801, 802, 803, 804, 773, 774, 775, 777, 778, 779, 780, 781, 782, 790}
CLASS_CFG = {
5: (50e6, 500e6, 0.08, 0.15, 1), 9: (50e6, 500e6, 0.10, 0.18, 1), 42: (40e6, 400e6, 0.10, 0.18, 1),
36: (40e6, 400e6, 0.05, 0.12, 1), 45: (35e6, 350e6, 0.08, 0.15, 1), 3: (20e6, 200e6, 0.15, 0.25, 2),
10: (25e6, 250e6, 0.08, 0.15, 2), 44: (25e6, 250e6, 0.08, 0.15, 2), 35: (20e6, 200e6, 0.10, 0.18, 2),
38: (25e6, 250e6, 0.08, 0.15, 2), 41: (20e6, 200e6, 0.12, 0.20, 2), 25: (15e6, 150e6, 0.15, 0.25, 3),
29: (10e6, 100e6, 0.12, 0.22, 3), 30: (10e6, 100e6, 0.12, 0.22, 3), 32: (15e6, 150e6, 0.15, 0.25, 3),
33: (20e6, 200e6, 0.18, 0.28, 3), 34: (25e6, 250e6, 0.15, 0.25, 3), 43: (10e6, 100e6, 0.15, 0.25, 3),
28: (10e6, 100e6, 0.12, 0.20, 3), 14: (15e6, 150e6, 0.12, 0.20, 3), 18: (15e6, 150e6, 0.18, 0.28, 3),
7: (8e6, 80e6, 0.05, 0.12, 4), 11: (8e6, 80e6, 0.08, 0.15, 4), 12: (10e6, 100e6, 0.08, 0.15, 4),
37: (5e6, 50e6, 0.05, 0.10, 4), 39: (8e6, 80e6, 0.08, 0.15, 4), 20: (5e6, 50e6, 0.10, 0.18, 4),
21: (5e6, 50e6, 0.10, 0.18, 4), 1: (3e6, 40e6, 0.03, 0.08, 5), 2: (3e6, 40e6, 0.05, 0.10, 5),
4: (3e6, 40e6, 0.03, 0.08, 5), 6: (2e6, 30e6, 0.02, 0.06, 5), 8: (3e6, 40e6, 0.05, 0.12, 5),
13: (5e6, 50e6, 0.08, 0.15, 5), 15: (3e6, 30e6, 0.10, 0.18, 5), 16: (2e6, 25e6, 0.05, 0.10, 5),
17: (2e6, 25e6, 0.03, 0.08, 5), 19: (3e6, 35e6, 0.03, 0.08, 5), 22: (1.5e6, 20e6, 0.03, 0.08, 5),
23: (1.5e6, 20e6, 0.03, 0.08, 5), 24: (2e6, 25e6, 0.05, 0.10, 5), 26: (1e6, 15e6, 0.05, 0.10, 5),
27: (2e6, 25e6, 0.05, 0.10, 5), 31: (3e6, 35e6, 0.03, 0.08, 5), 40: (2e6, 25e6, 0.03, 0.08, 5),
}
DEFAULT_CFG = (1e6, 10e6, 0.02, 0.05, 6)
GENERICS = {
'THE', 'BEST', 'QUALITY', 'SUPER', 'PREMIUM', 'SERVICES', 'SOLUTIONS', 'GROUP', 'GLOBAL',
'INTERNATIONAL', 'ENTERPRISE', 'CONSULTING', 'DIGITAL', 'TECH', 'CLOUD', 'SMART', 'PRO',
'PLUS', 'MAX', 'ULTRA', 'ELITE', 'PRIME', 'EXPRESS', 'DIRECT', 'ONLINE', 'NETWORK', 'SYSTEMS'
}
INDUSTRY_KW = {
5: ['pharm', 'med', 'health', 'cure', 'heal', 'vita', 'bio', 'thera', 'care'],
9: ['tech', 'soft', 'data', 'cyber', 'digit', 'smart', 'logic', 'byte', 'net', 'web', 'app', 'cloud', 'code'],
35: ['biz', 'corp', 'market', 'brand', 'consult', 'strat', 'manage'],
36: ['fin', 'bank', 'fund', 'invest', 'capital', 'wealth', 'pay', 'credit'],
42: ['tech', 'soft', 'dev', 'code', 'cyber', 'cloud', 'data', 'lab'],
45: ['law', 'legal', 'just', 'counsel', 'attorney', 'protect', 'secure'],
}
PARENT_COS = {
'johnson & johnson': ['kenvue', 'janssen', 'neutrogena', 'tylenol', 'band-aid', 'listerine'],
'procter & gamble': ['tide', 'pampers', 'gillette', 'oral-b', 'crest', 'pantene', 'olay', 'old spice'],
'unilever': ['dove', 'axe', 'lipton', 'knorr', 'hellmanns', 'ben & jerrys', 'vaseline'],
'nestle': ['nescafe', 'nespresso', 'kitkat', 'gerber', 'purina', 'perrier', 'haagen-dazs'],
'pepsico': ['frito-lay', 'doritos', 'lays', 'cheetos', 'quaker', 'gatorade', 'tropicana'],
'coca-cola': ['sprite', 'fanta', 'minute maid', 'powerade', 'dasani', 'smartwater'],
'meta': ['facebook', 'instagram', 'whatsapp', 'oculus', 'threads'],
'alphabet': ['google', 'youtube', 'android', 'chrome', 'gmail', 'waymo', 'nest'],
'amazon': ['alexa', 'kindle', 'prime', 'aws', 'whole foods', 'twitch', 'audible', 'ring'],
'apple': ['iphone', 'ipad', 'macbook', 'airpods', 'apple watch', 'apple music', 'siri'],
'disney': ['marvel', 'pixar', 'lucasfilm', 'star wars', 'espn', 'abc', 'hulu'],
}
PREMIUM_DESIGN = {'02.01', '02.03', '03.01', '03.03', '03.05', '04.01', '01.01', '01.03', '24.01', '26.03', '26.05'}
STANDARD_DESIGN = {'05.01', '05.03', '06.01', '07.01', '08.01', '19.01'}
ENTITY_HINTS = [
'INC', 'LLC', 'CORP', 'CORPORATION', 'LTD', 'LIMITED', 'GMBH', 'PLC', 'COMPANY', 'CO', 'CO ',
'HOLDINGS', 'GROUP', 'LP', 'LLP', 'PC', 'SA', 'S A', 'SAS', 'BV', 'AB', 'AG', 'NV', 'PTY'
]
def evaluate_trademark(
mark: str,
party_name: str,
primary_code: int,
status_code: int,
goods_and_service: str = "",
registration_date_epoch: int = None,
market_cap: float = 0,
portfolio_size: int = 1,
design_codes: str = "",
mark_drawing_code: int = 4,
multi_class_count: int = 1,
intl_reg_count: int = 0,
competitor_count: int = 0,
office_action_count: int = 0,
actual_renewal_count: int = 0,
late_renewal_count: int = 0,
survived_trial: bool = False,
section15_filed: bool = False,
filing_66a: bool = False,
is_supplemental: bool = False,
clamp_total_mult: bool = True,
total_mult_min: float = 0.20,
total_mult_max: float = 6.00,
) -> Dict[str, Any]:
"""
Evaluate a single trademark and return full breakdown.
"""
result: Dict[str, Any] = {}
now = int(datetime.now().timestamp() * 1000)
# Class config
cfg = CLASS_CFG.get(primary_code, DEFAULT_CFG)
base_min, base_max, pct_min, pct_max, tier = cfg
brand_pct = (pct_min + pct_max) / 2
result['class_tier'] = tier
result['class_mult'] = {1: 1.25, 2: 1.15, 3: 1.10}.get(tier, 1.0)
# Status flags
is_registered = status_code in REGISTERED
is_itu = status_code in ITU
is_pou = status_code in POU
has_boost = status_code in STATUS_BOOST
# Base value calculation - PRIMARY DRIVER OF VALUATION
portfolio = max(1, min(portfolio_size, 1000))
# Portfolio-based factor: Larger portfolios indicate established businesses
# Log scale prevents extreme multipliers while rewarding size
if portfolio >= 500:
portfolio_factor = 3.5 + math.log10(portfolio / 100) * 0.5
elif portfolio >= 100:
portfolio_factor = 2.5 + (portfolio - 100) / 200
elif portfolio >= 50:
portfolio_factor = 2.0 + (portfolio - 50) / 100
elif portfolio >= 20:
portfolio_factor = 1.5 + (portfolio - 20) / 60
elif portfolio >= 10:
portfolio_factor = 1.2 + (portfolio - 10) / 30
elif portfolio >= 5:
portfolio_factor = 1.0 + (portfolio - 5) / 20
else:
portfolio_factor = 0.7 + (portfolio * 0.06)
# Class-based positioning adjustment
# Higher tier classes get more aggressive base values
if tier == 1: # Premium tech/pharma/finance
class_base_factor = 1.8
elif tier == 2: # High-value services
class_base_factor = 1.4
elif tier == 3: # Consumer goods
class_base_factor = 1.2
elif tier == 4: # Industrial/Manufacturing
class_base_factor = 1.0
else: # Misc/Low-value
class_base_factor = 0.7
if is_itu:
# Intent-to-use: speculative value only
base = 2000 if market_cap > 0 else 800
result['base_value'] = base
elif is_pou:
# Proof of use: some value but limited
base = 5000 * class_base_factor
result['base_value'] = base
elif market_cap > 0:
# Known company: use market cap proportional valuation
base = (market_cap * brand_pct) / portfolio
result['base_value'] = max(base, base_min * 0.1)
elif is_registered:
# Registered mark: main valuation path
# Combine class value with portfolio strength
base = ((base_min * 0.3) + (base_max * 0.15)) * class_base_factor * portfolio_factor
# Add randomness to prevent clustering (±15%)
variance = random.uniform(0.85, 1.15)
base = base * variance
result['base_value'] = max(base, base_min * 0.05)
else:
# Abandoned/dead marks
base = base_min * 0.02
result['base_value'] = max(base, 1000)
# Temporal multiplier - AGE PREMIUM
# Older marks = more established brand recognition and goodwill
if registration_date_epoch and registration_date_epoch > 0:
years = (now - registration_date_epoch) / 31557600000
years = max(0.0, years)
else:
years = 0.0
if years <= 0:
# No registration date: penalty
result['temporal_mult'] = 0.6
elif years < 1:
# Very new: still establishing
result['temporal_mult'] = 0.7 + 0.4 * years
elif years < 3:
# Young but registered
result['temporal_mult'] = 1.1 + 0.2 * (years - 1.0)
elif years < 7:
# Established presence
result['temporal_mult'] = 1.5 + 0.15 * (years - 3.0)
elif years < 15:
# Strong history
result['temporal_mult'] = 2.1 + 0.12 * (years - 7.0)
elif years < 30:
# Legacy brand
result['temporal_mult'] = 3.06 + 0.08 * (years - 15.0)
else:
# Historic mark (rare premium)
result['temporal_mult'] = 4.26 + 0.04 * min(50.0, years - 30.0)
# Renewal multiplier
result['renewal_mult'] = 1.0 + actual_renewal_count * 0.03 - late_renewal_count * 0.07
result['renewal_mult'] = max(0.70, min(1.30, result['renewal_mult']))
# Description multiplier
desc_words = len(goods_and_service.split()) if goods_and_service else 0
result['desc_mult'] = min(1.10, 1.0 + 0.02 * math.sqrt(desc_words / 50.0)) if desc_words > 0 else 1.0
# Multi-class registration - SCOPE OF PROTECTION
# More classes = broader market coverage = higher value
mc = min(10, max(1, multi_class_count))
if mc >= 8:
result['multi_class_mult'] = 1.85
elif mc >= 6:
result['multi_class_mult'] = 1.65
elif mc >= 4:
result['multi_class_mult'] = 1.42
elif mc >= 3:
result['multi_class_mult'] = 1.28
elif mc >= 2:
result['multi_class_mult'] = 1.15
else:
result['multi_class_mult'] = 1.0
# Geographic reach - INTERNATIONAL PRESENCE
# International registrations show serious commercial intent
geo = min(15, max(1, intl_reg_count + 1))
if geo >= 10:
result['geo_mult'] = 2.5
elif geo >= 6:
result['geo_mult'] = 2.0
elif geo >= 4:
result['geo_mult'] = 1.6
elif geo >= 3:
result['geo_mult'] = 1.35
elif geo >= 2:
result['geo_mult'] = 1.18
else:
result['geo_mult'] = 1.0
# Competitor proximity - MARKET POSITION
# Fewer similar marks = stronger distinctiveness and market power
if competitor_count <= 0:
result['proximity_mult'] = 1.60 # Unique in class
result['market_dominance'] = 'DOMINANT'
elif competitor_count < 3:
result['proximity_mult'] = 1.45 # Near monopoly
result['market_dominance'] = 'STRONG'
elif competitor_count < 10:
result['proximity_mult'] = 1.25 # Leading position
result['market_dominance'] = 'STRONG'
elif competitor_count < 30:
result['proximity_mult'] = 1.05 # Competitive space
result['market_dominance'] = 'MODERATE'
elif competitor_count < 75:
result['proximity_mult'] = 0.80 # Crowded field
result['market_dominance'] = 'WEAK'
elif competitor_count < 150:
result['proximity_mult'] = 0.60 # Saturated
result['market_dominance'] = 'CROWDED'
else:
result['proximity_mult'] = 0.40 # Commodity space
result['market_dominance'] = 'SATURATED'
# Section 15
result['section15_mult'] = 1.30 if section15_filed else 1.0
# Individual vs company
party_upper = (party_name or '').upper()
party_clean = party_upper.replace('.', ' ').replace(',', ' ')
is_individual = not any(h in party_clean for h in ENTITY_HINTS)
if is_individual and market_cap == 0 and portfolio_size <= 3:
result['individual_mult'] = 0.75
elif is_individual:
result['individual_mult'] = 0.90
else:
result['individual_mult'] = 1.0
# Uniqueness heuristic
mark_upper = (mark or '').upper()
mark_len = len(mark_upper)
has_digit = bool(re.search(r'\d', mark_upper))
vowels = sum(1 for c in mark_upper if c in 'AEIOU')
vowel_ratio = vowels / max(1, mark_len)
if mark_len <= 3:
result['uniqueness_mult'] = 1.05
elif has_digit:
result['uniqueness_mult'] = 1.08
elif vowel_ratio < 0.2 or vowel_ratio > 0.6:
result['uniqueness_mult'] = 1.10
else:
result['uniqueness_mult'] = 1.0
# Industry relevance
mark_lower = mark_upper.lower()
kws = INDUSTRY_KW.get(primary_code, [])
matches = sum(1 for kw in kws if kw in mark_lower)
if matches >= 2:
result['industry_relevance_mult'] = 1.15
elif matches == 1:
result['industry_relevance_mult'] = 1.08
else:
result['industry_relevance_mult'] = 1.0
# Generic / descriptive checks
is_generic = mark_upper in GENERICS
mark_in_gs = False
if mark and goods_and_service and len(mark) >= 4:
pattern = r'\b' + re.escape(mark_lower) + r'\b'
mark_in_gs = bool(re.search(pattern, goods_and_service.lower()))
if is_generic and mark_in_gs:
result['generic_mult'] = 0.10
elif is_generic:
result['generic_mult'] = 0.30
elif mark_in_gs:
result['generic_mult'] = 0.60
else:
result['generic_mult'] = 1.0
# Parent vs subsidiary
party_lower = (party_name or '').lower()
is_parent_mark = False
for parent, subs in PARENT_COS.items():
parent_words = parent.replace('&', '').split()
if all(w in party_lower for w in parent_words):
if all(w in mark_lower for w in parent_words):
is_parent_mark = True
break
result['parent_mult'] = 1.15 if is_parent_mark else 1.0
# Office actions
oa = min(6, max(0, office_action_count))
result['oa_mult'] = max(0.70, 1.0 - 0.05 * oa)
# Portfolio multiplier - CORPORATE STRENGTH INDICATOR
# Large portfolios = institutional backing and resources for enforcement
if portfolio_size >= 500:
result['portfolio_mult'] = 1.65 # Major corporation
elif portfolio_size >= 250:
result['portfolio_mult'] = 1.50 # Large enterprise
elif portfolio_size >= 100:
result['portfolio_mult'] = 1.35 # Established company
elif portfolio_size >= 50:
result['portfolio_mult'] = 1.22 # Mid-size business
elif portfolio_size >= 25:
result['portfolio_mult'] = 1.14 # Growing portfolio
elif portfolio_size >= 10:
result['portfolio_mult'] = 1.08 # Active filer
elif portfolio_size >= 5:
result['portfolio_mult'] = 1.02 # Small business
else:
result['portfolio_mult'] = 0.90 # Individual/startup
# Logo value
has_design = mark_drawing_code in [2, 3, 5, 6]
codes = set(design_codes.split(';')) if design_codes else set()
if not has_design:
result['logo_value'] = 0
elif codes & PREMIUM_DESIGN:
result['logo_value'] = 75000 + min(25000, len(codes) * 5000)
elif codes & STANDARD_DESIGN:
result['logo_value'] = 35000 + min(25000, len(codes) * 5000)
else:
result['logo_value'] = 10000 + min(25000, len(codes) * 5000)
# Flat bonuses / penalties
if filing_66a:
result['filing_66a_bonus'] = {1: 20000, 2: 20000, 3: 12500, 4: 12500}.get(tier, 5000)
else:
result['filing_66a_bonus'] = 0
result['trial_bonus'] = 50000 if survived_trial else 0
result['status_bonus'] = 10000 if has_boost else 0
result['supplemental_penalty'] = -25000 if is_supplemental else 0
# Final calculation
mult_keys = [
'temporal_mult', 'renewal_mult', 'class_mult', 'desc_mult',
'multi_class_mult', 'geo_mult', 'proximity_mult', 'section15_mult',
'individual_mult', 'uniqueness_mult', 'industry_relevance_mult',
'generic_mult', 'parent_mult', 'oa_mult', 'portfolio_mult'
]
mult_product = 1.0
for k in mult_keys:
mult_product *= float(result[k])
if clamp_total_mult:
mult_product = max(total_mult_min, min(total_mult_max, mult_product))
computed = (
result['base_value'] * mult_product
+ result['logo_value']
+ result['filing_66a_bonus']
+ result['trial_bonus']
+ result['status_bonus']
+ result['supplemental_penalty']
)
result['computed_value'] = max(500, min(50e9, computed))
# CONFIDENCE SCORE - ESTIMATION RELIABILITY
# Measures how confident we are in the valuation (data quality + factors)
conf = 30 # Baseline: minimal data scenario
# === STRONG POSITIVE INDICATORS ===
# Public company data (highest confidence)
if market_cap > 0:
conf += 28
# Core registration strength
if is_registered:
conf += 18
if section15_filed: # Incontestable status
conf += 10
if registration_date_epoch:
conf += 8
# Portfolio sophistication (signals professional management)
if portfolio_size >= 200: conf += 15
elif portfolio_size >= 100: conf += 12
elif portfolio_size >= 50: conf += 9
elif portfolio_size >= 20: conf += 6
elif portfolio_size >= 10: conf += 4
# Market coverage breadth
if multi_class_count >= 6: conf += 12
elif multi_class_count >= 4: conf += 8
elif multi_class_count >= 2: conf += 4
# International validation
if intl_reg_count >= 5: conf += 14
elif intl_reg_count >= 3: conf += 10
elif intl_reg_count >= 1: conf += 5
# Goods/services specificity
if desc_words > 60: conf += 9
elif desc_words > 30: conf += 6
elif desc_words > 15: conf += 3
# Proven longevity
if actual_renewal_count >= 4: conf += 12
elif actual_renewal_count >= 2: conf += 8
elif actual_renewal_count >= 1: conf += 4
# Temporal confidence
if years >= 25: conf += 12
elif years >= 15: conf += 9
elif years >= 10: conf += 6
elif years >= 5: conf += 3
# Recognized brand indicators
if is_parent_mark: conf += 18
if result['industry_relevance_mult'] >= 1.12: conf += 7
elif result['industry_relevance_mult'] > 1: conf += 3
# Legal strength
if survived_trial: conf += 10
# Market position clarity
if competitor_count == 0: conf += 10
elif competitor_count <= 5: conf += 6
elif competitor_count <= 20: conf += 2
# === NEGATIVE INDICATORS (REDUCE CONFIDENCE) ===
# Individual ownership (less data, more variability)
if is_individual and market_cap == 0:
if portfolio_size <= 2: conf -= 20
elif portfolio_size <= 5: conf -= 12
elif portfolio_size <= 10: conf -= 6
# Weakness indicators
if is_generic: conf -= 30 # Highly uncertain value
if mark_in_gs: conf -= 18 # Descriptive = lower distinctiveness
if is_itu: conf -= 30 # Not yet in commerce
if not party_name: conf -= 25 # Missing critical data
if is_supplemental: conf -= 22 # Weaker protection
if late_renewal_count >= 3: conf -= 15
elif late_renewal_count > 0: conf -= 8 * late_renewal_count
# Prosecution issues
if office_action_count >= 5: conf -= 15
elif office_action_count >= 3: conf -= 10
elif office_action_count > 0: conf -= 5
# Market saturation uncertainty
if competitor_count >= 200: conf -= 20
elif competitor_count >= 100: conf -= 12
elif competitor_count >= 50: conf -= 6
# Immaturity penalty
if years < 0.5: conf -= 15
elif years < 2: conf -= 8
# Finalize confidence
conf = max(5, min(100, conf))
result['confidence_score'] = conf
# Classification thresholds
if conf >= 78:
result['confidence_level'] = 'HIGH'
elif conf >= 58:
result['confidence_level'] = 'MEDIUM'
elif conf >= 35:
result['confidence_level'] = 'LOW'
else:
result['confidence_level'] = 'VERY_LOW'
return result