@fragus/sam-types 1.1.7 → 1.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.1.7",
2
+ "version": "1.1.9",
3
3
  "name": "@fragus/sam-types",
4
4
  "description": "Typescript interfaces for all types used to communicate between SAM client(s) and the SAM API",
5
5
  "author": "Fragus",
@@ -1,5 +1,8 @@
1
1
  import { AccessKeyPermission, AccessKeyService, TIsoCountry } from '../index'
2
2
 
3
+ /** How an access key chooses its providers: country-scoped, manually scoped, or group-scoped. */
4
+ export type TAccessKeyScopeMode = 'all' | 'manual' | 'group'
5
+
3
6
  export interface IAccessKey {
4
7
  accessKeyId: number
5
8
  apiKey: string
@@ -10,5 +13,7 @@ export interface IAccessKey {
10
13
  contractProviderIds: number[]
11
14
  allProvidersAllowed: boolean
12
15
  isoCountries: TIsoCountry[] // scope for allProvidersAllowed; non-empty when it is true
16
+ scopeMode: TAccessKeyScopeMode
17
+ contractProviderGroupId: number | null // set only in 'group' mode; resolved live at request time
13
18
  description?: string
14
19
  }
@@ -150,23 +150,6 @@ interface ICommonContractCreationRequest extends ICommonContractUpdateRequest {
150
150
  invoiceCustomerId?: number
151
151
  invoiceCustomer?: IAdminCustomer
152
152
  isDownpaymentDistributed: boolean
153
- /**
154
- * What the seller agreed one service would cost, sent only where they put it on the agreement:
155
- * the template proposes a basis, they adjust it, and an unticked one is not an agreement.
156
- * Money is ex VAT, in the contract's currency.
157
- */
158
- serviceCostOilPricePerLitre?: number | null
159
- serviceCostOilLitresPerService?: number | null
160
- serviceCostLabourRatePerHour?: number | null
161
- serviceCostLabourHoursPerService?: number | null
162
- /** How many services the seller expects over the term; what the cost per service is multiplied by. */
163
- serviceCostNumberOfServices?: number | null
164
- /**
165
- * Replacing the HV traction battery, where the template covers it and the vehicle is electric.
166
- * Once
167
- * over the term rather than once per service: a battery is not a service item.
168
- */
169
- serviceCostBattery?: number | null
170
153
  }
171
154
 
172
155
  export interface IContractAdjustmentRequest extends ICommonContractUpdateRequest {
@@ -80,19 +80,4 @@ export interface IContractCalculationResponse {
80
80
  providerShare: PriceSpecification
81
81
  x_debugData?: any // For debugging purposes.
82
82
  serviceInfo?: string[] // Service information, mainly for ax.
83
- /**
84
- * Present only when the price came from a price prediction template. Caveats are codes with
85
- * parameters rather than sentences, so the client renders them in the reader's own language;
86
- * a code the client does not know can be dropped safely, which lets the API add more freely.
87
- */
88
- pricePrediction?: {
89
- /**
90
- * Which source answered. `AI` means nothing comparable has ever been sold on this kind of
91
- * vehicle, so the figure is reasoned from the vehicle itself rather than from what we charged.
92
- */
93
- source?: 'Comparables' | 'AI'
94
- confidence: 'high' | 'medium' | 'low'
95
- comparableCount?: number
96
- caveats?: Array<{ code: string; params?: { [key: string]: string | number } }>
97
- }
98
83
  }
@@ -6,6 +6,10 @@ export interface IBlacklistedVehicleRequest {
6
6
  add: number[]
7
7
  remove: number[]
8
8
  }
9
+ contractProviderGroupIds?: {
10
+ add: number[]
11
+ remove: number[]
12
+ }
9
13
  }
10
14
 
11
15
  export interface IBlacklistedVehicleResponse {
@@ -14,11 +18,21 @@ export interface IBlacklistedVehicleResponse {
14
18
  model: string
15
19
  fuelType: string
16
20
  parentRecord?: boolean
21
+ groupRecord?: boolean
17
22
  }
18
23
 
19
24
  export interface IBlacklistedVehicleListRecord extends IBlacklistedVehicleResponse {
20
- providerId: number
21
- providerName: string
25
+ // Null on a group-owned entry: the two owners are exclusive.
26
+ providerId: number | null
27
+ providerName: string | null
28
+ contractProviderGroupId: number | null
29
+ contractProviderGroupName: string | null
30
+ }
31
+
32
+ /** Which providers and groups a blacklisted vehicle is registered for. */
33
+ export interface IBlacklistedVehicleScope {
34
+ providerIds: number[]
35
+ contractProviderGroupIds: number[]
22
36
  }
23
37
 
24
38
  export type BlacklistedVehicleOrderByType = 'id' | 'brand' | 'model' | 'fuelType'
@@ -1,5 +1,7 @@
1
1
  import { IContractHealth } from './../admin/contract/health'
2
+ import { TContractObject } from './../admin/product'
2
3
  import { ContractState } from './../index'
4
+ import { IPaginatedQueryParams, IPaginatedResponse } from './../queryParams'
3
5
  import { IContractFilterDate, IContractFilterOption } from './contractFilter'
4
6
 
5
7
  export interface IContractListRecord {
@@ -51,3 +53,40 @@ export interface IWarrantyListRecord {
51
53
  registrationNumber: string
52
54
  sellerName: string
53
55
  }
56
+
57
+ /** A contract-list row from the superadmin cross-provider search, tagged with its owning provider. */
58
+ export interface IContractCrossProviderSearchRecord extends IContractListRecord {
59
+ contractProviderId: number
60
+ contractProviderName: string
61
+ }
62
+
63
+ export interface IContractCrossProviderSearchResponse {
64
+ result: IContractCrossProviderSearchRecord[]
65
+ }
66
+
67
+ export interface IContractCrossProviderSearchParams {
68
+ search: string
69
+ limit?: number
70
+ includeInactiveProviders?: 'yes'
71
+ }
72
+
73
+ /**
74
+ * The provider-agnostic contract-list filters. Sellers, templates, options and sub dealers are
75
+ * vocabularies of a single provider and have no cross-provider meaning.
76
+ */
77
+ export interface IContractCrossProviderFilterParams {
78
+ contractStates?: ContractState[]
79
+ created?: IContractFilterDate
80
+ expired?: IContractFilterDate
81
+ activeTypes?: TContractObject[]
82
+ }
83
+
84
+ export interface IContractCrossProviderListParams
85
+ extends IPaginatedQueryParams<ContractListOrderByType, IContractCrossProviderFilterParams> {
86
+ includeInactiveProviders?: 'yes'
87
+ }
88
+
89
+ export type IContractCrossProviderListResponse = IPaginatedResponse<
90
+ IContractCrossProviderSearchRecord[],
91
+ ContractListOrderByType
92
+ >
@@ -42,6 +42,8 @@ export interface IContractProviderRequest extends IProviderCustomizationResponse
42
42
  fetchWarrantiesFromV4: boolean
43
43
  hiddenNetPrice: boolean
44
44
  v4ProviderId: number | null
45
+ /** Groups the provider joins on create; an update ignores it. */
46
+ contractProviderGroupIds?: number[]
45
47
  }
46
48
 
47
49
  export interface IContractProviderResponse extends IProviderCustomizationResponse {
@@ -1,13 +1,17 @@
1
1
  export type TContractProviderFlagKey = 'RENAULT_UNIQ_PDF_FORMAT'
2
2
 
3
+ /** Exactly one of the two ids is sent: a flag row is owned by a provider or by a group. */
3
4
  export interface ContractProviderFlagRequest {
4
- contractProviderId: number
5
+ contractProviderId?: number
6
+ contractProviderGroupId?: number
5
7
  }
6
8
 
7
9
  export interface ContractProviderFlagResponse {
8
10
  id: number
9
11
  featureKey: TContractProviderFlagKey
10
- contractProviderId: number
11
- contractProviderName: string
12
+ contractProviderId: number | null
13
+ contractProviderName: string | null
14
+ contractProviderGroupId: number | null
15
+ contractProviderGroupName: string | null
12
16
  updatedByUser: string
13
17
  }
@@ -0,0 +1,58 @@
1
+ import { ProviderState } from './contractProvider'
2
+ import { EmailCampaignStatus } from './emailCampaign'
3
+
4
+ export interface IContractProviderGroupMember {
5
+ contractProviderId: number
6
+ administrativeName: string
7
+ isoName: string // country
8
+ state: ProviderState // lets every picker count Inactive members
9
+ }
10
+
11
+ export interface IProviderGroupTag {
12
+ providerGroupTagId: number
13
+ name: string
14
+ }
15
+
16
+ export interface IContractProviderGroupListItem {
17
+ contractProviderGroupId: number
18
+ name: string
19
+ description: string | null
20
+ tags: IProviderGroupTag[]
21
+ memberCount: number
22
+ inactiveMemberCount: number
23
+ archivedAt: string | null // ISO
24
+ updatedByEmail: string // 'USER NOT FOUND' when the acting user is gone
25
+ }
26
+
27
+ export type IContractProviderGroupDetail = IContractProviderGroupListItem & {
28
+ members: IContractProviderGroupMember[]
29
+ createdAt: string // ISO
30
+ updatedAt: string // ISO
31
+ }
32
+
33
+ export interface IContractProviderGroupUsage {
34
+ accessKeys: { accessKeyId: number; description: string | null }[]
35
+ emailCampaigns: { id: number; name: string; status: EmailCampaignStatus }[]
36
+ blacklistEntryCount: number
37
+ documentCount: number
38
+ dashboardBannerCount: number
39
+ featureFlagCount: number
40
+ }
41
+
42
+ export interface IUpsertContractProviderGroupRequest {
43
+ name: string
44
+ description: string | null
45
+ tags: string[]
46
+ contractProviderIds: number[]
47
+ }
48
+
49
+ // The provider-side view of membership: every joinable group, plus any archived group this provider is
50
+ // still in (archived groups can be left but not joined).
51
+ export interface IContractProviderGroupMembership {
52
+ groups: IContractProviderGroupListItem[]
53
+ memberGroupIds: number[]
54
+ }
55
+
56
+ export interface ISetContractProviderGroupsRequest {
57
+ contractProviderGroupIds: number[] // the full set for one provider; the api diffs it against what is stored
58
+ }
@@ -4,7 +4,7 @@ import { PriceSpecification } from './priceSpecification'
4
4
  import { ITermsOfServiceRequest, ITermsOfServiceResponse } from './termsOfService'
5
5
  import { TV4PTPaymentType, TV4PTProductType } from './v4PricingTool'
6
6
 
7
- export type PriceSource = 'Pricelist' | 'Autoexperten' | 'V4PricingTool' | 'PricePrediction'
7
+ export type PriceSource = 'Pricelist' | 'Autoexperten' | 'V4PricingTool'
8
8
 
9
9
  export interface IContractTemplateCloneRequest {
10
10
  templateIds: number[]
@@ -34,42 +34,6 @@ export interface IGenericContractTemplateRequest {
34
34
  overdrivenCharge: PriceSpecification
35
35
  totalPriceFactorInTenthOfCents: number | null
36
36
  disableServiceInfoFile: boolean | null
37
- /**
38
- * Price prediction templates only: the terms that may be sold, in months. Whole years only
39
- * (12, 24, ... ), never above 84. There is no price list to read the available terms off, so
40
- * they come from here instead; leave empty to offer every term up to 84.
41
- */
42
- predictionDurationMonths: number[] | null
43
- /**
44
- * Price prediction templates only: the mileage allowances that may be sold, in km over the whole
45
- * agreement. The same allowances are offered whatever the term, which is how price lists work.
46
- */
47
- predictionMileageKm: number[] | null
48
- /**
49
- * Price prediction templates only: the fuel types this template may be sold on. Empty or
50
- * absent means every fuel; the vehicle lookup drops the template for anything not listed.
51
- */
52
- predictionFuelTypes: string[] | null
53
- /**
54
- * Price prediction templates only: what one service is expected to cost, used as the starting
55
- * point an operator adjusts from. Money is ex VAT, in the provider's currency.
56
- */
57
- predictionOilPricePerLitre: number | null
58
- predictionOilLitresPerService: number | null
59
- predictionLabourRatePerHour: number | null
60
- predictionLabourHoursPerService: number | null
61
- /**
62
- * Price prediction templates only: whether this agreement covers replacing the HV traction
63
- * battery of an electric vehicle, never the 12 V battery. Off unless a provider deliberately turns it on -- it is the
64
- * one bill that dwarfs everything else an agreement covers.
65
- */
66
- predictionCoversBattery: boolean | null
67
- /**
68
- * Price prediction templates only: what replacing that HV traction battery is expected to cost,
69
- * ex VAT in
70
- * the provider's currency. Only meaningful where the cover is on.
71
- */
72
- predictionBatteryCost: number | null
73
37
  }
74
38
 
75
39
  export interface IContractTemplateRequest extends IGenericContractTemplateRequest {
@@ -110,42 +74,6 @@ export interface IGenericContractTemplateResponse {
110
74
  maxEndMileage?: number
111
75
  totalPriceFactorInTenthOfCents: number | null
112
76
  disableServiceInfoFile: boolean | null
113
- /**
114
- * Price prediction templates only: the terms that may be sold, in months. Whole years only
115
- * (12, 24, ... ), never above 84. There is no price list to read the available terms off, so
116
- * they come from here instead; leave empty to offer every term up to 84.
117
- */
118
- predictionDurationMonths: number[] | null
119
- /**
120
- * Price prediction templates only: the mileage allowances that may be sold, in km over the whole
121
- * agreement. The same allowances are offered whatever the term, which is how price lists work.
122
- */
123
- predictionMileageKm: number[] | null
124
- /**
125
- * Price prediction templates only: the fuel types this template may be sold on. Empty or
126
- * absent means every fuel; the vehicle lookup drops the template for anything not listed.
127
- */
128
- predictionFuelTypes: string[] | null
129
- /**
130
- * Price prediction templates only: what one service is expected to cost, used as the starting
131
- * point an operator adjusts from. Money is ex VAT, in the provider's currency.
132
- */
133
- predictionOilPricePerLitre: number | null
134
- predictionOilLitresPerService: number | null
135
- predictionLabourRatePerHour: number | null
136
- predictionLabourHoursPerService: number | null
137
- /**
138
- * Price prediction templates only: whether this agreement covers replacing the HV traction
139
- * battery of an electric vehicle, never the 12 V battery. Off unless a provider deliberately turns it on -- it is the
140
- * one bill that dwarfs everything else an agreement covers.
141
- */
142
- predictionCoversBattery: boolean | null
143
- /**
144
- * Price prediction templates only: what replacing that HV traction battery is expected to cost,
145
- * ex VAT in
146
- * the provider's currency. Only meaningful where the cover is on.
147
- */
148
- predictionBatteryCost: number | null
149
77
  }
150
78
 
151
79
  export interface IContractTemplateResponse extends IGenericContractTemplateResponse {
@@ -1,32 +1,39 @@
1
- import { TIsoCountry } from './country';
1
+ import { TIsoCountry } from './country'
2
+
2
3
  /**
3
4
  * Request body for creating or updating a dashboard banner (Superadmin)
4
5
  */
5
6
  export interface DashboardBannerRequest {
6
- text: string;
7
- countryIso: TIsoCountry;
8
- /**
9
- * ISO date string (YYYY-MM-DD) for the start date of the banner
10
- */
11
- startDate: string;
12
- /**
13
- * ISO date string (YYYY-MM-DD) for the end date of the banner
14
- */
15
- endDate: string;
7
+ text: string
8
+ /**
9
+ * Exactly one of the two targets the banner: a whole country, or the members of one provider group.
10
+ * A group banner takes precedence over a country one.
11
+ */
12
+ countryIso?: TIsoCountry | null
13
+ contractProviderGroupId?: number | null
14
+ /**
15
+ * ISO date string (YYYY-MM-DD) for the start date of the banner
16
+ */
17
+ startDate: string
18
+ /**
19
+ * ISO date string (YYYY-MM-DD) for the end date of the banner
20
+ */
21
+ endDate: string
16
22
  }
17
23
  /**
18
24
  * Response body for retrieving a dashboard banner (Superadmin)
19
25
  */
20
26
  export interface DashboardBannerResponse extends DashboardBannerRequest {
21
- id: number;
22
- /**
23
- * Email of the user who last updated the banner
24
- */
25
- updatedByUser: string;
27
+ id: number
28
+ contractProviderGroupName: string | null
29
+ /**
30
+ * Email of the user who last updated the banner
31
+ */
32
+ updatedByUser: string
26
33
  }
27
34
  /**
28
35
  * Response body for retrieving dashboard banner text in Admin
29
36
  */
30
37
  export interface ProviderDashboardBannerResponse {
31
- text: string;
38
+ text: string
32
39
  }
@@ -10,12 +10,19 @@ export declare enum EmailCampaignStatus {
10
10
  }
11
11
  export declare type EmailCampaignRole = 'admin' | 'seller';
12
12
  export declare type EmailCampaignSelectionMode = 'all' | 'subset';
13
+ export declare type EmailCampaignProviderMode = EmailCampaignSelectionMode | 'group';
13
14
  export declare type EmailCampaignProviderSelection = {
14
15
  providerMode: 'all';
15
16
  selectedProviderIds: null;
17
+ selectedGroupIds: null;
16
18
  } | {
17
19
  providerMode: 'subset';
18
20
  selectedProviderIds: number[];
21
+ selectedGroupIds: null;
22
+ } | {
23
+ providerMode: 'group';
24
+ selectedProviderIds: null;
25
+ selectedGroupIds: number[];
19
26
  };
20
27
  export declare type EmailCampaignRecipientSelection = {
21
28
  recipientMode: 'all';
@@ -21,11 +21,15 @@ export type EmailCampaignRole = 'admin' | 'seller'
21
21
  // How a pool (providers or recipients) is narrowed: 'all' = every match, 'subset' = explicit ids.
22
22
  export type EmailCampaignSelectionMode = 'all' | 'subset'
23
23
 
24
+ // The provider dimension has a third mode the recipient dimension does not: 'group'.
25
+ export type EmailCampaignProviderMode = EmailCampaignSelectionMode | 'group'
26
+
24
27
  // Provider dimension: mode is discriminated against its selection, so an invalid combination can't type-check.
25
28
  // The candidate-pool filter (`filters`) is carried separately on the request/detail DTOs.
26
29
  export type EmailCampaignProviderSelection =
27
- | { providerMode: 'all'; selectedProviderIds: null }
28
- | { providerMode: 'subset'; selectedProviderIds: number[] }
30
+ | { providerMode: 'all'; selectedProviderIds: null; selectedGroupIds: null }
31
+ | { providerMode: 'subset'; selectedProviderIds: number[]; selectedGroupIds: null }
32
+ | { providerMode: 'group'; selectedProviderIds: null; selectedGroupIds: number[] }
29
33
 
30
34
  // Recipient dimension: mode is discriminated against its user picks.
31
35
  export type EmailCampaignRecipientSelection =
package/types/index.d.ts CHANGED
@@ -180,7 +180,6 @@ export * from './outage';
180
180
  export * from './payment';
181
181
  export * from './priceCalculation';
182
182
  export * from './priceSpecification';
183
- export * from './pricePrediction';
184
183
  export * from './product';
185
184
  export * from './providerBranding';
186
185
  export * from './queryParams';
@@ -220,3 +219,4 @@ export * from './ISendEmail';
220
219
  export * from './emailCampaign';
221
220
  export * from './contractProviderFlags';
222
221
  export * from './providerDocument';
222
+ export * from './contractProviderGroup';
package/types/index.js CHANGED
@@ -170,6 +170,5 @@ var AccessKeyService;
170
170
  AccessKeyService[AccessKeyService["WorkshopOperation"] = 1] = "WorkshopOperation";
171
171
  AccessKeyService[AccessKeyService["Report"] = 2] = "Report";
172
172
  })(AccessKeyService = exports.AccessKeyService || (exports.AccessKeyService = {}));
173
- __export(require("./pricePrediction"));
174
173
  __export(require("./emailCampaign"));
175
174
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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"}
package/types/index.ts CHANGED
@@ -199,7 +199,6 @@ export * from './outage'
199
199
  export * from './payment'
200
200
  export * from './priceCalculation'
201
201
  export * from './priceSpecification'
202
- export * from './pricePrediction'
203
202
  export * from './product'
204
203
  export * from './providerBranding'
205
204
  export * from './queryParams'
@@ -239,3 +238,4 @@ export * from './ISendEmail'
239
238
  export * from './emailCampaign'
240
239
  export * from './contractProviderFlags'
241
240
  export * from './providerDocument'
241
+ export * from './contractProviderGroup'
@@ -16,9 +16,16 @@ export interface IProviderDocument {
16
16
  // Admin sees the document and its upload date only — no internal staff names.
17
17
  export interface IProviderDocumentAdminView extends IProviderDocument {}
18
18
 
19
- // Superadmin additionally sees the acting support users and the tombstone state.
19
+ export interface IProviderDocumentGroup {
20
+ contractProviderGroupId: number
21
+ name: string
22
+ }
23
+
24
+ // Superadmin also sees the acting support users, the tombstone state, and the sharing groups, sorted by
25
+ // name. `groups` is empty on a provider-owned row.
20
26
  export interface IProviderDocumentSuperadminView extends IProviderDocument {
21
27
  uploadedByName: string | null
22
28
  deletedAt: string | null
23
29
  deletedByName: string | null
30
+ groups: IProviderDocumentGroup[]
24
31
  }
@@ -0,0 +1,4 @@
1
+ export type ContractProviderGroupErrors =
2
+ | 'CONTRACT_PROVIDER_GROUP_NAME_TAKEN'
3
+ | 'CONTRACT_PROVIDER_GROUP_IN_USE'
4
+ | 'CONTRACT_PROVIDER_GROUP_DOCUMENT_SHARED'
@@ -1,6 +1,7 @@
1
1
  import { AdjustmentRequestResponseErrors } from './adjustmentRequestResponseErrors'
2
2
  import { AdminPaymentErrors } from './adminPaymentErrors'
3
3
  import { CalculatorResponseErrors } from './calculatorResponseErrors'
4
+ import { ContractProviderGroupErrors } from './contractProviderGroupErrors'
4
5
  import { ContractResponseErrors } from './contractResponseErrors'
5
6
  import { GenericResponseErrors } from './genericResponseTypes'
6
7
  import { ImportErrors } from './importErrors'
@@ -17,6 +18,7 @@ export type ResponseErrors =
17
18
  | AdjustmentRequestResponseErrors
18
19
  | AdminPaymentErrors
19
20
  | CalculatorResponseErrors
21
+ | ContractProviderGroupErrors
20
22
  | ContractResponseErrors
21
23
  | GenericResponseErrors
22
24
  | ImportErrors
@@ -1,219 +0,0 @@
1
- import { IsoLocale } from './locale';
2
- import { PriceSpecification } from './priceSpecification';
3
- import { TVehicleType } from './vehicle';
4
- /**
5
- * The contract between the API and the clients for a predicted price.
6
- *
7
- * It lives here because three repositories were keeping their own copy of it by hand. Admin
8
- * redeclared the response shape and said so in a comment; superadmin wrote the duration ladder out
9
- * as a literal; the ceiling existed as a separate constant in each of the three. A field added on
10
- * one side and forgotten on another was caught only by someone remembering to look.
11
- *
12
- * What stays in the API is what never crosses the wire: how comparables are matched, what the
13
- * model is asked, and every tuning constant behind the estimate.
14
- */
15
- /** Agreements are sold in whole years, so every offered term is a multiple of this. */
16
- export declare const PREDICTION_DURATION_STEP_MONTHS = 12;
17
- /** Nothing longer may be sold, whatever a template asks for. */
18
- export declare const PREDICTION_MAX_DURATION_MONTHS = 108;
19
- /**
20
- * Every term a predicted template may offer when it names none itself: 12, 24 ... up to the
21
- * ceiling. Derived rather than written out, so raising the ceiling cannot leave a stale list
22
- * behind in another repository.
23
- */
24
- export declare function predictionDurationLadder(): number[];
25
- /** True for a term that may actually be sold: a whole number of years, up to the ceiling. */
26
- export declare function isSellablePredictionDuration(months: number): boolean;
27
- /**
28
- * The fuel types a predicted template may be restricted to, as the vehicle catalogue spells them.
29
- *
30
- * A template that names none supports every fuel: an empty restriction is not "nothing", it is
31
- * "no restriction", which is what a provider who never thinks about this should get.
32
- */
33
- export declare const PREDICTION_FUEL_TYPES: string[];
34
- export declare type TPredictionFuelType = 'Petrol' | 'Diesel' | 'Electric' | 'Hybrid' | 'Diesel-Electric' | 'Cng' | 'Lpg' | 'Lpg-Petrol' | 'Lpg-Diesel';
35
- /**
36
- * Whether a template restricted to these fuel types may be offered on this vehicle. No restriction
37
- * means every fuel; an unknown fuel on the vehicle matches nothing but the absence of a rule.
38
- */
39
- export declare function supportsPredictionFuelType(supported: string[] | null | undefined, fuelTypeName: string | null | undefined): boolean;
40
- /**
41
- * How closely the comparable contracts had to match the requested vehicle. Tiers are tried in
42
- * order and the first with enough samples wins, so a wide tier signals a weak estimate.
43
- */
44
- export declare type TMatchTier = 'BrandModelFuel' | 'BrandFuelPower' | 'BrandFuel' | 'FuelPower' | 'Fuel';
45
- export declare type TPredictionConfidence = 'high' | 'medium' | 'low';
46
- export declare type TPriceSuggestionSource = 'Comparables' | 'AI';
47
- /**
48
- * Why an estimate may be weaker than its single number suggests. Sent as a code and parameters
49
- * rather than a sentence: a quote is read on a Danish, Swedish, Finnish or Norwegian screen, and
50
- * English prose from the server would land untranslated in the middle of it.
51
- *
52
- * A client may render only the codes it knows and drop the rest, so the API can add one without
53
- * waiting for every client. A dropped code is a reason the seller never sees, though, so a client
54
- * that maps these should aim to map all of them.
55
- */
56
- export declare type TPredictionCaveatCode = 'VEHICLE_AGE_UNKNOWN' | 'VEHICLE_AGE_OUTSIDE_COMPARABLES' | 'START_MILEAGE_UNKNOWN' | 'START_MILEAGE_OUTSIDE_COMPARABLES' | 'DURATION_OUTSIDE_COMPARABLES' | 'TOO_FEW_COMPARABLES' | 'SMALL_COMPARABLE_SAMPLE' | 'NO_BRAND_OR_MODEL_HISTORY' | 'MILEAGE_OUTSIDE_COMPARABLES' | 'COST_UNVERIFIED' | 'SEGMENT_UNDERPRICED' | 'COMPARABLES_STALE' | 'AI_DIVERGES_FROM_COMPARABLES' | 'TIMING_BELT_DUE_IN_TERM' | 'PRICED_BY_AI_WITHOUT_COMPARABLES';
57
- export interface IPredictionCaveat {
58
- code: TPredictionCaveatCode;
59
- params?: {
60
- [key: string]: string | number;
61
- };
62
- /** English, for the admin prediction endpoint only. Never render this to a customer. */
63
- message: string;
64
- }
65
- export interface IPricePredictionRequest {
66
- brand: string;
67
- model: string;
68
- fuelType: string;
69
- modelYear?: number;
70
- /** Preferred over modelYear for the vehicle's age: it is set on far more vehicles. */
71
- dateVehicleFirstRegistered?: string;
72
- engineMaxPower?: number;
73
- vehicleType?: TVehicleType;
74
- /** Odometer when the agreement starts, not the allowance it grants. */
75
- startMileage?: number;
76
- /** The reader's own locale. Their choice wins over the provider's, which stands in when absent. */
77
- locale?: IsoLocale;
78
- durationMonths: number;
79
- /** Allowance over the whole contract, not per year. */
80
- mileage: number;
81
- adjustmentFactor?: number;
82
- /** Annual price drift used to bring old comparables into today's money, e.g. 0.03 for 3%. */
83
- annualPriceIndex?: number;
84
- /**
85
- * Whether the servicing and coverage advice is wanted alongside the price. Defaults to true.
86
- * Asking for prose nobody reads is most of the output bill and most of the wait, so a caller
87
- * that shows the price alone should say so.
88
- */
89
- withAdvice?: boolean;
90
- }
91
- export interface IPricePercentiles {
92
- p25: PriceSpecification;
93
- p50: PriceSpecification;
94
- p75: PriceSpecification;
95
- }
96
- /**
97
- * Realized workshop cost for the same comparables. Only populated when enough of them actually
98
- * carry cost data, otherwise absence of claims is indistinguishable from claims never recorded.
99
- */
100
- export interface ICostEvidence {
101
- contractsWithCost: number;
102
- medianMonthlyCost: PriceSpecification;
103
- impliedLossRatio: number;
104
- }
105
- export interface IPricePredictionBasis {
106
- matchTier: TMatchTier;
107
- comparableCount: number;
108
- contractsWithPrice: number;
109
- durationMatched: boolean;
110
- vehicleAgeMatched: boolean;
111
- startMileageMatched: boolean;
112
- requestedStartMileage: number | null;
113
- medianComparableStartMileage: number | null;
114
- /** Age of the vehicle being priced, from first registration where given, else from model year. */
115
- requestedVehicleAgeYears: number | null;
116
- /** Median age of the comparable vehicles at the time each of those contracts was sold. */
117
- medianComparableVehicleAgeYears: number | null;
118
- medianComparableDurationMonths: number;
119
- medianComparableMileagePerMonth: number;
120
- mileageAdjustment: number;
121
- adjustmentFactor: number;
122
- annualPriceIndex: number;
123
- medianComparableAgeYears: number;
124
- /**
125
- * Annual drift actually observed between the oldest and newest third of the comparables. Shown
126
- * so the applied index can be sanity checked against the data; too noisy on small or
127
- * narrow-dated samples to apply automatically.
128
- */
129
- observedAnnualDrift: number | null;
130
- oldestContractStart: string | null;
131
- newestContractStart: string | null;
132
- }
133
- /**
134
- * One estimate of the price. Sources are meant to coexist rather than replace one another: an
135
- * added estimate is only worth acting on if it can be read against the others and the evidence.
136
- */
137
- export interface IPriceSuggestion {
138
- source: TPriceSuggestionSource;
139
- monthlyPrice: PriceSpecification;
140
- /** The same figure in sub-units ex-VAT, the unit prices are stored and calculated in. */
141
- monthlyPriceMinor: number;
142
- /** Adjusted identically to monthlyPrice, so the three figures are directly comparable. */
143
- range: {
144
- low: PriceSpecification;
145
- high: PriceSpecification;
146
- };
147
- rationale?: string;
148
- }
149
- /**
150
- * What the model expects this vehicle to need over the term. This is NOT the contract's coverage,
151
- * which the template's terms decide. Never quote it as what the customer is entitled to.
152
- */
153
- export interface IAnticipatedServicing {
154
- summary: string;
155
- items: string[];
156
- /**
157
- * How many ordinary services fall inside the term. A suggestion for the seller to confirm or
158
- * correct, as with the timing belt: it prices an agreement only if they put it there.
159
- */
160
- ordinaryServicesWithinTerm?: number;
161
- }
162
- /**
163
- * Whether a timing belt change falls inside the agreed term or mileage. Asked on its own because
164
- * it is usually the largest single bill in an agreement, and whether it lands inside the window or
165
- * just outside swings the price more than anything else the vehicle will need.
166
- */
167
- export interface ITimingBeltAssessment {
168
- dueWithinTerm: 'yes' | 'no' | 'unknown';
169
- /** One plain sentence: the interval, where this vehicle sits against it, or that it runs a chain. */
170
- note: string;
171
- /**
172
- * How many changes fall inside the term, where one is due. A suggestion for the seller to
173
- * confirm or correct: it is written onto an agreement only if they put it there.
174
- */
175
- replacementsWithinTerm?: number;
176
- }
177
- /**
178
- * Whether this electric vehicle is likely to need its HV traction battery replaced during the agreed
179
- * term or mileage. Asked only of electric vehicles, and asked because it decides whether a seller
180
- * should put the battery on the agreement at all -- it is the largest single thing an agreement
181
- * can carry, and the cost is the provider's to bear once it is on there.
182
- *
183
- * A judgement about the vehicle, not about the warranty: age, distance, chemistry and how hard the
184
- * car has been driven. Unknown is a real answer and better than a guess dressed as one.
185
- */
186
- export interface IBatteryAssessment {
187
- replacementLikelyWithinTerm: 'yes' | 'no' | 'unknown';
188
- /** One plain sentence a seller can repeat: what the battery is likely to do over this term. */
189
- note: string;
190
- }
191
- /**
192
- * What the agreement ought to cover for this vehicle, in the model's own words. Advice for whoever
193
- * configures or sells it, never a statement of what a customer has bought.
194
- */
195
- export interface IRecommendedCoverage {
196
- summary: string;
197
- items: string[];
198
- }
199
- /** The model's read on the vehicle. Absent when the integration is disabled or unreachable. */
200
- export interface IAiReview {
201
- confidence: TPredictionConfidence;
202
- anticipatedServicing?: IAnticipatedServicing;
203
- recommendedCoverage?: IRecommendedCoverage;
204
- timingBelt?: ITimingBeltAssessment;
205
- battery?: IBatteryAssessment;
206
- }
207
- export interface IPricePredictionEvidence {
208
- basis: IPricePredictionBasis;
209
- /** Prices as recorded on the comparables, before mileage or caller adjustments. */
210
- historicalMonthlyPrice: IPricePercentiles;
211
- costEvidence?: ICostEvidence;
212
- aiReview?: IAiReview;
213
- }
214
- export interface IPricePredictionResult {
215
- suggestions: IPriceSuggestion[];
216
- evidence: IPricePredictionEvidence;
217
- caveats: IPredictionCaveat[];
218
- confidence: TPredictionConfidence;
219
- }
@@ -1,84 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- /**
4
- * The contract between the API and the clients for a predicted price.
5
- *
6
- * It lives here because three repositories were keeping their own copy of it by hand. Admin
7
- * redeclared the response shape and said so in a comment; superadmin wrote the duration ladder out
8
- * as a literal; the ceiling existed as a separate constant in each of the three. A field added on
9
- * one side and forgotten on another was caught only by someone remembering to look.
10
- *
11
- * What stays in the API is what never crosses the wire: how comparables are matched, what the
12
- * model is asked, and every tuning constant behind the estimate.
13
- */
14
- /** Agreements are sold in whole years, so every offered term is a multiple of this. */
15
- exports.PREDICTION_DURATION_STEP_MONTHS = 12;
16
- /** Nothing longer may be sold, whatever a template asks for. */
17
- exports.PREDICTION_MAX_DURATION_MONTHS = 108;
18
- /**
19
- * Every term a predicted template may offer when it names none itself: 12, 24 ... up to the
20
- * ceiling. Derived rather than written out, so raising the ceiling cannot leave a stale list
21
- * behind in another repository.
22
- */
23
- function predictionDurationLadder() {
24
- const months = [];
25
- for (let m = exports.PREDICTION_DURATION_STEP_MONTHS; m <= exports.PREDICTION_MAX_DURATION_MONTHS; m += exports.PREDICTION_DURATION_STEP_MONTHS) {
26
- months.push(m);
27
- }
28
- return months;
29
- }
30
- exports.predictionDurationLadder = predictionDurationLadder;
31
- /** True for a term that may actually be sold: a whole number of years, up to the ceiling. */
32
- function isSellablePredictionDuration(months) {
33
- return months > 0 && months <= exports.PREDICTION_MAX_DURATION_MONTHS && months % exports.PREDICTION_DURATION_STEP_MONTHS === 0;
34
- }
35
- exports.isSellablePredictionDuration = isSellablePredictionDuration;
36
- /**
37
- * The fuel types a predicted template may be restricted to, as the vehicle catalogue spells them.
38
- *
39
- * A template that names none supports every fuel: an empty restriction is not "nothing", it is
40
- * "no restriction", which is what a provider who never thinks about this should get.
41
- */
42
- exports.PREDICTION_FUEL_TYPES = [
43
- 'Petrol',
44
- 'Diesel',
45
- 'Electric',
46
- 'Hybrid',
47
- 'Diesel-Electric',
48
- 'Cng',
49
- 'Lpg',
50
- 'Lpg-Petrol',
51
- 'Lpg-Diesel',
52
- ];
53
- /**
54
- * The same fuel written more than one way. Lookups and imports have filed Danish and Swedish
55
- * spellings alongside the canonical English, and a vehicle that arrives as EL must still match a
56
- * template that says Electric -- otherwise the restriction silently hides templates that were
57
- * meant to be offered.
58
- */
59
- const FUEL_SPELLINGS = [
60
- ['PETROL', 'BENZIN', 'BENSIN'],
61
- ['ELECTRIC', 'EL', 'EV', 'ELEKTRISK'],
62
- ['DIESEL'],
63
- ];
64
- const canonical = (fuelTypeName) => {
65
- const wanted = fuelTypeName.trim().toUpperCase();
66
- const group = FUEL_SPELLINGS.find((spellings) => spellings.indexOf(wanted) !== -1);
67
- return group ? group[0] : wanted;
68
- };
69
- /**
70
- * Whether a template restricted to these fuel types may be offered on this vehicle. No restriction
71
- * means every fuel; an unknown fuel on the vehicle matches nothing but the absence of a rule.
72
- */
73
- function supportsPredictionFuelType(supported, fuelTypeName) {
74
- if (!supported || !supported.length) {
75
- return true;
76
- }
77
- if (!fuelTypeName) {
78
- return false;
79
- }
80
- const wanted = canonical(fuelTypeName);
81
- return supported.some((entry) => canonical(entry) === wanted);
82
- }
83
- exports.supportsPredictionFuelType = supportsPredictionFuelType;
84
- //# sourceMappingURL=pricePrediction.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pricePrediction.js","sourceRoot":"","sources":["pricePrediction.ts"],"names":[],"mappings":";;AAIA;;;;;;;;;;GAUG;AAEH,uFAAuF;AAC1E,QAAA,+BAA+B,GAAG,EAAE,CAAA;AAEjD,gEAAgE;AACnD,QAAA,8BAA8B,GAAG,GAAG,CAAA;AAEjD;;;;GAIG;AACH,SAAgB,wBAAwB;IACtC,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,KACE,IAAI,CAAC,GAAG,uCAA+B,EACvC,CAAC,IAAI,sCAA8B,EACnC,CAAC,IAAI,uCAA+B,EACpC;QACA,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;KACf;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAVD,4DAUC;AAED,6FAA6F;AAC7F,SAAgB,4BAA4B,CAAC,MAAc;IACzD,OAAO,MAAM,GAAG,CAAC,IAAI,MAAM,IAAI,sCAA8B,IAAI,MAAM,GAAG,uCAA+B,KAAK,CAAC,CAAA;AACjH,CAAC;AAFD,oEAEC;AAGD;;;;;GAKG;AACU,QAAA,qBAAqB,GAAa;IAC7C,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,QAAQ;IACR,iBAAiB;IACjB,KAAK;IACL,KAAK;IACL,YAAY;IACZ,YAAY;CACb,CAAA;AAaD;;;;;GAKG;AACH,MAAM,cAAc,GAAe;IACjC,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC;IAC9B,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,CAAC;IACrC,CAAC,QAAQ,CAAC;CACX,CAAA;AAED,MAAM,SAAS,GAAG,CAAC,YAAoB,EAAU,EAAE;IACjD,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IAChD,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IAElF,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;AAClC,CAAC,CAAA;AAED;;;GAGG;AACH,SAAgB,0BAA0B,CACxC,SAAsC,EACtC,YAAuC;IAEvC,IAAI,CAAC,SAAS,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE;QACnC,OAAO,IAAI,CAAA;KACZ;IAED,IAAI,CAAC,YAAY,EAAE;QACjB,OAAO,KAAK,CAAA;KACb;IAED,MAAM,MAAM,GAAG,SAAS,CAAC,YAAY,CAAC,CAAA;IAEtC,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,CAAA;AAC/D,CAAC;AAfD,gEAeC"}
@@ -1,320 +0,0 @@
1
- import { IsoLocale } from './locale'
2
- import { PriceSpecification } from './priceSpecification'
3
- import { TVehicleType } from './vehicle'
4
-
5
- /**
6
- * The contract between the API and the clients for a predicted price.
7
- *
8
- * It lives here because three repositories were keeping their own copy of it by hand. Admin
9
- * redeclared the response shape and said so in a comment; superadmin wrote the duration ladder out
10
- * as a literal; the ceiling existed as a separate constant in each of the three. A field added on
11
- * one side and forgotten on another was caught only by someone remembering to look.
12
- *
13
- * What stays in the API is what never crosses the wire: how comparables are matched, what the
14
- * model is asked, and every tuning constant behind the estimate.
15
- */
16
-
17
- /** Agreements are sold in whole years, so every offered term is a multiple of this. */
18
- export const PREDICTION_DURATION_STEP_MONTHS = 12
19
-
20
- /** Nothing longer may be sold, whatever a template asks for. */
21
- export const PREDICTION_MAX_DURATION_MONTHS = 108
22
-
23
- /**
24
- * Every term a predicted template may offer when it names none itself: 12, 24 ... up to the
25
- * ceiling. Derived rather than written out, so raising the ceiling cannot leave a stale list
26
- * behind in another repository.
27
- */
28
- export function predictionDurationLadder(): number[] {
29
- const months: number[] = []
30
- for (
31
- let m = PREDICTION_DURATION_STEP_MONTHS;
32
- m <= PREDICTION_MAX_DURATION_MONTHS;
33
- m += PREDICTION_DURATION_STEP_MONTHS
34
- ) {
35
- months.push(m)
36
- }
37
- return months
38
- }
39
-
40
- /** True for a term that may actually be sold: a whole number of years, up to the ceiling. */
41
- export function isSellablePredictionDuration(months: number): boolean {
42
- return months > 0 && months <= PREDICTION_MAX_DURATION_MONTHS && months % PREDICTION_DURATION_STEP_MONTHS === 0
43
- }
44
-
45
-
46
- /**
47
- * The fuel types a predicted template may be restricted to, as the vehicle catalogue spells them.
48
- *
49
- * A template that names none supports every fuel: an empty restriction is not "nothing", it is
50
- * "no restriction", which is what a provider who never thinks about this should get.
51
- */
52
- export const PREDICTION_FUEL_TYPES: string[] = [
53
- 'Petrol',
54
- 'Diesel',
55
- 'Electric',
56
- 'Hybrid',
57
- 'Diesel-Electric',
58
- 'Cng',
59
- 'Lpg',
60
- 'Lpg-Petrol',
61
- 'Lpg-Diesel',
62
- ]
63
-
64
- export type TPredictionFuelType =
65
- | 'Petrol'
66
- | 'Diesel'
67
- | 'Electric'
68
- | 'Hybrid'
69
- | 'Diesel-Electric'
70
- | 'Cng'
71
- | 'Lpg'
72
- | 'Lpg-Petrol'
73
- | 'Lpg-Diesel'
74
-
75
- /**
76
- * The same fuel written more than one way. Lookups and imports have filed Danish and Swedish
77
- * spellings alongside the canonical English, and a vehicle that arrives as EL must still match a
78
- * template that says Electric -- otherwise the restriction silently hides templates that were
79
- * meant to be offered.
80
- */
81
- const FUEL_SPELLINGS: string[][] = [
82
- ['PETROL', 'BENZIN', 'BENSIN'],
83
- ['ELECTRIC', 'EL', 'EV', 'ELEKTRISK'],
84
- ['DIESEL'],
85
- ]
86
-
87
- const canonical = (fuelTypeName: string): string => {
88
- const wanted = fuelTypeName.trim().toUpperCase()
89
- const group = FUEL_SPELLINGS.find((spellings) => spellings.indexOf(wanted) !== -1)
90
-
91
- return group ? group[0] : wanted
92
- }
93
-
94
- /**
95
- * Whether a template restricted to these fuel types may be offered on this vehicle. No restriction
96
- * means every fuel; an unknown fuel on the vehicle matches nothing but the absence of a rule.
97
- */
98
- export function supportsPredictionFuelType(
99
- supported: string[] | null | undefined,
100
- fuelTypeName: string | null | undefined,
101
- ): boolean {
102
- if (!supported || !supported.length) {
103
- return true
104
- }
105
-
106
- if (!fuelTypeName) {
107
- return false
108
- }
109
-
110
- const wanted = canonical(fuelTypeName)
111
-
112
- return supported.some((entry) => canonical(entry) === wanted)
113
- }
114
-
115
- /**
116
- * How closely the comparable contracts had to match the requested vehicle. Tiers are tried in
117
- * order and the first with enough samples wins, so a wide tier signals a weak estimate.
118
- */
119
- export type TMatchTier = 'BrandModelFuel' | 'BrandFuelPower' | 'BrandFuel' | 'FuelPower' | 'Fuel'
120
-
121
- export type TPredictionConfidence = 'high' | 'medium' | 'low'
122
-
123
- export type TPriceSuggestionSource = 'Comparables' | 'AI'
124
-
125
- /**
126
- * Why an estimate may be weaker than its single number suggests. Sent as a code and parameters
127
- * rather than a sentence: a quote is read on a Danish, Swedish, Finnish or Norwegian screen, and
128
- * English prose from the server would land untranslated in the middle of it.
129
- *
130
- * A client may render only the codes it knows and drop the rest, so the API can add one without
131
- * waiting for every client. A dropped code is a reason the seller never sees, though, so a client
132
- * that maps these should aim to map all of them.
133
- */
134
- export type TPredictionCaveatCode =
135
- | 'VEHICLE_AGE_UNKNOWN'
136
- | 'VEHICLE_AGE_OUTSIDE_COMPARABLES'
137
- | 'START_MILEAGE_UNKNOWN'
138
- | 'START_MILEAGE_OUTSIDE_COMPARABLES'
139
- | 'DURATION_OUTSIDE_COMPARABLES'
140
- | 'TOO_FEW_COMPARABLES'
141
- | 'SMALL_COMPARABLE_SAMPLE'
142
- | 'NO_BRAND_OR_MODEL_HISTORY'
143
- | 'MILEAGE_OUTSIDE_COMPARABLES'
144
- | 'COST_UNVERIFIED'
145
- | 'SEGMENT_UNDERPRICED'
146
- | 'COMPARABLES_STALE'
147
- | 'AI_DIVERGES_FROM_COMPARABLES'
148
- | 'TIMING_BELT_DUE_IN_TERM'
149
- | 'PRICED_BY_AI_WITHOUT_COMPARABLES'
150
-
151
- export interface IPredictionCaveat {
152
- code: TPredictionCaveatCode
153
- params?: { [key: string]: string | number }
154
- /** English, for the admin prediction endpoint only. Never render this to a customer. */
155
- message: string
156
- }
157
-
158
- export interface IPricePredictionRequest {
159
- brand: string
160
- model: string
161
- fuelType: string
162
- modelYear?: number
163
- /** Preferred over modelYear for the vehicle's age: it is set on far more vehicles. */
164
- dateVehicleFirstRegistered?: string
165
- engineMaxPower?: number
166
- vehicleType?: TVehicleType
167
- /** Odometer when the agreement starts, not the allowance it grants. */
168
- startMileage?: number
169
- /** The reader's own locale. Their choice wins over the provider's, which stands in when absent. */
170
- locale?: IsoLocale
171
- durationMonths: number
172
- /** Allowance over the whole contract, not per year. */
173
- mileage: number
174
- adjustmentFactor?: number
175
- /** Annual price drift used to bring old comparables into today's money, e.g. 0.03 for 3%. */
176
- annualPriceIndex?: number
177
- /**
178
- * Whether the servicing and coverage advice is wanted alongside the price. Defaults to true.
179
- * Asking for prose nobody reads is most of the output bill and most of the wait, so a caller
180
- * that shows the price alone should say so.
181
- */
182
- withAdvice?: boolean
183
- }
184
-
185
- export interface IPricePercentiles {
186
- p25: PriceSpecification
187
- p50: PriceSpecification
188
- p75: PriceSpecification
189
- }
190
-
191
- /**
192
- * Realized workshop cost for the same comparables. Only populated when enough of them actually
193
- * carry cost data, otherwise absence of claims is indistinguishable from claims never recorded.
194
- */
195
- export interface ICostEvidence {
196
- contractsWithCost: number
197
- medianMonthlyCost: PriceSpecification
198
- impliedLossRatio: number
199
- }
200
-
201
- export interface IPricePredictionBasis {
202
- matchTier: TMatchTier
203
- comparableCount: number
204
- contractsWithPrice: number
205
- durationMatched: boolean
206
- vehicleAgeMatched: boolean
207
- startMileageMatched: boolean
208
- requestedStartMileage: number | null
209
- medianComparableStartMileage: number | null
210
- /** Age of the vehicle being priced, from first registration where given, else from model year. */
211
- requestedVehicleAgeYears: number | null
212
- /** Median age of the comparable vehicles at the time each of those contracts was sold. */
213
- medianComparableVehicleAgeYears: number | null
214
- medianComparableDurationMonths: number
215
- medianComparableMileagePerMonth: number
216
- mileageAdjustment: number
217
- adjustmentFactor: number
218
- annualPriceIndex: number
219
- medianComparableAgeYears: number
220
- /**
221
- * Annual drift actually observed between the oldest and newest third of the comparables. Shown
222
- * so the applied index can be sanity checked against the data; too noisy on small or
223
- * narrow-dated samples to apply automatically.
224
- */
225
- observedAnnualDrift: number | null
226
- oldestContractStart: string | null
227
- newestContractStart: string | null
228
- }
229
-
230
- /**
231
- * One estimate of the price. Sources are meant to coexist rather than replace one another: an
232
- * added estimate is only worth acting on if it can be read against the others and the evidence.
233
- */
234
- export interface IPriceSuggestion {
235
- source: TPriceSuggestionSource
236
- monthlyPrice: PriceSpecification
237
- /** The same figure in sub-units ex-VAT, the unit prices are stored and calculated in. */
238
- monthlyPriceMinor: number
239
- /** Adjusted identically to monthlyPrice, so the three figures are directly comparable. */
240
- range: { low: PriceSpecification; high: PriceSpecification }
241
- rationale?: string
242
- }
243
-
244
- /**
245
- * What the model expects this vehicle to need over the term. This is NOT the contract's coverage,
246
- * which the template's terms decide. Never quote it as what the customer is entitled to.
247
- */
248
- export interface IAnticipatedServicing {
249
- summary: string
250
- items: string[]
251
- /**
252
- * How many ordinary services fall inside the term. A suggestion for the seller to confirm or
253
- * correct, as with the timing belt: it prices an agreement only if they put it there.
254
- */
255
- ordinaryServicesWithinTerm?: number
256
- }
257
-
258
- /**
259
- * Whether a timing belt change falls inside the agreed term or mileage. Asked on its own because
260
- * it is usually the largest single bill in an agreement, and whether it lands inside the window or
261
- * just outside swings the price more than anything else the vehicle will need.
262
- */
263
- export interface ITimingBeltAssessment {
264
- dueWithinTerm: 'yes' | 'no' | 'unknown'
265
- /** One plain sentence: the interval, where this vehicle sits against it, or that it runs a chain. */
266
- note: string
267
- /**
268
- * How many changes fall inside the term, where one is due. A suggestion for the seller to
269
- * confirm or correct: it is written onto an agreement only if they put it there.
270
- */
271
- replacementsWithinTerm?: number
272
- }
273
-
274
- /**
275
- * Whether this electric vehicle is likely to need its HV traction battery replaced during the agreed
276
- * term or mileage. Asked only of electric vehicles, and asked because it decides whether a seller
277
- * should put the battery on the agreement at all -- it is the largest single thing an agreement
278
- * can carry, and the cost is the provider's to bear once it is on there.
279
- *
280
- * A judgement about the vehicle, not about the warranty: age, distance, chemistry and how hard the
281
- * car has been driven. Unknown is a real answer and better than a guess dressed as one.
282
- */
283
- export interface IBatteryAssessment {
284
- replacementLikelyWithinTerm: 'yes' | 'no' | 'unknown'
285
- /** One plain sentence a seller can repeat: what the battery is likely to do over this term. */
286
- note: string
287
- }
288
-
289
- /**
290
- * What the agreement ought to cover for this vehicle, in the model's own words. Advice for whoever
291
- * configures or sells it, never a statement of what a customer has bought.
292
- */
293
- export interface IRecommendedCoverage {
294
- summary: string
295
- items: string[]
296
- }
297
-
298
- /** The model's read on the vehicle. Absent when the integration is disabled or unreachable. */
299
- export interface IAiReview {
300
- confidence: TPredictionConfidence
301
- anticipatedServicing?: IAnticipatedServicing
302
- recommendedCoverage?: IRecommendedCoverage
303
- timingBelt?: ITimingBeltAssessment
304
- battery?: IBatteryAssessment
305
- }
306
-
307
- export interface IPricePredictionEvidence {
308
- basis: IPricePredictionBasis
309
- /** Prices as recorded on the comparables, before mileage or caller adjustments. */
310
- historicalMonthlyPrice: IPricePercentiles
311
- costEvidence?: ICostEvidence
312
- aiReview?: IAiReview
313
- }
314
-
315
- export interface IPricePredictionResult {
316
- suggestions: IPriceSuggestion[]
317
- evidence: IPricePredictionEvidence
318
- caveats: IPredictionCaveat[]
319
- confidence: TPredictionConfidence
320
- }