python-casahome 0.2.0__py3-none-any.whl
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.
- casahome/__init__.py +30 -0
- casahome/_base_http.py +21 -0
- casahome/api.py +1135 -0
- casahome/auth.py +199 -0
- casahome/client.py +168 -0
- casahome/const.py +34 -0
- casahome/exceptions.py +52 -0
- casahome/models/__init__.py +477 -0
- casahome/models/_base.py +25 -0
- casahome/models/ai.py +58 -0
- casahome/models/assets.py +123 -0
- casahome/models/availability.py +39 -0
- casahome/models/bootstrap.py +134 -0
- casahome/models/calendar.py +33 -0
- casahome/models/catalogs.py +73 -0
- casahome/models/credits.py +29 -0
- casahome/models/documents.py +127 -0
- casahome/models/enums.py +183 -0
- casahome/models/feed.py +30 -0
- casahome/models/help_center.py +45 -0
- casahome/models/home.py +175 -0
- casahome/models/images.py +48 -0
- casahome/models/mailbox.py +160 -0
- casahome/models/maintenance.py +200 -0
- casahome/models/membership.py +65 -0
- casahome/models/paint.py +56 -0
- casahome/models/preferences.py +104 -0
- casahome/models/purchases.py +106 -0
- casahome/models/read_todos.py +22 -0
- casahome/models/referral.py +21 -0
- casahome/models/requests.py +149 -0
- casahome/models/tasks.py +77 -0
- casahome/models/taxes.py +47 -0
- casahome/models/todos.py +52 -0
- casahome/models/visits.py +158 -0
- casahome/models/write_contracts.py +147 -0
- casahome/py.typed +0 -0
- casahome/streaming.py +278 -0
- python_casahome-0.2.0.dist-info/METADATA +272 -0
- python_casahome-0.2.0.dist-info/RECORD +42 -0
- python_casahome-0.2.0.dist-info/WHEEL +4 -0
- python_casahome-0.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Static availability detail, never slot submission. UNVERIFIED wire shape.
|
|
2
|
+
|
|
3
|
+
Fields inferred from the app. Status remains a string so new values survive.
|
|
4
|
+
Integer times are retained without promoting their units to observed evidence.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from casahome.models._base import CasaModel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MemberAvailabilitySummary(CasaModel):
|
|
11
|
+
id: str
|
|
12
|
+
request_id: str
|
|
13
|
+
home_id: str
|
|
14
|
+
status: str
|
|
15
|
+
slot_count: int
|
|
16
|
+
first_submitted_at: int | None = None
|
|
17
|
+
last_submitted_at: int | None = None
|
|
18
|
+
created_at: int
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MemberAvailabilitySlot(CasaModel):
|
|
22
|
+
id: str
|
|
23
|
+
member_availability_request_id: str
|
|
24
|
+
start_time: int
|
|
25
|
+
end_time: int
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class MemberAvailabilityConstraints(CasaModel):
|
|
29
|
+
snap_minutes: int
|
|
30
|
+
min_slot_minutes: int
|
|
31
|
+
default_duration_minutes: int
|
|
32
|
+
lead_time_minutes: int
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class MemberAvailabilityDetail(CasaModel):
|
|
36
|
+
availability_request: MemberAvailabilitySummary
|
|
37
|
+
slots: list[MemberAvailabilitySlot]
|
|
38
|
+
home_timezone: str
|
|
39
|
+
constraints: MemberAvailabilityConstraints
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Models for ``POST /protected/member/bootstrap`` (VERIFIED)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from pydantic import Field
|
|
9
|
+
|
|
10
|
+
from ._base import CasaModel, epoch_to_dt
|
|
11
|
+
from .enums import HomeStatusField, PaymentMethodTypeField
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class UserDeviceRequest(CasaModel):
|
|
15
|
+
device_id: str
|
|
16
|
+
device_name: str
|
|
17
|
+
system_name: str
|
|
18
|
+
system_version: str
|
|
19
|
+
model: str
|
|
20
|
+
language: str
|
|
21
|
+
timezone: str
|
|
22
|
+
app: str
|
|
23
|
+
app_version: str
|
|
24
|
+
app_build: str
|
|
25
|
+
fcm_token: str | None = None
|
|
26
|
+
voip_token: str | None = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class HomeBadgeCounts(CasaModel):
|
|
30
|
+
computed_at: int
|
|
31
|
+
total: int
|
|
32
|
+
features: dict[str, int] = Field(default_factory=dict)
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def computed_at_dt(self) -> datetime | None:
|
|
36
|
+
return epoch_to_dt(self.computed_at)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class PendingAgreement(CasaModel):
|
|
40
|
+
"""``MemberSimpleHomeAgreement``. ``content`` is the full agreement text."""
|
|
41
|
+
|
|
42
|
+
id: str
|
|
43
|
+
name: str
|
|
44
|
+
content: str
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class AppUpdate(CasaModel):
|
|
48
|
+
force_update_required: bool
|
|
49
|
+
current_build: str
|
|
50
|
+
min_required_build: str
|
|
51
|
+
message: str
|
|
52
|
+
update_url: str | None = None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class MemberWaitlistEntry(CasaModel):
|
|
56
|
+
id: str
|
|
57
|
+
address_street_number: str
|
|
58
|
+
address_street: str
|
|
59
|
+
address_city: str
|
|
60
|
+
address_state: str
|
|
61
|
+
address_postal_code: str
|
|
62
|
+
status: str
|
|
63
|
+
created_at: int
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class MemberSimpleHome(CasaModel):
|
|
67
|
+
id: str
|
|
68
|
+
address_street_number: str
|
|
69
|
+
address_street: str
|
|
70
|
+
address_city: str
|
|
71
|
+
address_state: str
|
|
72
|
+
address_postal_code: str
|
|
73
|
+
latitude: float | None = None
|
|
74
|
+
longitude: float | None = None
|
|
75
|
+
timezone: str | None = None
|
|
76
|
+
status: HomeStatusField
|
|
77
|
+
card_surcharge_rate: float = 0.0
|
|
78
|
+
has_payment_method: bool = False
|
|
79
|
+
membership_active: bool = False
|
|
80
|
+
is_multi_user_home: bool = False
|
|
81
|
+
has_pending_post_onboarding: bool = False
|
|
82
|
+
pending_agreements: list[PendingAgreement] = Field(default_factory=list)
|
|
83
|
+
address_subpremise: str | None = None
|
|
84
|
+
home_image_url: str | None = None
|
|
85
|
+
home_image_url_cache_key: str | None = None
|
|
86
|
+
payment_description: str | None = None
|
|
87
|
+
payment_method_type: PaymentMethodTypeField | None = None
|
|
88
|
+
payment_method_last4: str | None = None
|
|
89
|
+
payment_method_brand: str | None = None
|
|
90
|
+
membership_name: str | None = None
|
|
91
|
+
membership_plan: Any | None = None
|
|
92
|
+
membership_amount: str | None = None
|
|
93
|
+
membership_next_renewal: int | None = None
|
|
94
|
+
matterport_model_id: str | None = None
|
|
95
|
+
matterport_model_password: str | None = None
|
|
96
|
+
badge_counts: HomeBadgeCounts | None = None
|
|
97
|
+
# Observed in traffic but absent from the app's Codable model.
|
|
98
|
+
plan_state: str | None = None
|
|
99
|
+
route: str | None = None
|
|
100
|
+
is_primary_member: bool | None = None
|
|
101
|
+
past_due_until: int | None = None
|
|
102
|
+
scheduled_change: Any | None = None
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def display_address(self) -> str:
|
|
106
|
+
street = f"{self.address_street_number} {self.address_street}"
|
|
107
|
+
if self.address_subpremise:
|
|
108
|
+
street = f"{street} {self.address_subpremise}"
|
|
109
|
+
return f"{street}, {self.address_city}, {self.address_state} {self.address_postal_code}"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class Bootstrap(CasaModel):
|
|
113
|
+
id: str
|
|
114
|
+
created_at: int
|
|
115
|
+
phone_number: str
|
|
116
|
+
email: str
|
|
117
|
+
first_name: str
|
|
118
|
+
last_name: str
|
|
119
|
+
homes: list[MemberSimpleHome] = Field(default_factory=list)
|
|
120
|
+
waitlist_entries: list[MemberWaitlistEntry] = Field(default_factory=list)
|
|
121
|
+
is_casa_collective: bool = False
|
|
122
|
+
avatar_url: str | None = None
|
|
123
|
+
avatar_url_cache_key: str | None = None
|
|
124
|
+
app_update: AppUpdate | None = None
|
|
125
|
+
# Observed in traffic but absent from the app's Codable model.
|
|
126
|
+
calendar_sync_active: bool | None = None
|
|
127
|
+
calendar_sync_email: str | None = None
|
|
128
|
+
calendar_sync_insufficient_scope: bool | None = None
|
|
129
|
+
calendar_sync_options: list[Any] = Field(default_factory=list)
|
|
130
|
+
user_prefs: list[Any] = Field(default_factory=list)
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def created_at_dt(self) -> datetime | None:
|
|
134
|
+
return epoch_to_dt(self.created_at)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Visit calendar models (UNVERIFIED endpoint)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from pydantic import Field
|
|
9
|
+
|
|
10
|
+
from ._base import CasaModel, epoch_to_dt
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CalendarEvent(CasaModel):
|
|
14
|
+
"""``VisitCalendarEvent``. ``kind`` is a Swift enum with associated payloads
|
|
15
|
+
(vendor visit or DTD), so its wire shape is unknown. Kept loose until traffic
|
|
16
|
+
shows it."""
|
|
17
|
+
|
|
18
|
+
id: str
|
|
19
|
+
start_time: int
|
|
20
|
+
end_time: int
|
|
21
|
+
kind: Any = None
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def start_dt(self) -> datetime | None:
|
|
25
|
+
return epoch_to_dt(self.start_time)
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def end_dt(self) -> datetime | None:
|
|
29
|
+
return epoch_to_dt(self.end_time)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class VisitsCalendarResponse(CasaModel):
|
|
33
|
+
events: list[CalendarEvent] = Field(default_factory=list)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Inventory catalogs and recurring vendors. Vendor list items remain UNVERIFIED."""
|
|
2
|
+
|
|
3
|
+
from pydantic import Field, JsonValue, RootModel
|
|
4
|
+
|
|
5
|
+
from casahome.models._base import CasaModel
|
|
6
|
+
from casahome.models.home import EntityMake
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RoomType(CasaModel):
|
|
10
|
+
id: str
|
|
11
|
+
name: str
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RoomTypesResponse(RootModel[list[RoomType]]):
|
|
15
|
+
"""The room type catalog is a bare list."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class InventoryMakesResponse(CasaModel):
|
|
19
|
+
makes: list[EntityMake]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class InventoryEnumOption(CasaModel):
|
|
23
|
+
"""One inventory enum option. VERIFIED populated (live GET, 2026-10)."""
|
|
24
|
+
|
|
25
|
+
id: str
|
|
26
|
+
type: str
|
|
27
|
+
name: str
|
|
28
|
+
symbol_name: str | None = None
|
|
29
|
+
category: str | None = None
|
|
30
|
+
icon_url: str | None = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class InventoryEnumsResponse(CasaModel):
|
|
34
|
+
"""Inventory enum catalog. VERIFIED envelope (live GET, 2026-10)."""
|
|
35
|
+
|
|
36
|
+
enums: list[InventoryEnumOption]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class RecurringVendorCategory(CasaModel):
|
|
40
|
+
id: str
|
|
41
|
+
name: str
|
|
42
|
+
sort_order: int
|
|
43
|
+
primary_color: str
|
|
44
|
+
icon_url: str
|
|
45
|
+
icon_symbol_name: str
|
|
46
|
+
pattern_url: str
|
|
47
|
+
vendor_type_description: str
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class RecurringVendorCategoriesResponse(RootModel[list[RecurringVendorCategory]]):
|
|
51
|
+
"""Populated category metadata observed."""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class RecurringVendor(CasaModel):
|
|
55
|
+
"""Minimal static SimpleDtd fields. Nested events and invoices remain opaque."""
|
|
56
|
+
|
|
57
|
+
id: str | None = None
|
|
58
|
+
home_id: str | None = None
|
|
59
|
+
vendor_id: str | None = None
|
|
60
|
+
vendor_name: str | None = None
|
|
61
|
+
service_category: str | None = None
|
|
62
|
+
dtd_category_id: str | None = None
|
|
63
|
+
rrule: str | None = None
|
|
64
|
+
start_date: str | None = None
|
|
65
|
+
duration_seconds: int | None = None
|
|
66
|
+
next_visit_epoch_seconds: int | None = None
|
|
67
|
+
payment_pending: bool | None = None
|
|
68
|
+
events: list[JsonValue] = Field(default_factory=list)
|
|
69
|
+
invoices: list[JsonValue] = Field(default_factory=list)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class RecurringVendorsResponse(RootModel[list[RecurringVendor]]):
|
|
73
|
+
"""Bare list. Empty in the saved response."""
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Credits models (UNVERIFIED endpoint)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
|
|
7
|
+
from ._base import CasaModel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CreditHistory(CasaModel):
|
|
11
|
+
id: str
|
|
12
|
+
issued_at: int
|
|
13
|
+
amount: int
|
|
14
|
+
available_amount: int
|
|
15
|
+
expires_at: int | None = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ConsumptionHistory(CasaModel):
|
|
19
|
+
id: str
|
|
20
|
+
date: int
|
|
21
|
+
consumed_amount: int
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class MemberCredits(CasaModel):
|
|
25
|
+
home_id: str
|
|
26
|
+
total_available: int
|
|
27
|
+
handyman_hourly_rate_cents: int
|
|
28
|
+
credits_history: list[CreditHistory] = Field(default_factory=list)
|
|
29
|
+
consumption_history: list[ConsumptionHistory] = Field(default_factory=list)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Document envelopes observed empty. Item schemas are static and UNVERIFIED."""
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
|
|
5
|
+
from casahome.models._base import CasaModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DocumentOrganization(CasaModel):
|
|
9
|
+
id: str | None = None
|
|
10
|
+
status: str | None = None
|
|
11
|
+
error_code: str | None = None
|
|
12
|
+
can_retry: bool | None = None
|
|
13
|
+
completed_at: int | None = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DocumentExtraction(DocumentOrganization):
|
|
17
|
+
pending_count: int | None = None
|
|
18
|
+
pending_structure_count: int | None = None
|
|
19
|
+
accepted_count: int | None = None
|
|
20
|
+
dismissed_count: int | None = None
|
|
21
|
+
already_handled_count: int | None = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class HomeDocument(CasaModel):
|
|
25
|
+
id: str | None = None
|
|
26
|
+
home_id: str | None = None
|
|
27
|
+
original_filename: str | None = None
|
|
28
|
+
name: str | None = None
|
|
29
|
+
document_type: str | None = None
|
|
30
|
+
document_date: str | None = None
|
|
31
|
+
mimetype: str | None = None
|
|
32
|
+
size_bytes: int | None = None
|
|
33
|
+
uploaded_by_name: str | None = None
|
|
34
|
+
created_at: int | None = None
|
|
35
|
+
updated_at: int | None = None
|
|
36
|
+
organization: DocumentOrganization | None = None
|
|
37
|
+
extraction: DocumentExtraction | None = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class DocumentsResponse(CasaModel):
|
|
41
|
+
documents: list[HomeDocument]
|
|
42
|
+
next_cursor: str | None = None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class DocumentType(CasaModel):
|
|
46
|
+
key: str
|
|
47
|
+
label: str
|
|
48
|
+
description: str
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class DocumentTypesResponse(CasaModel):
|
|
52
|
+
"""Populated document policy and upload limits were observed."""
|
|
53
|
+
|
|
54
|
+
types: list[DocumentType]
|
|
55
|
+
max_file_size_bytes: int
|
|
56
|
+
max_files_per_upload: int
|
|
57
|
+
allowed_mimetypes: list[str]
|
|
58
|
+
max_organization_size_bytes: int | None = None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class DocumentCitation(CasaModel):
|
|
62
|
+
page_index: int | None = None
|
|
63
|
+
page_label: str | None = None
|
|
64
|
+
excerpt: str | None = None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class WorkDocumentSource(CasaModel):
|
|
68
|
+
document_id: str | None = None
|
|
69
|
+
name: str | None = None
|
|
70
|
+
available: bool | None = None
|
|
71
|
+
document_date: str | None = None
|
|
72
|
+
citations: list[DocumentCitation] = Field(default_factory=list)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class WorkDocumentSourceResponse(CasaModel):
|
|
76
|
+
source: WorkDocumentSource | None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class DocumentDetailsResponse(CasaModel):
|
|
80
|
+
"""UNVERIFIED document detail envelope."""
|
|
81
|
+
|
|
82
|
+
document: HomeDocument
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class DocumentSuggestion(CasaModel):
|
|
86
|
+
"""Static document suggestion fields."""
|
|
87
|
+
|
|
88
|
+
id: str
|
|
89
|
+
todo_id: str
|
|
90
|
+
title: str
|
|
91
|
+
action: str
|
|
92
|
+
reason: str
|
|
93
|
+
area: str | None = None
|
|
94
|
+
citations: list[DocumentCitation]
|
|
95
|
+
state: str
|
|
96
|
+
work_deleted: bool
|
|
97
|
+
task_id: str | None = None
|
|
98
|
+
request_id: str | None = None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class DocumentSuggestionsResponse(CasaModel):
|
|
102
|
+
"""UNVERIFIED suggestions envelope."""
|
|
103
|
+
|
|
104
|
+
suggestions: list[DocumentSuggestion]
|
|
105
|
+
analysis_status: str | None = None
|
|
106
|
+
limitations: list[str]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class DocumentStructureSuggestion(CasaModel):
|
|
110
|
+
"""Static structure suggestion metadata."""
|
|
111
|
+
|
|
112
|
+
id: str
|
|
113
|
+
section: str
|
|
114
|
+
title: str
|
|
115
|
+
current_values: list[str]
|
|
116
|
+
current_label: str
|
|
117
|
+
proposed_label: str
|
|
118
|
+
reason: str
|
|
119
|
+
state: str
|
|
120
|
+
changed_since_analysis: bool
|
|
121
|
+
citations: list[DocumentCitation]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class DocumentStructureSuggestionsResponse(CasaModel):
|
|
125
|
+
"""UNVERIFIED structure suggestion page."""
|
|
126
|
+
|
|
127
|
+
suggestions: list[DocumentStructureSuggestion]
|
casahome/models/enums.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""String enums transcribed from the Casa app binary.
|
|
2
|
+
|
|
3
|
+
Values are the Swift case names. Model fields type these as ``Enum | str`` so an
|
|
4
|
+
unseen value from the server degrades to a plain string instead of failing.
|
|
5
|
+
The Swift ``unknown`` fallback case is included because the app defines it,
|
|
6
|
+
but the server is not expected to send it.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from enum import StrEnum
|
|
12
|
+
from typing import Annotated
|
|
13
|
+
|
|
14
|
+
from pydantic import Field
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MemberSimpleHomeStatus(StrEnum):
|
|
18
|
+
ONBOARDING = "onboarding"
|
|
19
|
+
ACTIVE = "active"
|
|
20
|
+
INACTIVE = "inactive"
|
|
21
|
+
UNKNOWN = "unknown"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PaymentMethodType(StrEnum):
|
|
25
|
+
CARD = "card"
|
|
26
|
+
US_BANK_ACCOUNT = "us_bank_account"
|
|
27
|
+
UNKNOWN = "unknown"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class RequestType(StrEnum):
|
|
31
|
+
CONCIERGE = "concierge"
|
|
32
|
+
HANDYMAN = "handyman"
|
|
33
|
+
THIRD_PARTY = "third_party"
|
|
34
|
+
UNKNOWN = "unknown"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class RequestStatus(StrEnum):
|
|
38
|
+
PENDING_CONCIERGE = "pending_concierge"
|
|
39
|
+
PENDING_MEMBER = "pending_member"
|
|
40
|
+
PENDING_VENDOR = "pending_vendor"
|
|
41
|
+
COMPLETED = "completed"
|
|
42
|
+
CANCELED = "canceled"
|
|
43
|
+
UNKNOWN = "unknown"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class SenderRole(StrEnum):
|
|
47
|
+
CONCIERGE = "concierge"
|
|
48
|
+
MEMBER = "member"
|
|
49
|
+
VENDOR = "vendor"
|
|
50
|
+
SYSTEM = "system"
|
|
51
|
+
UNKNOWN = "unknown"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class VisitType(StrEnum):
|
|
55
|
+
HANDYMAN = "handyman"
|
|
56
|
+
THIRD_PARTY = "third_party"
|
|
57
|
+
UNKNOWN = "unknown"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class VendorVisitStatus(StrEnum):
|
|
61
|
+
DRAFT = "draft"
|
|
62
|
+
SCHEDULED = "scheduled"
|
|
63
|
+
IN_PROGRESS = "in_progress"
|
|
64
|
+
WRAP_UP = "wrap_up"
|
|
65
|
+
COMPLETED = "completed"
|
|
66
|
+
CANCELED = "canceled"
|
|
67
|
+
UNKNOWN = "unknown"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class TodoStatus(StrEnum):
|
|
71
|
+
PREPARING = "preparing"
|
|
72
|
+
NEEDS_SUPPORT_CONFIRMATION = "needs_support_confirmation"
|
|
73
|
+
NEEDS_INPUT = "needs_input"
|
|
74
|
+
READY_FOR_CONCIERGE = "ready_for_concierge"
|
|
75
|
+
FAILED = "failed"
|
|
76
|
+
MATERIALIZED = "materialized"
|
|
77
|
+
SUGGESTION_CONCIERGE_APPROVAL_PENDING = "suggestion_concierge_approval_pending"
|
|
78
|
+
SUGGESTION_CONCIERGE_APPROVED = "suggestion_concierge_approved"
|
|
79
|
+
SUGGESTION_CONCIERGE_REJECTED = "suggestion_concierge_rejected"
|
|
80
|
+
SUGGESTION_MEMBER_REVIEW_PENDING = "suggestion_member_review_pending"
|
|
81
|
+
SUGGESTION_MEMBER_REJECTED = "suggestion_member_rejected"
|
|
82
|
+
UNKNOWN = "unknown"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class TodoCreatorRole(StrEnum):
|
|
86
|
+
MEMBER = "member"
|
|
87
|
+
CONCIERGE = "concierge"
|
|
88
|
+
VENDOR = "vendor"
|
|
89
|
+
CASA_AI = "casa_ai"
|
|
90
|
+
UNKNOWN = "unknown"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class TodoLaneCardKind(StrEnum):
|
|
94
|
+
TODO = "todo"
|
|
95
|
+
TASK = "task"
|
|
96
|
+
UNKNOWN = "unknown"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class TodoLaneCardState(StrEnum):
|
|
100
|
+
PREPARING = "preparing"
|
|
101
|
+
NEEDS_INPUT = "needs_input"
|
|
102
|
+
READY_FOR_CONCIERGE = "ready_for_concierge"
|
|
103
|
+
FAILED = "failed"
|
|
104
|
+
READY_FOR_SCHEDULING = "ready_for_scheduling"
|
|
105
|
+
UNKNOWN = "unknown"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class PackageDirection(StrEnum):
|
|
109
|
+
INBOUND = "inbound"
|
|
110
|
+
OUTBOUND = "outbound"
|
|
111
|
+
UNKNOWN = "unknown"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class PackagePickupStatus(StrEnum):
|
|
115
|
+
AWAITING_PICKUP = "awaiting_pickup"
|
|
116
|
+
PICKED_UP = "picked_up"
|
|
117
|
+
DROPPED_OFF = "dropped_off"
|
|
118
|
+
MISSED_PICKUP = "missed_pickup"
|
|
119
|
+
UNKNOWN = "unknown"
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class WeatherCondition(StrEnum):
|
|
123
|
+
BLIZZARD = "blizzard"
|
|
124
|
+
BLOWING_SNOW = "blowingSnow"
|
|
125
|
+
BREEZY = "breezy"
|
|
126
|
+
CLEAR = "clear"
|
|
127
|
+
CLOUDY = "cloudy"
|
|
128
|
+
DRIZZLE = "drizzle"
|
|
129
|
+
DUST = "dust"
|
|
130
|
+
FOG = "fog"
|
|
131
|
+
FREEZING_DRIZZLE = "freezingDrizzle"
|
|
132
|
+
FREEZING_RAIN = "freezingRain"
|
|
133
|
+
FLURRIES = "flurries"
|
|
134
|
+
FRIGID = "frigid"
|
|
135
|
+
HAIL = "hail"
|
|
136
|
+
HAZE = "haze"
|
|
137
|
+
HEAVY_RAIN = "heavyRain"
|
|
138
|
+
HEAVY_SNOW = "heavySnow"
|
|
139
|
+
HOT = "hot"
|
|
140
|
+
HURRICANE = "hurricane"
|
|
141
|
+
ISOLATED_THUNDERSTORMS = "isolatedThunderstorms"
|
|
142
|
+
MOSTLY_CLEAR = "mostlyClear"
|
|
143
|
+
MOSTLY_CLOUDY = "mostlyCloudy"
|
|
144
|
+
MIXED_RAIN_AND_SLEET = "mixedRainAndSleet"
|
|
145
|
+
MIXED_RAIN_AND_SNOW = "mixedRainAndSnow"
|
|
146
|
+
MIXED_RAINFALL = "mixedRainfall"
|
|
147
|
+
MIXED_SNOW_AND_SLEET = "mixedSnowAndSleet"
|
|
148
|
+
PARTLY_CLOUDY = "partlyCloudy"
|
|
149
|
+
RAIN = "rain"
|
|
150
|
+
SCATTERED_SHOWERS = "scatteredShowers"
|
|
151
|
+
SCATTERED_SNOW_SHOWERS = "scatteredSnowShowers"
|
|
152
|
+
SCATTERED_THUNDERSTORMS = "scatteredThunderstorms"
|
|
153
|
+
SEVERE_THUNDERSTORM = "severeThunderstorm"
|
|
154
|
+
SHOWERS = "showers"
|
|
155
|
+
SLEET = "sleet"
|
|
156
|
+
SMOKE = "smoke"
|
|
157
|
+
SNOW = "snow"
|
|
158
|
+
SNOW_SHOWERS = "snowShowers"
|
|
159
|
+
THUNDERSTORM = "thunderstorm"
|
|
160
|
+
TORNADO = "tornado"
|
|
161
|
+
TROPICAL_STORM = "tropicalStorm"
|
|
162
|
+
WINDY = "windy"
|
|
163
|
+
UNKNOWN = "unknown"
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# Lenient field types. ``left_to_right`` makes pydantic try the enum first and
|
|
167
|
+
# fall back to ``str`` for values the app binary didn't know about.
|
|
168
|
+
_LTR = Field(union_mode="left_to_right")
|
|
169
|
+
|
|
170
|
+
HomeStatusField = Annotated[MemberSimpleHomeStatus | str, _LTR]
|
|
171
|
+
PaymentMethodTypeField = Annotated[PaymentMethodType | str, _LTR]
|
|
172
|
+
RequestTypeField = Annotated[RequestType | str, _LTR]
|
|
173
|
+
RequestStatusField = Annotated[RequestStatus | str, _LTR]
|
|
174
|
+
SenderRoleField = Annotated[SenderRole | str, _LTR]
|
|
175
|
+
VisitTypeField = Annotated[VisitType | str, _LTR]
|
|
176
|
+
VendorVisitStatusField = Annotated[VendorVisitStatus | str, _LTR]
|
|
177
|
+
TodoStatusField = Annotated[TodoStatus | str, _LTR]
|
|
178
|
+
TodoCreatorRoleField = Annotated[TodoCreatorRole | str, _LTR]
|
|
179
|
+
TodoLaneCardKindField = Annotated[TodoLaneCardKind | str, _LTR]
|
|
180
|
+
TodoLaneCardStateField = Annotated[TodoLaneCardState | str, _LTR]
|
|
181
|
+
PackageDirectionField = Annotated[PackageDirection | str, _LTR]
|
|
182
|
+
PackagePickupStatusField = Annotated[PackagePickupStatus | str, _LTR]
|
|
183
|
+
WeatherConditionField = Annotated[WeatherCondition | str, _LTR]
|
casahome/models/feed.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Home feed cards, ``/protected/home/{home_id}/feed``.
|
|
2
|
+
|
|
3
|
+
VERIFIED by a live GET (2026-10) with ``app_build=698``: an envelope
|
|
4
|
+
``cards`` holding populated items. Every card had the keys modeled below.
|
|
5
|
+
``props`` varies by ``template_id`` (titles, captions, actions, images,
|
|
6
|
+
colors, ``visual_*`` styling, onboarding counts), so it stays a JSON dict.
|
|
7
|
+
``children`` was empty on every observed card. It's typed recursively on the
|
|
8
|
+
assumption children are cards too, which is UNVERIFIED. Unknown fields ride
|
|
9
|
+
along in ``model_extra``.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from pydantic import Field, JsonValue
|
|
15
|
+
|
|
16
|
+
from casahome.models._base import CasaModel
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class HomeFeedCard(CasaModel):
|
|
20
|
+
id: str
|
|
21
|
+
template_id: str
|
|
22
|
+
name: str
|
|
23
|
+
props: dict[str, JsonValue] = Field(default_factory=dict)
|
|
24
|
+
show_starts_at_minutes_after_onboarding: int
|
|
25
|
+
show_ends_at_minutes_after_onboarding: int | None = None
|
|
26
|
+
children: list[HomeFeedCard] = Field(default_factory=list)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class HomeFeedResponse(CasaModel):
|
|
30
|
+
cards: list[HomeFeedCard]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Help center bundle models, inferred from the app.
|
|
2
|
+
|
|
3
|
+
A populated ``member_ios`` bundle parsed with these models in live traffic
|
|
4
|
+
(2026-10). Because ``populate_by_name``
|
|
5
|
+
accepts either spelling, that doesn't confirm the camelCase aliases are the
|
|
6
|
+
wire keys. No other app ID, article-view tracking or feedback is inferred.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from pydantic import Field
|
|
10
|
+
|
|
11
|
+
from casahome.models._base import CasaModel
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class HelpCenterTopic(CasaModel):
|
|
15
|
+
id: str
|
|
16
|
+
slug: str
|
|
17
|
+
name: str
|
|
18
|
+
blurb: str
|
|
19
|
+
system_image: str = Field(alias="systemImage")
|
|
20
|
+
hero_image_url: str | None = Field(default=None, alias="heroImageURL")
|
|
21
|
+
sort_order: int = Field(alias="sortOrder")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class HelpCenterArticle(CasaModel):
|
|
25
|
+
id: str
|
|
26
|
+
topic_id: str = Field(alias="topicId")
|
|
27
|
+
slug: str
|
|
28
|
+
sort_order: int = Field(alias="sortOrder")
|
|
29
|
+
is_top_question: bool = Field(alias="isTopQuestion")
|
|
30
|
+
is_featured: bool = Field(alias="isFeatured")
|
|
31
|
+
article_version_id: str = Field(alias="articleVersionId")
|
|
32
|
+
version_number: int = Field(alias="versionNumber")
|
|
33
|
+
title: str
|
|
34
|
+
summary: str
|
|
35
|
+
body_markdown: str = Field(alias="bodyMarkdown")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class HelpCenterBundle(CasaModel):
|
|
39
|
+
app: str
|
|
40
|
+
topics: list[HelpCenterTopic]
|
|
41
|
+
articles: list[HelpCenterArticle]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class HelpCenterBundleResponse(CasaModel):
|
|
45
|
+
data: HelpCenterBundle
|