diff --git a/internal/domains/api/services/processors/qygl/qyglj1u9_processor.go b/internal/domains/api/services/processors/qygl/qyglj1u9_processor.go index 679056f..d7758f2 100644 --- a/internal/domains/api/services/processors/qygl/qyglj1u9_processor.go +++ b/internal/domains/api/services/processors/qygl/qyglj1u9_processor.go @@ -17,9 +17,9 @@ import ( "tyapi-server/internal/domains/api/services/processors" ) -// ProcessQYGLJ1U9Request 企业全景报告处理器:并发调用企业全量(QYGLUY3S)、股权全景(QYGLJ0Q1)、司法涉诉(QYGL5S1I), -// 以及企业年报(QYGLDJ12)、税收违法(QYGL8848)、欠税公告(QYGL7D9A); -// 前三者走 buildReport / map*;后三者在 buildReport 中转为 annualReports、taxViolations、ownTaxNotices 供页面展示。 +// ProcessQYGLJ1U9Request 企业全景报告处理器:并发调用企业全量(QYGLUY3S)、股权全景(QYGLJ0Q1)、司法涉诉(QYGL5S1I)、 +// 企业年报(QYGLDJ12)、税收违法(QYGL8848)、欠税公告(QYGL7D9A)。 +// 单路失败、查无、解析失败时该路按空数据处理并继续合并;仅当合并后的报告仍无任何可展示的企业要素时返回查询为空。 func ProcessQYGLJ1U9Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) { // 复用 QYGLUY3S 的入参结构:企业名称/注册号/统一社会信用代码 var p dto.QYGLJ1U9Req @@ -30,7 +30,7 @@ func ProcessQYGLJ1U9Request(ctx context.Context, params []byte, deps *processors return nil, errors.Join(processors.ErrInvalidParam, err) } - // 并发调用三个已有处理器 + // 并发调用六个子处理器;单路失败或无数据时降级为空结果,仅当合并后仍无任何企业要素时返回查询为空 type apiResult struct { key string data map[string]interface{} @@ -108,12 +108,15 @@ func ProcessQYGLJ1U9Request(ctx context.Context, params []byte, deps *processors wg.Wait() close(resultsCh) - var jiguang, judicial, equity map[string]interface{} - var annualReport, taxViolation, taxArrears map[string]interface{} + jiguang := map[string]interface{}{} + judicial := map[string]interface{}{} + equity := map[string]interface{}{} + annualReport := map[string]interface{}{} + taxViolation := map[string]interface{}{} + taxArrears := map[string]interface{}{} for r := range resultsCh { - if r.err != nil { - // 任一关键数据源异常,则返回系统错误(也可以根据需求做降级) - return nil, errors.Join(processors.ErrSystem, fmt.Errorf("%s 调用失败: %w", r.key, r.err)) + if r.err != nil || r.data == nil { + continue } switch r.key { case "jiguangFull": @@ -130,27 +133,12 @@ func ProcessQYGLJ1U9Request(ctx context.Context, params []byte, deps *processors taxArrears = r.data } } - if jiguang == nil { - jiguang = map[string]interface{}{} - } - if judicial == nil { - judicial = map[string]interface{}{} - } - if equity == nil { - equity = map[string]interface{}{} - } - if annualReport == nil { - annualReport = map[string]interface{}{} - } - if taxViolation == nil { - taxViolation = map[string]interface{}{} - } - if taxArrears == nil { - taxArrears = map[string]interface{}{} - } // 复用构建逻辑生成企业报告结构(含年报 / 税收违法 / 欠税公告的转化结果) report := buildReport(jiguang, judicial, equity, annualReport, taxViolation, taxArrears) + if !qyglJ1U9ReportHasSubstantiveData(report) { + return nil, errors.Join(processors.ErrNotFound, errors.New("未查询到可用于生成报告的企业数据")) + } // 为报告生成唯一编号并缓存,供后续通过编号查看 reportID := saveQYGLReport(report) diff --git a/internal/domains/api/services/processors/qygl/qyglj1u9_processor_build.go b/internal/domains/api/services/processors/qygl/qyglj1u9_processor_build.go index b1deae1..3c71d67 100644 --- a/internal/domains/api/services/processors/qygl/qyglj1u9_processor_build.go +++ b/internal/domains/api/services/processors/qygl/qyglj1u9_processor_build.go @@ -58,9 +58,654 @@ func buildReport(jiguang, judicial, equity, annualRaw, taxViolationRaw, taxArrea report["annualReports"] = annualReports report["taxViolations"] = mapTaxViolations(taxViolationRaw) report["ownTaxNotices"] = mapOwnTaxNotices(taxArrearsRaw) + applyQYGLJ1U9ReportFieldDefaults(report) return report } +// applyQYGLJ1U9ReportFieldDefaults 在子数据源缺失时仍保证报告字段齐全:字符串 ""、数值 0、数组 []、对象按约定填空结构(不向客户暴露「缺键」)。 +func applyQYGLJ1U9ReportFieldDefaults(report map[string]interface{}) { + if report == nil { + return + } + // 顶层字符串 + report["entName"] = str(report["entName"]) + report["creditCode"] = str(report["creditCode"]) + report["reportTime"] = str(report["reportTime"]) + + report["basic"] = mergeBasicDefaults(report["basic"]) + b, _ := report["basic"].(map[string]interface{}) + if str(report["entName"]) == "" { + report["entName"] = str(b["entName"]) + } + if str(report["creditCode"]) == "" { + report["creditCode"] = str(b["creditCode"]) + } + + report["branches"] = ensureSlice(report["branches"]) + report["guarantees"] = ensureSlice(report["guarantees"]) + report["inspections"] = ensureSlice(report["inspections"]) + report["timeline"] = ensureSlice(report["timeline"]) + report["beneficiaries"] = ensureSlice(report["beneficiaries"]) + report["annualReports"] = ensureSlice(report["annualReports"]) + if _, ok := report["basicList"]; !ok { + report["basicList"] = []interface{}{} + } else { + report["basicList"] = ensureSlice(report["basicList"]) + } + + report["shareholding"] = mergeShareholdingDefaults(report["shareholding"]) + report["controller"] = mergeControllerDefaults(report["controller"]) + report["investments"] = mergeInvestmentsDefaults(report["investments"]) + report["management"] = mergeManagementDefaults(report["management"]) + report["assets"] = mergeAssetsDefaults(report["assets"]) + report["licenses"] = mergeLicensesDefaults(report["licenses"]) + report["activities"] = mergeActivitiesDefaults(report["activities"]) + report["risks"] = mergeRisksDefaults(report["risks"]) + report["listed"] = mergeListedDefaults(report["listed"]) + + report["taxViolations"] = mergeTaxViolationsDefaults(report["taxViolations"]) + report["ownTaxNotices"] = mergeOwnTaxNoticesDefaults(report["ownTaxNotices"]) + + if ro, ok := report["riskOverview"].(map[string]interface{}); ok { + report["riskOverview"] = mergeRiskOverviewDefaults(ro) + } else { + report["riskOverview"] = mergeRiskOverviewDefaults(nil) + } +} + +func mergeBasicDefaults(v interface{}) map[string]interface{} { + out, _ := v.(map[string]interface{}) + if out == nil { + out = map[string]interface{}{} + } + skel := map[string]interface{}{ + "entName": "", + "creditCode": "", + "regNo": "", + "orgCode": "", + "entType": "", + "entTypeCode": "", + "entityTypeCode": "", + "establishDate": "", + "registeredCapital": float64(0), + "regCapCurrency": "", + "regCapCurrencyCode": "", + "regOrg": "", + "regOrgCode": "", + "regProvince": "", + "provinceCode": "", + "regCity": "", + "regCityCode": "", + "regDistrict": "", + "districtCode": "", + "address": "", + "postalCode": "", + "legalRepresentative": "", + "compositionForm": "", + "approvedBusinessItem": "", + "status": "", + "statusCode": "", + "operationPeriodFrom": "", + "operationPeriodTo": "", + "approveDate": "", + "cancelDate": "", + "revokeDate": "", + "cancelReason": "", + "revokeReason": "", + "businessScope": "", + "lastAnnuReportYear": "", + "oldNames": []interface{}{}, + } + for k, def := range skel { + if _, ok := out[k]; !ok { + out[k] = def + } + } + if out["oldNames"] == nil { + out["oldNames"] = []interface{}{} + } + return out +} + +func ensureSlice(v interface{}) []interface{} { + if v == nil { + return []interface{}{} + } + arr, ok := v.([]interface{}) + if !ok { + return []interface{}{} + } + return arr +} + +func mergeShareholdingDefaults(v interface{}) map[string]interface{} { + out, _ := v.(map[string]interface{}) + if out == nil { + out = map[string]interface{}{} + } + skel := map[string]interface{}{ + "shareholders": []interface{}{}, + "equityChanges": []interface{}{}, + "equityPledges": []interface{}{}, + "paidInDetails": []interface{}{}, + "subscribedCapitalDetails": []interface{}{}, + "hasEquityPledges": false, + "shareholderCount": 0, + "registeredCapital": float64(0), + "currency": "", + "topHolderName": "", + "topHolderPercent": float64(0), + "top5TotalPercent": float64(0), + } + for k, def := range skel { + if _, ok := out[k]; !ok { + out[k] = def + } + } + for _, k := range []string{"shareholders", "equityChanges", "equityPledges", "paidInDetails", "subscribedCapitalDetails"} { + out[k] = ensureSlice(out[k]) + } + if out["registeredCapital"] == nil { + out["registeredCapital"] = float64(0) + } + if _, ok := out["hasEquityPledges"].(bool); !ok { + out["hasEquityPledges"] = false + } + return out +} + +func mergeControllerDefaults(v interface{}) map[string]interface{} { + if m, ok := v.(map[string]interface{}); ok && m != nil { + if m["path"] == nil { + m["path"] = map[string]interface{}{ + "nodes": []interface{}{}, + "links": []interface{}{}, + } + } + if p, ok := m["path"].(map[string]interface{}); ok { + if _, ok := p["nodes"]; !ok { + p["nodes"] = []interface{}{} + } + if _, ok := p["links"]; !ok { + p["links"] = []interface{}{} + } + } + ensureStrKeys(m, []string{"id", "name", "type", "reason", "source"}) + if m["percent"] == nil { + m["percent"] = float64(0) + } else if _, ok := m["percent"].(float64); !ok { + m["percent"] = num(m["percent"]) + } + return m + } + return map[string]interface{}{ + "id": "", + "name": "", + "type": "", + "percent": float64(0), + "path": map[string]interface{}{ + "nodes": []interface{}{}, + "links": []interface{}{}, + }, + "reason": "", + "source": "", + } +} + +func mergeInvestmentsDefaults(v interface{}) map[string]interface{} { + out, _ := v.(map[string]interface{}) + if out == nil { + out = map[string]interface{}{} + } + if _, ok := out["totalCount"]; !ok { + out["totalCount"] = 0 + } + if _, ok := out["totalAmount"]; !ok { + out["totalAmount"] = float64(0) + } + if out["totalAmount"] == nil { + out["totalAmount"] = float64(0) + } + out["list"] = ensureSlice(out["list"]) + out["legalRepresentativeInvestments"] = ensureSlice(out["legalRepresentativeInvestments"]) + return out +} + +func mergeManagementDefaults(v interface{}) map[string]interface{} { + out, _ := v.(map[string]interface{}) + if out == nil { + out = map[string]interface{}{} + } + if _, ok := out["executives"]; !ok { + out["executives"] = []interface{}{} + } else { + out["executives"] = ensureSlice(out["executives"]) + } + if _, ok := out["legalRepresentativeOtherPositions"]; !ok { + out["legalRepresentativeOtherPositions"] = []interface{}{} + } else { + out["legalRepresentativeOtherPositions"] = ensureSlice(out["legalRepresentativeOtherPositions"]) + } + if _, ok := out["employeeCount"]; !ok || out["employeeCount"] == nil { + out["employeeCount"] = float64(0) + } + if _, ok := out["femaleEmployeeCount"]; !ok || out["femaleEmployeeCount"] == nil { + out["femaleEmployeeCount"] = float64(0) + } + if out["socialSecurity"] == nil { + out["socialSecurity"] = map[string]interface{}{} + } + return out +} + +func mergeAssetsDefaults(v interface{}) map[string]interface{} { + out, _ := v.(map[string]interface{}) + if out == nil { + out = map[string]interface{}{} + } + if _, ok := out["years"]; !ok { + out["years"] = []interface{}{} + } else { + out["years"] = ensureSlice(out["years"]) + } + return out +} + +func mergeLicensesDefaults(v interface{}) map[string]interface{} { + out, _ := v.(map[string]interface{}) + if out == nil { + out = map[string]interface{}{} + } + for _, k := range []string{"permits", "permitChanges", "ipPledges", "otherLicenses"} { + if _, ok := out[k]; !ok { + out[k] = []interface{}{} + } else { + out[k] = ensureSlice(out[k]) + } + } + return out +} + +func mergeActivitiesDefaults(v interface{}) map[string]interface{} { + out, _ := v.(map[string]interface{}) + if out == nil { + out = map[string]interface{}{} + } + if _, ok := out["bids"]; !ok { + out["bids"] = []interface{}{} + } else { + out["bids"] = ensureSlice(out["bids"]) + } + if _, ok := out["websites"]; !ok { + out["websites"] = []interface{}{} + } else { + out["websites"] = ensureSlice(out["websites"]) + } + return out +} + +func litigationTypeKeys() []string { + return []string{ + "administrative", "implement", "preservation", "civil", "criminal", + "bankrupt", "jurisdict", "compensate", + } +} + +func defaultLitigationShell() map[string]interface{} { + out := map[string]interface{}{"totalCases": 0} + for _, k := range litigationTypeKeys() { + out[k] = map[string]interface{}{ + "count": 0, + "cases": []interface{}{}, + } + } + return out +} + +func mergeLitigationShape(v interface{}) map[string]interface{} { + out := defaultLitigationShell() + if v == nil { + return out + } + m, ok := v.(map[string]interface{}) + if !ok { + return out + } + known := map[string]struct{}{} + for _, k := range litigationTypeKeys() { + known[k] = struct{}{} + } + for k, val := range m { + if k == "totalCases" { + out["totalCases"] = intFromAny(val) + continue + } + if _, isCat := known[k]; isCat { + sm, ok := val.(map[string]interface{}) + if !ok { + continue + } + out[k] = map[string]interface{}{ + "count": intFromAny(sm["count"]), + "cases": ensureSlice(sm["cases"]), + } + continue + } + out[k] = val + } + return out +} + +func defaultQuickCancelShell() map[string]interface{} { + return map[string]interface{}{ + "entName": "", + "creditCode": "", + "regNo": "", + "regOrg": "", + "noticeFromDate": "", + "noticeToDate": "", + "cancelResult": "", + "dissents": []interface{}{}, + } +} + +func defaultLiquidationShell() map[string]interface{} { + return map[string]interface{}{ + "principal": "", + "members": []interface{}{}, + } +} + +func mergeRisksDefaults(v interface{}) map[string]interface{} { + out, _ := v.(map[string]interface{}) + if out == nil { + out = map[string]interface{}{} + } + boolKeys := []string{ + "hasCourtJudgments", "hasJudicialAssists", "hasDishonestDebtors", "hasLimitHighDebtors", + "hasAdminPenalty", "hasException", "hasSeriousIllegal", "hasTaxOwing", "hasSeriousTaxIllegal", + "hasMortgage", "hasEquityPledges", "hasQuickCancel", + } + for _, k := range boolKeys { + if _, ok := out[k]; !ok { + out[k] = false + } + } + if _, ok := out["riskLevel"]; !ok { + out["riskLevel"] = "低" + } + if _, ok := out["riskScore"]; !ok { + out["riskScore"] = 80 + } + for _, k := range []string{"dishonestDebtorCount", "limitHighDebtorCount"} { + if _, ok := out[k]; !ok { + out[k] = 0 + } + } + for _, k := range []string{ + "courtJudgments", "judicialAssists", "dishonestDebtors", "limitHighDebtors", + "adminPenalties", "adminPenaltyUpdates", "exceptions", "seriousIllegals", "mortgages", + } { + if _, ok := out[k]; !ok { + out[k] = []interface{}{} + } else { + out[k] = ensureSlice(out[k]) + } + } + out["litigation"] = mergeLitigationShape(out["litigation"]) + if out["quickCancel"] == nil { + out["quickCancel"] = defaultQuickCancelShell() + } else if qm, ok := out["quickCancel"].(map[string]interface{}); ok { + dc := defaultQuickCancelShell() + for k, def := range dc { + if _, ok := qm[k]; !ok { + qm[k] = def + } + } + if qm["dissents"] == nil { + qm["dissents"] = []interface{}{} + } else { + qm["dissents"] = ensureSlice(qm["dissents"]) + } + out["quickCancel"] = qm + } + if out["liquidation"] == nil { + out["liquidation"] = defaultLiquidationShell() + } else if lm, ok := out["liquidation"].(map[string]interface{}); ok { + dc := defaultLiquidationShell() + for k, def := range dc { + if _, ok := lm[k]; !ok { + lm[k] = def + } + } + if lm["members"] == nil { + lm["members"] = []interface{}{} + } else { + lm["members"] = ensureSlice(lm["members"]) + } + out["liquidation"] = lm + } + tr, _ := out["taxRecords"].(map[string]interface{}) + if tr == nil { + tr = map[string]interface{}{} + } + for _, k := range []string{"taxLevelAYears", "seriousTaxIllegal", "taxOwings"} { + if _, ok := tr[k]; !ok { + tr[k] = []interface{}{} + } else { + tr[k] = ensureSlice(tr[k]) + } + } + out["taxRecords"] = tr + return out +} + +// normalizeListedStock 无股票结构时用 JSON null,避免前端把 {} 当成有值而 JSON.stringify 出 "{}"。 +func normalizeListedStock(v interface{}) interface{} { + if v == nil { + return nil + } + m, ok := v.(map[string]interface{}) + if !ok { + return v + } + if len(m) == 0 { + return nil + } + return v +} + +func mergeListedDefaults(v interface{}) map[string]interface{} { + if m, ok := v.(map[string]interface{}); ok && m != nil { + if _, ok := m["isListed"].(bool); !ok { + m["isListed"] = false + } + co, _ := m["company"].(map[string]interface{}) + if co == nil { + co = map[string]interface{}{} + } + for k, def := range map[string]interface{}{ + "bizScope": "", "creditCode": "", "regAddr": "", "regCapital": "", + "orgCode": "", "cur": "", "curName": "", + } { + if _, ok := co[k]; !ok { + co[k] = def + } + } + m["company"] = co + m["stock"] = normalizeListedStock(m["stock"]) + if _, ok := m["topShareholders"]; !ok { + m["topShareholders"] = []interface{}{} + } else { + m["topShareholders"] = ensureSlice(m["topShareholders"]) + } + if _, ok := m["listedManagers"]; !ok { + m["listedManagers"] = []interface{}{} + } else { + m["listedManagers"] = ensureSlice(m["listedManagers"]) + } + return m + } + return map[string]interface{}{ + "isListed": false, + "company": map[string]interface{}{ + "bizScope": "", "creditCode": "", "regAddr": "", "regCapital": "", + "orgCode": "", "cur": "", "curName": "", + }, + "stock": nil, + "topShareholders": []interface{}{}, + "listedManagers": []interface{}{}, + } +} + +func mergeTaxViolationsDefaults(v interface{}) map[string]interface{} { + out, _ := v.(map[string]interface{}) + if out == nil { + out = map[string]interface{}{} + } + if _, ok := out["total"]; !ok { + out["total"] = 0 + } + if _, ok := out["items"]; !ok { + out["items"] = []interface{}{} + } else { + out["items"] = ensureSlice(out["items"]) + } + return out +} + +func mergeOwnTaxNoticesDefaults(v interface{}) map[string]interface{} { + return mergeTaxViolationsDefaults(v) +} + +func mergeRiskOverviewDefaults(v interface{}) map[string]interface{} { + out, _ := v.(map[string]interface{}) + if out == nil { + out = map[string]interface{}{} + } + if _, ok := out["riskLevel"]; !ok { + out["riskLevel"] = "低" + } + if _, ok := out["riskScore"]; !ok { + out["riskScore"] = 100 + } + if _, ok := out["tags"]; !ok { + out["tags"] = []interface{}{} + } else { + out["tags"] = ensureSlice(out["tags"]) + } + if _, ok := out["items"]; !ok { + out["items"] = []interface{}{} + } else { + out["items"] = ensureSlice(out["items"]) + } + return out +} + +func ensureStrKeys(m map[string]interface{}, keys []string) { + if m == nil { + return + } + for _, k := range keys { + if _, ok := m[k]; !ok { + m[k] = "" + } else if m[k] == nil { + m[k] = "" + } + } +} + +// qyglJ1U9ReportHasSubstantiveData 判断合并后的报告是否至少含一项可展示的企业要素。 +// 当所有子数据源均失败或等价于无数据时返回 false,用于 QYGLJ1U9 整体返回「查询为空」。 +func qyglJ1U9ReportHasSubstantiveData(report map[string]interface{}) bool { + if report == nil { + return false + } + trim := func(v interface{}) string { return strings.TrimSpace(str(v)) } + + basic, _ := report["basic"].(map[string]interface{}) + if basic != nil { + if trim(basic["entName"]) != "" || trim(basic["creditCode"]) != "" { + return true + } + } + if trim(report["entName"]) != "" || trim(report["creditCode"]) != "" { + return true + } + if ar, ok := report["annualReports"].([]interface{}); ok && len(ar) > 0 { + return true + } + if tv, ok := report["taxViolations"].(map[string]interface{}); ok { + if intFromAny(tv["total"]) > 0 { + return true + } + if items, ok := tv["items"].([]interface{}); ok && len(items) > 0 { + return true + } + } + if ot, ok := report["ownTaxNotices"].(map[string]interface{}); ok { + if intFromAny(ot["total"]) > 0 { + return true + } + if items, ok := ot["items"].([]interface{}); ok && len(items) > 0 { + return true + } + } + if br, ok := report["branches"].([]interface{}); ok && len(br) > 0 { + return true + } + if bl, ok := report["basicList"].([]interface{}); ok && len(bl) > 0 { + return true + } + if sh, ok := report["shareholding"].(map[string]interface{}); ok { + if arr, ok := sh["shareholders"].([]interface{}); ok && len(arr) > 0 { + return true + } + if intFromAny(sh["shareholderCount"]) > 0 { + return true + } + } + if inv, ok := report["investments"].(map[string]interface{}); ok { + if arr, ok := inv["list"].([]interface{}); ok && len(arr) > 0 { + return true + } + if intFromAny(inv["totalCount"]) > 0 { + return true + } + } + if ben, ok := report["beneficiaries"].([]interface{}); ok && len(ben) > 0 { + return true + } + if tl, ok := report["timeline"].([]interface{}); ok && len(tl) > 0 { + return true + } + if ctl, _ := report["controller"].(map[string]interface{}); ctl != nil && trim(ctl["name"]) != "" { + return true + } + if risks, ok := report["risks"].(map[string]interface{}); ok { + for _, key := range []string{ + "dishonestDebtors", "limitHighDebtors", "adminPenalties", "exceptions", + "seriousIllegals", "mortgages", "courtJudgments", "judicialAssists", + } { + if arr, ok := risks[key].([]interface{}); ok && len(arr) > 0 { + return true + } + } + if lit, ok := risks["litigation"].(map[string]interface{}); ok { + for _, v := range lit { + sub, ok := v.(map[string]interface{}) + if !ok { + continue + } + if arr, ok := sub["cases"].([]interface{}); ok && len(arr) > 0 { + return true + } + } + } + } + return false +} + // jiguangWithoutYearReportTables 浅拷贝全量 map,并去掉企业全量 V2 中与「公示年报」对应的 YEARREPORT* 键。 // 在已接入 QYGLDJ12 且年报列表非空时使用,避免 build 与 HTML 中与「十六、企业年报」重复展示。 func jiguangWithoutYearReportTables(jiguang map[string]interface{}) map[string]interface{} { diff --git a/resources/dev-report/QYGLJ1U9_客户字段说明.md b/resources/dev-report/QYGLJ1U9_客户字段说明.md new file mode 100644 index 0000000..5dc3eb1 --- /dev/null +++ b/resources/dev-report/QYGLJ1U9_客户字段说明.md @@ -0,0 +1,203 @@ +# QYGLJ1U9 企业全景报告字段说明 + +## 一、返回对象总览 + +| 字段 | 类型 | 说明 | +| --------------- | ------------- | -------------------------------------------------- | +| `reportId` | string | 报告编号,可用于后续按编号访问报告页面或下载 PDF。 | +| `reportUrl` | string | 报告访问链接。 | +| `reportTime` | string | 报告生成时间,格式示例:`2026-03-21 19:30:45`。 | +| `entName` | string | 企业名称。 | +| `creditCode` | string | 统一社会信用代码。 | +| `basic` | object | 企业主体基础信息。 | +| `branches` | array | 分支机构列表。 | +| `shareholding` | object | 股权结构与股东信息。 | +| `controller` | object / null | 实际控制人信息。 | +| `beneficiaries` | array | 最终受益人列表。 | +| `investments` | object | 对外投资信息。 | +| `guarantees` | array | 对外担保信息。 | +| `management` | object | 高管、人员和社保相关信息。 | +| `assets` | object | 资产经营类年度信息。 | +| `licenses` | object | 行政许可、许可变更、知识产权出质等。 | +| `activities` | object | 招投标、网站网店等经营活动信息。 | +| `inspections` | array | 抽查检查信息。 | +| `risks` | object | 风险与合规信息。 | +| `timeline` | array | 工商变更时间线。 | +| `listed` | object / null | 上市相关信息。 | +| `riskOverview` | object | 风险总览(等级、分值、标签)。 | +| `annualReports` | array | 企业年报列表。 | +| `taxViolations` | object | 税收违法信息,结构为 `{ total, items }`。 | +| `ownTaxNotices` | object | 欠税公告信息,结构为 `{ total, items }`。 | + +--- + +## 二、核心字段说明 + +### 1) `basic` 企业主体基础信息 + +常见字段如下: + +| 字段 | 说明 | +| ------------------------------------------- | ---------------- | +| `entName` | 企业名称 | +| `creditCode` | 统一社会信用代码 | +| `regNo` | 注册号 | +| `orgCode` | 组织机构代码 | +| `entType` | 企业类型 | +| `establishDate` | 成立日期 | +| `registeredCapital` | 注册资本 | +| `regCapCurrency` | 注册资本币种 | +| `legalRepresentative` | 法定代表人 | +| `status` | 经营状态 | +| `operationPeriodFrom` / `operationPeriodTo` | 营业期限起止 | +| `regOrg` | 登记机关 | +| `address` | 企业地址 | +| `businessScope` | 经营范围 | +| `oldNames` | 曾用名列表 | + +### 2) `shareholding` 股权结构 + +| 字段 | 说明 | +| -------------------------- | ---------------------- | +| `shareholders` | 股东列表 | +| `shareholderCount` | 股东人数 | +| `topHolderName` | 第一大股东名称 | +| `topHolderPercent` | 第一大股东持股比例 | +| `top5TotalPercent` | 前五大股东持股比例合计 | +| `equityChanges` | 股权变更记录 | +| `equityPledges` | 股权出质记录 | +| `paidInDetails` | 实缴出资明细 | +| `subscribedCapitalDetails` | 认缴出资明细 | + +`shareholders` 常见子字段: +`name`、`type`、`ownershipPercent`、`subscribedAmount`、`paidAmount`、`subscribedDate`、`paidDate`。 + +### 3) `controller` 实际控制人 + +常见字段:`id`、`name`、`type`、`percent`、`path`、`reason`。 + +### 4) `beneficiaries` 最终受益人 + +每条常见字段:`id`、`name`、`type`、`percent`、`path`、`reason`。 + +### 5) `investments` 对外投资 + +| 字段 | 说明 | +| -------------------------------- | ------------------------ | +| `totalCount` | 对外投资企业数量 | +| `totalAmount` | 对外投资金额汇总(如有) | +| `list` | 对外投资企业列表 | +| `legalRepresentativeInvestments` | 法定代表人对外投资列表 | + +`list` 常见子字段:`entName`、`creditCode`、`entStatus`、`regCap`、`investAmount`、`investPercent`。 + +### 6) `management` 管理层与人员信息 + +| 字段 | 说明 | +| ----------------------------------- | ---------------------- | +| `executives` | 高管列表(姓名、职务) | +| `legalRepresentativeOtherPositions` | 法人对外任职信息 | +| `employeeCount` | 员工人数 | +| `femaleEmployeeCount` | 女性员工人数 | +| `socialSecurity` | 社保相关字段集合 | + +### 7) `assets` 资产经营信息 + +`assets.years` 为按年度的经营数据,常见字段: +`year`、`assetTotal`、`revenueTotal`、`mainBusinessRevenue`、`taxTotal`、`equityTotal`、`profitTotal`、`netProfit`、`liabilityTotal`。 + +### 8) `licenses` 许可与资质信息 + +| 字段 | 说明 | +| --------------- | ---------------- | +| `permits` | 行政许可列表 | +| `permitChanges` | 行政许可变更列表 | +| `ipPledges` | 知识产权出质列表 | +| `otherLicenses` | 其他许可信息 | + +### 9) `activities` 经营活动信息 + +| 字段 | 说明 | +| ---------- | -------------- | +| `bids` | 招投标信息 | +| `websites` | 网站或网店信息 | + +### 10) `inspections` 抽查检查 + +每条常见字段:`dataType`、`regOrg`、`inspectDate`、`result`。 + +--- + +## 三、风险相关字段 + +### 1) `riskOverview` 风险总览(建议用于首页展示) + +| 字段 | 类型 | 说明 | +| ----------- | ------ | ---------------------------------------------------- | +| `riskLevel` | string | 风险等级:`低` / `中` / `高`。 | +| `riskScore` | number | 风险分值(0-100)。 | +| `tags` | array | 风险标签列表。 | +| `items` | array | 各类风险项命中情况,元素结构通常为 `{ name, hit }`。 | + +### 2) `risks` 风险详情 + +常见布尔字段: +`hasCourtJudgments`、`hasJudicialAssists`、`hasDishonestDebtors`、`hasLimitHighDebtors`、`hasAdminPenalty`、`hasException`、`hasSeriousIllegal`、`hasTaxOwing`、`hasSeriousTaxIllegal`、`hasMortgage`、`hasEquityPledges`、`hasQuickCancel`。 + +常见明细字段: +`dishonestDebtors`、`limitHighDebtors`、`litigation`、`adminPenalties`、`adminPenaltyUpdates`、`exceptions`、`seriousIllegals`、`mortgages`、`taxRecords`、`courtJudgments`、`judicialAssists`、`quickCancel`、`liquidation`。 + +--- + +## 四、年报与税务字段 + +### 1) `annualReports` 企业年报列表 + +每个元素代表一个年度年报,字段较多,常见包括: + +- 基础信息(如年度、企业基本经营情况) +- 股东与出资信息 +- 对外投资信息 +- 网站网店信息 +- 社保信息 +- 对外担保信息 +- 股权变更信息 +- 年报变更信息 + +### 2) `taxViolations` 税收违法信息 + +结构示例: + +```json +{ + "total": 2, + "items": [ + { + "entityName": "示例企业", + "taxpayerCode": "xxxx", + "illegalFact": "......", + "publishDepartment": "......", + "illegalTime": "2025-06-12" + } + ] +} +``` + +### 3) `ownTaxNotices` 欠税公告信息 + +结构示例: + +```json +{ + "total": 1, + "items": [ + { + "taxpayerName": "示例企业", + "taxIdNumber": "xxxx", + "taxCategory": "增值税", + "ownTaxBalance": "100000", + "publishDate": "2025-12-01" + } + ] +} +```