From aee8c10effc39d1203b7bd80c9c89b3c54bee5b9 Mon Sep 17 00:00:00 2001
From: zhizhijie <zhizhijie@users.noreply.gitee.com>
Date: 星期二, 08 九月 2026 10:14:14 +0800
Subject: [PATCH] feat: 汇总大表数据驱动 M1-网约车——新增 WycSplitCalc 拆分计算服务与 7 月回归测试报告
---
docs/功能测试报告/功能测试报告_M1-网约车拆分WycSplitCalc_2026-09-08.md | 33 ++++++
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/WycSplitCalc.java | 266 +++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 299 insertions(+), 0 deletions(-)
diff --git "a/docs/\345\212\237\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212/\345\212\237\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212_M1-\347\275\221\347\272\246\350\275\246\346\213\206\345\210\206WycSplitCalc_2026-09-08.md" "b/docs/\345\212\237\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212/\345\212\237\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212_M1-\347\275\221\347\272\246\350\275\246\346\213\206\345\210\206WycSplitCalc_2026-09-08.md"
new file mode 100644
index 0000000..5b22be6
--- /dev/null
+++ "b/docs/\345\212\237\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212/\345\212\237\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212_M1-\347\275\221\347\272\246\350\275\246\346\213\206\345\210\206WycSplitCalc_2026-09-08.md"
@@ -0,0 +1,33 @@
+# 鍔熻兘娴嬭瘯鎶ュ憡锛歁1-缃戠害杞� WycSplitCalc 鍙栨暟/鎷嗗垎鏍稿涓� 7 鏈堝洖褰掞紙2026-09-08锛�
+
+## 涓�銆佸姛鑳借鏄�
+M1 绗笁鍧楋細鏂板缃戠害杞︽媶鍒嗚绠楁湇鍔� `WycSplitCalc`锛坄traffic-audit-server/.../reportexport/calc/WycSplitCalc.java`锛夛紝鎶� exportWycSplit 鐨勬媶鍒嗙畻娉曟娊鎴愬彲澶嶇敤鐭╅樀锛氳緭鍏�=銆婄綉绾﹁溅璁㈠崟鍙婂叏鐪佹�婚噺.xlsx銆嬶紙pin B-E + 17 甯傚窞璁㈠崟 G-W锛�+ 宸℃父鍑虹鏈堟姤锛圖B锛夛紝杈撳嚭 17 甯傚窞+鍏ㄧ渷鐨� 瀹㈣繍閲�/鍩庡競鍐呭杩愰噺/鍛ㄨ浆閲�/鍩庡競鍐呭懆杞噺銆備緵姹囨�诲ぇ琛ㄧ綉绾﹁溅鍙拌处椤典笌鍩庡競瀹㈣繍鍚勯〉鍏辩敤锛堝彛寰� B5锛夈��
+
+## 浜屻�佹祴璇曠幆澧�
+- 搴擄細`traffic_audit`锛�2026-07 鐜扮姸锛夛紱
+- 杈撳叆鏂囦欢锛歚docs/鍩庡競瀹㈣繍/妯℃澘_缃戠害杞﹁鍗曞強鍏ㄧ渷鎬婚噺.xlsx`锛堟湀搴﹁緭鍏� sheet锛�2026-07 琛屽湪 openpyxl 绗� 8 琛岋級锛�
+- 瀵硅处姣嶇増锛歚docs/鐢熸垚姹囨�诲ぇ琛�/_澶囦唤_2026骞�7鏈堥亾璺繍杈撻噺姹囨�昏〃_娓呯悊鍓峗20260904.xlsx` 缃戠害杞﹀彴璐﹂〉锛�
+- 宸ュ叿锛歰penpyxl + mysql CLI + Python 澶嶅埢绠楁硶姣斿锛涘悗绔� `mvn -q -DskipTests compile` 閫氳繃銆�
+
+## 涓夈�佺畻娉曪紙涓� exportWycSplit 涓�鑷达級
+- C[i]=璁㈠崟鍗犳瘮脳鍏ㄧ渷缃戠害杞﹀杩愰噺 pinK锛汧[i]=宸℃父鍑虹鍩庡競鍐呭杩愰噺/瀹㈣繍閲忥紙鍩庡競鍐呭崰姣斾唬鐞嗭級锛�
+- 鍩庡競鍐� H锛氭姹�=C0脳F0锛堜繚鎸佽嚜韬級锛涘叾浣� = (Ci脳Fi)/(危G鈭扖0F0) 脳 (pinCK鈭扖0F0)锛�
+- 鍛ㄨ浆閲� Gl[i]=Ci脳骞冲潎杩愯窛 / 危(Ci脳骞冲潎杩愯窛) 脳 pinZ锛涘煄甯傚唴鍛ㄨ浆閲� Hl 鍚屾硶锛堢敤鍩庡競鍐呭钩鍧囪繍璺濄�乸inCZ锛夛紱
+- 宸℃父鍑虹骞冲潎杩愯窛缂虹渷鏃舵寜 鍛ㄨ浆閲�/瀹㈣繍閲� 鍙� 2 浣嶅皬鏁板厹搴曘��
+
+## 鍥涖�佺敤渚嬭〃
+| # | 鐢ㄤ緥 | 杈撳叆 | 棰勬湡 | 瀹為檯 | 缁撹 |
+|---|---|---|---|---|---|
+| 1 | 2026-07 鎷嗗垎锛�17 甯傚窞脳4 鎸囨爣锛� | 璁㈠崟+pin+鍑虹 DB | 涓庢瘝鐗堢綉绾﹁溅鍙拌处 O 鍒椾竴鑷� | diff=0 | 閫氳繃 |
+| 2 | 鍏ㄧ渷琛� | pin 杈撳叆 | 瀹㈣繍閲� 4852.30 / 鍛ㄨ浆閲� 28348.02 / 鍩庡競鍐� 4430.84 / 鍩庡競鍐呭懆杞� 22705.61 | 涓庢瘝鐗堜竴鑷� | 閫氳繃 |
+| 3 | 鍔犳�昏嚜娲� | 璁$畻鏁扮粍 | 危C=pinK銆佄=pinCK銆佄l=pinZ銆佄l=pinCZ | 鍏ㄧ瓑 | 閫氳繃 |
+| 4 | 姝︽眽鍙e緞 | 璁$畻 | 鍩庡競鍐�=鎬婚噺锛堟棤鍩庨檯鍩庝埂锛夛紝H=1825.1975銆丠l=13036.3929 | 涓�鑷� | 閫氳繃 |
+| 5 | Java 缂栬瘧 | `mvn -q -DskipTests compile` | 鏃犻敊璇� | exit=0 | 閫氳繃 |
+
+## 浜斻�佸彂鐜扮殑闂涓庡缃�
+1. 杈撳叆鏂囦欢鐜扮姸鍚嶇О涓� `妯℃澘_缃戠害杞﹁鍗曞強鍏ㄧ渷鎬婚噺.xlsx`锛岃�� ReportExportService.exportWycSplit 浠嶆寜绮剧‘鍚� `缃戠害杞﹁鍗曞強鍏ㄧ渷鎬婚噺.xlsx` 瀹氫綅锛坮esolveAnyTemplate 涓嶅仛鍓嶇紑鍖归厤锛夆啋 璇ュ皬琛ㄧ敓鎴愯矾寰勫綋鍓嶅彲鑳芥姤鈥滄湭鎵惧埌妯℃澘鈥濄�俉ycSplitCalc.findInput 宸叉敮鎸佷袱绉嶅懡鍚嶏紝鍏堜繚璇佸ぇ琛ㄥ彇鏁板彲鐢紱灏忚〃鍏ュ彛鐨勬ā鏉垮畾浣嶅緟 M4 缁熶竴鎺ョ嚎鏃朵慨姝c��
+2. 绫诲悕涓庢枃浠跺悕涓嶄竴鑷存浘瀵艰嚧 javac 澶辫触骞剁壍杩炴棤鍏虫枃浠舵姤閿欙紙javac 閿欒鎭㈠绾ц仈锛夆啋 宸茬粺涓�涓� WycSplitCalc.java锛岀紪璇戦�氳繃銆�
+
+## 鍏�佺粨璁轰笌涓嬩竴姝�
+- 缁撹锛氱綉绾﹁溅鎷嗗垎鍙畬鏁村绠� 7 鏈堟瘝鐗堬紙0 宸紓锛夛紝`WycSplitCalc` 宸插叆搴撳苟閫氳繃缂栬瘧銆�
+- 涓嬩竴姝ワ細鍩庡競瀹㈣繍鍙拌处/鍩庡競姹囨��/鍩庡競鍒嗗競宸� 7 鏈堟暣閾惧洖褰掞紙鍏氦+鍑虹+缃戠害+杞ㄩ亾杞浮缁勫悎鍙e緞 C7锛夆啋 M1-MidCalc 鈫� M2 SummaryFiller銆�
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/WycSplitCalc.java b/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/WycSplitCalc.java
new file mode 100644
index 0000000..baf3d26
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/WycSplitCalc.java
@@ -0,0 +1,266 @@
+package com.trafficaudit.reportexport.calc;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.trafficaudit.common.util.RegionUtil;
+import com.trafficaudit.dataimport.entity.CityTaxiMonthly;
+import com.trafficaudit.dataimport.mapper.CityTaxiMonthlyMapper;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.DataFormatter;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 缃戠害杞︽媶鍒嗗彛寰勮绠楁湇鍔★紙姹囨�诲ぇ琛ㄦ暟鎹┍鍔ㄦ敼閫� M1-缃戠害杞︼紝2026-09-08锛夈��
+ * 绠楁硶涓� exportWycSplit / 銆婄綉绾﹁溅鎷嗗垎.xlsx銆嬫湀椤典竴鑷达紙B5锛氳鍗�+鍏ㄧ渷 pin+宸℃父鍑虹鎷嗗垎锛夛細
+ * 瀹㈣繍閲�=璁㈠崟鍗犳瘮脳鍏ㄧ渷鎬婚噺锛涘煄甯傚唴=宸℃父鍑虹鍩庡競鍐呭崰姣斾唬鐞嗭紙姝︽眽淇濇寔鑷韩銆佸叾浣欐寜鍓╀綑鍗犳瘮鍒嗘憡鍒板叏鐪佸煄甯傚唴 pin锛夛紱
+ * 鍛ㄨ浆閲�=瀹㈣繍閲徝楀贰娓稿嚭绉熷钩鍧囪繍璺濆崰姣斿垎鎽婂叏鐪佸懆杞噺 pin锛涘煄甯傚唴鍛ㄨ浆閲忓悓娉曪紙鐢ㄥ煄甯傚唴骞冲潎杩愯窛锛夈��
+ * 2026-07 涓� _澶囦唤鈥娓呯悊鍓峗20260904.xlsx 缃戠害杞﹀彴璐﹂〉 O 鍒楀洖褰� 0 宸紓锛�17 甯傚窞脳瀹㈣繍閲�/鍛ㄨ浆閲�/鍩庡競鍐呭杩愰噺/鍩庡競鍐呭懆杞噺锛夈��
+ */
+@Service
+public class WycSplitCalc {
+
+ /** 鍗曞競宸炴媶鍒嗙粨鏋滐紙鍚煄闄呭煄涔″彛寰勶紝渚涗笉鍚屽彴璐﹁浣跨敤锛� */
+ public static class WycMetrics {
+ private final double totalPax; // 瀹㈣繍閲忔�婚噺锛堜竾浜猴級
+ private final double cityPax; // 鍩庡競鍐呭杩愰噺锛堜竾浜猴級
+ private final double suburbanPax; // 鍩庨檯鍩庝埂瀹㈣繍閲� = totalPax - cityPax
+ private final double totalTurnover; // 鏃呭鍛ㄨ浆閲忔�婚噺锛堜竾浜哄叕閲岋級
+ private final double cityTurnover; // 鍩庡競鍐呮梾瀹㈠懆杞噺锛堜竾浜哄叕閲岋級
+ private final double suburbanTurnover;
+
+ public WycMetrics(double totalPax, double cityPax, double totalTurnover, double cityTurnover) {
+ this.totalPax = totalPax;
+ this.cityPax = cityPax;
+ this.suburbanPax = totalPax - cityPax;
+ this.totalTurnover = totalTurnover;
+ this.cityTurnover = cityTurnover;
+ this.suburbanTurnover = totalTurnover - cityTurnover;
+ }
+
+ public double getTotalPax() { return totalPax; }
+ public double getCityPax() { return cityPax; }
+ public double getSuburbanPax() { return suburbanPax; }
+ public double getTotalTurnover() { return totalTurnover; }
+ public double getCityTurnover() { return cityTurnover; }
+ public double getSuburbanTurnover() { return suburbanTurnover; }
+ }
+
+ public static class WycResult {
+ private final String period;
+ private final Map<String, WycMetrics> byCity; // key=RegionUtil 瑙勮寖甯傚窞鍚�
+ private final WycMetrics province; // 鍏ㄧ渷 = pin 杈撳叆
+
+ public WycResult(String period, Map<String, WycMetrics> byCity, WycMetrics province) {
+ this.period = period;
+ this.byCity = byCity;
+ this.province = province;
+ }
+
+ public String getPeriod() { return period; }
+ public Map<String, WycMetrics> getByCity() { return byCity; }
+ public WycMetrics getProvince() { return province; }
+ }
+
+ @Resource
+ private CityTaxiMonthlyMapper cityTaxiMapper;
+ @Value("${city-passenger.template-dir:docs/鍩庡競瀹㈣繍}")
+ private String templateDir;
+
+ private static final String[] INPUT_CANDIDATES = {"缃戠害杞﹁鍗曞強鍏ㄧ渷鎬婚噺.xlsx", "妯℃澘_缃戠害杞﹁鍗曞強鍏ㄧ渷鎬婚噺.xlsx"};
+
+ /** 璁$畻 period锛堝 2026-07锛夌綉绾﹁溅鎷嗗垎缁撴灉 */
+ public WycResult calc(String period) throws Exception {
+ Map<String, Object> input = loadMonthlyInput(period);
+ if (input == null) {
+ throw new RuntimeException("銆婄綉绾﹁溅璁㈠崟鍙婂叏鐪佹�婚噺.xlsx銆嬬己灏戞姤琛ㄦ湡 " + period + " 鐨勬暟鎹锛岃鍏堢淮鎶ゅ綋鏈堣鍗曚笌鍏ㄧ渷 pin");
+ }
+ double pinK = (Double) input.get("pinK");
+ double pinCK = (Double) input.get("pinCK");
+ double pinZ = (Double) input.get("pinZ");
+ double pinCZ = (Double) input.get("pinCZ");
+ double orderSum = (Double) input.get("orderSum");
+ double[] orderArr = (double[]) input.get("orders");
+ if (pinK <= 0 || pinCK <= 0 || pinZ <= 0 || pinCZ <= 0 || orderSum <= 0) {
+ throw new RuntimeException("缃戠害杞﹁緭鍏� " + period + " 琛屼笉瀹屾暣锛氬叏鐪� pin / 璁㈠崟缂哄け锛岃鏍稿銆婄綉绾﹁溅璁㈠崟鍙婂叏鐪佹�婚噺.xlsx銆�");
+ }
+ List<String> cities = RegionUtil.cityList();
+ int n = cities.size();
+ if (orderArr == null || orderArr.length < n) {
+ throw new RuntimeException("缃戠害杞﹁鍗曞垪鏁颁笉瓒筹紙搴斾负 17 甯傚窞璁㈠崟鍒楋級");
+ }
+ Map<String, double[]> taxi = taxiMonthlyOf(period);
+ List<String> missingTaxi = new ArrayList<>();
+ double[] Dtax = new double[n];
+ double[] Etax = new double[n];
+ double[] avgD = new double[n];
+ double[] avgDc = new double[n];
+ for (int i = 0; i < n; i++) {
+ double[] a = taxi.get(cities.get(i));
+ if (a == null || a[0] <= 0) {
+ missingTaxi.add(cities.get(i));
+ continue;
+ }
+ Dtax[i] = a[0];
+ Etax[i] = a[2];
+ avgD[i] = a[4];
+ avgDc[i] = a[5];
+ }
+ if (!missingTaxi.isEmpty()) {
+ throw new RuntimeException("宸℃父鍑虹鏈堟姤缂哄皯浠ヤ笅甯傚窞鏁版嵁锛�" + String.join("銆�", missingTaxi));
+ }
+ double[] C = new double[n];
+ double[] H = new double[n];
+ double[] Gl = new double[n];
+ double[] Hl = new double[n];
+ double sumG = 0.0;
+ double sumDl = 0.0;
+ double[] cityRatio = new double[n];
+ for (int i = 0; i < n; i++) {
+ C[i] = orderArr[i] / orderSum * pinK;
+ cityRatio[i] = Etax[i] / Dtax[i];
+ sumG += C[i] * cityRatio[i];
+ sumDl += C[i] * avgD[i];
+ }
+ for (int i = 0; i < n; i++) {
+ if (i == 0) {
+ H[i] = C[i] * cityRatio[i];
+ } else {
+ double rest = sumG - C[0] * cityRatio[0];
+ H[i] = rest == 0.0 ? 0.0 : (C[i] * cityRatio[i]) / rest * (pinCK - C[0] * cityRatio[0]);
+ }
+ }
+ double[] el2 = new double[n];
+ double sumEl2 = 0.0;
+ for (int i = 0; i < n; i++) {
+ el2[i] = H[i] * avgDc[i];
+ sumEl2 += el2[i];
+ Gl[i] = sumDl == 0.0 ? 0.0 : C[i] * avgD[i] / sumDl * pinZ;
+ }
+ for (int i = 0; i < n; i++) {
+ if (i == 0) {
+ Hl[i] = Gl[i];
+ } else {
+ double rest = sumEl2 - el2[0];
+ Hl[i] = rest == 0.0 ? 0.0 : el2[i] / rest * (pinCZ - Gl[0]);
+ }
+ }
+ Map<String, WycMetrics> byCity = new LinkedHashMap<>();
+ for (int i = 0; i < n; i++) {
+ byCity.put(cities.get(i), new WycMetrics(C[i], H[i], Gl[i], Hl[i]));
+ }
+ WycMetrics province = new WycMetrics(pinK, pinCK, pinZ, pinCZ);
+ return new WycResult(period, byCity, province);
+ }
+
+ /** 宸℃父鍑虹鏈堟姤锛圖B锛夋寜鏈堝姞杞斤細甯傚窞 -> [瀹㈣繍閲�,鍛ㄨ浆閲�,鍩庡競鍐呭杩愰噺,鍩庡競鍐呭懆杞噺,骞冲潎杩愯窛,鍩庡競鍐呭钩鍧囪繍璺漖 */
+ private Map<String, double[]> taxiMonthlyOf(String period) {
+ Map<String, double[]> map = new LinkedHashMap<>();
+ List<CityTaxiMonthly> rows = cityTaxiMapper.selectList(
+ new LambdaQueryWrapper<CityTaxiMonthly>().eq(CityTaxiMonthly::getReportPeriod, period));
+ for (CityTaxiMonthly r : rows) {
+ String city = RegionUtil.normalizeCityName(r.getCity());
+ if (city == null || city.isEmpty()) city = "鏈煡";
+ double[] a = map.computeIfAbsent(city, k -> new double[6]);
+ a[0] += nz(r.getPassengerVolume());
+ a[1] += nz(r.getTurnover());
+ a[2] += nz(r.getPassengerCity());
+ a[3] += nz(r.getTurnoverCity());
+ if (r.getAvgDistance() != null && r.getAvgDistance() > 0) a[4] = r.getAvgDistance();
+ if (r.getAvgDistanceCity() != null && r.getAvgDistanceCity() > 0) a[5] = r.getAvgDistanceCity();
+ }
+ for (double[] a : map.values()) {
+ if (a[4] <= 0 && a[0] > 0) a[4] = round(a[1] / a[0], 2);
+ if (a[5] <= 0 && a[2] > 0) a[5] = round(a[3] / a[2], 2);
+ }
+ return map;
+ }
+
+ /** 璇诲彇 docs/鍩庡競瀹㈣繍/銆婄綉绾﹁溅璁㈠崟鍙婂叏鐪佹�婚噺.xlsx銆嬶細鎸� period(yyyy-MM) 琛屽彇 pin(B-E) 涓� 17 甯傚窞璁㈠崟(G-W) */
+ private Map<String, Object> loadMonthlyInput(String period) throws Exception {
+ File f = findInput();
+ try (InputStream in = new FileInputStream(f);
+ Workbook wb = WorkbookFactory.create(in)) {
+ org.apache.poi.ss.usermodel.Sheet sh = wb.getSheetAt(0);
+ DataFormatter df = new DataFormatter();
+ List<String> cities = RegionUtil.cityList();
+ int n = cities.size();
+ for (int i = 1; i <= sh.getLastRowNum(); i++) {
+ Row row = sh.getRow(i);
+ if (row == null) continue;
+ Cell c0 = row.getCell(0);
+ if (c0 == null) continue;
+ String m = df.formatCellValue(c0).trim();
+ if (m.isEmpty()) continue;
+ if (m.length() > 7) m = m.substring(0, 7);
+ if (!period.equals(m)) continue;
+ Map<String, Object> map = new LinkedHashMap<>();
+ map.put("pinK", cellNum(row, 1, df, 0.0));
+ map.put("pinCK", cellNum(row, 2, df, 0.0));
+ map.put("pinZ", cellNum(row, 3, df, 0.0));
+ map.put("pinCZ", cellNum(row, 4, df, 0.0));
+ double[] orders = new double[n];
+ double orderSum = 0.0;
+ for (int k = 0; k < n; k++) {
+ orders[k] = cellNum(row, 6 + k, df, 0.0);
+ orderSum += orders[k];
+ }
+ if (orderSum <= 0) orderSum = cellNum(row, 5, df, 0.0);
+ map.put("orders", orders);
+ map.put("orderSum", orderSum);
+ return map;
+ }
+ return null;
+ }
+ }
+
+ private File findInput() throws Exception {
+ String rel = templateDir;
+ while (rel.startsWith("./")) rel = rel.substring(2);
+ String[] roots = {
+ templateDir,
+ System.getProperty("user.dir") + "/" + rel,
+ System.getProperty("user.dir") + "/../" + rel
+ };
+ for (String root : roots) {
+ if (root == null || root.trim().isEmpty()) continue;
+ for (String name : INPUT_CANDIDATES) {
+ File f = new File(root, name);
+ if (f.exists() && f.isFile()) return f;
+ }
+ }
+ throw new RuntimeException("鏈壘鍒般�婄綉绾﹁溅璁㈠崟鍙婂叏鐪佹�婚噺.xlsx銆嬶紙鍚� 妯℃澘_ 鍓嶇紑鍛藉悕锛夛紝璇锋斁鍒� docs/鍩庡競瀹㈣繍 鐩綍");
+ }
+
+ private double cellNum(Row row, int idx, DataFormatter df, double def) {
+ Cell c = row.getCell(idx);
+ if (c == null) return def;
+ if (c.getCellType() == CellType.NUMERIC) return c.getNumericCellValue();
+ String s = df.formatCellValue(c).replace(",", "").trim();
+ if (s.isEmpty()) return def;
+ try {
+ return Double.parseDouble(s);
+ } catch (NumberFormatException e) {
+ return def;
+ }
+ }
+
+ private double nz(Double v) { return v == null ? 0.0 : v; }
+
+ private double round(double v, int scale) {
+ double factor = Math.pow(10, scale);
+ return Math.round(v * factor) / factor;
+ }
+}
--
Gitblit v1.9.1