diff --git a/data/moutai/moutai_balance_annual.csv b/data/moutai/moutai_balance_annual.csv new file mode 100644 index 0000000..d715e4a --- /dev/null +++ b/data/moutai/moutai_balance_annual.csv @@ -0,0 +1,11 @@ +period,total_assets,equity,liabilities,cash_like,cash,receivables,inventory +2016,,,,,,, +2017,,,,,,, +2018,,,,,,, +2019,,,,,,, +2020,,,,,,, +2021,,,,,,, +2022,,,,,,, +2023,,,,,,, +2024,,,,,,, +2025,,,,,,, diff --git a/data/moutai/moutai_balance_quarterly.csv b/data/moutai/moutai_balance_quarterly.csv new file mode 100644 index 0000000..a7d3c7b --- /dev/null +++ b/data/moutai/moutai_balance_quarterly.csv @@ -0,0 +1,22 @@ +period,total_assets,equity,liabilities,cash_like,cash,receivables,inventory +2021Q2,,,,,,, +2021Q3,,,,,,, +2021Q4,,,,,,, +2022Q1,,,,,,, +2022Q2,,,,,,, +2022Q3,,,,,,, +2022Q4,,,,,,, +2023Q1,,,,,,, +2023Q2,,,,,,, +2023Q3,,,,,,, +2023Q4,,,,,,, +2024Q1,,,,,,, +2024Q2,,,,,,, +2024Q3,,,,,,, +2024Q4,,,,,,, +2025Q1,,,,,,, +2025Q2,,,,,,, +2025Q3,,,,,,, +2025Q4,,,,,,, +2026Q1,,,,,,, +2026Q2,,,,,,, diff --git a/data/moutai/moutai_base_wine_volume.csv b/data/moutai/moutai_base_wine_volume.csv new file mode 100644 index 0000000..fb8c715 --- /dev/null +++ b/data/moutai/moutai_base_wine_volume.csv @@ -0,0 +1,31 @@ +period,planned_capacity,released_capacity,base_wine_output,commercial_wine_sales +2000,,,5397.000000, +2001,,,7317.000000, +2002,,,8640.000000, +2003,,,9757.000000, +2004,,,11522.000000,6500.000000 +2005,,,12500.000000,7356.000000 +2006,,,13839.000000,7300.000000 +2007,,,16865.000000,8640.000000 +2008,,,20431.000000,9558.000000 +2009,,,23004.000000,10380.000000 +2010,,,26284.000000,10141.000000 +2011,,,30026.000000,11250.000000 +2012,,,33600.000000,14800.000000 +2013,,,38452.000000,15500.000000 +2014,,,38745.000000,18000.000000 +2015,,,32179.000000,19798.000000 +2016,32592.000000,31360.000000,39313.000000,22918.000000 +2017,35952.000000,32592.000000,42829.000000,30206.000000 +2018,37408.000000,35952.000000,49672.000000,32464.000000 +2019,38528.000000,37408.000000,49923.000000,34562.000000 +2020,42560.000000,38528.000000,50235.000000,34313.000000 +2021,42743.000000,42743.000000,56472.250000,36261.000000 +2022,42743.000000,42743.000000,56810.440000,37901.000000 +2023,42795.000000,42743.000000,57204.110000,42111.000000 +2024,44595.000000,,56271.990000,46412.950000 +2025,46395.000000,,58473.160000,46750.660000 +2026,,,, +2027,,,, +2028,,,, +2029,,,, diff --git a/data/moutai/moutai_cashflow_annual.csv b/data/moutai/moutai_cashflow_annual.csv new file mode 100644 index 0000000..d65cba8 --- /dev/null +++ b/data/moutai/moutai_cashflow_annual.csv @@ -0,0 +1,11 @@ +period,operating_cash_flow,investing_cash_flow,financing_cash_flow,net_cash_increase +2016,374.510000,-11.030000,-83.350000,280.140000 +2017,221.530000,-11.210000,-88.990000,121.330000 +2018,413.850000,-16.290000,-164.410000,233.150000 +2019,452.110000,-31.660000,-192.840000,227.610000 +2020,516.690000,-18.050000,-241.280000,257.370000 +2021,640.290000,-55.620000,-265.640000,319.000000 +2022,366.990000,-55.370000,-574.250000,-262.620000 +2023,665.930000,-97.240000,-588.890000,-20.190000 +2024,924.640000,-17.850000,-710.680000,196.100000 +2025,615.220000,-316.420000,-734.270000,-435.440000 diff --git a/data/moutai/moutai_cashflow_quarterly.csv b/data/moutai/moutai_cashflow_quarterly.csv new file mode 100644 index 0000000..9823eff --- /dev/null +++ b/data/moutai/moutai_cashflow_quarterly.csv @@ -0,0 +1,22 @@ +period,operating_cash_flow,investing_cash_flow,financing_cash_flow,net_cash_increase +2021Q2,217.190000,-38.720000,-264.080000,-85.610000 +2021Q3,150.330000,-3.940000,-1.200000,145.180000 +2021Q4,272.770000,-12.960000,-0.360000,259.430000 +2022Q1,-68.760000,-12.400000,-0.040000,-81.200000 +2022Q2,68.650000,-4.650000,-283.150000,-219.160000 +2022Q3,94.160000,-9.660000,-15.460000,69.050000 +2022Q4,272.940000,-28.660000,-275.600000,-31.310000 +2023Q1,52.450000,2.180000,-0.230000,54.400000 +2023Q2,251.420000,-6.980000,-325.740000,-81.290000 +2023Q3,196.150000,-78.360000,-21.280000,96.500000 +2023Q4,165.910000,-14.080000,-241.640000,-89.800000 +2024Q1,91.870000,-7.670000,-0.120000,84.080000 +2024Q2,274.350000,-21.320000,-388.040000,-135.010000 +2024Q3,77.990000,22.940000,-1.730000,99.200000 +2024Q4,480.430000,-11.800000,-320.790000,147.830000 +2025Q1,88.090000,-6.240000,-16.070000,65.780000 +2025Q2,43.100000,3.100000,-382.860000,-336.630000 +2025Q3,250.780000,-51.090000,-33.510000,166.170000 +2025Q4,233.250000,-262.190000,-301.830000,-330.760000 +2026Q1,269.100000,256.390000,-10.060000,515.430000 +2026Q2,437.810000,0.020000,-369.380000,68.420000 diff --git a/data/moutai/moutai_channel_revenue_annual.csv b/data/moutai/moutai_channel_revenue_annual.csv new file mode 100644 index 0000000..b5e1a8c --- /dev/null +++ b/data/moutai/moutai_channel_revenue_annual.csv @@ -0,0 +1,11 @@ +period,direct,wholesale,direct_share,wholesale_share +2016,34.850000,353.560000,0.089725,0.910275 +2017,,,, +2018,43.760000,691.890000,0.059485,0.940515 +2019,72.490000,780.960000,0.084938,0.915062 +2020,132.400000,815.820000,0.139630,0.860370 +2021,240.290000,820.300000,0.226563,0.773437 +2022,493.790000,743.940000,0.398948,0.601052 +2023,672.330000,799.860000,0.456687,0.543313 +2024,748.433270,957.685110,0.438676,0.561324 +2025,845.430319,842.315533,0.500923,0.499077 diff --git a/data/moutai/moutai_classified_revenue_cumulative.csv b/data/moutai/moutai_classified_revenue_cumulative.csv new file mode 100644 index 0000000..b934629 --- /dev/null +++ b/data/moutai/moutai_classified_revenue_cumulative.csv @@ -0,0 +1,31 @@ +period,moutai,series,direct,wholesale,moutai_share,direct_share +2019Q1,194.980000,21.320000,10.920000,205.380000,0.901433,0.050485 +2019Q2,347.950000,46.550000,16.020000,378.480000,0.882003,0.040608 +2019Q3,538.320000,70.380000,31.030000,577.670000,0.884377,0.050977 +2019Q4,758.020000,95.420000,72.490000,780.960000,0.888194,0.084938 +2020Q1,222.220000,21.690000,19.380000,224.530000,0.911074,0.079456 +2020Q2,392.610000,46.500000,51.530000,387.590000,0.894104,0.117348 +2020Q3,601.450000,70.000000,84.330000,587.130000,0.895748,0.125592 +2020Q4,848.310000,99.910000,132.400000,815.820000,0.894634,0.139630 +2021Q1,245.920000,26.430000,47.770000,224.570000,0.902956,0.175406 +2021Q2,429.490000,60.600000,95.040000,395.050000,0.876349,0.193924 +2021Q3,649.920000,95.400000,146.850000,598.470000,0.872001,0.197029 +2021Q4,934.650000,125.950000,240.290000,820.300000,0.881246,0.226563 +2022Q1,288.600000,34.280000,108.870000,214.010000,0.893831,0.337184 +2022Q2,499.650000,75.980000,209.490000,366.140000,0.868005,0.363932 +2022Q3,744.000000,125.400000,318.820000,550.590000,0.855763,0.366708 +2022Q4,1078.340000,159.390000,493.790000,743.940000,0.871224,0.398948 +2023Q1,337.220000,50.140000,178.070000,209.290000,0.870560,0.459702 +2023Q2,592.790000,100.740000,314.200000,379.330000,0.854743,0.453045 +2023Q3,872.700000,155.940000,462.070000,566.570000,0.848402,0.449205 +2023Q4,1265.890000,206.300000,672.330000,799.860000,0.859869,0.456687 +2024Q1,397.070000,59.360000,193.190000,263.240000,0.869947,0.423263 +2024Q2,685.670000,131.470000,337.280000,479.860000,0.839110,0.412757 +2024Q3,1011.260000,193.930000,519.890000,685.290000,0.839088,0.431380 +2024Q4,1459.280000,246.840000,748.430000,957.690000,0.855321,0.438674 +2025Q1,435.570000,70.220000,232.200000,273.600000,0.861168,0.459075 +2025Q2,755.900000,137.630000,400.100000,493.430000,0.845970,0.447775 +2025Q3,1105.140000,178.840000,555.550000,728.420000,0.860714,0.432681 +2025Q4,1465.000000,222.750000,845.430000,842.320000,0.868020,0.500921 +2026Q1,460.048686,78.808783,295.040339,243.817130,0.853748,0.547529 +2026Q2,777.240000,129.340000,519.620000,386.970000,0.857332,0.573159 diff --git a/data/moutai/moutai_classified_revenue_quarterly.csv b/data/moutai/moutai_classified_revenue_quarterly.csv new file mode 100644 index 0000000..d22a3d2 --- /dev/null +++ b/data/moutai/moutai_classified_revenue_quarterly.csv @@ -0,0 +1,31 @@ +period,moutai,series,direct,wholesale,moutai_share,direct_share +2019Q1,194.980000,21.320000,10.920000,205.380000,0.901433,0.050485 +2019Q2,152.970000,25.230000,5.100000,173.100000,0.858418,0.028620 +2019Q3,190.380000,23.830000,15.010000,199.190000,0.888754,0.070075 +2019Q4,219.700000,25.050000,41.460000,203.290000,0.897651,0.169397 +2020Q1,222.220000,21.690000,19.380000,224.530000,0.911074,0.079456 +2020Q2,170.390000,24.820000,32.140000,163.060000,0.872855,0.164652 +2020Q3,208.840000,23.500000,32.800000,199.540000,0.898855,0.141172 +2020Q4,246.860000,29.910000,48.070000,228.690000,0.891932,0.173688 +2021Q1,245.920000,26.430000,47.770000,224.570000,0.902956,0.175406 +2021Q2,183.570000,34.180000,47.270000,170.480000,0.843031,0.217084 +2021Q3,220.430000,34.790000,51.810000,203.410000,0.863686,0.203001 +2021Q4,284.730000,30.550000,93.440000,221.830000,0.903102,0.296381 +2022Q1,288.600000,34.280000,108.870000,214.010000,0.893831,0.337184 +2022Q2,211.050000,41.700000,100.620000,152.130000,0.835015,0.398101 +2022Q3,244.350000,49.420000,109.320000,184.450000,0.831773,0.372128 +2022Q4,334.340000,33.980000,174.970000,193.350000,0.907743,0.475049 +2023Q1,337.220000,50.140000,178.070000,209.290000,0.870560,0.459702 +2023Q2,255.570000,50.600000,136.130000,170.040000,0.834732,0.444622 +2023Q3,279.910000,55.200000,147.870000,187.240000,0.835278,0.441258 +2023Q4,393.200000,50.360000,210.260000,233.290000,0.886464,0.474039 +2024Q1,397.070000,59.360000,193.190000,263.240000,0.869947,0.423263 +2024Q2,288.600000,72.110000,144.090000,216.620000,0.800089,0.399462 +2024Q3,325.590000,62.460000,182.610000,205.430000,0.839041,0.470596 +2024Q4,448.020000,52.910000,228.540000,272.390000,0.894376,0.456231 +2025Q1,435.570000,70.220000,232.200000,273.600000,0.861168,0.459075 +2025Q2,320.320000,67.400000,167.890000,219.830000,0.826163,0.433019 +2025Q3,349.240000,41.220000,155.460000,235.000000,0.894432,0.398146 +2025Q4,359.860000,43.900000,289.880000,113.890000,0.891272,0.717933 +2026Q1,460.048686,78.808783,295.040339,243.817130,0.853748,0.547529 +2026Q2,317.190000,50.530000,224.580000,143.150000,0.862586,0.610720 diff --git a/data/moutai/moutai_distributors.csv b/data/moutai/moutai_distributors.csv new file mode 100644 index 0000000..ee03199 --- /dev/null +++ b/data/moutai/moutai_distributors.csv @@ -0,0 +1,10 @@ +period,domestic,overseas,total +2022年报,2084.000000,105.000000,2189.000000 +2023年报,2080.000000,106.000000,2186.000000 +2024年报,2143.000000,104.000000,2247.000000 +2025Q1,2174.000000,109.000000,2283.000000 +2025Q2,2280.000000,115.000000,2395.000000 +2025Q3,2325.000000,121.000000,2446.000000 +2025年报,2353.000000,126.000000,2479.000000 +2026Q1,2098.000000,124.000000,2222.000000 +2026Q2,2307.000000,119.000000,2426.000000 diff --git a/data/moutai/moutai_dividend.csv b/data/moutai/moutai_dividend.csv new file mode 100644 index 0000000..8e556cd --- /dev/null +++ b/data/moutai/moutai_dividend.csv @@ -0,0 +1,27 @@ +period,dividend,net_profit,payout_ratio +2001,1.500000,3.282907,0.456912 +2002,0.550000,3.767985,0.145967 +2003,0.907500,5.867478,0.154666 +2004,1.966250,8.205540,0.239625 +2005,1.415700,11.577382,0.122282 +2006,12.184458,15.448124,0.788734 +2007,7.890168,28.308316,0.278723 +2008,10.910328,37.994806,0.287153 +2009,11.184030,43.081146,0.259604 +2010,21.707400,50.511942,0.429748 +2011,41.496055,87.631459,0.473529 +2012,66.640774,133.080796,0.500754 +2013,45.409993,151.366398,0.300000 +2014,49.950993,153.498043,0.325418 +2015,77.519966,155.030903,0.500029 +2016,85.258145,167.183627,0.509967 +2017,138.169196,270.793603,0.510238 +2018,182.638598,352.036253,0.518806 +2019,213.867676,412.064710,0.519015 +2020,242.358242,466.972854,0.518999 +2021,272.280873,525.811027,0.517830 +2022,547.513811,627.164437,0.872999 +2023,627.872784,747.340716,0.840143 +2024,646.720000,862.281464,0.750010 +2025,650.330000,823.200671,0.790002 +2025,300.006375,646.267467,0.464214 diff --git a/data/moutai/moutai_expense_annual.csv b/data/moutai/moutai_expense_annual.csv new file mode 100644 index 0000000..6754d46 --- /dev/null +++ b/data/moutai/moutai_expense_annual.csv @@ -0,0 +1,23 @@ +period,sales_expense,admin_expense,rd_expense,tax_surcharge,revenue,sales_expense_rate,admin_expense_rate,rd_expense_rate,tax_surcharge_rate +2004,,,,,,,,, +2005,,,,,,,,, +2006,,,,,,,,, +2007,,,,,,,,, +2008,,,,,,,,, +2009,,,,,,,,, +2010,,,,,,,,, +2011,,,,,,,,, +2012,,,,,,,,, +2013,,,,,,,,, +2014,,,,,,,,, +2015,,,,,,,,, +2016,,,,,,,,, +2017,,,,,,,,, +2018,,,,,,,,, +2019,,,,,,,,, +2020,,,,,,,,, +2021,,,,,,,,, +2022,,,,,,,,, +2023,,,,,,,,, +2024,,,,,,,,, +2025,,,,,,,,, diff --git a/data/moutai/moutai_expense_quarterly.csv b/data/moutai/moutai_expense_quarterly.csv new file mode 100644 index 0000000..02bb0fe --- /dev/null +++ b/data/moutai/moutai_expense_quarterly.csv @@ -0,0 +1,27 @@ +period,sales_expense,admin_expense,rd_expense,tax_surcharge,revenue,gross_margin,sales_expense_rate,admin_expense_rate,rd_expense_rate,tax_surcharge_rate +2020Q1,,,,,,,,,, +2020Q2,,,,,,,,,, +2020Q3,,,,,,,,,, +2020Q4,,,,,,,,,, +2021Q1,,,,,,,,,, +2021Q2,,,,,,,,,, +2021Q3,,,,,,,,,, +2021Q4,,,,,,,,,, +2022Q1,,,,,,,,,, +2022Q2,,,,,,,,,, +2022Q3,,,,,,,,,, +2022Q4,,,,,,,,,, +2023Q1,,,,,,,,,, +2023Q2,,,,,,,,,, +2023Q3,,,,,,,,,, +2023Q4,,,,,,,,,, +2024Q1,,,,,,,,,, +2024Q2,,,,,,,,,, +2024Q3,,,,,,,,,, +2024Q4,,,,,,,,,, +2025Q1,,,,,,,,,, +2025Q2,,,,,,,,,, +2025Q3,,,,,,,,,, +2025Q4,,,,,,,,,, +2026Q1,,,,,,,,,, +2026Q2,,,,,,,,,, diff --git a/data/moutai/moutai_flying_price.csv b/data/moutai/moutai_flying_price.csv new file mode 100644 index 0000000..c9ad794 --- /dev/null +++ b/data/moutai/moutai_flying_price.csv @@ -0,0 +1,17 @@ +period,wholesale_price +2010,1000.000000 +2011,1200.000000 +2012,2300.000000 +2013,1500.000000 +2014,1000.000000 +2015,850.000000 +2016,1100.000000 +2017,1800.000000 +2018,2000.000000 +2019,2600.000000 +2020,2600.000000 +2021,3100.000000 +2022,3000.000000 +2023,2900.000000 +2024,2350.000000 +2025,1485.000000 diff --git a/data/moutai/moutai_liquor_industry_output.csv b/data/moutai/moutai_liquor_industry_output.csv new file mode 100644 index 0000000..0586f52 --- /dev/null +++ b/data/moutai/moutai_liquor_industry_output.csv @@ -0,0 +1,21 @@ +period,output,yoy +2006,397.100000, +2007,493.900000, +2008,569.300000, +2009,706.900000, +2010,890.800000, +2011,1025.600000, +2012,1153.200000, +2013,1226.200000, +2014,1257.100000, +2015,1312.800000, +2016,1358.400000, +2017,1198.100000, +2018,871.200000, +2019,786.000000, +2020,740.700000, +2021,715.600000, +2022,671.200000, +2023,449.200000, +2024,414.500000, +2025,354.900000, diff --git a/data/moutai/moutai_price_adjustments.csv b/data/moutai/moutai_price_adjustments.csv new file mode 100644 index 0000000..26a880d --- /dev/null +++ b/data/moutai/moutai_price_adjustments.csv @@ -0,0 +1,13 @@ +period,before_price,after_price,increase_rate +2001-08,185.000000,218.000000,0.178000 +2003-10,218.000000,268.000000,0.229000 +2006-02,268.000000,308.000000,0.149000 +2007-03,308.000000,358.000000,0.162000 +2008-01,358.000000,439.000000,0.226000 +2010-01,439.000000,499.000000,0.137000 +2011-01,499.000000,619.000000,0.240000 +2012-09,619.000000,819.000000,0.323000 +2018-01,819.000000,969.000000,0.183000 +2023-11,969.000000,1169.000000,0.206000 +2026-03,1169.000000,1269.000000,0.086000 +2026-07,1269.000000,1369.000000,0.079000 diff --git a/data/moutai/moutai_product_revenue_annual.csv b/data/moutai/moutai_product_revenue_annual.csv new file mode 100644 index 0000000..c80ce15 --- /dev/null +++ b/data/moutai/moutai_product_revenue_annual.csv @@ -0,0 +1,11 @@ +period,moutai,series,moutai_share,series_share +2016,367.140000,21.270000,0.945238,0.054762 +2017,523.940000,57.740000,0.900736,0.099264 +2018,654.870000,80.770000,0.890204,0.109796 +2019,758.020000,95.420000,0.888194,0.111806 +2020,848.310000,99.910000,0.894634,0.105366 +2021,934.650000,125.950000,0.881246,0.118754 +2022,1078.340000,159.390000,0.871224,0.128776 +2023,1265.890000,206.300000,0.859869,0.140131 +2024,1459.280760,246.837621,0.855322,0.144678 +2025,1464.999065,222.746787,0.868021,0.131979 diff --git a/data/moutai/moutai_profitability_annual.csv b/data/moutai/moutai_profitability_annual.csv new file mode 100644 index 0000000..de93d81 --- /dev/null +++ b/data/moutai/moutai_profitability_annual.csv @@ -0,0 +1,22 @@ +period,revenue,gross_profit,net_profit,gross_margin,revenue_yoy,net_profit_yoy +2005,39.310000,,11.190000,,, +2006,48.960000,,15.040000,,0.245485,0.344057 +2007,72.370000,,28.310000,,0.478145,0.882314 +2008,82.420000,,37.990000,,0.138870,0.341929 +2009,96.700000,,43.120000,,0.173259,0.135036 +2010,116.330000,,50.510000,,0.202999,0.171382 +2011,184.020000,,87.630000,,0.581879,0.734904 +2012,264.550000,,133.080000,,0.437615,0.518658 +2013,309.220000,,151.370000,,0.168853,0.137436 +2014,315.740000,,153.500000,,0.021085,0.014071 +2015,326.600000,,155.030000,,0.034395,0.009967 +2016,388.620000,,167.180000,0.912300,0.189896,0.078372 +2017,582.180000,,270.790000,0.898000,0.498070,0.619751 +2018,736.390000,,352.040000,0.911400,0.264884,0.300048 +2019,854.300000,,412.060000,0.913000,0.160119,0.170492 +2020,949.150000,867.390000,466.970000,0.914100,0.111027,0.133257 +2021,1061.900000,972.070000,524.600000,0.915400,0.118790,0.123413 +2022,1241.000000,1140.060000,627.160000,0.918700,0.168660,0.195501 +2023,1476.940000,1358.260000,747.340000,0.919600,0.190121,0.191626 +2024,1708.990000,1571.100000,862.280000,0.919300,0.157115,0.153799 +2025,1688.380000,1539.460000,823.200000,0.911800,-0.012060,-0.045322 diff --git a/data/moutai/moutai_profitability_quarterly.csv b/data/moutai/moutai_profitability_quarterly.csv new file mode 100644 index 0000000..4406822 --- /dev/null +++ b/data/moutai/moutai_profitability_quarterly.csv @@ -0,0 +1,53 @@ +period,revenue,gross_profit,net_profit,gross_margin,revenue_yoy,net_profit_yoy +2020Q1,244.050000,223.730000,130.940000,0.916700,, +2020Q1,20.320000,,,,-0.916738, +2020Q2,195.470000,178.280000,95.080000,0.912000,8.619587, +2020Q2,17.200000,,,,-0.912007, +2020Q3,232.620000,211.840000,112.250000,0.910600,12.524419, +2020Q3,20.790000,,,,-0.910627, +2020Q4,277.000000,253.770000,128.700000,0.916100,12.323713, +2020Q4,23.230000,,,,-0.916137, +2021Q1,272.710000,250.010000,139.540000,0.916800,10.739561, +2021Q1,22.700000,,,,-0.916761, +2021Q2,218.160000,198.540000,107.000000,0.910100,8.610573, +2021Q2,19.620000,,,,-0.910066, +2021Q3,255.550000,232.120000,126.120000,0.908300,12.024975, +2021Q3,23.430000,,,,-0.908315, +2021Q4,315.480000,291.400000,151.940000,0.923700,12.464789, +2021Q4,24.080000,,,,-0.923672, +2022Q1,322.960000,298.310000,172.450000,0.923700,12.411960, +2022Q1,24.650000,,,,-0.923675, +2022Q2,253.210000,232.400000,125.490000,0.917800,9.272211, +2022Q2,20.810000,,,,-0.917815, +2022Q3,295.430000,270.080000,146.060000,0.914200,13.196540, +2022Q3,25.360000,,,,-0.914159, +2022Q4,369.400000,339.280000,183.170000,0.918500,13.566246, +2022Q4,30.110000,,,,-0.918489, +2023Q1,387.560000,358.860000,207.950000,0.926000,11.871471, +2023Q1,28.700000,,,,-0.925947, +2023Q2,308.200000,279.830000,151.860000,0.908000,9.738676, +2023Q2,28.370000,,,,-0.907949, +2023Q3,336.920000,308.350000,168.960000,0.915200,10.875925, +2023Q3,28.570000,,,,-0.915202, +2023Q4,444.250000,411.220000,218.580000,0.925600,14.549527, +2023Q4,33.030000,,,,-0.925650, +2024Q1,457.760000,423.940000,240.650000,0.926100,12.858916, +2024Q1,33.810000,,,,-0.926140, +2024Q2,361.550000,327.860000,176.300000,0.906800,9.693582, +2024Q2,33.700000,,,,-0.906790, +2024Q3,388.450000,353.680000,191.320000,0.910500,10.526706, +2024Q3,34.770000,,,,-0.910490, +2024Q4,501.230000,465.620000,254.010000,0.928900,13.415588, +2024Q4,35.610000,,,,-0.928955, +2025Q1,506.010000,465.400000,268.470000,0.919700,13.209773, +2025Q1,40.610000,,,,-0.919745, +2025Q2,387.880000,350.720000,185.550000,0.904200,8.551342, +2025Q2,37.160000,,,,-0.904197, +2025Q3,390.640000,356.580000,192.240000,0.912800,9.512379, +2025Q3,34.070000,,,,-0.912784, +2025Q4,403.840000,366.760000,176.930000,0.908000,10.853243, +2025Q4,37.080000,,,,-0.908181, +2026Q1,539.090000,483.890000,272.430000,0.908000,13.538565, +2026Q1,55.210000,,,,-0.897587, +2026Q2,367.940000,328.400000,172.740000,,5.664372, +2026Q2,39.530000,,,,-0.892564, diff --git a/data/moutai/moutai_revenue_composition_halfyear.csv b/data/moutai/moutai_revenue_composition_halfyear.csv new file mode 100644 index 0000000..aeebc51 --- /dev/null +++ b/data/moutai/moutai_revenue_composition_halfyear.csv @@ -0,0 +1,21 @@ +period,moutai,series,domestic,overseas +2016H2,367.140000,21.270000,367.820000,20.590000 +2017H1,216.270000,25.490000,233.340000,8.420000 +2017H2,523.940000,57.740000,558.980000,22.700000 +2018H1,293.830000,39.930000,323.450000,10.310000 +2018H2,654.870000,80.770000,706.720000,28.930000 +2019H1,347.950000,46.550000,383.680000,10.810000 +2019H2,758.020000,95.420000,824.240000,29.200000 +2020H1,392.610000,46.500000,428.100000,11.010000 +2020H2,848.310000,99.910000,923.900000,24.320000 +2021H1,429.490000,60.600000,478.100000,12.000000 +2021H2,934.650000,125.950000,1034.410000,26.180000 +2022H1,499.650000,75.980000,554.970000,20.660000 +2022H2,1078.340000,159.390000,1195.330000,42.400000 +2023H1,592.790000,100.740000,673.110000,20.420000 +2023H2,1265.890000,206.300000,1428.690000,43.500000 +2024H1,685.670000,131.470000,797.180000,22.130000 +2024H2,1459.280000,246.840000,1654.230000,51.890000 +2025H1,755.900000,137.630000,864.900000,28.990000 +2025H2,1465.000000,222.750000,1639.240000,48.500000 +2026H1,777.240000,129.340000,896.660000,10.370000 diff --git a/data/moutai/moutai_ton_price.csv b/data/moutai/moutai_ton_price.csv new file mode 100644 index 0000000..f7c9ca1 --- /dev/null +++ b/data/moutai/moutai_ton_price.csv @@ -0,0 +1,9 @@ +period,moutai_revenue,series_revenue,moutai_volume,series_volume,moutai_price_per_ton,series_price_per_ton +2016,367.140000,21.270000,22918.000000,14027.000000,160.197225,15.163613 +2017,523.940000,57.740000,30206.000000,29903.000000,173.455605,19.309099 +2018,654.870000,80.770000,32464.000000,29774.000000,201.721907,27.127695 +2019,758.020000,95.420000,34562.000000,30083.000000,219.321799,31.718911 +2020,848.310000,99.910000,34313.000000,29743.000000,247.226999,33.591097 +2021,934.650000,125.950000,36261.000000,30177.000000,257.756267,41.737085 +2022,1078.340000,159.390000,37901.000000,30275.000000,284.514920,52.647399 +2023,1265.890000,206.300000,42111.000000,31165.000000,300.607917,66.196053 diff --git a/data/moutai/rawData/README.md b/data/moutai/rawData/README.md new file mode 100644 index 0000000..2d10b8b --- /dev/null +++ b/data/moutai/rawData/README.md @@ -0,0 +1,5 @@ +# 贵州茅台图表数据 + +本目录保留原始附件:年度与季度工作簿均不作编辑。图表使用的 `data/moutai/*.csv` 由 `scripts/normalize_moutai_financials.py` 生成。 + +口径说明:金额默认单位为人民币亿元;季度现金流由各披露累计值与同年度上一季度累计值相减得到单季金额;“分类营收”中的单季与累计数据分别保留,图表不混用两种口径。 diff --git a/data/moutai/rawData/贵州茅台-季度.xlsx b/data/moutai/rawData/贵州茅台-季度.xlsx new file mode 100644 index 0000000..4ca48f4 Binary files /dev/null and b/data/moutai/rawData/贵州茅台-季度.xlsx differ diff --git a/data/moutai/rawData/贵州茅台-年度.xlsx b/data/moutai/rawData/贵州茅台-年度.xlsx new file mode 100644 index 0000000..72788c1 Binary files /dev/null and b/data/moutai/rawData/贵州茅台-年度.xlsx differ diff --git a/js/charts/index.js b/js/charts/index.js index 68e440e..1d96c65 100644 --- a/js/charts/index.js +++ b/js/charts/index.js @@ -35,6 +35,18 @@ import { storesRetailShareChart } from './ppmt_stores_retail_share.js'; import { retailStoreRevenueChart } from './ppmt_retail_store_revenue.js'; import { robotStoreRevenueChart } from './ppmt_robot_store_revenue.js'; import { brkHoldingsChart } from './brk_holdings.js'; +import { + moutaiProfitabilityAnnualChart, moutaiProfitabilityQuarterlyChart, + moutaiExpenseAnnualChart, moutaiExpenseQuarterlyChart, + moutaiBalanceAnnualChart, moutaiBalanceQuarterlyChart, + moutaiCashflowAnnualChart, moutaiCashflowQuarterlyChart, + moutaiProductAnnualChart, moutaiChannelAnnualChart, + moutaiClassifiedProductQuarterlyChart, moutaiClassifiedChannelQuarterlyChart, + moutaiClassifiedProductCumulativeChart, moutaiClassifiedChannelCumulativeChart, + moutaiRevenueCompositionHalfyearChart, moutaiLiquorIndustryOutputChart, + moutaiBaseWineVolumeChart, moutaiFlyingPriceChart, moutaiPriceAdjustmentChart, + moutaiTonPriceChart, moutaiDividendChart, moutaiDistributorsChart, +} from './moutai.js'; /** 图表构建函数注册表。manifest 只引用这里的 key,不直接引用 JS 函数。 */ export const chartRegistry = { @@ -82,6 +94,28 @@ export const chartRegistry = { 'ppmt-retail-store-revenue': retailStoreRevenueChart, 'ppmt-robot-store-revenue': robotStoreRevenueChart, 'brk-holdings': brkHoldingsChart, + 'moutai-profitability-annual': moutaiProfitabilityAnnualChart, + 'moutai-profitability-quarterly': moutaiProfitabilityQuarterlyChart, + 'moutai-expense-annual': moutaiExpenseAnnualChart, + 'moutai-expense-quarterly': moutaiExpenseQuarterlyChart, + 'moutai-balance-annual': moutaiBalanceAnnualChart, + 'moutai-balance-quarterly': moutaiBalanceQuarterlyChart, + 'moutai-cashflow-annual': moutaiCashflowAnnualChart, + 'moutai-cashflow-quarterly': moutaiCashflowQuarterlyChart, + 'moutai-product-annual': moutaiProductAnnualChart, + 'moutai-channel-annual': moutaiChannelAnnualChart, + 'moutai-classified-product-quarterly': moutaiClassifiedProductQuarterlyChart, + 'moutai-classified-channel-quarterly': moutaiClassifiedChannelQuarterlyChart, + 'moutai-classified-product-cumulative': moutaiClassifiedProductCumulativeChart, + 'moutai-classified-channel-cumulative': moutaiClassifiedChannelCumulativeChart, + 'moutai-revenue-composition-halfyear': moutaiRevenueCompositionHalfyearChart, + 'moutai-liquor-industry-output': moutaiLiquorIndustryOutputChart, + 'moutai-base-wine-volume': moutaiBaseWineVolumeChart, + 'moutai-flying-price': moutaiFlyingPriceChart, + 'moutai-price-adjustment': moutaiPriceAdjustmentChart, + 'moutai-ton-price': moutaiTonPriceChart, + 'moutai-dividend': moutaiDividendChart, + 'moutai-distributors': moutaiDistributorsChart, }; export function getChartBuilder(key) { diff --git a/js/charts/moutai.js b/js/charts/moutai.js new file mode 100644 index 0000000..4d7f849 --- /dev/null +++ b/js/charts/moutai.js @@ -0,0 +1,286 @@ +import { col } from '../csv.js'; +import { + PALETTE, FONT, axisCommon, categoryAxis, titleStyle, standardLegend, + lineSeries, footnote, watermarks, LAYOUT, +} from '../theme.js'; + +const amountAxis = (name = '金额(亿元)', extra = {}) => ({ + ...axisCommon, + type: 'value', + name, + nameLocation: 'middle', + nameGap: 52, + nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT }, + ...extra, +}); + +const percentageAxis = (name = '比例(%)') => ({ + ...amountAxis(name), + axisLabel: { ...axisCommon.axisLabel, formatter: '{value}%' }, + splitLine: { show: false }, +}); + +const periodAxis = (rows) => ({ + ...categoryAxis, + data: col(rows, 'period'), + axisLabel: { + ...categoryAxis.axisLabel, + formatter: (value) => String(value).replace(/^20(\d{2})/, '$1'), + }, +}); + +function option({ title, subtitle, rows, legend, series, yAxis, width, height, source }) { + return { + backgroundColor: '#fff', + title: { ...titleStyle, text: title, subtext: subtitle }, + legend: standardLegend(legend), + grid: { left: 78, right: 82, top: LAYOUT.gridTop, bottom: 90 }, + tooltip: { + trigger: 'axis', + axisPointer: { type: 'shadow' }, + textStyle: { fontFamily: FONT }, + valueFormatter: (value) => (Number.isFinite(value) ? value.toFixed(1) : '—'), + }, + xAxis: periodAxis(rows), + yAxis, + series, + graphic: [...watermarks(width, height), footnote(`数据来源:贵州茅台定期报告及附件底稿;${source} | @Joey知行记`)], + }; +} + +function bar(name, rows, key, color, extras = {}) { + return { + name, + type: 'bar', + data: col(rows, key), + itemStyle: { color }, + emphasis: { focus: 'series' }, + ...extras, + }; +} + +function profitChart(rows, { width, height, label }) { + return option({ + title: `贵州茅台盈利能力(${label})`, + subtitle: '营业收入、毛利、归母净利润与毛利率', + rows, + legend: ['营业收入', '毛利', '归母净利润', '毛利率'], + yAxis: [amountAxis(), percentageAxis('毛利率(%)')], + series: [ + bar('营业收入', rows, 'revenue', PALETTE.navy), + bar('毛利', rows, 'gross_profit', PALETTE.sage), + bar('归母净利润', rows, 'net_profit', PALETTE.orange), + { ...lineSeries({ name: '毛利率', data: col(rows, 'gross_margin').map((value) => (Number.isFinite(value) ? value * 100 : null)), color: PALETTE.red, formatter: (point) => `${point.value.toFixed(1)}%` }), yAxisIndex: 1, z: 10 }, + ], + width, height, source: `${label}口径;金额单位为亿元`, + }); +} + +export const moutaiProfitabilityAnnualChart = (rows, context) => profitChart(rows, { ...context, label: '年度' }); +export const moutaiProfitabilityQuarterlyChart = (rows, context) => profitChart(rows, { ...context, label: '单季' }); + +function expenseChart(rows, { width, height, label }) { + const rates = rows.map((row) => { + const value = (row.sales_expense_rate || 0) + (row.admin_expense_rate || 0) + (row.rd_expense_rate || 0); + return Number.isFinite(value) ? value * 100 : null; + }); + return option({ + title: `贵州茅台费用结构(${label})`, + subtitle: '税金及附加单列展示;费用率为销售、管理和研发费用占营业收入比例', + rows, + legend: ['销售费用', '管理费用', '研发费用', '税金及附加', '三项费用率'], + yAxis: [amountAxis(), percentageAxis('三项费用率(%)')], + series: [ + bar('销售费用', rows, 'sales_expense', PALETTE.blue), + bar('管理费用', rows, 'admin_expense', PALETTE.orange), + bar('研发费用', rows, 'rd_expense', PALETTE.purple), + bar('税金及附加', rows, 'tax_surcharge', PALETTE.sage), + { ...lineSeries({ name: '三项费用率', data: rates, color: PALETTE.red, formatter: (point) => `${point.value.toFixed(1)}%` }), yAxisIndex: 1, z: 10 }, + ], + width, height, source: `${label}口径;金额单位为亿元`, + }); +} + +export const moutaiExpenseAnnualChart = (rows, context) => expenseChart(rows, { ...context, label: '年度' }); +export const moutaiExpenseQuarterlyChart = (rows, context) => expenseChart(rows, { ...context, label: '单季' }); + +function balanceChart(rows, { width, height, label }) { + return option({ + title: `贵州茅台资产负债结构(${label}期末)`, + subtitle: '资产总计、所有者权益、负债与类现金余额', + rows, + legend: ['资产总计', '所有者权益', '负债合计', '类现金'], + yAxis: amountAxis('期末余额(亿元)'), + series: [ + bar('资产总计', rows, 'total_assets', PALETTE.navy), + bar('所有者权益', rows, 'equity', PALETTE.sage), + bar('负债合计', rows, 'liabilities', PALETTE.orange), + { ...lineSeries({ name: '类现金', data: col(rows, 'cash_like'), color: PALETTE.red, formatter: (point) => point.value.toFixed(0) }), z: 10 }, + ], + width, height, source: '资产负债表;金额单位为亿元', + }); +} + +export const moutaiBalanceAnnualChart = (rows, context) => balanceChart(rows, { ...context, label: '年度' }); +export const moutaiBalanceQuarterlyChart = (rows, context) => balanceChart(rows, { ...context, label: '季度' }); + +function cashflowChart(rows, { width, height, label }) { + return option({ + title: `贵州茅台三大现金流(${label})`, + subtitle: label === '单季' ? '季度单期值由披露的累计现金流量表相邻期间相减得出' : '年度现金流量表披露值', + rows, + legend: ['经营现金流', '投资现金流', '筹资现金流', '现金净增加'], + yAxis: amountAxis('现金流量(亿元)'), + series: [ + bar('经营现金流', rows, 'operating_cash_flow', '#36A28F'), + bar('投资现金流', rows, 'investing_cash_flow', '#4D83D1'), + bar('筹资现金流', rows, 'financing_cash_flow', '#EEA64F'), + { ...lineSeries({ name: '现金净增加', data: col(rows, 'net_cash_increase'), color: '#D95567', formatter: (point) => point.value.toFixed(0) }), z: 10 }, + ], + width, height, source: `${label}口径;金额单位为亿元`, + }); +} + +export const moutaiCashflowAnnualChart = (rows, context) => cashflowChart(rows, { ...context, label: '年度' }); +export const moutaiCashflowQuarterlyChart = (rows, context) => cashflowChart(rows, { ...context, label: '单季' }); + +function revenueMixChart(rows, { width, height, title, subtitle, fields, source }) { + const [first, second] = fields; + return option({ + title, + subtitle, + rows, + legend: [first.name, second.name, first.shareName], + yAxis: [amountAxis(), percentageAxis(`${first.shareName}(%)`)], + series: [ + { ...bar(first.name, rows, first.key, first.color), stack: 'revenue' }, + { ...bar(second.name, rows, second.key, second.color), stack: 'revenue' }, + { ...lineSeries({ name: first.shareName, data: col(rows, first.shareKey).map((value) => (Number.isFinite(value) ? value * 100 : null)), color: PALETTE.red, formatter: (point) => `${point.value.toFixed(1)}%` }), yAxisIndex: 1, z: 10 }, + ], + width, height, source, + }); +} + +export const moutaiProductAnnualChart = (rows, context) => revenueMixChart(rows, { + ...context, title: '贵州茅台产品档次营收(年度)', subtitle: '茅台酒与系列酒营收及茅台酒营收占比', + fields: [{ name: '茅台酒', key: 'moutai', shareName: '茅台酒占比', shareKey: 'moutai_share', color: PALETTE.navy }, { name: '系列酒', key: 'series', color: PALETTE.orange }], source: '年度产品档次口径;金额单位为亿元', +}); +export const moutaiChannelAnnualChart = (rows, context) => revenueMixChart(rows, { + ...context, title: '贵州茅台销售渠道营收(年度)', subtitle: '直销与批发代理营收及直销营收占比', + fields: [{ name: '直销', key: 'direct', shareName: '直销占比', shareKey: 'direct_share', color: PALETTE.blue }, { name: '批发代理', key: 'wholesale', color: PALETTE.orange }], source: '年度销售渠道口径;金额单位为亿元', +}); + +function classifiedMixChart(rows, { width, height, label, kind }) { + const isProduct = kind === 'product'; + return revenueMixChart(rows, { + width, height, + title: `贵州茅台${isProduct ? '产品档次' : '销售渠道'}营收(${label})`, + subtitle: isProduct ? '茅台酒与系列酒营收及茅台酒营收占比' : '直销与批发代理营收及直销营收占比', + fields: isProduct + ? [{ name: '茅台酒', key: 'moutai', shareName: '茅台酒占比', shareKey: 'moutai_share', color: PALETTE.navy }, { name: '系列酒', key: 'series', color: PALETTE.orange }] + : [{ name: '直销', key: 'direct', shareName: '直销占比', shareKey: 'direct_share', color: PALETTE.blue }, { name: '批发代理', key: 'wholesale', color: PALETTE.orange }], + source: `${label}分类营收口径;金额单位为亿元`, + }); +} + +export const moutaiClassifiedProductQuarterlyChart = (rows, context) => classifiedMixChart(rows, { ...context, label: '单季', kind: 'product' }); +export const moutaiClassifiedChannelQuarterlyChart = (rows, context) => classifiedMixChart(rows, { ...context, label: '单季', kind: 'channel' }); +export const moutaiClassifiedProductCumulativeChart = (rows, context) => classifiedMixChart(rows, { ...context, label: '累计', kind: 'product' }); +export const moutaiClassifiedChannelCumulativeChart = (rows, context) => classifiedMixChart(rows, { ...context, label: '累计', kind: 'channel' }); + +export function moutaiRevenueCompositionHalfyearChart(rows, { width, height }) { + return option({ + title: '贵州茅台半年度累计营收构成', + subtitle: '实线为产品档次口径,虚线为经营地区口径;两组口径不作加总', + rows, + legend: ['茅台酒', '系列酒', '国内', '国外'], + yAxis: amountAxis(), + series: [ + { ...lineSeries({ name: '茅台酒', data: col(rows, 'moutai'), color: PALETTE.navy, formatter: (point) => point.value.toFixed(0) }), z: 10 }, + { ...lineSeries({ name: '系列酒', data: col(rows, 'series'), color: PALETTE.orange, formatter: (point) => point.value.toFixed(0) }), z: 10 }, + { ...lineSeries({ name: '国内', data: col(rows, 'domestic'), color: PALETTE.green, formatter: (point) => point.value.toFixed(0) }), lineStyle: { color: PALETTE.green, width: 2, type: 'dashed' }, z: 9 }, + { ...lineSeries({ name: '国外', data: col(rows, 'overseas'), color: PALETTE.purple, formatter: (point) => point.value.toFixed(1) }), lineStyle: { color: PALETTE.purple, width: 2, type: 'dashed' }, z: 9 }, + ], + width, height, source: '半年度累计营收构成;金额单位为亿元', + }); +} + +export function moutaiLiquorIndustryOutputChart(rows, { width, height }) { + return option({ + title: '全国白酒产量与同比变化', subtitle: '行业产量趋势,用于观察白酒行业供给环境', rows, + legend: ['白酒产量', '同比变化'], + yAxis: [amountAxis('白酒产量(万千升)'), percentageAxis('同比变化(%)')], + series: [bar('白酒产量', rows, 'output', PALETTE.sage), { ...lineSeries({ name: '同比变化', data: col(rows, 'yoy').map((value) => (Number.isFinite(value) ? value * 100 : null)), color: PALETTE.red, formatter: (point) => `${point.value.toFixed(1)}%` }), yAxisIndex: 1, z: 10 }], + width, height, source: '附件“全国白酒产量”工作表', + }); +} + +export function moutaiBaseWineVolumeChart(rows, { width, height }) { + return option({ + title: '茅台基酒产能、产量与商品酒销量', subtitle: '基酒产量与商品酒销售年份存在生产周期差异', rows, + legend: ['设计产能', '已释放产能', '基酒产量', '商品酒销量'], + yAxis: amountAxis('数量(吨)'), + series: [ + bar('设计产能', rows, 'planned_capacity', PALETTE.sand), + bar('基酒产量', rows, 'base_wine_output', PALETTE.navy), + { ...lineSeries({ name: '已释放产能', data: col(rows, 'released_capacity'), color: PALETTE.orange, formatter: (point) => point.value.toFixed(0) }), z: 10 }, + { ...lineSeries({ name: '商品酒销量', data: col(rows, 'commercial_wine_sales'), color: PALETTE.green, formatter: (point) => point.value.toFixed(0) }), z: 10 }, + ], + width, height, source: '附件“茅台基酒产销量”工作表;单位为吨', + }); +} + +export function moutaiFlyingPriceChart(rows, { width, height }) { + return option({ + title: '飞天茅台年度批价', subtitle: '年度批价为附件记录值,单位为元/瓶', rows, + legend: ['飞天茅台批价'], yAxis: amountAxis('批价(元/瓶)'), + series: [{ ...lineSeries({ name: '飞天茅台批价', data: col(rows, 'wholesale_price'), color: PALETTE.red, formatter: (point) => point.value.toFixed(0) }), z: 10 }], + width, height, source: '附件“飞天茅台历年批价”工作表', + }); +} + +export function moutaiPriceAdjustmentChart(rows, { width, height }) { + return option({ + title: '飞天茅台出厂价调整记录', subtitle: '折线为调整后价格,柱形为本次调价幅度', rows, + legend: ['调整后价格', '提价幅度'], yAxis: [amountAxis('出厂价(元/瓶)'), percentageAxis('提价幅度(%)')], + series: [ + { ...lineSeries({ name: '调整后价格', data: col(rows, 'after_price'), color: PALETTE.navy, formatter: (point) => point.value.toFixed(0) }), z: 10 }, + { ...bar('提价幅度', rows, 'increase_rate', PALETTE.orange), yAxisIndex: 1, data: col(rows, 'increase_rate').map((value) => (Number.isFinite(value) ? value * 100 : null)) }, + ], width, height, source: '附件“提价记录”工作表', + }); +} + +export function moutaiTonPriceChart(rows, { width, height }) { + return option({ + title: '茅台酒与系列酒平均吨价', subtitle: '由各产品收入除以商品酒销量计算,单位为万元/吨', rows, + legend: ['茅台酒平均吨价', '系列酒平均吨价'], yAxis: amountAxis('万元/吨'), + series: [ + { ...lineSeries({ name: '茅台酒平均吨价', data: col(rows, 'moutai_price_per_ton'), color: PALETTE.navy, formatter: (point) => point.value.toFixed(1) }), z: 10 }, + { ...lineSeries({ name: '系列酒平均吨价', data: col(rows, 'series_price_per_ton'), color: PALETTE.orange, formatter: (point) => point.value.toFixed(1) }), z: 10 }, + ], width, height, source: '附件“吨价”工作表;由收入与销量计算', + }); +} + +export function moutaiDividendChart(rows, { width, height }) { + return option({ + title: '贵州茅台分红与归母净利润', subtitle: '分红总金额与归母净利润;折线为分红率', rows, + legend: ['分红总金额', '归母净利润', '分红率'], yAxis: [amountAxis(), percentageAxis('分红率(%)')], + series: [ + bar('分红总金额', rows, 'dividend', PALETTE.orange), + bar('归母净利润', rows, 'net_profit', PALETTE.navy), + { ...lineSeries({ name: '分红率', data: col(rows, 'payout_ratio').map((value) => (Number.isFinite(value) ? value * 100 : null)), color: PALETTE.red, formatter: (point) => `${point.value.toFixed(1)}%` }), yAxisIndex: 1, z: 10 }, + ], width, height, source: '附件“历年分红”工作表;金额单位为亿元', + }); +} + +export function moutaiDistributorsChart(rows, { width, height }) { + return option({ + title: '贵州茅台经销商数量', subtitle: '国内与国外经销商期末数量', rows, + legend: ['国内经销商', '国外经销商', '合计'], yAxis: amountAxis('数量(家)'), + series: [ + { ...bar('国内经销商', rows, 'domestic', PALETTE.navy), stack: 'dealer' }, + { ...bar('国外经销商', rows, 'overseas', PALETTE.orange), stack: 'dealer' }, + { ...lineSeries({ name: '合计', data: col(rows, 'total'), color: PALETTE.red, formatter: (point) => point.value.toFixed(0) }), z: 10 }, + ], width, height, source: '附件“经销商数量”工作表;单位为家', + }); +} diff --git a/manifest.json b/manifest.json index c3ab12b..30f8efe 100644 --- a/manifest.json +++ b/manifest.json @@ -53,6 +53,42 @@ { "id": "store-revenue", "name": "单店收入", "chartRefs": ["ppmt-retail-store-revenue", "ppmt-robot-store-revenue"] } ] }, + { + "id": "moutai", + "name": "贵州茅台", + "chartCatalog": { + "moutai-profitability-annual": { "key": "moutai-profitability-annual", "title": "年度盈利能力", "csv": "data/moutai/moutai_profitability_annual.csv", "height": 680, "filename": "贵州茅台年度盈利能力" }, + "moutai-profitability-quarterly": { "key": "moutai-profitability-quarterly", "title": "单季盈利能力", "csv": "data/moutai/moutai_profitability_quarterly.csv", "height": 680, "filename": "贵州茅台单季盈利能力" }, + "moutai-expense-annual": { "key": "moutai-expense-annual", "title": "年度费用结构", "csv": "data/moutai/moutai_expense_annual.csv", "height": 680, "filename": "贵州茅台年度费用结构" }, + "moutai-expense-quarterly": { "key": "moutai-expense-quarterly", "title": "单季费用结构", "csv": "data/moutai/moutai_expense_quarterly.csv", "height": 680, "filename": "贵州茅台单季费用结构" }, + "moutai-balance-annual": { "key": "moutai-balance-annual", "title": "年度资产负债结构", "csv": "data/moutai/moutai_balance_annual.csv", "height": 680, "filename": "贵州茅台年度资产负债结构" }, + "moutai-balance-quarterly": { "key": "moutai-balance-quarterly", "title": "季度资产负债结构", "csv": "data/moutai/moutai_balance_quarterly.csv", "height": 680, "filename": "贵州茅台季度资产负债结构" }, + "moutai-cashflow-annual": { "key": "moutai-cashflow-annual", "title": "年度三大现金流", "csv": "data/moutai/moutai_cashflow_annual.csv", "height": 680, "filename": "贵州茅台年度三大现金流" }, + "moutai-cashflow-quarterly": { "key": "moutai-cashflow-quarterly", "title": "单季三大现金流", "csv": "data/moutai/moutai_cashflow_quarterly.csv", "height": 680, "filename": "贵州茅台单季三大现金流" }, + "moutai-product-annual": { "key": "moutai-product-annual", "title": "年度产品档次营收", "csv": "data/moutai/moutai_product_revenue_annual.csv", "height": 680, "filename": "贵州茅台年度产品档次营收" }, + "moutai-channel-annual": { "key": "moutai-channel-annual", "title": "年度销售渠道营收", "csv": "data/moutai/moutai_channel_revenue_annual.csv", "height": 680, "filename": "贵州茅台年度销售渠道营收" }, + "moutai-classified-product-quarterly": { "key": "moutai-classified-product-quarterly", "title": "单季产品档次营收", "csv": "data/moutai/moutai_classified_revenue_quarterly.csv", "height": 680, "filename": "贵州茅台单季产品档次营收" }, + "moutai-classified-channel-quarterly": { "key": "moutai-classified-channel-quarterly", "title": "单季销售渠道营收", "csv": "data/moutai/moutai_classified_revenue_quarterly.csv", "height": 680, "filename": "贵州茅台单季销售渠道营收" }, + "moutai-classified-product-cumulative": { "key": "moutai-classified-product-cumulative", "title": "累计产品档次营收", "csv": "data/moutai/moutai_classified_revenue_cumulative.csv", "height": 680, "filename": "贵州茅台累计产品档次营收" }, + "moutai-classified-channel-cumulative": { "key": "moutai-classified-channel-cumulative", "title": "累计销售渠道营收", "csv": "data/moutai/moutai_classified_revenue_cumulative.csv", "height": 680, "filename": "贵州茅台累计销售渠道营收" }, + "moutai-revenue-composition-halfyear": { "key": "moutai-revenue-composition-halfyear", "title": "半年度累计营收构成", "csv": "data/moutai/moutai_revenue_composition_halfyear.csv", "height": 680, "filename": "贵州茅台半年度累计营收构成" }, + "moutai-liquor-industry-output": { "key": "moutai-liquor-industry-output", "title": "全国白酒产量", "csv": "data/moutai/moutai_liquor_industry_output.csv", "height": 680, "filename": "全国白酒产量" }, + "moutai-base-wine-volume": { "key": "moutai-base-wine-volume", "title": "基酒产能、产量与销量", "csv": "data/moutai/moutai_base_wine_volume.csv", "height": 680, "filename": "贵州茅台基酒产能产量销量" }, + "moutai-flying-price": { "key": "moutai-flying-price", "title": "飞天茅台年度批价", "csv": "data/moutai/moutai_flying_price.csv", "height": 680, "filename": "飞天茅台年度批价" }, + "moutai-price-adjustment": { "key": "moutai-price-adjustment", "title": "飞天茅台提价记录", "csv": "data/moutai/moutai_price_adjustments.csv", "height": 680, "filename": "飞天茅台提价记录" }, + "moutai-ton-price": { "key": "moutai-ton-price", "title": "产品平均吨价", "csv": "data/moutai/moutai_ton_price.csv", "height": 680, "filename": "贵州茅台产品平均吨价" }, + "moutai-dividend": { "key": "moutai-dividend", "title": "分红与归母净利润", "csv": "data/moutai/moutai_dividend.csv", "height": 680, "filename": "贵州茅台分红与归母净利润" }, + "moutai-distributors": { "key": "moutai-distributors", "title": "经销商数量", "csv": "data/moutai/moutai_distributors.csv", "height": 680, "filename": "贵州茅台经销商数量" } + }, + "tabs": [ + { "id": "overview", "name": "概览", "chartRefs": ["moutai-profitability-annual", "moutai-profitability-quarterly", "moutai-product-annual", "moutai-channel-annual"] }, + { "id": "profitability", "name": "盈利与现金流", "chartRefs": ["moutai-profitability-annual", "moutai-profitability-quarterly", "moutai-expense-annual", "moutai-expense-quarterly", "moutai-cashflow-annual", "moutai-cashflow-quarterly"] }, + { "id": "balance", "name": "资产负债", "chartRefs": ["moutai-balance-annual", "moutai-balance-quarterly"] }, + { "id": "classified-revenue", "name": "分类营收", "chartRefs": ["moutai-product-annual", "moutai-channel-annual", "moutai-classified-product-quarterly", "moutai-classified-channel-quarterly", "moutai-classified-product-cumulative", "moutai-classified-channel-cumulative", "moutai-revenue-composition-halfyear"] }, + { "id": "operations", "name": "产能与渠道", "chartRefs": ["moutai-liquor-industry-output", "moutai-base-wine-volume", "moutai-ton-price", "moutai-distributors"] }, + { "id": "price-shareholder", "name": "价格与股东回报", "chartRefs": ["moutai-flying-price", "moutai-price-adjustment", "moutai-dividend"] } + ] + }, { "id": "brk", "name": "巴菲特致股东信", diff --git a/scripts/normalize_moutai_financials.py b/scripts/normalize_moutai_financials.py new file mode 100644 index 0000000..e541dfc --- /dev/null +++ b/scripts/normalize_moutai_financials.py @@ -0,0 +1,405 @@ +"""Generate the chart-ready CSV layer for Guizhou Moutai from the two source workbooks. + +The workbooks in data/moutai/rawData are retained unchanged. All values written by +this script are in RMB 100m unless a CSV column name explicitly gives another unit. +""" + +from __future__ import annotations + +import math +import re +from pathlib import Path + +import pandas as pd + + +ROOT = Path(__file__).resolve().parents[1] +RAW = ROOT / "data" / "moutai" / "rawData" +OUT = ROOT / "data" / "moutai" +ANNUAL = RAW / "贵州茅台-年度.xlsx" +QUARTERLY = RAW / "贵州茅台-季度.xlsx" + + +def clean(value): + if pd.isna(value): + return None + if isinstance(value, str): + value = value.replace("\u200b", "").strip() + return value or None + return value + + +def number(value): + value = clean(value) + if value is None: + return None + try: + result = float(value) + return result if math.isfinite(result) else None + except (TypeError, ValueError): + return None + + +def label(value): + value = clean(value) + if value is None: + return None + if isinstance(value, float) and value.is_integer(): + return str(int(value)) + return str(value) + + +def year(value): + numeric = number(value) + if numeric and 1900 <= numeric <= 2100: + return str(int(numeric)) + match = re.search(r"(?:19|20)\d{2}", str(value or "")) + return match.group(0) if match else None + + +def quarter(value): + text = str(value or "").replace(" ", "") + match = re.search(r"(?:(20)?(\d{2}))Q([1-4])", text) + if not match: + return None + return f"{match.group(1) or '20'}{match.group(2)}Q{match.group(3)}" + + +def quarter_sort(period): + text = quarter(period) + return int(text[:4]) * 10 + int(text[-1]) + + +def sheet(workbook, name): + return pd.read_excel(workbook, sheet_name=name, header=None).map(clean) + + +def csv(name, rows, columns): + OUT.mkdir(parents=True, exist_ok=True) + frame = pd.DataFrame(rows, columns=columns) + frame.to_csv(OUT / name, index=False, encoding="utf-8", float_format="%.6f") + print(f"{name}: {len(frame)} rows") + + +def wide_rows(frame, labels, start_col=1, header_row=0): + periods = [(index, year(frame.iat[header_row, index])) for index in range(start_col, frame.shape[1])] + periods = [(index, period) for index, period in periods if period] + locations = {} + for row_index in range(header_row + 1, frame.shape[0]): + key = label(frame.iat[row_index, 0]) + if key in labels: + locations[key] = row_index + result = [] + for column, period in periods: + row = {"period": period} + for field, source_name in labels.items(): + source_row = locations.get(source_name) + row[field] = number(frame.iat[source_row, column]) if source_row is not None else None + result.append(row) + return sorted(result, key=lambda row: int(row["period"])) + + +def quarter_wide_rows(frame, labels, header_row=0): + headers = [(index, label(frame.iat[header_row, index])) for index in range(1, frame.shape[1])] + headers = [(index, quarter(value)) for index, value in headers] + headers = [(index, value) for index, value in headers if value] + locations = {} + for row_index in range(header_row + 1, frame.shape[0]): + key = label(frame.iat[row_index, 0]) + if key in labels: + locations[key] = row_index + result = [] + for column, period in headers: + row = {"period": period} + for field, source_name in labels.items(): + source_row = locations.get(source_name) + row[field] = number(frame.iat[source_row, column]) if source_row is not None else None + result.append(row) + return sorted(result, key=lambda row: quarter_sort(row["period"])) + + +def enrich_profit(rows): + for index, row in enumerate(rows): + prior = rows[index - 1] if index else None + revenue = row.get("revenue") + profit = row.get("net_profit") + row["revenue_yoy"] = None + row["net_profit_yoy"] = None + if prior and revenue is not None and prior.get("revenue") not in (None, 0): + row["revenue_yoy"] = revenue / prior["revenue"] - 1 + if prior and profit is not None and prior.get("net_profit") not in (None, 0): + row["net_profit_yoy"] = profit / prior["net_profit"] - 1 + return rows + + +def normalized_classified(frame): + headers = [(index, label(frame.iat[1, index])) for index in range(2, frame.shape[1])] + headers = [(index, quarter(value)) for index, value in headers] + headers = [(index, value) for index, value in headers if value] + wanted = {"茅台酒": "moutai", "系列酒": "series", "直销": "direct", "批发代理": "wholesale"} + locations = {} + for row_index in range(2, frame.shape[0]): + key = label(frame.iat[row_index, 1]) + if key in wanted: + locations[wanted[key]] = row_index + rows = [] + for column, period in headers: + row = {"period": period} + for field in wanted.values(): + source_row = locations.get(field) + row[field] = number(frame.iat[source_row, column]) if source_row is not None else None + product_total = sum(value for value in (row["moutai"], row["series"]) if value is not None) + channel_total = sum(value for value in (row["direct"], row["wholesale"]) if value is not None) + row["moutai_share"] = row["moutai"] / product_total if product_total and row["moutai"] is not None else None + row["direct_share"] = row["direct"] / channel_total if channel_total and row["direct"] is not None else None + rows.append(row) + return sorted(rows, key=lambda row: quarter_sort(row["period"])) + + +def annual_product_or_channel(frame, source_names, header_row=1): + headers = [(index, year(frame.iat[header_row, index])) for index in range(1, frame.shape[1])] + headers = [(index, value) for index, value in headers if value] + locations = {} + for row_index in range(header_row + 1, frame.shape[0]): + key = label(frame.iat[row_index, 0]) + if key in source_names: + locations[source_names[key]] = row_index + rows = [] + for column, period in headers: + row = {"period": period} + for field in source_names.values(): + source_row = locations.get(field) + row[field] = number(frame.iat[source_row, column]) if source_row is not None else None + rows.append(row) + return sorted(rows, key=lambda row: int(row["period"])) + + +def halfyear_composition(frame): + headers = [(index, label(frame.iat[0, index])) for index in range(3, frame.shape[1])] + headers = [(index, value) for index, value in headers if value and "H" in value] + category = None + locations = {} + for row_index in range(1, frame.shape[0]): + category = label(frame.iat[row_index, 1]) or category + metric = label(frame.iat[row_index, 2]) + if category in {"茅台酒", "系列酒", "国内", "国外"} and metric == "营业收入": + locations[category] = row_index + key_map = {"茅台酒": "moutai", "系列酒": "series", "国内": "domestic", "国外": "overseas"} + rows = [] + for column, period in headers: + row = {"period": period} + for category_name, field in key_map.items(): + source_row = locations.get(category_name) + row[field] = number(frame.iat[source_row, column]) if source_row is not None else None + rows.append(row) + return sorted(rows, key=lambda row: (int(row["period"][:4]), row["period"][-1])) + + +def cashflow_rows(frame, single_quarter=False): + rows = [] + for row_index in range(1, frame.shape[0]): + date = pd.to_datetime(frame.iat[row_index, 0], errors="coerce") + if pd.isna(date): + continue + quarter = (date.month - 1) // 3 + 1 + rows.append({ + "period": f"{date.year}Q{quarter}" if single_quarter else str(date.year), + "year": date.year, + "quarter": quarter, + "operating_cash_flow": number(frame.iat[row_index, 1]), + "investing_cash_flow": number(frame.iat[row_index, 2]), + "financing_cash_flow": number(frame.iat[row_index, 3]), + "net_cash_increase": number(frame.iat[row_index, 4]), + }) + if not single_quarter: + return sorted(rows, key=lambda row: row["year"]) + cumulative = sorted(rows, key=lambda row: (row["year"], row["quarter"])) + previous = {} + for row in cumulative: + prior = previous.get(row["year"]) + if prior: + for field in ("operating_cash_flow", "investing_cash_flow", "financing_cash_flow", "net_cash_increase"): + row[field] = row[field] - prior[field] if row[field] is not None and prior[field] is not None else None + previous[row["year"]] = {field: row[field] if prior is None else (row[field] + prior[field] if row[field] is not None and prior[field] is not None else None) for field in ("operating_cash_flow", "investing_cash_flow", "financing_cash_flow", "net_cash_increase")} + return cumulative + + +def annual_dividend(frame): + rows = [] + for row_index in range(1, frame.shape[0]): + period = year(frame.iat[row_index, 0]) + if not period: + continue + dividend = number(frame.iat[row_index, 4]) + profit = number(frame.iat[row_index, 6]) + if dividend is None and profit is None: + continue + rows.append({ + "period": period, + "dividend": dividend, + "net_profit": profit, + "payout_ratio": dividend / profit if dividend is not None and profit not in (None, 0) else None, + }) + return sorted(rows, key=lambda row: int(row["period"])) + + +def price_adjustments(frame): + rows = [] + for row_index in range(1, frame.shape[0]): + date = pd.to_datetime(frame.iat[row_index, 0], errors="coerce") + before = number(frame.iat[row_index, 1]) + after = number(frame.iat[row_index, 2]) + if pd.isna(date) or after is None: + continue + rows.append({ + "period": date.strftime("%Y-%m"), + "before_price": before, + "after_price": after, + "increase_rate": number(frame.iat[row_index, 4]), + }) + return rows + + +def ton_price(frame): + headers = [(column, year(frame.iat[1, column])) for column in range(1, frame.shape[1])] + headers = [(column, period) for column, period in headers if period] + names = {"茅台酒营收": "moutai_revenue", "系列酒营收": "series_revenue", "茅台商品酒销量(吨)": "moutai_volume", "系列酒销量(吨)": "series_volume"} + locations = {} + for row_index in range(2, frame.shape[0]): + source = label(frame.iat[row_index, 0]) + if source in names: + locations[names[source]] = row_index + rows = [] + for column, period in headers: + row = {"period": period} + for field in names.values(): + source_row = locations.get(field) + row[field] = number(frame.iat[source_row, column]) if source_row is not None else None + row["moutai_price_per_ton"] = row["moutai_revenue"] * 10000 / row["moutai_volume"] if row["moutai_revenue"] is not None and row["moutai_volume"] not in (None, 0) else None + row["series_price_per_ton"] = row["series_revenue"] * 10000 / row["series_volume"] if row["series_revenue"] is not None and row["series_volume"] not in (None, 0) else None + rows.append(row) + return sorted(rows, key=lambda row: int(row["period"])) + + +def annual_profitability(frame): + headers = {label(frame.iat[0, column]): column for column in range(1, frame.shape[1])} + field_names = {"营业收入": "revenue", "毛利": "gross_profit", "毛利率": "gross_margin", "归母净利润": "net_profit"} + rows = [] + for row_index in range(1, frame.shape[0]): + period = year(frame.iat[row_index, 0]) + if not period: + continue + row = {"period": period} + for source, field in field_names.items(): + column = headers.get(source) + row[field] = number(frame.iat[row_index, column]) if column is not None else None + if any(row[field] is not None for field in field_names.values()): + rows.append(row) + return sorted(rows, key=lambda row: int(row["period"])) + + +def distributors(frame): + rows = [] + for row_index in range(3, frame.shape[0]): + period = label(frame.iat[row_index, 0]) + cutoff = pd.to_datetime(frame.iat[row_index, 1], errors="coerce") + if not period or pd.isna(cutoff): + continue + domestic = number(frame.iat[row_index, 2]) + overseas = number(frame.iat[row_index, 5]) + total = number(frame.iat[row_index, 8]) + if total is None: + continue + rows.append({"period": period.replace(" ", ""), "domestic": domestic, "overseas": overseas, "total": total}) + return rows + + +def main(): + annual_profit = enrich_profit(annual_profitability(sheet(ANNUAL, "盈利能力"))) + csv("moutai_profitability_annual.csv", annual_profit, ["period", "revenue", "gross_profit", "net_profit", "gross_margin", "revenue_yoy", "net_profit_yoy"]) + + quarterly_profit = sheet(QUARTERLY, "盈利能力") + field_names = {"营业收入": "revenue", "毛利": "gross_profit", "毛利率": "gross_margin", "归母净利润": "net_profit"} + metric_columns = {field: next((column for column in range(8, quarterly_profit.shape[1]) if label(quarterly_profit.iat[1, column]) == source), None) for source, field in field_names.items()} + quarterly_rows = [] + for row_index in range(2, quarterly_profit.shape[0]): + period = quarter(quarterly_profit.iat[row_index, 8]) + if not period: + continue + row = {"period": period} + for field, column in metric_columns.items(): + row[field] = number(quarterly_profit.iat[row_index, column]) if column is not None else None + quarterly_rows.append(row) + quarterly_rows = enrich_profit(sorted(quarterly_rows, key=lambda row: quarter_sort(row["period"]))) + csv("moutai_profitability_quarterly.csv", quarterly_rows, ["period", "revenue", "gross_profit", "net_profit", "gross_margin", "revenue_yoy", "net_profit_yoy"]) + + annual_expense = wide_rows(sheet(ANNUAL, "费用率"), { + "sales_expense": "销售费用", "admin_expense": "管理费用", "rd_expense": "研发费用", "tax_surcharge": "税金及附加", "revenue": "营业收入", + }) + for row in annual_expense: + for expense in ("sales_expense", "admin_expense", "rd_expense", "tax_surcharge"): + row[f"{expense}_rate"] = row[expense] / row["revenue"] if row[expense] is not None and row["revenue"] not in (None, 0) else None + csv("moutai_expense_annual.csv", annual_expense, ["period", "sales_expense", "admin_expense", "rd_expense", "tax_surcharge", "revenue", "sales_expense_rate", "admin_expense_rate", "rd_expense_rate", "tax_surcharge_rate"]) + + quarter_expense = quarter_wide_rows(sheet(QUARTERLY, "费用率"), { + "sales_expense": "销售费用", "admin_expense": "管理费用", "rd_expense": "研发费用", "tax_surcharge": "税金及附加", "revenue": "营业收入", "gross_margin": "毛利率", + }, header_row=1) + for row in quarter_expense: + for expense in ("sales_expense", "admin_expense", "rd_expense", "tax_surcharge"): + row[f"{expense}_rate"] = row[expense] / row["revenue"] if row[expense] is not None and row["revenue"] not in (None, 0) else None + csv("moutai_expense_quarterly.csv", quarter_expense, ["period", "sales_expense", "admin_expense", "rd_expense", "tax_surcharge", "revenue", "gross_margin", "sales_expense_rate", "admin_expense_rate", "rd_expense_rate", "tax_surcharge_rate"]) + + balance_fields = {"total_assets": "资产总计", "equity": "所有者权益合计", "liabilities": "负债合计", "cash_like": "类现金", "cash": "货币资金", "receivables": "应收账款", "inventory": "存货"} + csv("moutai_balance_annual.csv", wide_rows(sheet(ANNUAL, "资产负债表"), balance_fields), ["period", *balance_fields]) + csv("moutai_balance_quarterly.csv", quarter_wide_rows(sheet(QUARTERLY, "资产负债表"), balance_fields), ["period", *balance_fields]) + + cf_columns = ["period", "operating_cash_flow", "investing_cash_flow", "financing_cash_flow", "net_cash_increase"] + csv("moutai_cashflow_annual.csv", cashflow_rows(sheet(ANNUAL, "现金流量表")), cf_columns) + csv("moutai_cashflow_quarterly.csv", cashflow_rows(sheet(QUARTERLY, "现金流量表"), single_quarter=True), cf_columns) + + product = annual_product_or_channel(sheet(ANNUAL, "酒类占比"), {"茅台酒营收": "moutai", "系列酒营收": "series"}, header_row=0) + for row in product: + total = sum(value for value in (row["moutai"], row["series"]) if value is not None) + row["moutai_share"] = row["moutai"] / total if total and row["moutai"] is not None else None + row["series_share"] = row["series"] / total if total and row["series"] is not None else None + csv("moutai_product_revenue_annual.csv", product, ["period", "moutai", "series", "moutai_share", "series_share"]) + + channels = annual_product_or_channel(sheet(ANNUAL, "渠道占比"), {"直销营收(亿)": "direct", "批发代理营收(亿)": "wholesale"}) + for row in channels: + total = sum(value for value in (row["direct"], row["wholesale"]) if value is not None) + row["direct_share"] = row["direct"] / total if total and row["direct"] is not None else None + row["wholesale_share"] = row["wholesale"] / total if total and row["wholesale"] is not None else None + csv("moutai_channel_revenue_annual.csv", channels, ["period", "direct", "wholesale", "direct_share", "wholesale_share"]) + + classified_columns = ["period", "moutai", "series", "direct", "wholesale", "moutai_share", "direct_share"] + csv("moutai_classified_revenue_quarterly.csv", normalized_classified(sheet(QUARTERLY, "分类营收-单机")), classified_columns) + csv("moutai_classified_revenue_cumulative.csv", normalized_classified(sheet(QUARTERLY, "分类营收-累计")), classified_columns) + csv("moutai_revenue_composition_halfyear.csv", halfyear_composition(sheet(QUARTERLY, "营收构成-累计")), ["period", "moutai", "series", "domestic", "overseas"]) + + liquor = sheet(ANNUAL, "全国白酒产量") + liquor_rows = [{"period": year(liquor.iat[i, 0]), "output": number(liquor.iat[i, 1]), "yoy": number(liquor.iat[i, 2])} for i in range(1, liquor.shape[0])] + liquor_rows = [row for row in liquor_rows if row["period"]] + csv("moutai_liquor_industry_output.csv", liquor_rows, ["period", "output", "yoy"]) + + base = sheet(ANNUAL, "茅台基酒产销量") + production = {} + sales = {} + for i in range(2, base.shape[0]): + production[year(base.iat[i, 0])] = {"planned_capacity": number(base.iat[i, 1]), "released_capacity": number(base.iat[i, 2]), "base_wine_output": number(base.iat[i, 3])} + sales[year(base.iat[i, 6])] = number(base.iat[i, 7]) + volume_rows = [] + for period in sorted({value for value in production if value} | {value for value in sales if value}, key=int): + volume_rows.append({"period": period, **production.get(period, {}), "commercial_wine_sales": sales.get(period)}) + csv("moutai_base_wine_volume.csv", volume_rows, ["period", "planned_capacity", "released_capacity", "base_wine_output", "commercial_wine_sales"]) + + price = sheet(ANNUAL, "飞天茅台历年批价") + price_rows = [{"period": year(price.iat[i, 0]), "wholesale_price": number(price.iat[i, 1])} for i in range(1, price.shape[0])] + csv("moutai_flying_price.csv", [row for row in price_rows if row["period"] and row["wholesale_price"] is not None], ["period", "wholesale_price"]) + csv("moutai_price_adjustments.csv", price_adjustments(sheet(ANNUAL, "提价记录")), ["period", "before_price", "after_price", "increase_rate"]) + csv("moutai_ton_price.csv", ton_price(sheet(ANNUAL, "吨价")), ["period", "moutai_revenue", "series_revenue", "moutai_volume", "series_volume", "moutai_price_per_ton", "series_price_per_ton"]) + csv("moutai_dividend.csv", annual_dividend(sheet(ANNUAL, "历年分红")), ["period", "dividend", "net_profit", "payout_ratio"]) + csv("moutai_distributors.csv", distributors(sheet(QUARTERLY, "经销商数量")), ["period", "domestic", "overseas", "total"]) + + +if __name__ == "__main__": + main()