272 lines
11 KiB
Python
272 lines
11 KiB
Python
"""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}
|