"""GNACS 编码生成路由""" from fastapi import APIRouter, HTTPException from pydantic import BaseModel from typing import Optional import database import hashlib import time router = APIRouter() # 资产大类编码映射(class_id -> 2位编码) CLASS_CODE_MAP = { "RE": "01", # 不动产 "FA": "02", # 金融资产 "CM": "03", # 大宗商品 "AT": "04", # 艺术品与收藏品 "IP": "05", # 知识产权 "DA": "06", # 数字资产 "IF": "07", # 基础设施 "NR": "08", # 自然资源 "EC": "09", # 环境权益 "EQ": "10", # 企业权益 "CR": "11", # 债权资产 "IN": "12", # 保险资产 "AG": "13", # 农业资产 "TR": "14", # 交通运输资产 "EQ2": "15", # 设备与机械 "DD": "16", # 数据资产 "IA": "17", # 无形商业资产 "SP": "18", # 体育资产 "CE": "19", # 文化娱乐资产 "CU": "20", # 自定义资产 } # ACC代币标准映射 ACC_STANDARD_MAP = { "RE": "20", # ACC-20 "FA": "14", # ACC-1400 "CM": "20", # ACC-20 "AT": "21", # ACC-721 NFT "IP": "21", # ACC-721 NFT "DA": "20", # ACC-20 "IF": "22", # ACC-1155 "NR": "22", # ACC-1155 "EC": "22", # ACC-1155(碳信用) "EQ": "20", # ACC-20 "CR": "14", # ACC-1400 "IN": "14", # ACC-1400 "AG": "22", # ACC-1155 "TR": "21", # ACC-721 "EQ2": "22", # ACC-1155 "DD": "21", # ACC-721 "IA": "20", # ACC-20 "SP": "21", # ACC-721 "CE": "21", # ACC-721 "CU": "20", # ACC-20 } # 司法辖区编码映射(ISO 3166-1 alpha-2 -> 2位数字) JURISDICTION_CODE_MAP = { "US": "01", "GB": "02", "EU": "03", "SG": "04", "HK": "05", "JP": "06", "AU": "07", "CA": "08", "CH": "09", "AE": "10", "CN": "11", "TW": "12", "IN": "13", "SA": "14", "BR": "15", "KR": "16", "MX": "17", "ZA": "18", "NG": "19", "KE": "20", "DE": "21", "FR": "22", "NL": "23", "LU": "24", "IE": "25", "MY": "26", "TH": "27", "ID": "28", "PH": "29", "VN": "30", "QA": "31", "KW": "32", "BH": "33", "OM": "34", "JO": "35", "IL": "36", "TR": "37", "RU": "38", "PL": "39", "CZ": "40", "SE": "41", "NO": "42", "DK": "43", "FI": "44", "PT": "45", "ES": "46", "IT": "47", "AT": "48", "BE": "49", "GR": "50", "NZ": "51", "AR": "52", "CL": "53", "CO": "54", "PE": "55", "EG": "56", "MA": "57", "GH": "58", "ET": "59", "TZ": "60", "GLOBAL": "00", } class GNACSEncodeRequest(BaseModel): asset_id: str asset_class: str sub_class: Optional[str] = None jurisdiction: str # 资产所在辖区 investor_jurisdiction: Optional[str] = None # 投资者辖区(跨境时填写) asset_name: str asset_value: float # USD价值 currency: Optional[str] = "USD" liquidity: Optional[str] = "M" # H/M/L status: Optional[str] = "active" def generate_gnacs_48bit(req: GNACSEncodeRequest) -> dict: """生成48位GNACS编码""" asset_class = req.asset_class.upper() jurisdiction = req.jurisdiction.upper() investor_j = (req.investor_jurisdiction or req.jurisdiction).upper() is_cross_border = jurisdiction != investor_j # AA: 资产大类(2位) aa = CLASS_CODE_MAP.get(asset_class, "20") # BB: 子类编码(2位) bb = "01" if not req.sub_class else req.sub_class[-2:].zfill(2) # CC: HS编码(2位,简化) cc = "00" # DD: 资产状态(2位) status_map = {"active": "01", "frozen": "02", "cancelled": "03"} dd = status_map.get(req.status, "01") # EE: 流动性等级(2位) liquidity_map = {"H": "01", "M": "02", "L": "03"} ee = liquidity_map.get(req.liquidity, "02") # FF: 风险权重(2位,10=1.0倍) risk_map = {"RE": "08", "FA": "12", "CM": "15", "AT": "20", "IP": "18", "DA": "25", "EC": "10", "EQ": "12", "CR": "10"} ff = risk_map.get(asset_class, "15") # GG: ACC代币标准(2位) gg = ACC_STANDARD_MAP.get(asset_class, "20") # HH: 托管类型(2位) custody_map = {"RE": "01", "FA": "01", "AT": "03", "DA": "03", "EC": "02"} hh = custody_map.get(asset_class, "01") # II: 主权法律管辖(2位) ii = JURISDICTION_CODE_MAP.get(jurisdiction, "00") # JJ: 投资者辖区(2位,同辖区为00) jj = "00" if not is_cross_border else JURISDICTION_CODE_MAP.get(investor_j, "00") # KK: 合规等级(2位,即最低KYC等级) kyc_map = {"RE": "2", "FA": "3", "AT": "2", "IP": "2", "EC": "2", "DA": "1", "EQ": "3", "CR": "3", "IN": "3"} kk = kyc_map.get(asset_class, "2") if is_cross_border: kk = str(max(int(kk), 3)) # 跨境交易最低KYC-3 kk = kk.zfill(2) # LL: 区域联盟(2位) alliance_map = { "SG": "01", "MY": "01", "TH": "01", "ID": "01", "PH": "01", "VN": "01", # ASEAN "AE": "02", "SA": "02", "QA": "02", "KW": "02", "BH": "02", "OM": "02", # GCC "DE": "03", "FR": "03", "NL": "03", "IT": "03", "ES": "03", # EU } ll = alliance_map.get(jurisdiction, "00") # MM: 资产价值区间(2位) if req.asset_value < 1_000_000: mm = "01" elif req.asset_value < 10_000_000: mm = "02" elif req.asset_value < 100_000_000: mm = "03" else: mm = "04" # NN: 计价货币(2位) currency_map = {"USD": "01", "CNY": "02", "EUR": "03", "HKD": "04", "SGD": "05"} nn = currency_map.get(req.currency or "USD", "01") # OO: 发行年份(2位,取年份后2位) import datetime now = datetime.datetime.now() oo = str(now.year)[-2:] # PP: 发行月份(2位) pp = str(now.month).zfill(2) # QQ: 实时状态(2位) qq = "01" # RR: 跨链标识(2位,00=NAC原生) rr = "00" # SS TT UU: 资产序列号(6位,3×2位) hash_input = f"{req.asset_id}{req.asset_name}{time.time()}" hash_hex = hashlib.sha256(hash_input.encode()).hexdigest() ss = hash_hex[0:2] tt = hash_hex[2:4] uu = hash_hex[4:6] # VV WW: 校验位(4位,2×2位) code_so_far = aa+bb+cc+dd+ee+ff+gg+hh+ii+jj+kk+ll+mm+nn+oo+pp+qq+rr+ss+tt+uu checksum = hashlib.md5(code_so_far.encode()).hexdigest() vv = checksum[0:2] ww = checksum[2:4] # XX: 版本号(2位) xx = "01" gnacs_code = aa+bb+cc+dd+ee+ff+gg+hh+ii+jj+kk+ll+mm+nn+oo+pp+qq+rr+ss+tt+uu+vv+ww+xx # 格式化:每4位加横线 formatted = "-".join([gnacs_code[i:i+4] for i in range(0, 48, 4)]) acc_standard_display = { "20": "ACC-20", "21": "ACC-721", "22": "ACC-1155", "14": "ACC-1400" }.get(gg, "ACC-20") return { "gnacs_code": gnacs_code, "formatted": formatted, "segments": { "AA_asset_class": aa, "BB_sub_class": bb, "CC_hs_code": cc, "DD_status": dd, "EE_liquidity": ee, "FF_risk_weight": ff, "GG_acc_standard": gg, "HH_custody_type": hh, "II_jurisdiction": ii, "JJ_investor_jurisdiction": jj, "KK_compliance_level": kk, "LL_regional_alliance": ll, "MM_value_range": mm, "NN_currency": nn, "OO_issue_year": oo, "PP_issue_month": pp, "QQ_realtime_status": qq, "RR_cross_chain": rr, "SS_serial_a": ss, "TT_serial_b": tt, "UU_serial_c": uu, "VV_checksum_a": vv, "WW_checksum_b": ww, "XX_version": xx }, "metadata": { "acc_standard": acc_standard_display, "is_cross_border": is_cross_border, "risk_weight": float(ff) / 10, "min_kyc_level": int(kk), "asset_class": asset_class, "jurisdiction": jurisdiction, "investor_jurisdiction": investor_j } } @router.post("/generate") async def generate_gnacs_code(req: GNACSEncodeRequest): """生成48位GNACS编码并注册到数据库""" result = generate_gnacs_48bit(req) col = database.gnacs_codes_col existing = await col.find_one({"asset_id": req.asset_id}) if existing: existing["_id"] = str(existing["_id"]) return {"success": True, "message": "GNACS编码已存在", "data": result, "registered": True} doc = { "asset_id": req.asset_id, "asset_name": req.asset_name, "gnacs_code": result["gnacs_code"], "formatted": result["formatted"], "segments": result["segments"], "metadata": result["metadata"], "created_at": database.now_utc() } await col.insert_one(doc) return {"success": True, "message": "GNACS编码生成并注册成功", "data": result, "registered": True} @router.get("/decode/{gnacs_code}") async def decode_gnacs_code(gnacs_code: str): """解码48位GNACS编码""" code = gnacs_code.replace("-", "").replace(" ", "") if len(code) != 48: raise HTTPException(status_code=400, detail=f"GNACS编码长度错误:期望48位,实际{len(code)}位") reverse_class = {v: k for k, v in CLASS_CODE_MAP.items()} reverse_acc = {"20": "ACC-20", "21": "ACC-721", "22": "ACC-1155", "14": "ACC-1400"} reverse_jurisdiction = {v: k for k, v in JURISDICTION_CODE_MAP.items()} segments = [code[i:i+2] for i in range(0, 48, 2)] return { "success": True, "data": { "gnacs_code": code, "formatted": "-".join([code[i:i+4] for i in range(0, 48, 4)]), "decoded": { "资产大类": reverse_class.get(segments[0], f"未知({segments[0]})"), "子类编码": segments[1], "资产状态": {"01": "活跃", "02": "冻结", "03": "注销"}.get(segments[3], segments[3]), "流动性等级": {"01": "高(H)", "02": "中(M)", "03": "低(L)"}.get(segments[4], segments[4]), "风险权重": f"{int(segments[5]) / 10:.1f}", "ACC代币标准": reverse_acc.get(segments[6], segments[6]), "主权法律管辖": reverse_jurisdiction.get(segments[8], f"未知({segments[8]})"), "投资者辖区": reverse_jurisdiction.get(segments[9], "同辖区") if segments[9] != "00" else "同辖区", "合规等级": f"KYC-{int(segments[10])}", "是否跨境": "是" if segments[9] != "00" else "否", "资产价值区间": {"01": "<100万USD", "02": "100万-1000万USD", "03": "1000万-1亿USD", "04": ">1亿USD"}.get(segments[12], segments[12]), "计价货币": {"01": "USD", "02": "CNY", "03": "EUR", "04": "HKD", "05": "SGD"}.get(segments[13], segments[13]), "发行年份": f"20{segments[14]}", "发行月份": segments[15], "版本号": segments[23] } } } @router.get("/lookup/{asset_id}") async def lookup_by_asset_id(asset_id: str): """通过资产ID查询已注册的GNACS编码""" col = database.gnacs_codes_col doc = await col.find_one({"asset_id": asset_id}) if not doc: raise HTTPException(status_code=404, detail=f"资产 {asset_id} 尚未注册GNACS编码") doc["_id"] = str(doc["_id"]) if "created_at" in doc: doc["created_at"] = str(doc["created_at"]) return {"success": True, "data": doc}