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.
@@ -0,0 +1,175 @@
1
+ """Home inventory and weather models.
2
+
3
+ Inventory entity fields are live-verified against ``inventory/v3`` (2026-10).
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from datetime import datetime
9
+ from typing import Any
10
+
11
+ from pydantic import Field
12
+
13
+ from ._base import CasaModel, epoch_to_dt
14
+ from .enums import WeatherConditionField
15
+
16
+
17
+ class SimpleEntityProp(CasaModel):
18
+ id: str
19
+ definition_id: str
20
+ title: str
21
+ key: str
22
+ value: Any = None
23
+ data_type: str | None = None
24
+ is_required_to_publish: bool = False
25
+ is_readonly: bool = False
26
+ format: str | None = None
27
+ enum_details: Any | None = None # TODO: unresolved type SimpleEntityEnumProp
28
+ multi_enums: list[Any] = Field(default_factory=list)
29
+ enum_type: str | None = None
30
+ symbol_name: str | None = None
31
+ help_center_article_id: str | None = None
32
+
33
+
34
+ class HomeRoom(CasaModel):
35
+ id: str
36
+ name: str
37
+ is_removable: bool = False
38
+ room_type_id: str | None = None
39
+ image_url: str | None = None
40
+ image_url_cache_key: str | None = None
41
+ room_properties: list[SimpleEntityProp] = Field(default_factory=list)
42
+
43
+
44
+ class HomeStory(CasaModel):
45
+ id: str
46
+ floor_number: int
47
+ name: str
48
+ window_count: int | None = None
49
+ rooms: list[HomeRoom] = Field(default_factory=list)
50
+
51
+
52
+ class HomeDetails(CasaModel):
53
+ id: str
54
+ display_address: str
55
+ stories: list[HomeStory] = Field(default_factory=list)
56
+
57
+
58
+ PropertyValue = str | int | float | bool | None
59
+ """Scalar value carried by entity and catalog model properties (live-verified)."""
60
+
61
+
62
+ class EntityProperty(SimpleEntityProp):
63
+ """A typed property on an inventory entity, e.g. ``serial_number``.
64
+
65
+ Same wire shape as :class:`SimpleEntityProp`, but the value is narrowed to
66
+ the scalar types observed live on ``inventory/v3``.
67
+ """
68
+
69
+ value: PropertyValue = None
70
+
71
+
72
+ class EntityMake(CasaModel):
73
+ id: str
74
+ name: str
75
+
76
+
77
+ class ModelProperty(CasaModel):
78
+ """A catalog property on a make/model, e.g. key ``summary``."""
79
+
80
+ id: str
81
+ key: str
82
+ caption: str | None = None
83
+ value: PropertyValue = None
84
+ data_type: str | None = None
85
+ format: str | None = None
86
+ symbol_name: str | None = None
87
+
88
+
89
+ class EntityModel(CasaModel):
90
+ id: str
91
+ name: str
92
+ properties: list[ModelProperty] = Field(default_factory=list)
93
+
94
+ def property_value(self, key: str) -> PropertyValue:
95
+ """Return the value of the first catalog property with ``key``, else None."""
96
+ return next((p.value for p in self.properties if p.key == key), None)
97
+
98
+
99
+ class EntityIdentity(CasaModel):
100
+ """How confidently Casa has identified the make and model."""
101
+
102
+ state: str | None = None
103
+ unverified_make: str | None = None
104
+ unverified_model: str | None = None
105
+ candidates: list[Any] = Field(default_factory=list)
106
+
107
+
108
+ class EntityImage(CasaModel):
109
+ """Image metadata. URL and cache-key fields ride along in ``model_extra``."""
110
+
111
+ id: str
112
+ name: str | None = None
113
+ role: str | None = None
114
+ is_hero: bool = False
115
+
116
+
117
+ class SimpleEntity(CasaModel):
118
+ """An inventory item (appliance, fixture, etc.) from ``inventory/v3``."""
119
+
120
+ id: str
121
+ category_id: str
122
+ category: str
123
+ status: str
124
+ type_id: str | None = None
125
+ type: str | None = None
126
+ type_symbol_name: str | None = None
127
+ created_at: int | None = None
128
+ inventoried_by: str | None = None
129
+ failure_reason: str | None = None
130
+ failure_item_label: str | None = None
131
+ issues: list[Any] = Field(default_factory=list)
132
+ rooms: list[str] = Field(default_factory=list)
133
+ stories: list[str] = Field(default_factory=list)
134
+ images: list[EntityImage] = Field(default_factory=list)
135
+ manuals: list[Any] = Field(default_factory=list)
136
+ thumbnail_url: str | None = None
137
+ thumbnail_url_cache_key: str | None = None
138
+ catalog_make_requirement: str | None = None
139
+ catalog_model_requirement: str | None = None
140
+ catalog_model_required: bool | None = None
141
+ make: EntityMake | None = None
142
+ model: EntityModel | None = None
143
+ identity: EntityIdentity | None = None
144
+ entity_properties: list[EntityProperty] = Field(default_factory=list)
145
+ associated_entity_ids: list[str] | None = None
146
+ is_removable: bool = False
147
+ is_retypable: bool | None = None
148
+ removal_blocked_reason: str | None = None
149
+ paint_match: Any | None = None
150
+
151
+ @property
152
+ def created_at_dt(self) -> datetime | None:
153
+ return epoch_to_dt(self.created_at)
154
+
155
+ def property_value(self, key: str) -> PropertyValue:
156
+ """Return the value of the first entity property with ``key``, else None.
157
+
158
+ Example: ``entity.property_value("serial_number")``.
159
+ """
160
+ return next((p.value for p in self.entity_properties if p.key == key), None)
161
+
162
+
163
+ class HomeDetailsResponse(CasaModel):
164
+ home: HomeDetails
165
+ entities: list[SimpleEntity] = Field(default_factory=list)
166
+ room_entity_map: dict[str, list[str]] = Field(default_factory=dict)
167
+ associated_entities: dict[str, list[str]] = Field(default_factory=dict)
168
+
169
+
170
+ class WeatherResponse(CasaModel):
171
+ temperature_degrees: float
172
+ temperature_units: str
173
+ condition_type: WeatherConditionField
174
+ sunrise_at: int | None = None
175
+ sunset_at: int | None = None
@@ -0,0 +1,48 @@
1
+ """Photo metadata only. These models never fetch their URL strings."""
2
+
3
+ from pydantic import RootModel
4
+
5
+ from casahome.models._base import CasaModel
6
+
7
+
8
+ class RoomImageCandidate(CasaModel):
9
+ id: str
10
+ image_url: str
11
+ cache_key: str
12
+ is_current: bool
13
+
14
+
15
+ class RoomMemberImage(CasaModel):
16
+ """UNVERIFIED populated member image. Static Codable fields."""
17
+
18
+ image_url: str | None = None
19
+ cache_key: str | None = None
20
+
21
+
22
+ class RoomImageOptionsResponse(CasaModel):
23
+ matterport_images: list[RoomImageCandidate]
24
+ member_image: RoomMemberImage | None = None
25
+
26
+
27
+ class MatterportImage(CasaModel):
28
+ id: str
29
+ image_url: str
30
+ cache_key: str
31
+ is_home_hero_image: bool
32
+
33
+
34
+ class MatterportImagesResponse(RootModel[list[MatterportImage]]):
35
+ """Bare list of populated image metadata."""
36
+
37
+
38
+ class MediaAttachment(CasaModel):
39
+ """Observed on to-dos. created_at is static and wasn't present there."""
40
+
41
+ id: str
42
+ name: str
43
+ mimetype: str
44
+ size: int
45
+ url: str
46
+ cache_key: str
47
+ thumbnail_url: str | None = None
48
+ created_at: int | None = None
@@ -0,0 +1,160 @@
1
+ """Mailbox and package models (UNVERIFIED endpoints).
2
+
3
+ Mailbox models use ISO 8601 string timestamps. Packages use epoch seconds.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Any
9
+
10
+ from pydantic import Field
11
+
12
+ from ._base import CasaModel
13
+ from .enums import PackageDirectionField, PackagePickupStatusField
14
+
15
+
16
+ class MailboxAddress(CasaModel):
17
+ line1: str
18
+ city: str
19
+ state: str
20
+ postal_code: str
21
+ line2: str | None = None
22
+
23
+
24
+ class MailboxQuota(CasaModel):
25
+ month: str
26
+ used: int
27
+ included: int
28
+ overage_rate_usd_cents: int
29
+ resets_on: str | None = None
30
+
31
+
32
+ class MailboxRecipient(CasaModel):
33
+ id: str
34
+ kind: str
35
+ always_included: bool = False
36
+ first_name: str | None = None
37
+ last_name: str | None = None
38
+ business_name: str | None = None
39
+ is_primary: bool | None = None
40
+
41
+
42
+ class MemberMailbox(CasaModel):
43
+ id: str
44
+ status: str
45
+ onboarding_status: str
46
+ address: MailboxAddress
47
+ quota: MailboxQuota
48
+ claimed_at: str
49
+ recipients: list[MailboxRecipient] = Field(default_factory=list)
50
+ auto_archive_days: int | None = None
51
+
52
+
53
+ class MailboxPlan(CasaModel):
54
+ monthly_piece_quota: int
55
+ overage_rate_usd_cents: int
56
+
57
+
58
+ class MailboxOverviewResponse(CasaModel):
59
+ mailbox: MemberMailbox | None = None
60
+ plan: MailboxPlan | None = None
61
+
62
+
63
+ class MailboxMailItem(CasaModel):
64
+ id: str
65
+ scan_status: str
66
+ received_at: str
67
+ is_returned_to_sender: bool = False
68
+ check_detected: bool = False
69
+ sender: str | None = None
70
+ addressed_to: str | None = None
71
+ summary: str | None = None
72
+ scan_notice_type: str | None = None
73
+ clear_at: str | None = None
74
+ forward_status: str | None = None
75
+ forward_tracking_number: str | None = None
76
+ shred_status: str | None = None
77
+ deposit_status: str | None = None
78
+ archived_at: str | None = None
79
+ read_at: str | None = None
80
+ envelope_image_url: str | None = None
81
+
82
+
83
+ class MailboxMailItemsResponse(CasaModel):
84
+ mail_items: list[MailboxMailItem] = Field(default_factory=list)
85
+
86
+
87
+ class TrackingHistoryData(CasaModel):
88
+ id: str
89
+ source: str
90
+ message: str
91
+ timestamp: int
92
+ status_display_name: str
93
+ status_detail_display_name: str
94
+ city: str | None = None
95
+ state: str | None = None
96
+ country: str | None = None
97
+ zip: str | None = None
98
+ timezone: str | None = None
99
+ latitude: float | None = None
100
+ longitude: float | None = None
101
+
102
+
103
+ class PackageCarrierInfo(CasaModel):
104
+ carrier_id: str
105
+ display_name: str
106
+ logo_url: str
107
+ primary_color: str
108
+ pickup_eligible: bool = False
109
+ trackable: bool = False
110
+ secondary_color: str | None = None
111
+ pickup_requirements: list[Any] | None = None
112
+ pickup_qr_regex: str | None = None
113
+ pickup_category: str | None = None
114
+
115
+
116
+ class PackageItem(CasaModel):
117
+ id: str
118
+ carrier_id: str
119
+ created_at: int
120
+ updated_at: int
121
+ status: str
122
+ status_detail: str
123
+ direction: PackageDirectionField
124
+ archived: bool = False
125
+ is_pickup: bool = False
126
+ can_archive: bool = False
127
+ suggest_archive: bool = False
128
+ tracking_number: str | None = None
129
+ weight: float | None = None
130
+ est_delivery_date: int | None = None
131
+ signed_by: str | None = None
132
+ service: str | None = None
133
+ container_type: str | None = None
134
+ origin_city: str | None = None
135
+ origin_state: str | None = None
136
+ origin_zip: str | None = None
137
+ origin_country: str | None = None
138
+ name: str | None = None
139
+ tracking_history: list[TrackingHistoryData] = Field(default_factory=list)
140
+ pickup_amazon_location: Any | None = None
141
+ pickup_qr_code: str | None = None
142
+ pickup_image_url: str | None = None
143
+ pickup_image_url_cache_key: str | None = None
144
+ pickup_status: PackagePickupStatusField | None = None
145
+ purchased_item: Any | None = None
146
+
147
+
148
+ class PackagesResponse(CasaModel):
149
+ next_pickup_lockdown: int
150
+ next_pickup_start: int
151
+ next_pickup_end: int
152
+ current_pickup_lockdown: int | None = None
153
+ current_pickup_start: int | None = None
154
+ current_pickup_end: int | None = None
155
+ carriers: list[PackageCarrierInfo] = Field(default_factory=list)
156
+ packages: list[PackageItem] = Field(default_factory=list)
157
+ next_pickup_item_ids: list[str] = Field(default_factory=list)
158
+ current_pickup_item_ids: list[str] = Field(default_factory=list)
159
+ package_auto_archive_days: int | None = None
160
+ package_tracking_inbound_email: str | None = None
@@ -0,0 +1,200 @@
1
+ """Autopilot and inventory service history.
2
+
3
+ Autopilot's populated variants were observed in the saved follow-up response.
4
+ Service history lists were empty. Their item fields come from static Codable data.
5
+ String discriminators stay open to future server values.
6
+ """
7
+
8
+ from pydantic import RootModel
9
+
10
+ from casahome.models._base import CasaModel
11
+ from casahome.models.requests import Pagination, RequestCard
12
+
13
+
14
+ class AutopilotRequirementOption(CasaModel):
15
+ type_id: str
16
+ name: str
17
+ symbol_name: str | None = None
18
+ help_center_article_id: str | None = None
19
+
20
+
21
+ class AutopilotRequirementProperty(CasaModel):
22
+ definition_id: str
23
+ entity_type_id: str
24
+ title: str
25
+
26
+
27
+ class AutopilotRequirementRoomProperty(CasaModel):
28
+ definition_id: str
29
+ title: str
30
+ data_type: str
31
+ symbol_name: str | None = None
32
+
33
+
34
+ class AutopilotRequirementRoomCandidate(CasaModel):
35
+ room_id: str
36
+ name: str
37
+ story_name: str
38
+ story_floor_number: int
39
+ duration_minutes: int
40
+ image_url: str | None = None
41
+ image_url_cache_key: str | None = None
42
+ price_usd_cents: int | None = None
43
+ property_value: bool | None = None
44
+
45
+
46
+ class AutopilotPlanRequirement(CasaModel):
47
+ kind: str
48
+ options: list[AutopilotRequirementOption]
49
+ noun_with_article: str | None = None
50
+ property: AutopilotRequirementProperty | None = None
51
+ room_property: AutopilotRequirementRoomProperty | None = None
52
+ room_candidates: list[AutopilotRequirementRoomCandidate] | None = None
53
+
54
+
55
+ class AutopilotIllustration(CasaModel):
56
+ bg_color: str
57
+ url: str | None = None
58
+ url_cache_key: str | None = None
59
+
60
+
61
+ class AutopilotPricingPolicy(CasaModel):
62
+ symbol_name: str
63
+ title: str
64
+ description: str
65
+
66
+
67
+ class AutopilotHomeAssociation(CasaModel):
68
+ id: str
69
+ duration_minutes: int
70
+ enabled: bool | None = None
71
+ price_usd_cents: int | None = None
72
+
73
+
74
+ class AutopilotRoomAssociation(CasaModel):
75
+ id: str
76
+ room_id: str
77
+ room_name: str
78
+ room_story_name: str
79
+ duration_minutes: int
80
+ enabled: bool | None = None
81
+ room_image_url: str | None = None
82
+ room_image_cache_key: str | None = None
83
+ price_usd_cents: int | None = None
84
+
85
+
86
+ class AutopilotEntityAssociation(CasaModel):
87
+ id: str
88
+ entity_id: str
89
+ entity_make: str
90
+ entity_model: str
91
+ duration_minutes: int
92
+ enabled: bool | None = None
93
+ entity_display_name: str | None = None
94
+ entity_location: str | None = None
95
+ entity_image_url: str | None = None
96
+ entity_image_cache_key: str | None = None
97
+ price_usd_cents: int | None = None
98
+
99
+
100
+ class AutopilotPlan(CasaModel):
101
+ plan_id: str
102
+ plan_template_id: str
103
+ title: str
104
+ subtitle: str
105
+ description: str
106
+ applies_to: str
107
+ type: str
108
+ enabled: bool
109
+ illustration: AutopilotIllustration
110
+ asap_schedule_description: str
111
+ pricing_policies: list[AutopilotPricingPolicy]
112
+ fulfillment_policy: str
113
+ savings_percentage: int
114
+ cadence_options: list[str]
115
+ default_cadence: str
116
+ next_projected_occurrence_range_start: str
117
+ next_projected_occurrence_range_end: str
118
+ room_associations: list[AutopilotRoomAssociation]
119
+ entity_associations: list[AutopilotEntityAssociation]
120
+ requirement: AutopilotPlanRequirement | None = None
121
+ handyman_credit_policy: str | None = None
122
+ selected_cadence: str | None = None
123
+ next_desired_occurrence_date: str | None = None
124
+ next_calendar_event_start_at: int | None = None
125
+ home_association: AutopilotHomeAssociation | None = None
126
+
127
+
128
+ class AutopilotIneligiblePlan(CasaModel):
129
+ plan_template_id: str
130
+ title: str
131
+ subtitle: str
132
+ description: str
133
+ type: str
134
+ fulfillment_policy: str
135
+ illustration: AutopilotIllustration
136
+ requirement: AutopilotPlanRequirement
137
+
138
+
139
+ class AutopilotCategory(CasaModel):
140
+ category_id: str
141
+ category_title: str
142
+ category_description: str
143
+ category_rendering_type: str
144
+ plans: list[AutopilotPlan]
145
+ ineligible_plans: list[AutopilotIneligiblePlan]
146
+
147
+
148
+ class AutopilotCategoriesResponse(RootModel[list[AutopilotCategory]]):
149
+ """The wire response is a bare list, not a plans envelope."""
150
+
151
+
152
+ class ServiceHistoryPlan(CasaModel):
153
+ """UNVERIFIED item. Known static fields are optional to tolerate drift."""
154
+
155
+ id: str | None = None
156
+ enabled: bool | None = None
157
+ title: str | None = None
158
+ selected_cadence: str | None = None
159
+ next_service_at: int | None = None
160
+
161
+
162
+ class ServiceHistoryItem(CasaModel):
163
+ """UNVERIFIED item from static Codable metadata."""
164
+
165
+ task_id: str | None = None
166
+ task_type: str | None = None
167
+ task_title: str | None = None
168
+ task_emoji: str | None = None
169
+ serviced_at: int | None = None
170
+ task_hero_image_url: str | None = None
171
+ task_hero_image_cache_key: str | None = None
172
+ handyman_job_id: str | None = None
173
+ vendor_visit_id: str | None = None
174
+ autopilot_plan_id: str | None = None
175
+
176
+
177
+ class InventoryServiceHistoryResponse(CasaModel):
178
+ autopilot_plans: list[ServiceHistoryPlan]
179
+ history_items: list[ServiceHistoryItem]
180
+
181
+
182
+ class CalendarBusyBlock(CasaModel):
183
+ """UNVERIFIED block from static Codable metadata."""
184
+
185
+ start_time: int | None = None
186
+ end_time: int | None = None
187
+ title: str | None = None
188
+
189
+
190
+ class CalendarBusyResponse(CasaModel):
191
+ blocks: list[CalendarBusyBlock]
192
+ google_status: str
193
+
194
+
195
+ class CompletedRequestsResponse(CasaModel):
196
+ """Empty list observed. RequestCard is the existing static item model."""
197
+
198
+ kind: str
199
+ items: list[RequestCard]
200
+ pagination: Pagination
@@ -0,0 +1,65 @@
1
+ """Static membership summaries, not billing or lifecycle action contracts.
2
+
3
+ Fields inferred from the app.
4
+ All fields and response linkage remain UNVERIFIED. Cents/seconds are retained
5
+ as named, without inventing a reporting period, currency or timezone.
6
+ """
7
+
8
+ from pydantic import JsonValue
9
+
10
+ from casahome.models._base import CasaModel
11
+
12
+
13
+ class MembershipBenefit(CasaModel):
14
+ # The app's id type isn't known to be a String, so keep it as JSON.
15
+ id: JsonValue
16
+ title: str
17
+ emoji: str
18
+ symbol: str
19
+ description: str
20
+
21
+
22
+ class MembershipDetails(CasaModel):
23
+ name: str
24
+ amount: str
25
+ amount_cents: int | None = None
26
+ interval: str
27
+ next_billing_date: int | None = None
28
+ member_since: int | None = None
29
+ benefits: list[MembershipBenefit] | None = None
30
+
31
+
32
+ class MembershipSavingsSummary(CasaModel):
33
+ total_time_saved_seconds: int
34
+ total_money_saved_cents: int
35
+
36
+
37
+ class MembershipSavingsBreakdown(CasaModel):
38
+ title: str
39
+ value: str
40
+
41
+
42
+ class MembershipSavingsCategory(CasaModel):
43
+ type: str
44
+ label: str
45
+ color: str
46
+ preview: str | None = None
47
+ is_visible: bool
48
+ time_saved_seconds: int
49
+ money_saved_cents: int
50
+ breakdown: list[MembershipSavingsBreakdown]
51
+
52
+
53
+ class MembershipSavingsMetadata(CasaModel):
54
+ id: str
55
+ created_at: str
56
+ updated_at: str
57
+
58
+
59
+ class MembershipSavings(CasaModel):
60
+ home_id: str
61
+ calculated_at: str
62
+ should_promote: bool
63
+ summary: MembershipSavingsSummary
64
+ categories: list[MembershipSavingsCategory]
65
+ metadata: MembershipSavingsMetadata
@@ -0,0 +1,56 @@
1
+ """Paint library models. Populated integer IDs were observed, not UUIDs."""
2
+
3
+ from casahome.models._base import CasaModel
4
+
5
+
6
+ class PaintBrand(CasaModel):
7
+ id: int | str
8
+ color_count: int
9
+ name: str | None = None
10
+
11
+
12
+ class PaintCollection(CasaModel):
13
+ id: int | str
14
+ color_count: int
15
+ name: str | None = None
16
+
17
+
18
+ class PaintColor(CasaModel):
19
+ id: int | str
20
+ color_name: str | None = None
21
+ collection_name: str | None = None
22
+ brand_name: str | None = None
23
+ color_hex: str | None = None
24
+
25
+
26
+ class PaintBrandsResponse(CasaModel):
27
+ brands: list[PaintBrand]
28
+
29
+
30
+ class PaintCollectionsResponse(CasaModel):
31
+ collections: list[PaintCollection]
32
+
33
+
34
+ class PaintColorsResponse(CasaModel):
35
+ colors: list[PaintColor]
36
+
37
+
38
+ class PaintSearchResponse(CasaModel):
39
+ results: list[PaintColor]
40
+
41
+
42
+ class InventoryPaintMatch(CasaModel):
43
+ """One paint match. VERIFIED populated (live GET, 2026-10)."""
44
+
45
+ id: int
46
+ color_name: str
47
+ brand_name: str
48
+ color_hex: str
49
+ closeness_percentage: float
50
+
51
+
52
+ class InventoryPaintMatchesResponse(CasaModel):
53
+ """Paint matches. VERIFIED envelope (live GET, 2026-10). No match application."""
54
+
55
+ matches: list[InventoryPaintMatch]
56
+ is_manually_added: bool