Skip to main content

futucli/cmd/analysis/company/
profile.rs

1use anyhow::{Result, anyhow, bail};
2use prost::Message;
3use serde::Serialize;
4use tabled::Tabled;
5
6use crate::common::{connect_gateway, parse_symbol};
7use crate::output::OutputFormat;
8
9use super::{display_opt, display_opt_f64, display_opt_i32, display_opt_i64};
10
11#[derive(Tabled)]
12struct CompanyProfileRow {
13    #[tabled(rename = "Name")]
14    name: String,
15    #[tabled(rename = "Value")]
16    value: String,
17    #[tabled(rename = "Field Type")]
18    field_type: String,
19}
20
21#[derive(Serialize)]
22struct CompanyProfileJson {
23    symbol: String,
24    item_list: Vec<CompanyProfileItemJson>,
25}
26
27#[derive(Serialize)]
28struct CompanyProfileItemJson {
29    name: Option<String>,
30    value: Option<String>,
31    field_type: Option<i32>,
32}
33
34pub async fn run_company_profile(gateway: &str, symbol: &str, format: OutputFormat) -> Result<()> {
35    let sec = parse_symbol(symbol)?;
36    let (client, _rx) = connect_gateway(gateway, "futucli-company-profile").await?;
37
38    let req = futu_proto::qot_get_company_profile::Request {
39        c2s: futu_proto::qot_get_company_profile::C2s {
40            security: futu_proto::qot_common::Security {
41                market: sec.market as i32,
42                code: sec.code.clone(),
43            },
44        },
45    };
46    let body = req.encode_to_vec();
47    let frame = client
48        .request(futu_core::proto_id::QOT_GET_COMPANY_PROFILE, body)
49        .await?;
50    let resp = futu_proto::qot_get_company_profile::Response::decode(frame.body.as_ref())
51        .map_err(|e| anyhow!("decode company_profile: {e}"))?;
52    if resp.ret_type != 0 {
53        bail!(
54            "company_profile ret_type={} msg={:?}",
55            resp.ret_type,
56            resp.ret_msg
57        );
58    }
59    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
60
61    let rows: Vec<CompanyProfileRow> = s2c
62        .item_list
63        .iter()
64        .map(|item| CompanyProfileRow {
65            name: display_opt(&item.name),
66            value: display_opt(&item.value),
67            field_type: item.field_type.map(|v| v.to_string()).unwrap_or_default(),
68        })
69        .collect();
70    let json = CompanyProfileJson {
71        symbol: symbol.to_string(),
72        item_list: s2c
73            .item_list
74            .iter()
75            .map(|item| CompanyProfileItemJson {
76                name: item.name.clone(),
77                value: item.value.clone(),
78                field_type: item.field_type,
79            })
80            .collect(),
81    };
82    format.print_rows(&rows, &[json])?;
83    Ok(())
84}
85
86#[derive(Tabled)]
87struct CompanyExecutiveRow {
88    #[tabled(rename = "Name")]
89    name: String,
90    #[tabled(rename = "Position")]
91    position: String,
92    #[tabled(rename = "Begin")]
93    begin_date: String,
94    #[tabled(rename = "Salary")]
95    annual_salary: String,
96}
97
98#[derive(Serialize)]
99struct CompanyExecutivesJson {
100    symbol: String,
101    director_list: Vec<DirectorJson>,
102}
103
104#[derive(Serialize)]
105struct DirectorJson {
106    display_leader_name: Option<String>,
107    leader_name: Option<String>,
108    position_name: Option<String>,
109    begin_date: Option<u64>,
110    begin_date_str: Option<String>,
111    leader_gender: Option<String>,
112    leader_age: Option<String>,
113    highest_education: Option<String>,
114    annual_salary: Option<u64>,
115    issue_date: Option<u64>,
116    issue_date_str: Option<String>,
117}
118
119pub async fn run_company_executives(
120    gateway: &str,
121    symbol: &str,
122    format: OutputFormat,
123) -> Result<()> {
124    let sec = parse_symbol(symbol)?;
125    let (client, _rx) = connect_gateway(gateway, "futucli-company-executives").await?;
126
127    let req = futu_proto::qot_get_company_executives::Request {
128        c2s: futu_proto::qot_get_company_executives::C2s {
129            security: futu_proto::qot_common::Security {
130                market: sec.market as i32,
131                code: sec.code.clone(),
132            },
133        },
134    };
135    let body = req.encode_to_vec();
136    let frame = client
137        .request(futu_core::proto_id::QOT_GET_COMPANY_EXECUTIVES, body)
138        .await?;
139    let resp = futu_proto::qot_get_company_executives::Response::decode(frame.body.as_ref())
140        .map_err(|e| anyhow!("decode company_executives: {e}"))?;
141    if resp.ret_type != 0 {
142        bail!(
143            "company_executives ret_type={} msg={:?}",
144            resp.ret_type,
145            resp.ret_msg
146        );
147    }
148    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
149
150    let rows: Vec<CompanyExecutiveRow> = s2c
151        .director_list
152        .iter()
153        .map(|item| CompanyExecutiveRow {
154            name: display_opt(&item.display_leader_name),
155            position: display_opt(&item.position_name),
156            begin_date: display_opt(&item.begin_date_str),
157            annual_salary: item
158                .annual_salary
159                .map(|v| v.to_string())
160                .unwrap_or_default(),
161        })
162        .collect();
163    let json = CompanyExecutivesJson {
164        symbol: symbol.to_string(),
165        director_list: s2c
166            .director_list
167            .iter()
168            .map(|item| DirectorJson {
169                display_leader_name: item.display_leader_name.clone(),
170                leader_name: item.leader_name.clone(),
171                position_name: item.position_name.clone(),
172                begin_date: item.begin_date,
173                begin_date_str: item.begin_date_str.clone(),
174                leader_gender: item.leader_gender.clone(),
175                leader_age: item.leader_age.clone(),
176                highest_education: item.highest_education.clone(),
177                annual_salary: item.annual_salary,
178                issue_date: item.issue_date,
179                issue_date_str: item.issue_date_str.clone(),
180            })
181            .collect(),
182    };
183    format.print_rows(&rows, &[json])?;
184    Ok(())
185}
186
187#[derive(Tabled)]
188struct CompanyExecutiveBackgroundRow {
189    #[tabled(rename = "Symbol")]
190    symbol: String,
191    #[tabled(rename = "Leader Name")]
192    leader_name: String,
193    #[tabled(rename = "Brief Background")]
194    brief_background: String,
195}
196
197#[derive(Serialize)]
198struct CompanyExecutiveBackgroundJson {
199    symbol: String,
200    leader_name: String,
201    brief_background: Option<String>,
202}
203
204pub async fn run_company_executive_background(
205    gateway: &str,
206    symbol: &str,
207    leader_name: &str,
208    format: OutputFormat,
209) -> Result<()> {
210    let sec = parse_symbol(symbol)?;
211    let (client, _rx) = connect_gateway(gateway, "futucli-company-executive-background").await?;
212
213    let req = futu_proto::qot_get_company_executive_background::Request {
214        c2s: futu_proto::qot_get_company_executive_background::C2s {
215            security: futu_proto::qot_common::Security {
216                market: sec.market as i32,
217                code: sec.code.clone(),
218            },
219            leader_name: Some(leader_name.to_string()),
220        },
221    };
222    let body = req.encode_to_vec();
223    let frame = client
224        .request(
225            futu_core::proto_id::QOT_GET_COMPANY_EXECUTIVE_BACKGROUND,
226            body,
227        )
228        .await?;
229    let resp =
230        futu_proto::qot_get_company_executive_background::Response::decode(frame.body.as_ref())
231            .map_err(|e| anyhow!("decode company_executive_background: {e}"))?;
232    if resp.ret_type != 0 {
233        bail!(
234            "company_executive_background ret_type={} msg={:?}",
235            resp.ret_type,
236            resp.ret_msg
237        );
238    }
239    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
240
241    let json = CompanyExecutiveBackgroundJson {
242        symbol: symbol.to_string(),
243        leader_name: leader_name.to_string(),
244        brief_background: s2c.brief_background.clone(),
245    };
246    let rows = [CompanyExecutiveBackgroundRow {
247        symbol: symbol.to_string(),
248        leader_name: leader_name.to_string(),
249        brief_background: display_opt(&s2c.brief_background),
250    }];
251    format.print_rows(&rows, &[json])?;
252    Ok(())
253}
254
255#[derive(Tabled)]
256struct CompanyOperationalEfficiencyRow {
257    #[tabled(rename = "Fiscal Year")]
258    fiscal_year: String,
259    #[tabled(rename = "Period")]
260    period: String,
261    #[tabled(rename = "End Date")]
262    end_date: String,
263    #[tabled(rename = "Employees")]
264    employee_num: String,
265    #[tabled(rename = "Income/Capita")]
266    income_per_capita: String,
267    #[tabled(rename = "Profit/Capita")]
268    profit_per_capita: String,
269    #[tabled(rename = "Net Profit/Capita")]
270    net_profit_per_capita: String,
271}
272
273#[derive(Serialize)]
274struct CompanyOperationalEfficiencyJson {
275    symbol: String,
276    item_list: Vec<OperationalEfficiencyItemJson>,
277    next_key: Option<String>,
278    currency_code: Option<String>,
279}
280
281#[derive(Serialize)]
282struct OperationalEfficiencyItemJson {
283    fiscal_year: Option<i32>,
284    financial_type: Option<i32>,
285    period_text: Option<String>,
286    end_date: Option<i64>,
287    end_date_str: Option<String>,
288    employee_num: Option<i64>,
289    employee_num_yoy: Option<f64>,
290    income_per_capita: Option<f64>,
291    income_per_capita_yoy: Option<f64>,
292    profit_per_capita: Option<f64>,
293    profit_per_capita_yoy: Option<f64>,
294    net_profit_per_capita: Option<f64>,
295    net_profit_per_capita_yoy: Option<f64>,
296}
297
298pub async fn run_company_operational_efficiency(
299    gateway: &str,
300    symbol: &str,
301    next_key: Option<&str>,
302    num: Option<i32>,
303    currency_code: Option<&str>,
304    financial_type: Option<i32>,
305    format: OutputFormat,
306) -> Result<()> {
307    let sec = parse_symbol(symbol)?;
308    let (client, _rx) = connect_gateway(gateway, "futucli-company-operational-efficiency").await?;
309
310    let req = futu_proto::qot_get_company_operational_efficiency::Request {
311        c2s: futu_proto::qot_get_company_operational_efficiency::C2s {
312            security: futu_proto::qot_common::Security {
313                market: sec.market as i32,
314                code: sec.code.clone(),
315            },
316            next_key: next_key.map(str::to_string),
317            num,
318            currency_code: currency_code.map(str::to_string),
319            financial_type,
320        },
321    };
322    let body = req.encode_to_vec();
323    let frame = client
324        .request(
325            futu_core::proto_id::QOT_GET_COMPANY_OPERATIONAL_EFFICIENCY,
326            body,
327        )
328        .await?;
329    let resp =
330        futu_proto::qot_get_company_operational_efficiency::Response::decode(frame.body.as_ref())
331            .map_err(|e| anyhow!("decode company_operational_efficiency: {e}"))?;
332    if resp.ret_type != 0 {
333        bail!(
334            "company_operational_efficiency ret_type={} msg={:?}",
335            resp.ret_type,
336            resp.ret_msg
337        );
338    }
339    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
340
341    let rows: Vec<CompanyOperationalEfficiencyRow> = s2c
342        .item_list
343        .iter()
344        .map(|item| CompanyOperationalEfficiencyRow {
345            fiscal_year: display_opt_i32(item.fiscal_year),
346            period: display_opt(&item.period_text),
347            end_date: display_opt(&item.end_date_str),
348            employee_num: display_opt_i64(item.employee_num),
349            income_per_capita: display_opt_f64(item.income_per_capita),
350            profit_per_capita: display_opt_f64(item.profit_per_capita),
351            net_profit_per_capita: display_opt_f64(item.net_profit_per_capita),
352        })
353        .collect();
354    let json = CompanyOperationalEfficiencyJson {
355        symbol: symbol.to_string(),
356        item_list: s2c
357            .item_list
358            .iter()
359            .map(|item| OperationalEfficiencyItemJson {
360                fiscal_year: item.fiscal_year,
361                financial_type: item.financial_type,
362                period_text: item.period_text.clone(),
363                end_date: item.end_date,
364                end_date_str: item.end_date_str.clone(),
365                employee_num: item.employee_num,
366                employee_num_yoy: item.employee_num_yoy,
367                income_per_capita: item.income_per_capita,
368                income_per_capita_yoy: item.income_per_capita_yoy,
369                profit_per_capita: item.profit_per_capita,
370                profit_per_capita_yoy: item.profit_per_capita_yoy,
371                net_profit_per_capita: item.net_profit_per_capita,
372                net_profit_per_capita_yoy: item.net_profit_per_capita_yoy,
373            })
374            .collect(),
375        next_key: s2c.next_key,
376        currency_code: s2c.currency_code,
377    };
378    format.print_rows(&rows, &[json])?;
379    Ok(())
380}