Added decimal precision and SGI groupings.

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Patrick Fic
2026-04-23 13:13:25 -07:00
parent 4ab41c97ce
commit 69e017ca7d
11 changed files with 3170 additions and 100 deletions

View File

@@ -65,10 +65,18 @@ async function DecodeEstimate(filePath, includeFilePathInReturnJob = false, clos
delete job.mat_rates;
job.insp_date = ad2.INSP_DATE;
const ins_rule_set = store.get("ins_rule_set");
if (job.OWNR_FN === "" || !job.OWNR_FN) job.OWNR_FN = ad2.CLMT_FN;
if (job.OWNR_LN === "" || !job.OWNR_LN) job.OWNR_LN = ad2.CLMT_LN;
if (ins_rule_set === "SGI") {
if (job.OWNR_FN === "" || !job.OWNR_FN) job.OWNR_FN = job.INSD_FN;
if (job.OWNR_LN === "" || !job.OWNR_LN) job.OWNR_LN = job.INSD_LN;
}
if (job.OWNR_CO_NM) job.OWNR_LN = `${job.OWNR_LN} ${job.OWNR_CO_NM}`;
delete job.OWNR_CO_NM;
delete job.INSD_LN;
delete job.INSD_FN;
const accepted_ins_co = store.get("accepted_ins_co");
let returnValue;
@@ -77,7 +85,6 @@ async function DecodeEstimate(filePath, includeFilePathInReturnJob = false, clos
// returnValue = { ERROR: "Vehicle mileage is less than 20,000kms." };
// } else
const ins_rule_set = store.get("ins_rule_set");
if (!accepted_ins_co.includes(job.INS_CO_NM)) {
returnValue = {
@@ -192,8 +199,8 @@ async function DecodeAd1File(extensionlessFilePath) {
// "CAT_NO",
"TLOS_IND",
// "CUST_PR",
// "INSD_LN",
// "INSD_FN",
"INSD_LN",
"INSD_FN",
// "INSD_TITLE",
// "INSD_CO_NM",
// "INSD_ADDR1",

View File

@@ -3,7 +3,7 @@
"productName": "ImEX RPS",
"author": "ImEX Systems Inc. <support@thinkimex.com>",
"description": "ImEX RPS",
"version": "1.6.0-alpha.7",
"version": "1.6.0-alpha.8",
"main": "electron/main.js",
"homepage": "./",
"dependencies": {

View File

@@ -5,28 +5,25 @@ import { createStructuredSelector } from "reselect";
import { selectSelectedJobTargetPc } from "../../../redux/application/application.selectors";
import { selectBodyshop } from "../../../redux/user/user.selectors";
import "./price-diff-pc-formatter.styles.scss";
import { getDecimalPrecision } from "../../../util/decimalPrecision";
const mapStateToProps = createStructuredSelector({
selectedJobTargetPc: selectSelectedJobTargetPc,
bodyshop: selectBodyshop,
bodyshop: selectBodyshop
});
export function PriceDiffPcFormatterAtom({
bodyshop,
price_diff_pc,
selectedJobTargetPc,
}) {
export function PriceDiffPcFormatterAtom({ bodyshop, price_diff_pc, selectedJobTargetPc }) {
const precision = getDecimalPrecision();
return (
<div
style={{
color: price_diff_pc > selectedJobTargetPc ? "green" : "red",
display: "flex",
alignItems: "center",
alignItems: "center"
}}
>
{(price_diff_pc * 100).toFixed(1)}%
{price_diff_pc === 1 ||
(price_diff_pc <= bodyshop.ppd_diff_alert && price_diff_pc > 0) ? (
{(price_diff_pc * 100).toFixed(precision)}%
{price_diff_pc === 1 || (price_diff_pc <= bodyshop.ppd_diff_alert && price_diff_pc > 0) ? (
<AlertFilled style={{ color: "tomato" }} className="blink_me" />
) : null}
</div>

View File

@@ -5,6 +5,7 @@ import { InfoCircleFilled } from "@ant-design/icons";
import { FakedGroupsForV3WithMake } from "../../../ipc/ipc-estimate-utils";
import { WhichMPIRulesetToApply } from "../../../util/constants";
import { alphaSort } from "../../../util/sorters";
import { getDecimalPrecision } from "../../../util/decimalPrecision";
const data = [
{
@@ -366,7 +367,7 @@ const v3DataSource = _.sortBy(
export function JobsGroupV3ModalMolecule() {
const [visible, setVisible] = useState(false);
const [search, setSearch] = useState("");
const precision = getDecimalPrecision();
const v3DataSourceFiltered =
search === null || search.trim() === ""
? v3DataSource
@@ -420,7 +421,7 @@ export function JobsGroupV3ModalMolecule() {
title: "Target",
dataIndex: "target",
key: "target",
render: (text, record) => `${(record.target * 100).toFixed(1)}%`
render: (text, record) => `${(record.target * 100).toFixed(precision)}%`
}
]}
/>

View File

@@ -9,6 +9,7 @@ import { CalculateJobRpsDollars, CalculateJobRpsPc } from "../../../util/Calcula
import ErrorResultAtom from "../../atoms/error-result/error-result.atom";
import { WhichMPIRulesetToApply } from "../../../util/constants";
import { selectBodyshop } from "../../../redux/user/user.selectors";
import { getDecimalPrecision } from "../../../util/decimalPrecision";
const mapStateToProps = createStructuredSelector({
selectedJobTargetPc: selectSelectedJobTargetPc,
@@ -39,7 +40,7 @@ export function JobsTargetsStatsMolecule({ loading, job, selectedJobTargetPc, bo
const targetRpsDollars = dbPriceSum.percentage(selectedJobTargetPc * 100);
const dollarDiff = jobRpsDollars.subtract(targetRpsDollars);
const MPIRulesetToApply = WhichMPIRulesetToApply(job.close_date);
const precision = getDecimalPrecision();
return (
<div
style={{
@@ -53,7 +54,7 @@ export function JobsTargetsStatsMolecule({ loading, job, selectedJobTargetPc, bo
<Space>
<Statistic
title="Target RPS %"
value={(selectedJobTargetPc * 100).toFixed(1)}
value={(selectedJobTargetPc * 100).toFixed(precision)}
suffix={
<Space size="small">
%
@@ -72,7 +73,7 @@ export function JobsTargetsStatsMolecule({ loading, job, selectedJobTargetPc, bo
valueStyle={{
color: selectedJobTargetPc > (jobRpsPc || 0) ? "tomato" : "seagreen"
}}
value={((jobRpsPc || 0) * 100).toFixed(1)}
value={((jobRpsPc || 0) * 100).toFixed(precision)}
suffix="%"
/>
</Space>

View File

@@ -19,6 +19,7 @@ import VehicleGroupAlertAtom from "../../atoms/vehicle-group-alert/vehicle-group
import GroupVerifySwitch from "../group-verify-switch/group-verify-switch.component";
import JobsClaimsClerkMolecule from "../jobs-claims-clerk/jobs-claims-clerk.molecule.jsx";
import { addExcludedId, removeExcludedId } from "../../../redux/reporting/reporting.actions.js";
import { getDecimalPrecision } from "../../../util/decimalPrecision.js";
const { ipcRenderer } = window;
@@ -44,7 +45,7 @@ export function ReportingJobsListMolecule({
removeExcludedId
}) {
const [searchText, setSearchText] = useState("");
const precision = getDecimalPrecision();
const columns = [
{
title: "Claim No.",
@@ -172,7 +173,7 @@ export function ReportingJobsListMolecule({
color: record.jobRpsPc > record.jobTarget ? "seagreen" : "tomato"
}}
>
{`${(record.jobRpsPc * 100 || 0).toFixed(1)}% / ${(record.jobTarget * 100).toFixed(1)}%`}
{`${(record.jobRpsPc * 100 || 0).toFixed(precision)}% / ${(record.jobTarget * 100).toFixed(precision)}%`}
</Space>
)
},
@@ -186,7 +187,7 @@ export function ReportingJobsListMolecule({
onChange={(checked) => {
checked ? addExcludedId(record.id) : removeExcludedId(record.id);
ipcRenderer.send(ipcTypes.app.toMain.track, {
event: "TOGGLE_SCENARIO_MANAGER",
event: "TOGGLE_SCENARIO_MANAGER"
});
}}
/>

View File

@@ -1,10 +1,4 @@
import {
Card,
Radio,
Skeleton,
Tooltip as AntdToolTip,
Typography,
} from "antd";
import { Card, Radio, Skeleton, Tooltip as AntdToolTip, Typography } from "antd";
import React, { useState } from "react";
import { connect } from "react-redux";
import {
@@ -16,37 +10,31 @@ import {
Tooltip,
XAxis,
YAxis,
ZAxis,
ZAxis
} from "recharts";
import { createStructuredSelector } from "reselect";
import {
selectReportLoading,
selectScorecard,
} from "../../../redux/reporting/reporting.selectors";
import { selectReportLoading, selectScorecard } from "../../../redux/reporting/reporting.selectors";
import DataLabelAtom from "../../atoms/data-label/data-label.atom";
import ErrorResultAtom from "../../atoms/error-result/error-result.atom";
import { getDecimalPrecision } from "../../../util/decimalPrecision";
const mapStateToProps = createStructuredSelector({
reportingLoading: selectReportLoading,
scoreCard: selectScorecard,
scoreCard: selectScorecard
});
const mapDispatchToProps = (dispatch) => ({});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ReportingScatterChartMolecule);
export default connect(mapStateToProps, mapDispatchToProps)(ReportingScatterChartMolecule);
export function ReportingScatterChartMolecule({ reportingLoading, scoreCard }) {
const [type, setType] = useState("percent");
if (reportingLoading) return <Skeleton active />;
if (!scoreCard)
return <ErrorResultAtom title="Error displaying score card data." />;
if (!scoreCard) return <ErrorResultAtom title="Error displaying score card data." />;
const handleTypeChange = (e) => {
setType(e.target.value);
};
const precision = getDecimalPrecision();
return (
<div>
<div
@@ -54,7 +42,7 @@ export function ReportingScatterChartMolecule({ reportingLoading, scoreCard }) {
marginTop: "1rem",
marginBottom: "1rem",
width: "100%",
height: "30rem",
height: "30rem"
}}
>
<div style={{ display: "flex", flexDirection: "row" }}>
@@ -63,19 +51,12 @@ export function ReportingScatterChartMolecule({ reportingLoading, scoreCard }) {
placement="bottomLeft"
title="Calculated as Actual Job RPS % less Target Job RPS %. E.g. 10% - 8% = 2%"
>
<Typography.Title level={4}>
% Variance from Target
</Typography.Title>
<Typography.Title level={4}>% Variance from Target</Typography.Title>
</AntdToolTip>
)}
{type === "dollars" && (
<AntdToolTip
placement="bottomLeft"
title="Calculated as Actual Job RPS $ less Target Job RPS $."
>
<Typography.Title level={4}>
$ Variance from Target
</Typography.Title>
<AntdToolTip placement="bottomLeft" title="Calculated as Actual Job RPS $ less Target Job RPS $.">
<Typography.Title level={4}>$ Variance from Target</Typography.Title>
</AntdToolTip>
)}
@@ -103,13 +84,7 @@ export function ReportingScatterChartMolecule({ reportingLoading, scoreCard }) {
unit="%"
/>
)}
{type === "dollars" && (
<XAxis
dataKey="deviationDollars"
type="number"
name="Deviation ($)"
/>
)}
{type === "dollars" && <XAxis dataKey="deviationDollars" type="number" name="Deviation ($)" />}
<YAxis type="number" dataKey="age" name="Age" unit="Years" />
<ZAxis dataKey="dbPriceSumAmt" name="DB Price Total" />
@@ -120,24 +95,12 @@ export function ReportingScatterChartMolecule({ reportingLoading, scoreCard }) {
return (
<Card title={item.clm_no}>
<DataLabelAtom label="Owner">{item.owner}</DataLabelAtom>
<DataLabelAtom label="Vehicle">
{item.vehicle}
</DataLabelAtom>
<DataLabelAtom label="Job RPS $">
{item.jobRpsDollars.toFormat()}
</DataLabelAtom>
<DataLabelAtom label="Job RPS %">
{(item.jobRpsPc * 100).toFixed(1)}%
</DataLabelAtom>
<DataLabelAtom label="DB Price">
{item.dbPriceSum.toFormat()}
</DataLabelAtom>
<DataLabelAtom label="Variance %">
{`${item.deviationPc}%`}
</DataLabelAtom>
<DataLabelAtom label="Variance $">
${item.deviationDollars}
</DataLabelAtom>
<DataLabelAtom label="Vehicle">{item.vehicle}</DataLabelAtom>
<DataLabelAtom label="Job RPS $">{item.jobRpsDollars.toFormat()}</DataLabelAtom>
<DataLabelAtom label="Job RPS %">{(item.jobRpsPc * 100).toFixed(precision)}%</DataLabelAtom>
<DataLabelAtom label="DB Price">{item.dbPriceSum.toFormat()}</DataLabelAtom>
<DataLabelAtom label="Variance %">{`${item.deviationPc.toFixed(precision)}%`}</DataLabelAtom>
<DataLabelAtom label="Variance $">${item.deviationDollars}</DataLabelAtom>
</Card>
);
}}
@@ -145,12 +108,7 @@ export function ReportingScatterChartMolecule({ reportingLoading, scoreCard }) {
/>
<Legend />
{Object.keys(scoreCard.scatterChart).map((key, idx) => (
<Scatter
name={key}
key={idx}
data={scoreCard.scatterChart[key]}
fill={colors[idx]}
></Scatter>
<Scatter name={key} key={idx} data={scoreCard.scatterChart[key]} fill={colors[idx]}></Scatter>
))}
</ScatterChart>
</ResponsiveContainer>
@@ -159,11 +117,4 @@ export function ReportingScatterChartMolecule({ reportingLoading, scoreCard }) {
);
}
const colors = [
"#0c344d",
"#6cc314",
"#f5782a",
"#f5be2a",
"#fbff90",
"dodgerblue",
];
const colors = ["#0c344d", "#6cc314", "#f5782a", "#f5be2a", "#fbff90", "dodgerblue"];

View File

@@ -1,11 +1,11 @@
import { Alert, Button, Skeleton, Statistic } from "antd";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { clearExcludedIds } from "../../../redux/reporting/reporting.actions";
import { selectExcludedIds, selectReportLoading, selectScorecard } from "../../../redux/reporting/reporting.selectors";
import ErrorResultAtom from "../../atoms/error-result/error-result.atom";
import { clearExcludedIds } from "../../../redux/reporting/reporting.actions";
import { getDecimalPrecision } from "../../../util/decimalPrecision";
const mapStateToProps = createStructuredSelector({
reportingLoading: selectReportLoading,
scoreCard: selectScorecard,
@@ -20,7 +20,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(ReportingTotalsStats
export function ReportingTotalsStatsMolecule({ reportingLoading, scoreCard, excludedJobIds, clearExcludedIds }) {
if (reportingLoading) return <Skeleton active />;
if (!scoreCard) return <ErrorResultAtom title="Error displaying score card data." />;
const precision = getDecimalPrecision();
return (
<>
<div
@@ -36,7 +36,7 @@ export function ReportingTotalsStatsMolecule({ reportingLoading, scoreCard, excl
<Statistic
title="Target RPS %"
style={{ margin: "0rem .5rem" }}
value={((scoreCard.targetRpsPc || 0) * 100).toFixed(1)}
value={((scoreCard.targetRpsPc || 0) * 100).toFixed(precision)}
suffix="%"
/>
<Statistic
@@ -45,7 +45,7 @@ export function ReportingTotalsStatsMolecule({ reportingLoading, scoreCard, excl
valueStyle={{
color: (scoreCard.currentRpsPc || 0) <= (scoreCard.targetRpsPc || 0) ? "tomato" : "seagreen"
}}
value={((scoreCard.currentRpsPc || 0) * 100).toFixed(1)}
value={((scoreCard.currentRpsPc || 0) * 100).toFixed(precision)}
suffix="%"
/>
<Statistic
@@ -54,7 +54,7 @@ export function ReportingTotalsStatsMolecule({ reportingLoading, scoreCard, excl
valueStyle={{
color: scoreCard.variancePc < 0 ? "tomato" : "seagreen"
}}
value={((scoreCard.variancePc || 0) * 100).toFixed(2)}
value={((scoreCard.variancePc || 0) * 100).toFixed(precision)}
suffix="%"
/>
</div>

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
import { V3TargetFinder } from "../ipc/ipc-estimate-utils";
import { V3TargetFinder, SgiGroupsV12026April } from "../ipc/ipc-estimate-utils";
import { store } from "../redux/store";
import { WhichMPIRulesetToApply } from "./constants";
@@ -7,7 +7,13 @@ export default function GetJobTarget({ group, v_age, targets, close_date, v_mile
switch (ins_rule_set) {
case "SGI":
return 0;
let type = job.v_type === "PC" || job.v_type === "SUV" ? "PC" : job.v_type;
const sgiTarget = SgiGroupsV12026April.find((f) => f.make === job.v_makedesc.toUpperCase()
&& (f.type === type || f.type === null)
&& job.v_age >= f.ageGte
&& (f.ageLt === null ? true : job.v_age < f.ageLt)
);
return sgiTarget?.target || 0;
case "MPI":
default:

View File

@@ -0,0 +1,8 @@
import { store } from "../redux/store";
export function getDecimalPrecision() {
const ins_rule_set = store.getState().user.bodyshop.ins_rule_set;
return ins_rule_set === "SGI" ? 2 : 1;
}
export default getDecimalPrecision;