Additional drizzle cleanup. Move scrub transformation to server side.

This commit is contained in:
Patrick Fic
2026-01-20 11:59:39 -08:00
parent 8954147976
commit 27370bba6d
16 changed files with 1589 additions and 1653 deletions

93
REFACTORING_NOTES.md Normal file
View File

@@ -0,0 +1,93 @@
# Refactoring Summary: Server-Side Transformation
## Overview
Moved the job transformation logic from the Electron app to the Lambda function to enable server-side processing and sharing of types between codebases.
## Changes Made
### 1. Shared Types Directory (`/shared/types/`)
Created a shared types directory that both the Electron app and serverless Lambda can use:
- **`raw-job-data.interface.ts`** - Defines the `RawJobDataObject` interface with all fields from the decoded job data
- **`es-job-object.interface.ts`** - Defines the `ESJobObject` interface for the transformed estimate scrubber format
- **`index.ts`** - Exports all shared types for easy importing
### 2. Serverless Changes
#### `serverless/src/lib/transformEstimate.ts`
- Moved transformation logic from `src/main/estimate-scrubber/es-transformer.ts`
- Exports `transformJobForEstimateScrubber()` function
- Imports shared types from `../../../shared/types`
- Handles all field omissions and transformations server-side
#### `serverless/src/handlers/scrub.ts`
- Updated to receive `rawJob` instead of pre-transformed `estimate`
- Calls `transformJobForEstimateScrubber()` to transform on the server
- Sets `v_type` using the existing `getVehicleType()` function
- Updated TypeScript interfaces to use shared types
#### `serverless/package.json`
- Added `lodash` dependency
- Added `@types/lodash` dev dependency
#### `serverless/tsconfig.json`
- Updated `include` to add `"../shared/**/*"` for shared types access
### 3. Electron App Changes
#### `src/main/estimate-scrubber/estimate-scrubber.ts`
- Removed call to `TransformJobForEstimateScrubber()`
- Now sends raw job object directly to Lambda as `rawJob`
- Removed import of `es-transformer`
- Transformation now happens server-side
#### `tsconfig.node.json`
- Updated `include` array to add `"shared/**/*"` for shared types access
### 4. Files That Can Be Deprecated (Optional)
- `src/main/estimate-scrubber/es-transformer.ts` - Logic moved to serverless
- `src/main/estimate-scrubber/es-job-object.interface.ts` - Moved to shared types
## Benefits
1. **Single Source of Truth**: Types are defined once in `/shared/types/` and used by both codebases
2. **Server-Side Processing**: Transformation logic runs on Lambda, reducing client-side processing
3. **Easier Maintenance**: Updates to transformation logic or types only need to be made in one place
4. **Type Safety**: Both Electron app and Lambda use the same TypeScript interfaces
5. **Reduced Bundle Size**: Electron app no longer includes transformation logic
## API Contract Changes
### Before
```typescript
POST /scrub
{
esApiKey: string,
estimate: ESJobObject, // Pre-transformed
fileName: string
}
```
### After
```typescript
POST /scrub
{
esApiKey: string,
rawJob: RawJobDataObject // Raw, untransformed
}
```
## Migration Notes
- The Lambda function now handles all transformation logic
- Ensure both codebases have access to the `/shared/types/` directory
- Run `npm install` in the serverless directory to get new dependencies
- The `fileName` parameter is now generated server-side based on the raw job data
## Testing Recommendations
1. Test that raw job objects are correctly sent from Electron to Lambda
2. Verify transformation logic produces identical output to previous version
3. Ensure PDF generation and report URLs are working correctly
4. Check that error handling still works as expected
5. Validate that logs are being written correctly with transformed data

File diff suppressed because it is too large Load Diff

View File

@@ -19,24 +19,25 @@
"license": "ISC",
"type": "commonjs",
"dependencies": {
"@aws-sdk/client-s3": "^3.965.0",
"@aws-sdk/client-secrets-manager": "^3.965.0",
"@aws-sdk/s3-request-presigner": "^3.965.0",
"@aws-sdk/client-s3": "^3.971.0",
"@aws-sdk/client-secrets-manager": "^3.971.0",
"@aws-sdk/s3-request-presigner": "^3.971.0",
"axios": "^1.13.2",
"drizzle-orm": "^0.44.5",
"form-data": "^4.0.1"
"form-data": "^4.0.5",
"lodash": "^4.17.21"
},
"devDependencies": {
"@eslint/js": "^9.39.2",
"@types/aws-lambda": "^8.10.159",
"@types/node": "^25.0.8",
"@typescript-eslint/eslint-plugin": "^8.53.0",
"@typescript-eslint/parser": "^8.53.0",
"@types/aws-lambda": "^8.10.160",
"@types/lodash": "^4.17.23",
"@types/node": "^25.0.9",
"@typescript-eslint/eslint-plugin": "^8.53.1",
"@typescript-eslint/parser": "^8.53.1",
"esbuild": "^0.27.2",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"prettier": "^3.7.4",
"eslint-plugin-prettier": "^5.5.5",
"prettier": "^3.8.0",
"serverless-esbuild": "^1.57.0",
"ts-node": "^10.9.2",
"typescript": "^5.9.3"

View File

@@ -2,43 +2,55 @@ service: esdp-api
app: esdp-api-app
frameworkVersion: '4'
# Common parameters shared across all stages
custom:
commonParams:
es_user: Imex2
es_password: Patrick
hasura_secret_arn: arn:aws:secretsmanager:ca-central-1:714144183158:secret:esdp-hasura-credentials-s81i1u-BDFgPi
stages:
prod:
# Enables observability in the prod stage
observability: true
# Stage-specific parameter values for prod
# Sepcify parameter values to be used in the prod stage
params:
es_endpoint: https://insurtechtoolkit.com
domain: es.imex.online
es_user: Imex2
es_password: Patrick
beta:
# Enables observability in the prod stage
observability: false
# Stage-specific parameter values for beta
# Sepcify parameter values to be used in the prod stage
params:
es_endpoint: https://4284-79073.el-alt.com
domain: beta.es.imex.online
es_user: Imex2
es_password: Patrick
alpha:
# Enables observability in the prod stage
observability: false
# Stage-specific parameter values for alpha
# Sepcify parameter values to be used in the prod stage
params:
es_endpoint: https://4284-79287.el-alt.com
domain: alpha.es.imex.online
es_user: Imex2
es_password: Patrick
dev:
# Enables observability in the prod stage
observability: false
# Stage-specific parameter values for dev
# Sepcify parameter values to be used in the prod stage
params:
es_endpoint: https://4284-79287.el-alt.com
domain: dev.es.imex.online
es_user: Imex2
es_password: Patrick
# params:
# dev:
# domain: dev.es.imex.online
# alpha:
# domain: alpha.es.imex.online
# beta:
# domain: beta.es.imex.online
# prod:
# domain: es.imex.online
provider:
name: aws
@@ -69,8 +81,8 @@ functions:
handler: src/handlers/scrub.handler
environment:
ES_ENDPOINT: ${param:es_endpoint}
ES_USER: ${self:custom.commonParams.es_user}
ES_PASSWORD: ${self:custom.commonParams.es_password}
ES_USER: ${param:es_user}
ES_PASSWORD: ${param:es_password}
events:
- httpApi:
path: /scrub

View File

@@ -1,31 +0,0 @@
import { AnyPgColumn, boolean, pgTable, text, timestamp, uuid, index } from 'drizzle-orm/pg-core';
export const shops = pgTable('shops', {
id: uuid('id').defaultRandom().primaryKey(),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
esApiKey: text('es_api_key').notNull().unique(),
active: boolean('active').notNull().default(true),
});
export const jobs = pgTable(
'jobs',
{
id: uuid('id').defaultRandom().primaryKey(),
shopId: uuid('shopId')
.references((): AnyPgColumn => shops.id)
.notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
clm_no: text('clm_no'),
ciecaid: text('ciecaid'),
},
(table) => [index('clm_no_idx').on(table.clm_no)]
);
export const joblines = pgTable('joblines', {
id: uuid('id').defaultRandom().primaryKey(),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
jobId: uuid('jobId')
.references((): AnyPgColumn => jobs.id)
.notNull(),
line_desc: text('line_desc'),
});

View File

@@ -1,22 +0,0 @@
import { APIGatewayProxyResult } from 'aws-lambda';
import { migrate } from 'drizzle-orm/node-postgres/migrator';
import { getDb } from '../lib/db';
export const handler = async (): Promise<APIGatewayProxyResult> => {
try {
const db = await getDb();
await migrate(db, { migrationsFolder: 'drizzle' });
return {
statusCode: 200,
body: JSON.stringify({ success: true, message: 'Migrations applied.' }),
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return {
statusCode: 500,
body: JSON.stringify({ success: false, error: errorMessage }),
};
}
};

View File

@@ -1,94 +0,0 @@
import { APIGatewayProxyResult } from 'aws-lambda';
import { sql } from 'drizzle-orm';
import { getDb } from '../lib/db';
export const handler = async (): Promise<APIGatewayProxyResult> => {
try {
const db = await getDb();
const ping = await db.execute(sql`select 1 as ok`);
const schemaRows = await db.execute(sql`
select
table_schema,
table_name,
column_name,
data_type,
is_nullable,
ordinal_position
from information_schema.columns
where table_schema not in ('pg_catalog', 'information_schema')
order by table_schema, table_name, ordinal_position
`);
type ColumnInfo = {
name: string;
type: string;
nullable: boolean;
position: number;
};
type TableInfo = {
schema: string;
name: string;
columns: ColumnInfo[];
};
const tableMap = new Map<string, TableInfo>();
const rows = (schemaRows as unknown as { rows?: unknown[] })?.rows ?? [];
for (const row of rows as Array<Record<string, unknown>>) {
const tableSchema = String(row.table_schema ?? '');
const tableName = String(row.table_name ?? '');
if (!tableSchema || !tableName) continue;
const key = `${tableSchema}.${tableName}`;
const existing = tableMap.get(key);
const tableInfo: TableInfo = existing ?? {
schema: tableSchema,
name: tableName,
columns: [],
};
tableInfo.columns.push({
name: String(row.column_name ?? ''),
type: String(row.data_type ?? ''),
nullable: String(row.is_nullable ?? '') === 'YES',
position: Number(row.ordinal_position ?? 0),
});
if (!existing) tableMap.set(key, tableInfo);
}
const tables = [...tableMap.values()];
return {
statusCode: 200,
body: JSON.stringify({ success: true, ping, schema: { tables } }),
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
const maybeAny = error as unknown as { cause?: unknown; code?: unknown };
const causeMessage =
maybeAny?.cause instanceof Error
? maybeAny.cause.message
: typeof maybeAny?.cause === 'string'
? maybeAny.cause
: undefined;
const causeCode =
typeof (maybeAny?.cause as { code?: unknown } | undefined)?.code === 'string'
? (maybeAny.cause as { code?: string }).code
: typeof maybeAny?.code === 'string'
? maybeAny.code
: undefined;
return {
statusCode: 500,
body: JSON.stringify({
success: false,
error: errorMessage,
cause: causeMessage,
code: causeCode,
}),
};
}
};

View File

@@ -1,23 +1,18 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import axios, { AxiosError } from 'axios';
import FormData from 'form-data';
import { ESJobObject, RawJobDataObject } from '../../../shared/types';
import { transformJobForEstimateScrubber } from '../lib/transformEstimate';
import { getVehicleType } from '../lib/vehicleTypes/vehicleType';
const ES_USER = process.env.ES_USER || '';
const ES_PASSWORD = process.env.ES_PASSWORD || '';
const ES_ENDPOINT = process.env.ES_ENDPOINT || '';
interface Estimate {
v_type?: string;
v_model: string;
clm_no: string;
sendingEntityId?: string;
[key: string]: unknown;
}
interface ScrubRequest {
esApiKey: string;
estimate: Estimate;
rawJob: RawJobDataObject;
}
interface ScrubResponse {
@@ -27,13 +22,16 @@ interface ScrubResponse {
export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
try {
const { esApiKey, estimate } = JSON.parse(event.body || '{}') as ScrubRequest;
const { esApiKey, rawJob } = JSON.parse(event.body || '{}') as ScrubRequest;
// Transform the raw job object to ES format
const estimate: ESJobObject = await transformJobForEstimateScrubber(rawJob);
// Set vehicle type and sending entity ID
estimate.v_type = getVehicleType(estimate.v_model).type;
estimate.sendingEntityId = '87330f61-412b-4251-baaa-d026565b23c5';
estimate.v_type = getVehicleType(estimate.v_model || '').type;
estimate.sending_entity_id = '87330f61-412b-4251-baaa-d026565b23c5';
const fileName = `${esApiKey}-${estimate.clm_no}-${Date.now()}`;
const fileName = `${esApiKey}-${rawJob.clm_no}-${Date.now()}`;
const formData = new FormData();
const jsonString = JSON.stringify(estimate);

View File

@@ -1,78 +0,0 @@
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
import { drizzle, NodePgDatabase } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';
import * as schema from '../db/schema';
type DbSecret = {
username: string;
password: string;
};
let cachedSecret: DbSecret | undefined;
let cachedPool: Pool | undefined;
let cachedDb: NodePgDatabase<typeof schema> | undefined;
function requireEnv(name: string): string {
const value = process.env[name];
if (!value) {
throw new Error(`Missing required env var: ${name}`);
}
return value;
}
async function getDbSecret(): Promise<DbSecret> {
if (cachedSecret) return cachedSecret;
const secretArn = requireEnv('DB_SECRET_ARN');
const client = new SecretsManagerClient({});
const result = await client.send(
new GetSecretValueCommand({
SecretId: secretArn,
})
);
if (!result.SecretString) {
throw new Error('SecretString was empty for DB_SECRET_ARN');
}
const parsed = JSON.parse(result.SecretString) as Partial<DbSecret>;
if (!parsed.username || !parsed.password) {
throw new Error('DB secret missing username/password');
}
cachedSecret = { username: parsed.username, password: parsed.password };
return cachedSecret;
}
export async function getPool(): Promise<Pool> {
if (cachedPool) return cachedPool;
const host = requireEnv('DB_HOST');
const port = Number.parseInt(requireEnv('DB_PORT'), 10);
const database = requireEnv('DB_NAME');
const { username: user, password } = await getDbSecret();
cachedPool = new Pool({
host,
port,
database,
user,
password,
ssl: {
rejectUnauthorized: true,
},
max: 5,
idleTimeoutMillis: 30_000,
connectionTimeoutMillis: 10_000,
});
return cachedPool;
}
export async function getDb(): Promise<NodePgDatabase<typeof schema>> {
if (cachedDb) return cachedDb;
const pool = await getPool();
cachedDb = drizzle(pool, { schema });
return cachedDb;
}

View File

@@ -0,0 +1,239 @@
import _ from 'lodash';
import { RawJobDataObject, ESJobObject } from '../../../shared/types';
export async function transformJobForEstimateScrubber(job: RawJobDataObject): Promise<ESJobObject> {
// Take the job object and strip off everything we don't need.
const omittedJob = _.omit(job, [
'cat_no',
'ciecaid',
'agt_co_id',
'agt_co_nm',
'agt_addr1',
'agt_addr2',
'agt_city',
'agt_st',
'agt_zip',
'agt_ctry',
'agt_ph1',
'agt_ph1x',
'agt_ph2',
'agt_ph2x',
'agt_fax',
'agt_faxx',
'agt_ct_ln',
'agt_ct_fn',
'agt_ct_ph',
'agt_ct_phx',
'agt_ea',
'agt_lic_no',
'adj_g_disc',
'adj_strdis',
'adj_towdis',
'asgn_date',
'asgn_no',
'asgn_type',
'clm_addr1',
'clm_addr2',
'clm_city',
'clm_ct_fn',
'clm_ct_ln',
'clm_ct_ph',
'clm_ct_phx',
'clm_ctry',
'clm_ea',
'clm_fax',
'clm_faxx',
'clm_ofc_id',
'clm_ofc_nm',
'clm_ph1',
'clm_ph1x',
'clm_ph2',
'clm_ph2x',
'clm_st',
'clm_title',
'clm_zip',
'cust_pr',
'date_estimated',
'ded_status',
'depreciation_taxes',
'est_addr1',
'est_addr2',
'est_city',
'est_co_nm',
'est_ct_fn',
'est_ct_ln',
'est_ctry',
'est_ea',
'est_ph1',
'est_st',
'est_zip',
'federal_tax_rate',
'ins_addr1',
'ins_addr2',
'ins_city',
'ins_co_id',
'ins_ct_fn',
'ins_ct_ln',
'ins_ct_ph',
'ins_ct_phx',
'ins_ctry',
'ins_ea',
'ins_fax',
'ins_faxx',
'ins_ph1',
'ins_ph1x',
'ins_ph2',
'ins_ph2x',
'ins_st',
'ins_title',
'ins_zip',
'insd_fax',
'insd_faxx',
'kmin',
'loss_cat',
'loss_type',
'ownr_addr2',
'ownr_co_nm',
'ownr_ctry',
'ownr_ea',
'ownr_ph2',
'ownr_st',
'ownr_title',
'ownr_zip',
'pay_amt',
'pay_chknm',
'pay_date',
'pay_type',
'payee_nms',
'plate_no',
'plate_st',
'policy_no',
'rate_la1',
'rate_la2',
'rate_la3',
'rate_la4',
'rate_laa',
'rate_lab',
'rate_lad',
'rate_lae',
'rate_laf',
'rate_lag',
'rate_lam',
'rate_lar',
'rate_las',
'rate_lau',
'rate_ma2s',
'rate_ma2t',
'rate_ma3s',
'rate_mabl',
'rate_macs',
'rate_mahw',
'rate_mapa',
'rate_mash',
'tax_lbr_rt',
'tax_levies_rt',
'tax_paint_mat_rt',
'tax_predis',
'tax_prethr',
'tax_pstthr',
'tax_shop_mat_rt',
'tax_str_rt',
'tax_sub_rt',
'tax_thramt',
'tax_tow_rt',
'theft_ind',
'v_color',
'tlos_ind',
'v_make_desc',
'v_model_desc',
'shopid',
'est_system',
// Object fields
'owner',
'vehicle',
'bodyshop',
'area_of_damage',
'joblines',
'clm_total',
// CIECA Fields
'cieca_pft',
'cieca_pfl',
'cieca_pfm',
'cieca_pfo',
'cieca_stl',
'cieca_ttl',
'parts_tax_rates',
'materials',
]);
// Apply the ES specific transformations needed.
const rates = [
...(job.cieca_pfm?.map((pfm) => ({
cal_prethr: pfm.cal_prethr,
mat_type: pfm.matl_type, // Rename required - presumed typo in ES API.
})) || []),
...(Object.keys(job.cieca_pfl || {}).map((key) =>
_.pick(job.cieca_pfl![key], ['lbr_desc', 'lbr_rate', 'lbr_type'])
) || []),
];
const supp_amt = job.cieca_ttl?.data?.supp_amt || 0;
const totals =
job.cieca_stl?.data?.map((ttl) =>
_.pick(ttl, ['nt_hrs', 't_amt', 't_hrs', 'ttl_typecd'])
) || [];
const now = new Date().toISOString();
return {
...omittedJob,
impact_1: job.area_of_damage?.impact1,
impact_2: job.area_of_damage?.impact2,
close_date: null,
created_at: now,
id: '00000000-0000-0000-0000-000000000000', // Placeholder ID
updated_at: now,
v_age: -1, // Needed? RPS calc.
v_type: 'TBD', // Will be set by caller
v_makedesc: job.v_make_desc,
v_model: job.v_model_desc,
rates,
supp_amt,
totals,
ro_number: null,
requires_reimport: false,
v_mileage: job.kmin?.toString() || '',
sending_entity_id: '',
sending_entity_accept_terms_of_use: true,
association_switch: 'ATAM',
rf_ph1: '2043792253',
rf_zip: 'R0G 1Z0',
g_ttl_amt: job.clm_total || 0,
source_system: job.est_system || 'M',
joblines:
job.joblines?.data?.map((line) => ({
..._.omit(line, [
'lbr_tax',
'lbr_typ_j',
'line_ref',
'misc_sublt',
'misc_tax',
'prt_dsmk_m',
'prt_dsmk_p',
'tran_code',
'unq_seq',
'alt_co_id',
'alt_overrd',
'alt_part_i',
'alt_partm',
'bett_type',
'bett_pctg',
'bett_amt',
'bett_tax',
'op_code_desc',
'paint_stg',
'paint_tone',
]),
})) || [],
} as ESJobObject;
}

View File

@@ -1,24 +1,24 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"lib": ["ES2022"],
"moduleResolution": "node",
"rootDir": "./src",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", ".serverless"]
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"lib": ["ES2022"],
"moduleResolution": "node",
"rootDir": "./src",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*", "../shared/**/*"],
"exclude": ["node_modules", "dist", ".serverless"]
}

View File

@@ -0,0 +1,243 @@
import { JobLine, RawJobDataObject } from "./raw-job-data.interface";
// ES Job Object with transformed fields
export interface ESJobObject extends Omit<
RawJobDataObject,
// Agent fields
| "cat_no"
| "ciecaid"
| "agt_co_id"
| "agt_co_nm"
| "agt_addr1"
| "agt_addr2"
| "agt_city"
| "agt_st"
| "agt_zip"
| "agt_ctry"
| "agt_ph1"
| "agt_ph1x"
| "agt_ph2"
| "agt_ph2x"
| "agt_fax"
| "agt_faxx"
| "agt_ct_ln"
| "agt_ct_fn"
| "agt_ct_ph"
| "agt_ct_phx"
| "agt_ea"
| "agt_lic_no"
// Adjustment fields
| "adj_g_disc"
| "adj_strdis"
| "adj_towdis"
// Assignment fields
| "asgn_date"
| "asgn_no"
| "asgn_type"
// Claim fields
| "clm_addr1"
| "clm_addr2"
| "clm_city"
| "clm_ct_fn"
| "clm_ct_ln"
| "clm_ct_ph"
| "clm_ct_phx"
| "clm_ctry"
| "clm_ea"
| "clm_fax"
| "clm_faxx"
| "clm_ofc_id"
| "clm_ofc_nm"
| "clm_ph1"
| "clm_ph1x"
| "clm_ph2"
| "clm_ph2x"
| "clm_st"
| "clm_title"
| "clm_zip"
| "clm_total"
// Misc fields
| "cust_pr"
| "date_estimated"
| "ded_status"
| "depreciation_taxes"
// Estimator fields
| "est_addr1"
| "est_addr2"
| "est_city"
| "est_co_nm"
| "est_ct_fn"
| "est_ct_ln"
| "est_ctry"
| "est_ea"
| "est_ph1"
| "est_st"
| "est_zip"
| "federal_tax_rate"
// Insurance fields
| "ins_addr1"
| "ins_addr2"
| "ins_city"
| "ins_co_id"
| "ins_ct_fn"
| "ins_ct_ln"
| "ins_ct_ph"
| "ins_ct_phx"
| "ins_ctry"
| "ins_ea"
| "ins_fax"
| "ins_faxx"
| "ins_ph1"
| "ins_ph1x"
| "ins_ph2"
| "ins_ph2x"
| "ins_st"
| "ins_title"
| "ins_zip"
| "insd_fax"
| "insd_faxx"
// Loss fields
| "kmin"
| "loss_cat"
| "loss_type"
// Owner fields
| "ownr_addr2"
| "ownr_co_nm"
| "ownr_ctry"
| "ownr_ea"
| "ownr_ph2"
| "ownr_st"
| "ownr_title"
| "ownr_zip"
// Payment fields
| "pay_amt"
| "pay_chknm"
| "pay_date"
| "pay_type"
| "payee_nms"
// Vehicle fields
| "plate_no"
| "plate_st"
| "policy_no"
// Rate fields
| "rate_la1"
| "rate_la2"
| "rate_la3"
| "rate_la4"
| "rate_laa"
| "rate_lab"
| "rate_lad"
| "rate_lae"
| "rate_laf"
| "rate_lag"
| "rate_lam"
| "rate_lar"
| "rate_las"
| "rate_lau"
| "rate_ma2s"
| "rate_ma2t"
| "rate_ma3s"
| "rate_mabl"
| "rate_macs"
| "rate_mahw"
| "rate_mapa"
| "rate_mash"
// Tax fields
| "tax_lbr_rt"
| "tax_levies_rt"
| "tax_paint_mat_rt"
| "tax_predis"
| "tax_prethr"
| "tax_pstthr"
| "tax_shop_mat_rt"
| "tax_str_rt"
| "tax_sub_rt"
| "tax_thramt"
| "tax_tow_rt"
// Additional vehicle fields
| "theft_ind"
| "v_color"
| "tlos_ind"
| "v_make_desc"
| "v_model_desc"
| "shopid"
| "est_system"
// Object fields
| "owner"
| "vehicle"
| "bodyshop"
| "area_of_damage"
| "joblines"
// CIECA fields
| "cieca_pft"
| "cieca_pfl"
| "cieca_pfm"
| "cieca_pfo"
| "cieca_stl"
| "cieca_ttl"
| "parts_tax_rates"
| "materials"
> {
// Fields added by the transformer
impact_1?: string;
impact_2?: string;
close_date: string | null;
created_at: string;
id: string;
group?: string;
group_verified?: boolean;
updated_at: string;
v_age: number;
v_type: string;
v_makedesc?: string;
v_model?: string;
supp_amt: number;
ro_number: string | null;
requires_reimport: boolean;
v_mileage: string;
id_pro_nam?: string;
g_ttl_amt: number;
source_system: string;
rf_ph1: string;
rf_zip: string;
association_switch: string;
sending_entity_id: string;
sending_entity_accept_terms_of_use: boolean;
// Transformed arrays
joblines: Omit<
JobLine,
| "lbr_tax"
| "lbr_typ_j"
| "line_ref"
| "misc_sublt"
| "misc_tax"
| "prt_dsmk_m"
| "prt_dsmk_p"
| "tran_code"
| "unq_seq"
| "alt_co_id"
| "alt_overrd"
| "alt_part_i"
| "alt_partm"
| "bett_type"
| "bett_pctg"
| "bett_amt"
| "bett_tax"
| "op_code_desc"
| "paint_stg"
| "paint_tone"
>[];
totals: Array<{
nt_hrs?: number;
t_amt?: number;
t_hrs?: number;
ttl_typecd?: string;
}>;
rates: Array<
| { cal_prethr?: number; mat_type?: string }
| { lbr_desc?: string; lbr_rate?: number; lbr_type?: string }
>;
}

2
shared/types/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export * from "./raw-job-data.interface";
export * from "./es-job-object.interface";

View File

@@ -0,0 +1,312 @@
import { UUID } from "crypto";
// Re-export interfaces needed for RawJobDataObject
// Note: The decoder interfaces would need to be moved to shared as well
// or we define the minimal structure here
export interface RawJobDataObject {
// From DecodedEnv
insp_date?: string;
loss_date?: string;
rf_city?: string;
rf_zip?: string;
rf_st?: string;
rf_ln?: string;
rf_fn?: string;
rf_ph1?: string;
rf_ph2?: string;
rf_addr1?: string;
rf_ctry?: string;
ins_co_nm?: string;
clm_no?: string;
est_system?: string;
// From other decoders
cat_no?: string;
ciecaid?: string;
agt_co_id?: string;
agt_co_nm?: string;
agt_addr1?: string;
agt_addr2?: string;
agt_city?: string;
agt_st?: string;
agt_zip?: string;
agt_ctry?: string;
agt_ph1?: string;
agt_ph1x?: string;
agt_ph2?: string;
agt_ph2x?: string;
agt_fax?: string;
agt_faxx?: string;
agt_ct_ln?: string;
agt_ct_fn?: string;
agt_ct_ph?: string;
agt_ct_phx?: string;
agt_ea?: string;
agt_lic_no?: string;
adj_g_disc?: number;
adj_strdis?: number;
adj_towdis?: number;
asgn_date?: string;
asgn_no?: string;
asgn_type?: string;
clm_addr1?: string;
clm_addr2?: string;
clm_city?: string;
clm_ct_fn?: string;
clm_ct_ln?: string;
clm_ct_ph?: string;
clm_ct_phx?: string;
clm_ctry?: string;
clm_ea?: string;
clm_fax?: string;
clm_faxx?: string;
clm_ofc_id?: string;
clm_ofc_nm?: string;
clm_ph1?: string;
clm_ph1x?: string;
clm_ph2?: string;
clm_ph2x?: string;
clm_st?: string;
clm_title?: string;
clm_zip?: string;
cust_pr?: string;
date_estimated?: string;
ded_status?: string;
depreciation_taxes?: number;
est_addr1?: string;
est_addr2?: string;
est_city?: string;
est_co_nm?: string;
est_ct_fn?: string;
est_ct_ln?: string;
est_ctry?: string;
est_ea?: string;
est_ph1?: string;
est_st?: string;
est_zip?: string;
federal_tax_rate?: number;
ins_addr1?: string;
ins_addr2?: string;
ins_city?: string;
ins_co_id?: string;
ins_ct_fn?: string;
ins_ct_ln?: string;
ins_ct_ph?: string;
ins_ct_phx?: string;
ins_ctry?: string;
ins_ea?: string;
ins_fax?: string;
ins_faxx?: string;
ins_ph1?: string;
ins_ph1x?: string;
ins_ph2?: string;
ins_ph2x?: string;
ins_st?: string;
ins_title?: string;
ins_zip?: string;
insd_fax?: string;
insd_faxx?: string;
kmin?: number;
loss_cat?: string;
loss_type?: string;
ownr_addr1?: string;
ownr_addr2?: string;
ownr_co_nm?: string;
ownr_ctry?: string;
ownr_ea?: string;
ownr_ph1?: string;
ownr_ph2?: string;
ownr_st?: string;
ownr_title?: string;
ownr_zip?: string;
ownr_ln?: string;
ownr_fn?: string;
ownr_city?: string;
pay_amt?: number;
pay_chknm?: string;
pay_date?: string;
pay_type?: string;
payee_nms?: string;
plate_no?: string;
plate_st?: string;
policy_no?: string;
rate_la1?: number;
rate_la2?: number;
rate_la3?: number;
rate_la4?: number;
rate_laa?: number;
rate_lab?: number;
rate_lad?: number;
rate_lae?: number;
rate_laf?: number;
rate_lag?: number;
rate_lam?: number;
rate_lar?: number;
rate_las?: number;
rate_lau?: number;
rate_ma2s?: number;
rate_ma2t?: number;
rate_ma3s?: number;
rate_mabl?: number;
rate_macs?: number;
rate_mahw?: number;
rate_mapa?: number;
rate_mash?: number;
tax_lbr_rt?: number;
tax_levies_rt?: number;
tax_paint_mat_rt?: number;
tax_predis?: number;
tax_prethr?: number;
tax_pstthr?: number;
tax_shop_mat_rt?: number;
tax_str_rt?: number;
tax_sub_rt?: number;
tax_thramt?: number;
tax_tow_rt?: number;
theft_ind?: boolean;
v_color?: string;
tlos_ind?: boolean;
v_make_desc?: string;
v_model_desc?: string;
v_year?: string;
// Claimant fields
clmt_ln?: string;
clmt_fn?: string;
clmt_title?: string;
clmt_co_nm?: string;
clmt_addr1?: string;
clmt_addr2?: string;
clmt_city?: string;
clmt_st?: string;
clmt_zip?: string;
clmt_ctry?: string;
clmt_ph1?: string;
clmt_ph2?: string;
clmt_ea?: string;
// Insured fields
insd_ln?: string;
insd_fn?: string;
insd_title?: string;
insd_co_nm?: string;
insd_addr1?: string;
insd_addr2?: string;
insd_city?: string;
insd_st?: string;
insd_zip?: string;
insd_ctry?: string;
insd_ph1?: string;
insd_ph2?: string;
insd_ea?: string;
// Object fields
owner?: {
data?: Record<string, unknown>;
};
vehicle?: Record<string, unknown>;
bodyshop?: Record<string, unknown>;
area_of_damage?: {
impact1?: string;
impact2?: string;
};
joblines?: {
data?: JobLine[];
};
clm_total?: number;
// CIECA fields
cieca_pft?: CiecaPft;
cieca_pfl?: Record<string, CiecaPfl>;
cieca_pfm?: CiecaPfm[];
cieca_pfo?: CiecaPfo;
cieca_stl?: CiecaStl;
cieca_ttl?: CiecaTtl;
parts_tax_rates?: Record<string, unknown>;
materials?: Record<string, unknown>;
vehicleid?: UUID;
shopid?: UUID;
}
export interface JobLine {
line_no?: string;
line_ind?: string;
line_ref?: string;
tran_code?: string;
db_ref?: string;
unq_seq?: string;
line_desc?: string;
part_type?: string;
glass_flag?: boolean;
oem_partno?: string;
price_inc?: boolean;
alt_part_i?: boolean;
tax_part?: boolean;
db_price?: number;
act_price?: number;
price_j?: boolean;
cert_part?: boolean;
part_qty?: number;
alt_co_id?: string;
alt_partno?: string;
alt_overrd?: boolean;
alt_partm?: string;
prt_dsmk_p?: string;
prt_dsmk_m?: string;
mod_lbr_ty?: string;
db_hrs?: number;
mod_lb_hrs?: number;
lbr_inc?: boolean;
lbr_op?: string;
lbr_hrs_j?: boolean;
lbr_typ_j?: boolean;
lbr_op_j?: boolean;
paint_stg?: string;
paint_tone?: string;
lbr_tax?: boolean;
lbr_amt?: number;
misc_amt?: number;
misc_sublt?: string;
misc_tax?: boolean;
bett_type?: string;
bett_pctg?: string | number;
bett_amt?: number;
bett_tax?: boolean;
op_code_desc?: string;
}
export interface CiecaPft {
data?: Record<string, unknown>;
}
export interface CiecaPfl {
lbr_desc?: string;
lbr_rate?: number;
lbr_type?: string;
}
export interface CiecaPfm {
cal_prethr?: number;
matl_type?: string;
}
export interface CiecaPfo {
data?: Record<string, unknown>;
}
export interface CiecaStl {
data?: Array<{
nt_hrs?: number;
t_amt?: number;
t_hrs?: number;
ttl_typecd?: string;
}>;
}
export interface CiecaTtl {
data?: {
supp_amt?: number;
};
}

View File

@@ -6,7 +6,6 @@ import { promises as fsPromises } from "fs";
import path from "path";
import { RawJobDataObject } from "../decoder/decoder";
import store from "../store/store";
import TransformJobForEstimateScrubber from "./es-transformer";
// Function to write job object to logs subfolder
async function writeJobToLogsFolder(job, fileName): Promise<string> {
@@ -37,13 +36,13 @@ async function writeJobToLogsFolder(job, fileName): Promise<string> {
throw error;
}
}
async function ScrubEstimate({
job,
}: {
job: RawJobDataObject;
}): Promise<string | undefined> {
const transformedJob = await TransformJobForEstimateScrubber(job); //Job should be transformed server side.
// No transformation here - send raw job to Lambda
const currentChannel = autoUpdater.channel;
let estimateScrubberUrl: string;
switch (currentChannel) {
@@ -67,29 +66,30 @@ async function ScrubEstimate({
try {
const esApiKey = store.get("settings.esApiKey") as string;
//Perform data manipulation on the job object
if (!transformedJob) {
if (!job) {
console.error("No job provided to ScrubEstimate");
return;
}
const fileName = `${esApiKey}-${transformedJob.clm_no}-${Date.now()}`;
const fileName = `${esApiKey}-${job.clm_no}-${Date.now()}`;
// Write job object to logs subfolder
try {
await writeJobToLogsFolder(transformedJob, fileName);
await writeJobToLogsFolder(job, fileName);
} catch (error) {
log.error("Failed to write job to logs folder:", error);
// Continue with the rest of the function even if this fails
}
// Send raw job to Lambda - transformation happens server-side
const result = await axios.post(estimateScrubberUrl, {
esApiKey,
estimate: transformedJob,
fileName,
rawJob: job, // Changed from 'estimate' to 'rawJob'
});
const { resultPDFUrl, reportIssueUrl } = result?.data ?? {};
const { resultPDFUrl, reportIssueUrl, identified_item } =
result?.data ?? {};
// log.log("Estimate Scrubber Result:", result.data, resultPDFUrl);
// const b = BrowserWindow.getAllWindows()[0];

View File

@@ -7,7 +7,8 @@
"src/util/**/*",
"src/interfaces/**/*",
"tests/index.spec.ts",
"/resources/**/*"
"/resources/**/*",
"shared/**/*"
],
"compilerOptions": {
"resolveJsonModule": true,