NAC_Blockchain/gnacs-service/routers/encode.py

272 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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}