| 1 | """Versioned platform benchmark keys resolved only through registered master rows.""" |
| 2 | from hashlib import md5 |
| 3 | from uuid import UUID |
| 4 | from datetime import datetime |
| 5 | |
| 6 | from app.sector_relative_strength import BenchmarkReference, SectorContext |
| 7 | |
| 8 | MAPPING_VERSION = 'SECTOR_BENCHMARK_MAPPING_V1' |
| 9 | CATALOG_VERSION = 'NSE_BENCHMARK_CATALOG_V1' |
| 10 | BENCHMARKS = { |
| 11 | 'INDIA_BROAD_PRICE': 'NIFTY 500', |
| 12 | 'INDIA_TECHNOLOGY_PRICE': 'NIFTY IT', |
| 13 | 'INDIA_FINANCIALS_PRICE': 'NIFTY FINANCIAL SERVICES', |
| 14 | 'INDIA_HEALTHCARE_PRICE': 'NIFTY HEALTHCARE INDEX', |
| 15 | } |
| 16 | SECTOR_KEYS = {'Technology': 'INDIA_TECHNOLOGY_PRICE', 'Financials': 'INDIA_FINANCIALS_PRICE', |
| 17 | 'Healthcare': 'INDIA_HEALTHCARE_PRICE'} |
| 18 | BROAD_KEY = 'INDIA_BROAD_PRICE' |
| 19 | |
| 20 | |
| 21 | def benchmark_id(key): |
| 22 | """Same platform-key UUIDv3 as portfolio-service; never a provider-symbol UUID.""" |
| 23 | if key not in BENCHMARKS: |
| 24 | raise ValueError('UNKNOWN_BENCHMARK_KEY') |
| 25 | return UUID(bytes=md5(('aip:benchmark:' + key).encode(), usedforsecurity=False).digest(), version=3) |
| 26 | |
| 27 | |
| 28 | def benchmark_identity(metadata, key): |
| 29 | if not isinstance(metadata, dict): |
| 30 | raise ValueError('BENCHMARK_IDENTITY_UNAVAILABLE') |
| 31 | mappings = metadata.get('providerMappings') or [] |
| 32 | nse = [m for m in mappings if isinstance(m, dict) and m.get('provider') == 'NSE'] |
| 33 | if (str(metadata.get('globalInstrumentId')) != str(benchmark_id(key)) |
| 34 | or metadata.get('assetType') != 'INDEX' or metadata.get('status') != 'ACTIVE' |
| 35 | or metadata.get('country') != 'IN' or metadata.get('primaryExchange') != 'NSE' |
| 36 | or metadata.get('currency') != 'INR' or len(nse) != 1): |
| 37 | raise ValueError('BENCHMARK_IDENTITY_UNAVAILABLE') |
| 38 | mapping = nse[0] |
| 39 | if (mapping.get('status') != 'VERIFIED' or mapping.get('active') is False |
| 40 | or mapping.get('resolutionSource') != CATALOG_VERSION or mapping.get('currency') != 'INR' |
| 41 | or mapping.get('exchange') != 'NSE' or mapping.get('providerSymbol') != BENCHMARKS[key]): |
| 42 | raise ValueError('BENCHMARK_IDENTITY_UNAVAILABLE') |
| 43 | return BenchmarkReference(benchmark_id(key), 'INR', frozenset({'NSE'})) |
| 44 | |
| 45 | |
| 46 | def build_sector_contexts(classifications, registered, instrument_ids): |
| 47 | """Pure adapter over canonical Nifty500 cache and registered benchmark metadata.""" |
| 48 | by_id = {} |
| 49 | for row in registered: |
| 50 | by_id.setdefault(str(row.get('globalInstrumentId')), []).append(row) |
| 51 | def reference(key): |
| 52 | matches = by_id.get(str(benchmark_id(key)), []) |
| 53 | try: |
| 54 | return benchmark_identity(matches[0], key) if len(matches) == 1 else None |
| 55 | except ValueError: |
| 56 | return None |
| 57 | broad = reference(BROAD_KEY) |
| 58 | grouped = {} |
| 59 | for row in classifications: |
| 60 | grouped.setdefault(str(row.get('globalInstrumentId')), []).append(row) |
| 61 | output = {} |
| 62 | for key in sorted(instrument_ids, key=str): |
| 63 | matches = grouped.get(str(key), []) |
| 64 | row = matches[0] if len(matches) == 1 else {} |
| 65 | sector = row.get('canonicalSector') |
| 66 | stamp = row.get('retrievedAt') |
| 67 | try: |
| 68 | stamp = datetime.fromisoformat(stamp.replace('Z', '+00:00')) if isinstance(stamp, str) else stamp |
| 69 | except ValueError: |
| 70 | stamp = None |
| 71 | india_member = (row.get('source') == 'NSE_INDICES_NIFTY500' and row.get('status') == 'ACTIVE' |
| 72 | and row.get('assetType') == 'EQUITY' and row.get('country') == 'IN' |
| 73 | and row.get('exchange') in {'NSE', 'XNSE'}) |
| 74 | if not india_member: |
| 75 | sector = None |
| 76 | target = SECTOR_KEYS.get(sector) |
| 77 | sector_ref = reference(target) if target else None |
| 78 | classification_ok = bool(sector and stamp) |
| 79 | status = ('NO_SECTOR_CLASSIFICATION' if not classification_ok else |
| 80 | 'UNMAPPED_SECTOR_BENCHMARK' if not target else |
| 81 | 'BENCHMARK_IDENTITY_UNAVAILABLE' if sector_ref is None else 'AVAILABLE') |
| 82 | market_ref = broad if india_member else None |
| 83 | output[key] = SectorContext(sector=sector, source=row.get('source'), as_of=stamp, region='INDIA' if india_member else None, |
| 84 | sector_benchmark=sector_ref if classification_ok else None, market_benchmark=market_ref, |
| 85 | mapping_version=MAPPING_VERSION, sector_mapping_status=status, |
| 86 | market_mapping_status='AVAILABLE' if market_ref else 'BENCHMARK_IDENTITY_UNAVAILABLE') |
| 87 | return output |