property-service — AI_INTEGRATION
Companion: DOMAIN_MODEL · API_CONTRACTS · SECURITY_MODEL · ../../docs/08-ai-architecture.md · ../../docs/02-enterprise-architecture.md §AI Architecture
property-service consumes AI capabilities for content authoring assistance, never for autonomous mutation of guest-visible data. Every AI call traverses ai-orchestrator-service, never a model provider directly. Every AI-derived field carries AIProvenance and stays in a suggested state until a human accepts it (HITL).
Hard rule: No AI suggestion ever flips a property to
published, mutates a price, changes a policy field, or alters a status transition. AI is a drafting peer; the operator is the publisher.
1. Capabilities
| ID | Capability | Models routed by orchestrator | Trigger | Output | HITL gate |
|---|---|---|---|---|---|
prop.describe.draft | Multi-lingual property description draft | Vertex AI Gemini (cloud); fallback ONNX (edge) for low-bandwidth tenants | POST /properties/:id/ai/draft-description or operator action in desktop | { locale, draft, rationale, sourceFields }[] for each enabled locale | Yes — decision_pending until `decision.recorded.v1(accept |
prop.translate | Translate an existing description to a missing locale | Vertex AI Gemini | POST /properties/:id/ai/translate with targetLocales[] | translated draft per locale | Yes |
prop.photo.tag | Auto-tag photos (front_facade, room_interior, pool, bathroom, lobby, …) | Vertex AI Vision | Triggered by inbound melmastoon.media.asset.scanned.v1 for photos linked to a property | tags[], confidence per tag | Yes — operator edits before commit |
prop.photo.amenity.suggest | Suggest amenity codes from photo evidence | Vertex AI Vision + Gemini | Same trigger as above; runs after tagging | amenitySuggestions[]: { code, confidence, evidencePhotoIds } | Yes |
prop.photo.altText | Generate accessibility alt text per photo | Vertex AI Vision | After tagging | altText: I18nString | Yes |
prop.geocode.fallback | Address → lat/lng when the primary geocoder (Mapbox / Google Places via geo-service) declines or returns low confidence | Vertex AI Gemini with grounded search | Only when geo-service returns confidence < 0.6 or unavailable | { lat, lng, confidence, source: 'ai_fallback' } | Yes — operator must visually confirm pin |
prop.policy.tone.lint | Lints free-text policy fields for clarity, tone, and culturally appropriate phrasing | Vertex AI Gemini | Operator-triggered | suggestions[] (rewrites) | Yes |
2. Orchestrator Contract
All calls go to ai-orchestrator-service over its internal RPC surface:
POST /v1/orchestration/run
{
"capability": "prop.describe.draft",
"tenantId": "tnt_…",
"callerService": "property-service",
"callerRequestId": "req_…",
"input": { "propertyId": "ppt_…", "locales": ["en","ps","fa","tg"], "tone": "warm-neutral" },
"constraints": { "maxLatencyMs": 8000, "redactPII": true },
"policy": { "moderation": "strict", "hitl": "required" }
}
The orchestrator returns:
{
"runId": "run_01H...",
"modelRoutingDecision": "vertex.gemini-1.5-pro",
"tokensIn": 1820, "tokensOut": 940,
"latencyMs": 4230,
"moderation": { "flagged": false, "categories": [] },
"output": { /* capability-specific */ },
"provenance": {
"runId": "run_01H...",
"provider": "vertex",
"model": "gemini-1.5-pro-002",
"policyVersion": "v3",
"promptHash": "sha256:...",
"occurredAt": "2026-04-22T10:00:00.000Z"
}
}
property-service persists provenance on the affected entity (e.g., photos.ai_provenance, property_translations.ai_provenance).
3. Suggestion Lifecycle
- Issue. A capability is invoked (operator-triggered or event-driven).
- Stage. Output is persisted as a suggestion: column
_suggested_*or row inai_suggestions(per DOMAIN_MODEL §11). The visible field on the entity is not mutated. - Surface. The desktop / tenant booking BFF surfaces a "Review AI suggestion" badge with
aiProvenance.runIdshown. - Decide. The operator accepts (full / partial), rejects, or edits-then-accepts. The decision is recorded via
POST /ai/decisionsand emitsmelmastoon.ai.decision.recorded.v1. - Apply. On
accept, the visible field is updated andaiProvenanceis preserved alongside the value. Onreject, the suggestion is archived.
Every applied AI value carries (forever):
aiProvenance.runIdaiProvenance.acceptedBy(usr_…)aiProvenance.acceptedAtaiProvenance.editedBeforeAccept(boolean)
4. Edge / Offline Inference
For tenants in low-bandwidth regions, prop.describe.draft and prop.photo.tag may run via ONNX Runtime packaged with the Electron app. The orchestrator decides routing based on the tenant's ai.routingPreference (cloud_only | edge_preferred | auto). Edge-derived suggestions still emit aiProvenance with provider: "onnx-edge" and a model digest.
The orchestrator (not property-service) is responsible for ONNX model distribution; property-service only consumes the orchestrator's run result.
5. Moderation
All free-text outputs (prop.describe.draft, prop.translate, prop.policy.tone.lint) pass through the orchestrator's moderation layer before being staged as suggestions. Categories enforced:
- Hate / political extremism
- Adult content
- Cultural / religious insensitivity (regional rule pack: Pashto/Dari/Persian/Tajik audiences)
- Disallowed claims (e.g., "best in country", "luxury" without star rating support)
A flagged output is not staged. The orchestrator returns { moderation.flagged: true } and property-service records a MELMASTOON.AI.SUGGESTION_BLOCKED_BY_MODERATION audit row; no domain event is emitted.
6. PII Handling
Property descriptions and addresses occasionally contain incidental PII (former owner names, contact phones). The orchestrator request sets constraints.redactPII: true, which directs the orchestrator to strip detected PII before model invocation. The redaction map is preserved in the run record (under platform retention) but is not echoed back to property-service.
7. Geocoding Fallback Specifics
The prop.geocode.fallback capability is only invoked when:
geo-servicereturnsconfidence < 0.6, orgeo-serviceis unavailable past its timeout, and- the operator has explicitly toggled "Try AI fallback" on the address form.
Output requires explicit operator confirmation (the desktop renders the candidate pin on a Leaflet map; no auto-commit). On accept, the property's geo_source is set to 'ai_fallback' and the resulting event melmastoon.property.updated.v1 carries aiProvenance for observability.
8. Cost & Quota Posture
- Each AI capability has a per-tenant monthly quota enforced by the orchestrator.
property-servicesurfaces a friendlyMELMASTOON.AI.QUOTA_EXHAUSTED(HTTP 429) response withRetry-After. - Operators can run any capability up to N times per entity per day (default 5 for description drafts, 20 for photo tagging on bulk uploads). Limits configurable per tenant.
property-serviceemitsmelmastoon.property.ai.usage.recorded.v1(audit-class event) for billing reconciliation.
9. Failure Modes
| Failure | Behavior |
|---|---|
| Orchestrator unavailable | API returns 503 with Retry-After; UI degrades to "AI temporarily unavailable" without blocking manual editing |
| Moderation flagged | Suggestion not staged; operator sees neutral message: "Couldn't generate a suitable draft." |
| Quota exhausted | 429 MELMASTOON.AI.QUOTA_EXHAUSTED |
| Output schema mismatch | Hard fail; alerts on-call (property_ai_schema_violations_total) |
| Edge model digest mismatch | Run rejected; orchestrator pushes a new digest before retrying |
10. Anti-Patterns (forbidden)
- Calling Vertex AI / OpenAI / any model provider SDK directly from
property-service. - Auto-applying any AI output without an explicit
decision.recordedevent. - Suggesting a price, an availability change, a policy override, or a publication transition.
- Persisting an AI value without
aiProvenance.
Provenance schema and orchestrator API are defined platform-wide in docs/08-ai-architecture.md. Suggestion endpoint shapes live in API_CONTRACTS §AI.