Skip to content
4 changes: 4 additions & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,21 @@
"useGitignore": true,
"words": [
"BEDES",
"bsyncr",
"CEJST",
"eeej",
"EPSG",
"EISA",
"FEMP",
"falsey",
"greenbutton",
"moveend",
"NMEC",
"overlaycontainer",
"pvwatts",
"SRID",
"Syncr",
"sqft",
"ubids",
"unlinkable",
"unpair",
Expand Down
19 changes: 18 additions & 1 deletion src/@seed/api/analysis/analysis.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { HttpClient } from '@angular/common/http'
import { inject, Injectable } from '@angular/core'
import type { Observable, Subscription } from 'rxjs'
import { BehaviorSubject, catchError, interval, map, Subject, takeUntil, takeWhile, tap, withLatestFrom } from 'rxjs'
import type { FullProgressResponse } from '@seed/api'
import { OrganizationService } from '@seed/api'
import { ErrorService } from '@seed/services'
import { SnackBarService } from 'app/core/snack-bar/snack-bar.service'
import { UserService } from '../user'
import type { AnalysesMessage, Analysis, AnalysisResponse, AnalysisServiceType, AnalysisSummary, AnalysisView, AnalysisViews, ListAnalysesResponse, ListMessagesResponse, PropertyAnalysesResponse, View } from './analysis.types'
import type { AnalysesMessage, Analysis, AnalysisCreateData, AnalysisResponse, AnalysisServiceType, AnalysisSummary, AnalysisView, AnalysisViews, ListAnalysesResponse, ListMessagesResponse, PropertyAnalysesResponse, View } from './analysis.types'

@Injectable({ providedIn: 'root' })
export class AnalysisService {
Expand Down Expand Up @@ -159,6 +160,22 @@ export class AnalysisService {
)
}

create(orgId: number, data: AnalysisCreateData): Observable<FullProgressResponse> {
const url = `/api/v3/analyses/?organization_id=${orgId}&start_analysis=true`
return this._httpClient.post<FullProgressResponse>(url, data).pipe(
tap((response) => {
if (response.status === 'error') {
return this._errorService.handleError(response.errors as HttpErrorResponse, 'Error creating analysis')
}
this._snackBar.success('Running Analysis')
this.getAnalyses(orgId)
}),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error creating analysis')
}),
)
}

summary(orgId: number, cycleId: number): Observable<AnalysisSummary> {
const url = `/api/v4/analyses/stats/?cycle_id=${cycleId}&organization_id=${orgId}`
return this._httpClient.get<AnalysisSummary>(url).pipe(
Expand Down
28 changes: 28 additions & 0 deletions src/@seed/api/analysis/analysis.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ export type Analysis = {
_finished_with_tasks: boolean; // used to determine if an analysis has no currently running tasks
}

export type AnalysisCreateData = {
name: string;
service: AnalysisServiceType;
configuration: Record<string, unknown>;
property_view_ids: number[];
access_level_instance_id: number;
}

export type AnalysisServiceType = 'BSyncr' | 'BETTER' | 'EUI' | 'CO2' | 'EEEJ' | 'Element Statistics' | 'Building Upgrade Recommendation'

// Analysis by View type
Expand Down Expand Up @@ -108,3 +116,23 @@ export type AnalysisSummaryStats = {
display_name: string;
is_extra_data: boolean;
}

export type SavingsTarget = 'AGGRESSIVE' | 'CONSERVATIVE' | 'NOMINAL'
export type SelectMetersType = 'all' | 'date_range' | 'select_cycle'
export type BenchmarkDataType = 'DEFAULT' | 'GENERATE'

export type BETTERConfig = {
benchmark_data_type: BenchmarkDataType;
cycle_id: number;
enable_pvwatts: boolean;
meter: { start_date: string; end_date: string };
min_model_r_squared: number;
preprocess_meters: boolean;
portfolio_analysis: boolean;
savings_target: SavingsTarget;
select_meters: SelectMetersType;
}

export type AnalysisConfig = BETTERConfig | Record<string, unknown>

export type BSyncrModelTypes = 'Simple Linear Regression' | 'Three bsyncrOptionsParameter Linear Model Cooling' | 'Three Parameter Linear Model Heating' | 'Four Parameter Linear Model'
10 changes: 5 additions & 5 deletions src/@seed/api/data-quality/data-quality.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { catchError, map, type Observable, ReplaySubject, switchMap, tap } from
import { OrganizationService } from '@seed/api'
import { ErrorService } from '@seed/services'
import { SnackBarService } from 'app/core/snack-bar/snack-bar.service'
import type { DQCProgressResponse } from '../progress'
import type { FullProgressResponse } from '../progress'
import type { DataQualityResults, DataQualityResultsResponse, Rule } from './data-quality.types'

@Injectable({ providedIn: 'root' })
Expand Down Expand Up @@ -81,24 +81,24 @@ export class DataQualityService {
)
}

startDataQualityCheckForImportFile(orgId: number, importFileId: number): Observable<DQCProgressResponse> {
startDataQualityCheckForImportFile(orgId: number, importFileId: number): Observable<FullProgressResponse> {
const url = `/api/v3/import_files/${importFileId}/start_data_quality_checks/?organization_id=${orgId}`
return this._httpClient.post<DQCProgressResponse>(url, {})
return this._httpClient.post<FullProgressResponse>(url, {})
.pipe(
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error starting data quality checks for import file')
}),
)
}

startDataQualityCheckForOrg(orgId: number, property_view_ids: number[], taxlot_view_ids: number[], goal_id: number): Observable<DQCProgressResponse> {
startDataQualityCheckForOrg(orgId: number, property_view_ids: number[], taxlot_view_ids: number[], goal_id: number): Observable<FullProgressResponse> {
const url = `/api/v3/data_quality_checks/${orgId}/start/`
const data = {
property_view_ids,
taxlot_view_ids,
goal_id,
}
return this._httpClient.post<DQCProgressResponse>(url, data).pipe(
return this._httpClient.post<FullProgressResponse>(url, data).pipe(
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error fetching data quality results for organization')
}),
Expand Down
72 changes: 72 additions & 0 deletions src/@seed/api/geocode/geocode.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { HttpErrorResponse } from '@angular/common/http'
import { HttpClient } from '@angular/common/http'
import { inject, Injectable } from '@angular/core'
import type { Observable } from 'rxjs'
import { catchError } from 'rxjs'
import { ErrorService } from '@seed/services'
import type { InventoryType } from 'app/modules/inventory'
import type { ConfidenceSummary, GeocodingColumns } from './geocode.types'

@Injectable({ providedIn: 'root' })
export class GeocodeService {
private _httpClient = inject(HttpClient)
private _errorService = inject(ErrorService)

geocode(orgId: number, viewIds: number[], type: InventoryType): Observable<unknown> {
const url = `/api/v3/geocode/geocode_by_ids/&organization_id=${orgId}`
const data = {
property_view_ids: type === 'taxlots' ? [] : viewIds,
taxlot_view_ids: type === 'taxlots' ? viewIds : [],
}
return this._httpClient.post(url, data)
.pipe(
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Geocode Error')
}),
)
}

confidenceSummary(orgId: number, viewIds: number[], type: InventoryType): Observable<ConfidenceSummary> {
const url = `/api/v3/geocode/confidence_summary/?organization_id=${orgId}`
const data = {
property_view_ids: type === 'taxlots' ? [] : viewIds,
taxlot_view_ids: type === 'taxlots' ? viewIds : [],
}
return this._httpClient.post<ConfidenceSummary>(url, data)
.pipe(
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Geocode Confidence Summary Error')
}),
)
}

checkApiKey(orgId: number): Observable<boolean> {
const url = `/api/v3/organizations/${orgId}/geocode_api_key_exists/`
return this._httpClient.get<boolean>(url)
.pipe(
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Geocode API Key Check Error')
}),
)
}

geocodingEnabled(orgId: number): Observable<boolean> {
const url = `/api/v3/organizations/${orgId}/geocoding_enabled/`
return this._httpClient.get<boolean>(url)
.pipe(
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Geocoding Enabled Check Error')
}),
)
}

geocodingColumns(orgId: number): Observable<GeocodingColumns> {
const url = `/api/v3/organizations/${orgId}/geocoding_columns/`
return this._httpClient.get<GeocodingColumns>(url)
.pipe(
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Geocoding Columns Error')
}),
)
}
}
18 changes: 18 additions & 0 deletions src/@seed/api/geocode/geocode.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export type ConfidenceSummary = {
properties: InventoryConfidenceSummary;
taxlots: InventoryConfidenceSummary;
}

export type InventoryConfidenceSummary = {
census_geocoder: number;
high_confidence: number;
low_confidence: number;
manual: number;
missing_address_components: number;
not_geocoded: number;
}

export type GeocodingColumns = {
PropertyState: string[]; // column_name
TaxLotState: string[];
}
2 changes: 2 additions & 0 deletions src/@seed/api/geocode/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './geocode.service'
export * from './geocode.types'
22 changes: 20 additions & 2 deletions src/@seed/api/groups/groups.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { inject, Injectable } from '@angular/core'
import { BehaviorSubject, catchError, map, type Observable, take, tap } from 'rxjs'
import { ErrorService } from '@seed/services'
import { SnackBarService } from 'app/core/snack-bar/snack-bar.service'
import type { InventoryType } from 'app/modules/inventory'
import { OrganizationService } from '../organization'
import type { InventoryGroup, InventoryGroupResponse, InventoryGroupsResponse } from './groups.types'

Expand Down Expand Up @@ -34,8 +35,9 @@ export class GroupsService {
.subscribe()
}

listForInventory(orgId: number, inventoryIds: number[]) {
const url = `/api/v3/inventory_groups/filter/?organization_id=${orgId}&inventory_type=properties`
// inventoryIds (Property/TaxLot[]) are not viewIds
listForInventory(orgId: number, inventoryIds: number[], type: InventoryType) {
const url = `/api/v3/inventory_groups/filter/?organization_id=${orgId}&inventory_type=${type}`
const body = { selected: inventoryIds }
this._httpClient
.post<InventoryGroupsResponse>(url, body)
Expand Down Expand Up @@ -92,4 +94,20 @@ export class GroupsService {
}),
)
}

bulkUpdate(orgId: number, addGroupIds: number[], removeGroupIds: number[], viewIds: number[], type: 'property' | 'tax_lot'): Observable<unknown> {
const url = `/api/v3/inventory_group_mappings/put/?organization_id=${orgId}`
const data = {
inventory_ids: viewIds,
add_group_ids: addGroupIds,
remove_group_ids: removeGroupIds,
inventory_type: type,
}
return this._httpClient.put(url, data).pipe(
tap(() => { this.list(orgId) }),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error updating groups')
}),
)
}
}
1 change: 1 addition & 0 deletions src/@seed/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from './cycle'
export * from './data-quality'
export * from './dataset'
export * from './derived-column'
export * from './geocode'
export * from './groups'
export * from './inventory'
export * from './label'
Expand Down
75 changes: 74 additions & 1 deletion src/@seed/api/inventory/inventory.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { HttpErrorResponse } from '@angular/common/http'
import { HttpClient } from '@angular/common/http'
import { inject, Injectable } from '@angular/core'
import type { Observable } from 'rxjs'
import { BehaviorSubject, catchError, map, tap, throwError } from 'rxjs'
import { BehaviorSubject, catchError, map, take, tap, throwError } from 'rxjs'
import { ErrorService } from '@seed/services'
import { SnackBarService } from 'app/core/snack-bar/snack-bar.service'
import type {
Expand All @@ -12,6 +12,7 @@ import type {
GenericView,
GenericViewsResponse,
InventoryDisplayType,
InventoryExportData,
InventoryType,
InventoryTypeGoal,
NewProfileData,
Expand All @@ -22,6 +23,7 @@ import type {
UpdateInventoryResponse,
ViewResponse,
} from 'app/modules/inventory/inventory.types'
import type { ProgressResponse } from '../progress'
import { UserService } from '../user'

@Injectable({ providedIn: 'root' })
Expand Down Expand Up @@ -324,4 +326,75 @@ export class InventoryService {
}),
)
}

movePropertiesToAccessLevelInstance(orgId: number, aliId: number, viewIds: number[]): Observable<unknown> {
const url = `/api/v3/properties/move_properties_to/?organization_id=${orgId}`
const data = { property_view_ids: viewIds, access_level_instance_id: aliId }
return this._httpClient.post(url, data).pipe(
tap(() => { this._snackBar.success('Properties moved successfully') }),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error moving properties')
}),
)
}

updateDerivedData(orgId: number, propertyViewIds: number[], taxlotViewIds: number[]): Observable<ProgressResponse> {
const url = '/api/v3/tax_lot_properties/update_derived_data/'
const data = {
organization_id: orgId,
property_view_ids: propertyViewIds,
taxlot_view_ids: taxlotViewIds,
}
return this._httpClient.post<ProgressResponse>(url, data).pipe(
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error updating derived data')
}),
)
}

startInventoryExport(orgId: number): Observable<ProgressResponse> {
const url = `/api/v3/tax_lot_properties/start_export/?organization_id=${orgId}`
return this._httpClient.get<ProgressResponse>(url).pipe(
take(1),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error starting export')
}),
)
}

exportInventory(orgId: number, type: InventoryType, data: InventoryExportData): Observable<Blob> {
const url = `/api/v3/tax_lot_properties/export/?inventory_type=${type}&organization_id=${orgId}`
return this._httpClient.post(url, data, { responseType: 'blob' }).pipe(
take(1),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error starting export')
}),
)
}

startRefreshMetadata(orgId: number): Observable<ProgressResponse> {
const url = `/api/v3/tax_lot_properties/start_set_update_to_now/?organization_id=${orgId}`
return this._httpClient.get<ProgressResponse>(url).pipe(
take(1),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error starting metadata refresh')
}),
)
}

refreshMetadata(orgId: number, propertyViews: number[], taxlotViews: number[], progressKey: string): Observable<ProgressResponse> {
const url = '/api/v3/tax_lot_properties/set_update_to_now/'
const data = {
organization_id: orgId,
property_views: propertyViews,
taxlot_views: taxlotViews,
progress_key: progressKey,
}
return this._httpClient.post<ProgressResponse>(url, data).pipe(
take(1),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error refreshing metadata')
}),
)
}
}
Loading