提交 1456a5ff authored 作者: 刘宇琪's avatar 刘宇琪

刘宇琪 法案预测

上级 247def06
......@@ -335,14 +335,18 @@ export function getProgressPrediction(billId) {
* @returns {Promise<Object>} 相似法案列表
*/
export function getSimiBills(params = {}) {
// domains 如果是数组则用逗号拼接
const domains = Array.isArray(params.domains)
? params.domains.join(',')
: params.domains
return request('/api/BillProgressPrediction/simiBills', {
method: 'GET',
params: {
billIds: params.billIds,
domains: params.domains,
patternType: params.patternType ,
proposalType: params.proposalType ,
...params
domains: domains,
patternType: params.patternType,
proposalType: params.proposalType
}
})
}
......@@ -353,80 +357,51 @@ export function getSimiBills(params = {}) {
* @returns {Object} 转换后的统计数据和法案列表
*/
export function transformSimiBillsData(apiData) {
if (!apiData || !apiData.data || !Array.isArray(apiData.data)) {
if (!apiData || !apiData.data) {
return { stats: null, bills: [] }
}
const bills = apiData.data
const data = apiData.data
const simiBills = data.simi_bills || []
// 计算统计数据
let becameLaw = 0
let notPassedOrShelved = 0
let totalDays = 0
let completedBills = 0
bills.forEach(bill => {
const actions = bill.bill_actions || []
const hasBecameLaw = actions.some(a =>
a.action_type === 'BecameLaw' ||
a.action_type === 'President' && a.action_desc?.includes('签署')
)
if (hasBecameLaw) {
becameLaw++
// 计算耗时
if (actions.length >= 2) {
const firstDate = new Date(actions[0].action_date)
const lastDate = new Date(actions[actions.length - 1].action_date)
const days = Math.ceil((lastDate - firstDate) / (1000 * 60 * 60 * 24))
if (days > 0) {
totalDays += days
completedBills++
}
}
} else {
notPassedOrShelved++
}
})
const medianDays = completedBills > 0 ? Math.round(totalDays / completedBills) : 223
const passRate = bills.length > 0 ? ((becameLaw / bills.length) * 100).toFixed(1) : '0'
// 直接使用 API 返回的统计数据
const stats = {
totalBills: bills.length,
becameLaw,
notPassedOrShelved,
medianDays,
passRate
totalBills: data.count || simiBills.length,
becameLaw: data.become_law || 0,
notPassedOrShelved: (data.count || simiBills.length) - (data.become_law || 0),
medianDays: data.become_law_avg_days || 0,
passRate: data.become_law_prop ? (data.become_law_prop * 100).toFixed(1) : '0'
}
// 转换法案列表格式
const transformedBills = bills.map(bill => ({
const transformedBills = simiBills.map(bill => ({
id: bill.bill_id,
title: bill.bill_name || bill.bill_id,
proposalDate: extractProposalDate(bill.poli_pattern_desc),
areas: bill.domain_name ? (Array.isArray(bill.domain_name) ? bill.domain_name : [bill.domain_name]) : [],
proposer: extractProposer(bill.bill_sponsors),
coProposers: bill.bill_proposal_desc || '',
proposalDate: bill.proposed_date ? formatDate(bill.proposed_date) : '',
areas: bill.bill_domain ? (Array.isArray(bill.bill_domain) ? bill.bill_domain : [bill.bill_domain]) : [],
proposer: bill.key_sponsor_name || extractProposer(bill.bill_sponsors),
proposerParty: bill.key_sponsor_party || '',
coProposers: bill.co_sponsor_desc || bill.bill_proposal_desc || '',
proposalType: bill.bill_proposal_type || '',
governmentType: formatGovernmentType(bill.poli_pattern_type, bill.poli_pattern_desc),
passDays: calculateTotalDays(bill.bill_actions),
patternType: bill.poli_pattern_type || '',
billStatus: bill.bill_status || '',
passDays: bill.bill_action_days || calculateTotalDays(bill.bill_actions),
yearsDifference: bill.years_difference || 0,
billActions: bill.bill_actions || [],
billSponsors: bill.bill_sponsors || [],
selected: true // 默认全选
}))
return { stats, bills: transformedBills }
}
/**
* 从政治格局描述中提取提案时间
* @param {string} desc - 政治格局描述
* @returns {string} 提案时间
*/
function extractProposalDate(desc) {
if (!desc) return ''
const match = desc.match(/(\d{4})-(\d{2})-(\d{2})/)
function formatDate(dateStr) {
if (!dateStr) return ''
const match = dateStr.match(/(\d{4})-(\d{2})-(\d{2})/)
if (match) {
return `${match[1]}${parseInt(match[2])}${parseInt(match[3])}日`
}
return ''
return dateStr
}
/**
* 从提案人列表中提取主提案人
......@@ -504,9 +479,9 @@ export function transformProposalInfo(apiData) {
return {
// 提案标题
title: data.bill_title || 'H.R.1-大而美法案',
title: data.bill_name_zh ,
// 提案时间 - 从政治格局描述中提取
date: extractDateFromDesc(data.poli_pattern_desc) || '2025年5月20日',
date: extractDateFromDesc(data.poli_pattern_desc) ,
// 涉及领域 TAG - 使用 domain_name
areas: Array.isArray(data.domain_name) ? data.domain_name : (data.domain_name ? [data.domain_name] : []),
// 政策领域完整选项列表 - 使用 bill_domain(用于筛选下拉框)
......
......@@ -62,7 +62,6 @@
<div class="field-content">
<el-select
v-model="localValues.oppositionProposer"
multiple
placeholder="请选择"
style="width: 420px"
@change="handleChange"
......@@ -83,7 +82,6 @@
<div class="field-content">
<el-select
v-model="localValues.proposalTime"
multiple
placeholder="请选择"
style="width: 420px"
@change="handleChange"
......@@ -115,18 +113,18 @@ const props = defineProps<{
const localValues = ref({
policyArea: [] as string[],
governmentType: [] as string[],
oppositionProposer: [] as string[],
proposalTime: [] as string[],
oppositionProposer: '' as string,
proposalTime: '' as string,
})
// 根据 proposalInfo 计算初始筛选值(即"设置为当前提案"的目标状态)
function buildInitialValues(info?: ProposalInfo | null): Record<string, string[]> {
if (!info) return { policyArea: [], governmentType: [], oppositionProposer: [], proposalTime: [] }
function buildInitialValues(info?: ProposalInfo | null): Record<string, string[] | string> {
if (!info) return { policyArea: [], governmentType: [], oppositionProposer: '', proposalTime: '' }
return {
policyArea: info.defaultDomains?.length ? [...info.defaultDomains] : [...(info.areas || [])],
governmentType: info.patternType ? [info.patternType] : [],
oppositionProposer: [],
proposalTime: [],
oppositionProposer: '',
proposalTime: '',
}
}
......@@ -144,8 +142,8 @@ function reset() {
localValues.value = {
policyArea: [],
governmentType: [],
oppositionProposer: [],
proposalTime: [],
oppositionProposer: '',
proposalTime: '',
}
}
......
......@@ -2,8 +2,8 @@
<div v-if="phase" class="phase-card" :class="borderColorClass">
<div class="phase-header flex-display-start">
<div>
<h3 class="phase-title main-color text-title-2-bold">{{ phase.title }}</h3>
<p class="text-tip-2 text-primary-50-clor">{{ phase.description }}</p>
<div class="phase-title main-color text-title-2-bold">{{ phase.title }}</div>
<div class="text-tip-2 text-primary-50-clor">{{ phase.description }}</div>
</div>
<div class="phase-status">
<span class="risk-badge" :class="riskColorClass">{{ riskLabel }}</span>
......@@ -18,32 +18,41 @@
<p v-if="phase.riskLevel !== 'passed'" class="text-tip-2 text-primary-50-clor">{{ phase.estimatedDays }}</p>
</div>
</div>
<div style="display: flex;">
<div class="box-title-row"><img src="../assets/input.svg" />
<span class="text-compact-bold text-primary-80-clor" style="margin-left: 8px;">预测模型数据输入</span></div>
<div class="box-hint flex-display-center text-tip-2 text-primary-50-clor" style="margin-left: auto;">
<img src="../assets/importent.svg"/>
<span>此阶段预测基于以下多维特征</span>
</div>
</div>
<div class="model-inputs-box">
<div class="box-header flex-display-start">
<div class="box-title-row flex-display-center">
<img src="../assets/input.svg" />
<span class="text-compact-bold">预测模型数据输入</span>
</div>
<div class="box-hint flex-display-center text-tip-2 text-primary-50-clor">
<img src="../assets/importent.svg"/>
<span>此阶段预测基于以下多维特征</span>
</div>
</div>
<div class="model-inputs">
<p
<div
v-for="(input, index) in phase.modelInputs"
:key="index"
class="text-tip-2 text-primary-65-clor"
>
{{ input }}
</p>
</div>
</div>
</div>
<div v-if="phase.predictionBasis" class="facts-section">
<div class="box-header flex-display-start">
<div class="box-title-row flex-display-center">
<img src="../assets/icon1.svg"/>
<span class="text-compact-bold">通过性预测依据</span>
<span class="text-compact-bold text-primary-80-clor">通过性预测依据</span>
</div>
<div class="box-hint flex-display-center text-tip-2 text-primary-50-clor">
<img src="../assets/importent.svg"/>
......@@ -51,9 +60,11 @@
</div>
</div>
<div class="prediction-basis-content">
<p class="text-tip-2 text-primary-65-clor">{{ phase.predictionBasis }}</p>
<div class="text-tip-2 text-primary-65-clor">{{ phase.predictionBasis }}</div>
</div>
</div>
<!-- 底部虚线分隔 -->
<div class="phase-divider"></div>
</div>
</template>
......@@ -144,7 +155,8 @@ const riskLabel = computed(() => {
<style scoped>
.phase-card {
padding-left: 24px;
padding-bottom: 32px;
/* padding-bottom: 16px; */
/* padding-top: 16px; */
}
.border-primary {
......@@ -166,7 +178,7 @@ const riskLabel = computed(() => {
.phase-header {
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16px;
/* margin-bottom: 16px; */
}
.phase-header > div:first-child {
......@@ -288,4 +300,10 @@ const riskLabel = computed(() => {
height: 16px;
color: var(--text-primary-65-color);
}
/* 底部虚线分隔 */
.phase-divider {
margin-top: 16px;
border-bottom: 1px solid var(--bg-black-10, #e5e5e5);
}
</style>
......@@ -125,22 +125,23 @@ async function loadData() {
// 使用真实 API,传入 billIds、domains、patternType、proposalType
const params = {
billIds: filterParams?.value.billIds,
domains:JSON.stringify(filterParams?.value.domains) || [],
domains: filterParams?.value.domains || [],
patternType: filterParams?.value.patternType || '统一政府',
proposalType: filterParams?.value.proposalType || '两党共同提案'
}
const response = await getSimiBills(params)
if (response && response.data) {
// 保存原始数据
rawBillsData.value = response.data
// 保存原始数据(新 API 返回结构中 simi_bills 是法案数组)
rawBillsData.value = response.data.simi_bills || []
const { stats: apiStats, bills: apiBills } = transformSimiBillsData(response)
stats.value = apiStats
bills.value = apiBills
} else {
stats.value = null
bills.value = []
rawBillsData.value = []
}
} catch (error) {
console.error('获取相似法案失败:', error)
......
......@@ -370,24 +370,26 @@ function transformPredictionResult(data: any) {
const factors = data?.factor_analysis || []
// 转换阶段分析
const phases = stages.map((stage: any, index: number) => ({
id: index + 1,
title: `阶段${index + 1}${stage.stage}`,
description: stage.stage,
riskLevel: probabilityToRisk(stage.predicted_pass_probability),
progressLevel: probabilityToProgressLevel(stage.predicted_pass_probability),
estimatedDays: `预计耗时${stage.predicted_passing_time}天`,
modelInputs: [stage.analysis],
supportingFacts: {
title: '通过性预测依据',
basedOn: '此阶段预测基于以下观点',
stats: [
{ value: `${stage.predicted_pass_probability}`, label: '通过概率' },
{ value: `${stage.predicted_passing_time}天`, label: '预计耗时' }
]
},
predictionBasis: stage.prediction_basis
}))
const chineseNum = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
const phases = stages.map((stage: any, index: number) => ({
id: index + 1,
title: `阶段${chineseNum[index] || index + 1}${stage.stage}`,
description: stage.stage,
riskLevel: probabilityToRisk(stage.predicted_pass_probability),
progressLevel: probabilityToProgressLevel(stage.predicted_pass_probability),
estimatedDays: `预计耗时${stage.predicted_passing_time}天`,
modelInputs: [stage.analysis],
supportingFacts: {
title: '通过性预测依据',
basedOn: '此阶段预测基于以下观点',
stats: [
{ value: `${stage.predicted_pass_probability}`, label: '通过概率' },
{ value: `${stage.predicted_passing_time}天`, label: '预计耗时' }
]
},
predictionBasis: stage.prediction_basis
}))
return {
title: '立法进展阶段预测分析',
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论