@happyvertical/smrt-planner 0.57.6 → 0.57.7
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/agents/assistant.md +29 -2
- package/agents/shell.md +13 -0
- package/app/404.html +26 -29
- package/app/_app/immutable/assets/0.BaRFeYGU.css +1 -0
- package/app/_app/immutable/assets/AppShell.BkKNrxND.css +1 -0
- package/app/_app/immutable/assets/{ModelPage.FEM0ZnPM.css → ModelPage.SIoeO-AU.css} +1 -1
- package/app/_app/immutable/assets/app.BHwMQ9Zv.css +1 -0
- package/app/_app/immutable/assets/{forms.DXOeP4wZ.css → forms.BRdbPdvs.css} +1 -1
- package/app/_app/immutable/assets/icons.BDx21rPt.css +1 -0
- package/app/_app/immutable/assets/labels.Dxa6EyRI.css +1 -0
- package/app/_app/immutable/assets/planner.DOQVfcOi.css +1 -0
- package/app/_app/immutable/assets/state.C0UZEhxS.css +1 -0
- package/app/_app/immutable/chunks/{BPsie9Wr.js → B2uXaQfZ.js} +1 -1
- package/app/_app/immutable/chunks/{D2Ykv2Y3.js → BQLQsso4.js} +1 -1
- package/app/_app/immutable/chunks/BdOPgmQ_.js +11 -0
- package/app/_app/immutable/chunks/BeL8dJx9.js +4 -0
- package/app/_app/immutable/chunks/{DypPefJO.js → Bhd-3niN.js} +1 -1
- package/app/_app/immutable/chunks/{exPME6UK.js → BqEBz5Vq.js} +1 -1
- package/app/_app/immutable/chunks/{GdHEbDDH.js → BxyJ_Q7v.js} +1 -1
- package/app/_app/immutable/chunks/{Dyf3Cj8R.js → C2OYATC6.js} +1 -1
- package/app/_app/immutable/chunks/C6AANt9H.js +26 -0
- package/app/_app/immutable/chunks/{0as86J1A.js → C8FX0hli.js} +6 -6
- package/app/_app/immutable/chunks/{BSYvnYu8.js → CYhwv6d2.js} +1 -1
- package/app/_app/immutable/chunks/CdKHckEF.js +1 -0
- package/app/_app/immutable/chunks/CsaWd8kG.js +2 -0
- package/app/_app/immutable/chunks/Cwz7QFS4.js +2 -0
- package/app/_app/immutable/chunks/{CoXf-L_3.js → D3lXu_rt.js} +25 -25
- package/app/_app/immutable/chunks/{PsiVGwJV.js → D8MqcCKq.js} +1 -1
- package/app/_app/immutable/chunks/DF-0MxNw.js +1 -0
- package/app/_app/immutable/chunks/DGaFQflg.js +1 -0
- package/app/_app/immutable/chunks/DIgo5ngo.js +1 -0
- package/app/_app/immutable/chunks/{CdlkzC7D.js → Db12RzZi.js} +1 -1
- package/app/_app/immutable/chunks/{eNZ3ToP2.js → DfN2k_EX.js} +1 -1
- package/app/_app/immutable/chunks/DmHVTNcC.js +4 -0
- package/app/_app/immutable/chunks/{DrjdLb0M.js → DmdvNG82.js} +2 -2
- package/app/_app/immutable/chunks/{CwNhmq3e.js → DwIRC3h5.js} +1 -1
- package/app/_app/immutable/chunks/{Dzh-1KQR.js → FeyhlLIZ.js} +1 -1
- package/app/_app/immutable/chunks/ICDFUCZL.js +2 -0
- package/app/_app/immutable/chunks/{D31rqKs1.js → IDZmjRDr.js} +1 -1
- package/app/_app/immutable/chunks/JqlEoPqZ.js +1 -0
- package/app/_app/immutable/chunks/{D8rEUyzO.js → TcjpRcdI.js} +1 -1
- package/app/_app/immutable/chunks/Vt9hDpHd.js +1 -0
- package/app/_app/immutable/chunks/ezs6CGyc.js +1 -0
- package/app/_app/immutable/chunks/lUJaueNM.js +16 -0
- package/app/_app/immutable/entry/app.C5bxPKCP.js +2 -0
- package/app/_app/immutable/entry/start.CZvO61qD.js +1 -0
- package/app/_app/immutable/nodes/0.DFJFBCCB.js +7 -0
- package/app/_app/immutable/nodes/{1.CTatAlE5.js → 1.D_79m87b.js} +1 -1
- package/app/_app/immutable/nodes/{10.DHnK3kbB.js → 10.20u2-jxn.js} +1 -1
- package/app/_app/immutable/nodes/{11.Ck1kcQLu.js → 11.CvMq32jC.js} +1 -1
- package/app/_app/immutable/nodes/{12.oK17D56_.js → 12.Cd0cy1AC.js} +1 -1
- package/app/_app/immutable/nodes/{13.DlEsuw6F.js → 13.DYYS3YVQ.js} +1 -1
- package/app/_app/immutable/nodes/14.BEf5BJDn.js +1 -0
- package/app/_app/immutable/nodes/2.C_kLkHNl.js +24 -0
- package/app/_app/immutable/nodes/{3.DOL1VeVM.js → 3.CZw3hv8O.js} +1 -1
- package/app/_app/immutable/nodes/{4.CiKq5-ek.js → 4.WMU9NDex.js} +1 -1
- package/app/_app/immutable/nodes/{5.nKUg_W9L.js → 5.CuqxSfl4.js} +1 -1
- package/app/_app/immutable/nodes/{6.CVGw-hLa.js → 6.BXLQB7tR.js} +1 -1
- package/app/_app/immutable/nodes/{7.Clo6kG4w.js → 7.BZvxNeL2.js} +1 -1
- package/app/_app/immutable/nodes/{8.C3moca98.js → 8.Bj2O4pyx.js} +1 -1
- package/app/_app/immutable/nodes/{9.BnVJx5kW.js → 9.yeFzeUHj.js} +1 -1
- package/app/_app/version.json +1 -1
- package/app/advanced-styles/index.html +32 -33
- package/app/ai/index.html +37 -38
- package/app/blueprint/index.html +27 -30
- package/app/cookbook/index.html +27 -30
- package/app/index.html +36 -39
- package/app/m/ads/AdDeliveryTier/index.html +34 -35
- package/app/m/ads/AdEvent/index.html +34 -35
- package/app/m/ads/AdFormat/index.html +34 -35
- package/app/m/ads/AdGroup/index.html +34 -35
- package/app/m/ads/AdVariation/index.html +34 -35
- package/app/m/analytics/AnalyticsDataStream/index.html +34 -35
- package/app/m/analytics/AnalyticsEvent/index.html +34 -35
- package/app/m/analytics/AnalyticsProperty/index.html +34 -35
- package/app/m/analytics/AnalyticsReport/index.html +34 -35
- package/app/m/approvals/ApprovalEvent/index.html +34 -35
- package/app/m/approvals/ApprovalPolicy/index.html +34 -35
- package/app/m/approvals/ApprovalRequest/index.html +34 -35
- package/app/m/assets/Asset/attachments/index.html +36 -37
- package/app/m/assets/Asset/index.html +34 -35
- package/app/m/assets/AssetAssociation/index.html +35 -36
- package/app/m/assets/AssetMetafield/index.html +34 -35
- package/app/m/assets/AssetStatus/index.html +34 -35
- package/app/m/assets/AssetType/index.html +34 -35
- package/app/m/assets/Folder/index.html +34 -35
- package/app/m/chat/AgentSession/index.html +34 -35
- package/app/m/chat/ChatMessage/index.html +35 -36
- package/app/m/chat/ChatParticipant/index.html +35 -36
- package/app/m/chat/ChatReaction/index.html +34 -35
- package/app/m/chat/ChatRoom/index.html +34 -35
- package/app/m/chat/ChatThread/index.html +35 -36
- package/app/m/chat/VoiceGatewayTurn/index.html +34 -35
- package/app/m/chat/VoiceSession/index.html +34 -35
- package/app/m/commerce/Agreement/index.html +34 -35
- package/app/m/commerce/Cart/index.html +34 -35
- package/app/m/commerce/Contract/index.html +34 -35
- package/app/m/commerce/ContractLineItem/index.html +35 -36
- package/app/m/commerce/Customer/index.html +34 -35
- package/app/m/commerce/DocumentTax/index.html +34 -35
- package/app/m/commerce/Estimate/index.html +34 -35
- package/app/m/commerce/Fulfillment/index.html +34 -35
- package/app/m/commerce/FulfillmentLineItem/index.html +35 -36
- package/app/m/commerce/HistoricalCustomerFunds/index.html +34 -35
- package/app/m/commerce/HistoricalCustomerFundsAllocation/index.html +34 -35
- package/app/m/commerce/HistoricalSalesLine/index.html +34 -35
- package/app/m/commerce/Invoice/index.html +34 -35
- package/app/m/commerce/InvoiceLineItem/index.html +35 -36
- package/app/m/commerce/Lease/index.html +34 -35
- package/app/m/commerce/LicenseSale/index.html +34 -35
- package/app/m/commerce/Order/index.html +34 -35
- package/app/m/commerce/Payment/index.html +34 -35
- package/app/m/commerce/PaymentAllocation/index.html +35 -36
- package/app/m/commerce/PaymentInstrument/index.html +34 -35
- package/app/m/commerce/PaymentIntent/index.html +34 -35
- package/app/m/commerce/Payout/index.html +34 -35
- package/app/m/commerce/ProductionOrder/index.html +34 -35
- package/app/m/commerce/PurchaseContract/index.html +34 -35
- package/app/m/commerce/PurchaseOrder/index.html +34 -35
- package/app/m/commerce/SalesContract/index.html +34 -35
- package/app/m/commerce/SupplierQuote/index.html +34 -35
- package/app/m/commerce/TaxCode/index.html +34 -35
- package/app/m/commerce/TaxComponent/index.html +34 -35
- package/app/m/commerce/TaxSettings/index.html +34 -35
- package/app/m/commerce/TaxSummaryReport/index.html +34 -35
- package/app/m/commerce/Vendor/index.html +34 -35
- package/app/m/commerce/WholesaleOrder/index.html +34 -35
- package/app/m/content/Content/index.html +34 -35
- package/app/m/content/ContentContribution/index.html +34 -35
- package/app/m/content/ContentContributionAttachment/index.html +34 -35
- package/app/m/content/ContentContributionRevision/index.html +34 -35
- package/app/m/content/ContentContributionType/index.html +34 -35
- package/app/m/content/ContentContributor/index.html +34 -35
- package/app/m/content/ContentCorrection/index.html +34 -35
- package/app/m/content/ContentFeedSource/index.html +34 -35
- package/app/m/content/ContentGovernanceAssignment/index.html +34 -35
- package/app/m/content/ContentGovernancePolicy/index.html +34 -35
- package/app/m/content/ContentGovernanceProfile/index.html +34 -35
- package/app/m/content/ContentReference/index.html +34 -35
- package/app/m/content/ContentReview/index.html +34 -35
- package/app/m/content/ContentVersion/index.html +34 -35
- package/app/m/events/Event/index.html +34 -35
- package/app/m/events/EventParticipant/index.html +35 -36
- package/app/m/events/EventSeries/index.html +34 -35
- package/app/m/events/EventType/index.html +34 -35
- package/app/m/expenses/Expense/index.html +34 -35
- package/app/m/facts/Fact/index.html +34 -35
- package/app/m/facts/FactContent/index.html +34 -35
- package/app/m/facts/FactEvidence/index.html +34 -35
- package/app/m/facts/FactSource/index.html +34 -35
- package/app/m/facts/FactSubject/index.html +34 -35
- package/app/m/facts/FactTag/index.html +34 -35
- package/app/m/fields/FieldPolicy/index.html +34 -35
- package/app/m/fields/FieldPolicySuggestion/index.html +34 -35
- package/app/m/fields/FieldUsageCounter/index.html +34 -35
- package/app/m/images/Image/index.html +34 -35
- package/app/m/inventory/InventoryLocation/index.html +34 -35
- package/app/m/inventory/StockLevel/index.html +34 -35
- package/app/m/inventory/StockMovement/index.html +34 -35
- package/app/m/ledgers/Account/index.html +34 -35
- package/app/m/ledgers/Journal/index.html +34 -35
- package/app/m/ledgers/JournalEntry/index.html +35 -36
- package/app/m/ledgers/Ledger/index.html +34 -35
- package/app/m/manufacturing/Assembly/index.html +34 -35
- package/app/m/manufacturing/BillOfMaterials/index.html +34 -35
- package/app/m/manufacturing/BomLine/index.html +34 -35
- package/app/m/manufacturing/Operation/index.html +34 -35
- package/app/m/manufacturing/ProductionRun/index.html +34 -35
- package/app/m/manufacturing/ProductionRunCompletion/index.html +34 -35
- package/app/m/manufacturing/RoutingStep/index.html +34 -35
- package/app/m/marketing/Campaign/index.html +34 -35
- package/app/m/marketing/CampaignChannel/index.html +34 -35
- package/app/m/marketing/CampaignMetricSnapshot/index.html +34 -35
- package/app/m/messages/Account/index.html +34 -35
- package/app/m/messages/Attachment/index.html +34 -35
- package/app/m/messages/Blacklist/index.html +34 -35
- package/app/m/messages/Email/failed-sends/index.html +36 -37
- package/app/m/messages/Email/index.html +34 -35
- package/app/m/messages/Email/mailbox/index.html +36 -37
- package/app/m/messages/EmailAccount/index.html +34 -35
- package/app/m/messages/EmailAttachment/index.html +34 -35
- package/app/m/messages/EmailFolder/index.html +34 -35
- package/app/m/messages/Message/index.html +34 -35
- package/app/m/messages/MessagingEndpoint/index.html +34 -35
- package/app/m/messages/PersonaMessageRoute/index.html +34 -35
- package/app/m/messages/SlackAccount/index.html +34 -35
- package/app/m/messages/SlackMessage/index.html +34 -35
- package/app/m/messages/TelegramAccount/index.html +34 -35
- package/app/m/messages/Tweet/index.html +34 -35
- package/app/m/messages/TwitterAccount/index.html +34 -35
- package/app/m/messages/Whitelist/index.html +34 -35
- package/app/m/messages/ZulipAccount/index.html +34 -35
- package/app/m/places/Place/index.html +34 -35
- package/app/m/places/PlaceType/index.html +34 -35
- package/app/m/products/Category/index.html +34 -35
- package/app/m/products/Material/index.html +34 -35
- package/app/m/products/Product/index.html +34 -35
- package/app/m/products/Product/ingredients/index.html +36 -37
- package/app/m/products/ProductVariant/index.html +35 -36
- package/app/m/products/Sku/index.html +35 -36
- package/app/m/profiles/ApiKey/index.html +34 -35
- package/app/m/profiles/Bot/index.html +34 -35
- package/app/m/profiles/MagicLinkToken/index.html +34 -35
- package/app/m/profiles/NostrIdentity/index.html +34 -35
- package/app/m/profiles/OidcIdentity/index.html +34 -35
- package/app/m/profiles/Organization/index.html +34 -35
- package/app/m/profiles/Person/index.html +34 -35
- package/app/m/profiles/Profile/index.html +34 -35
- package/app/m/profiles/ProfileMetadata/index.html +34 -35
- package/app/m/profiles/ProfileMetafield/index.html +34 -35
- package/app/m/profiles/ProfileRelationship/index.html +34 -35
- package/app/m/profiles/ProfileRelationshipTerm/index.html +34 -35
- package/app/m/profiles/ProfileRelationshipType/index.html +34 -35
- package/app/m/profiles/ProfileType/index.html +34 -35
- package/app/m/projects/Comment/index.html +35 -36
- package/app/m/projects/Issue/index.html +34 -35
- package/app/m/projects/Label/index.html +34 -35
- package/app/m/projects/Project/index.html +34 -35
- package/app/m/projects/ProjectIntegration/index.html +34 -35
- package/app/m/projects/PullRequest/index.html +34 -35
- package/app/m/projects/Repository/index.html +34 -35
- package/app/m/properties/Property/index.html +34 -35
- package/app/m/properties/Zone/index.html +34 -35
- package/app/m/reports/RuntimeReport/index.html +38 -36
- package/app/m/reports/SmrtPrincipalReportRefreshTask/index.html +34 -35
- package/app/m/reports/SmrtReportLock/index.html +34 -35
- package/app/m/reports/SmrtReportRefreshTask/index.html +34 -35
- package/app/m/reports/SmrtReportRun/index.html +34 -35
- package/app/m/reports/SmrtReportSchedule/index.html +34 -35
- package/app/m/reports/SmrtReportWatermark/index.html +34 -35
- package/app/m/sales/Agreement/index.html +34 -35
- package/app/m/sales/AttributionException/index.html +34 -35
- package/app/m/sales/AttributionPolicy/index.html +34 -35
- package/app/m/sales/Commission/index.html +34 -35
- package/app/m/sales/CommissionAdjustment/index.html +34 -35
- package/app/m/sales/CommissionPayout/index.html +34 -35
- package/app/m/sales/CommissionPlan/index.html +34 -35
- package/app/m/sales/Earner/index.html +34 -35
- package/app/m/sales/EarnerSourceAttribution/index.html +34 -35
- package/app/m/sales/EarningEvent/index.html +34 -35
- package/app/m/sales/ExecutedAgreement/index.html +34 -35
- package/app/m/sales/Lead/index.html +34 -35
- package/app/m/sales/Opportunity/index.html +34 -35
- package/app/m/sales/OpportunityConversion/index.html +34 -35
- package/app/m/sales/PipelineDefinition/index.html +34 -35
- package/app/m/sales/PipelineStage/index.html +35 -36
- package/app/m/sales/Referral/index.html +34 -35
- package/app/m/sales/ReferralAgreement/index.html +34 -35
- package/app/m/sales/ReferralLink/index.html +34 -35
- package/app/m/sales/ReferralProgram/index.html +34 -35
- package/app/m/sales/ReferralTermSnapshot/index.html +34 -35
- package/app/m/sales/ReferralTouch/index.html +34 -35
- package/app/m/sales/Referrer/index.html +34 -35
- package/app/m/sales/SalesActivity/index.html +34 -35
- package/app/m/sales/SalesRepresentative/index.html +34 -35
- package/app/m/sites/Site/index.html +34 -35
- package/app/m/sites/SiteAgentBinding/index.html +34 -35
- package/app/m/social/OAuthState/index.html +34 -35
- package/app/m/social/SocialAccount/index.html +34 -35
- package/app/m/social/SocialPost/index.html +34 -35
- package/app/m/social/SocialPostAnalyticsSnapshot/index.html +34 -35
- package/app/m/subscriptions/BillingAdjustment/index.html +34 -35
- package/app/m/subscriptions/ClientCharge/index.html +34 -35
- package/app/m/subscriptions/CreditGrant/index.html +34 -35
- package/app/m/subscriptions/PricingRule/index.html +34 -35
- package/app/m/subscriptions/RetailCharge/index.html +34 -35
- package/app/m/subscriptions/SpendingPolicy/index.html +34 -35
- package/app/m/subscriptions/SubscriptionPlan/index.html +34 -35
- package/app/m/subscriptions/TenantSubscription/index.html +34 -35
- package/app/m/subscriptions/TenantUsageMetric/index.html +34 -35
- package/app/m/support/ServiceTimeEntry/index.html +34 -35
- package/app/m/support/SupportAiRun/index.html +34 -35
- package/app/m/support/SupportAvailability/index.html +34 -35
- package/app/m/support/SupportCase/index.html +34 -35
- package/app/m/support/SupportCaseEvent/index.html +34 -35
- package/app/m/support/SupportChannelBinding/index.html +34 -35
- package/app/m/support/SupportCharge/index.html +34 -35
- package/app/m/support/SupportCompensation/index.html +34 -35
- package/app/m/support/SupportCompensationPlan/index.html +34 -35
- package/app/m/support/SupportEscalation/index.html +34 -35
- package/app/m/support/SupportInteraction/index.html +34 -35
- package/app/m/support/SupportPlan/index.html +34 -35
- package/app/m/support/SupportPolicy/index.html +34 -35
- package/app/m/support/SupportQualification/index.html +34 -35
- package/app/m/support/SupportServiceTarget/index.html +34 -35
- package/app/m/support/SupportSpecialist/index.html +34 -35
- package/app/m/support/SupportWorkLink/index.html +34 -35
- package/app/m/tags/Tag/index.html +34 -35
- package/app/m/tags/TagAlias/index.html +34 -35
- package/app/m/timesheets/AttendanceBreak/index.html +34 -35
- package/app/m/timesheets/AttendancePunch/index.html +34 -35
- package/app/m/timesheets/AttendanceSegment/index.html +34 -35
- package/app/m/timesheets/ServiceChargeSnapshot/index.html +34 -35
- package/app/m/timesheets/ServiceCompensationSnapshot/index.html +34 -35
- package/app/m/timesheets/ServiceTimeEntry/index.html +34 -35
- package/app/m/users/Group/index.html +34 -35
- package/app/m/users/GroupMember/index.html +34 -35
- package/app/m/users/GroupRole/index.html +34 -35
- package/app/m/users/Membership/index.html +35 -36
- package/app/m/users/MembershipOverride/index.html +34 -35
- package/app/m/users/Permission/index.html +34 -35
- package/app/m/users/Role/index.html +34 -35
- package/app/m/users/RolePermission/index.html +34 -35
- package/app/m/users/Session/index.html +34 -35
- package/app/m/users/Tenant/index.html +34 -35
- package/app/m/users/TenantIntegration/index.html +34 -35
- package/app/m/users/TenantPermissionOverride/index.html +34 -35
- package/app/m/users/User/index.html +34 -35
- package/app/m/video/Character/index.html +34 -35
- package/app/m/video/CharacterAsset/index.html +34 -35
- package/app/m/video/CompositeJob/index.html +34 -35
- package/app/m/video/Performer/index.html +34 -35
- package/app/m/video/PerformerAsset/index.html +34 -35
- package/app/m/video/Scene/index.html +34 -35
- package/app/m/video/SceneAsset/index.html +34 -35
- package/app/m/video/VideoComposition/index.html +34 -35
- package/app/m/video/VideoCompositionAsset/index.html +34 -35
- package/app/m/video/VideoSequence/index.html +34 -35
- package/app/m/video/VideoSequenceAsset/index.html +34 -35
- package/app/m/video/VideoShot/index.html +34 -35
- package/app/m/video/VideoShotAsset/index.html +34 -35
- package/app/m/video/VideoShotCharacter/index.html +34 -35
- package/app/m/video/VideoWorkflow/index.html +34 -35
- package/app/m/voice/VoiceOutput/index.html +34 -35
- package/app/m/voice/VoiceProfile/index.html +34 -35
- package/app/m/voice/VoiceSample/index.html +34 -35
- package/app/mcp.html +2 -2
- package/app/packages/ads/index.html +29 -32
- package/app/packages/analytics/index.html +29 -32
- package/app/packages/approvals/index.html +29 -32
- package/app/packages/assets/index.html +29 -32
- package/app/packages/chat/index.html +29 -32
- package/app/packages/comments/index.html +29 -32
- package/app/packages/commerce/index.html +29 -32
- package/app/packages/content/index.html +29 -32
- package/app/packages/events/index.html +29 -32
- package/app/packages/expenses/index.html +29 -32
- package/app/packages/facts/index.html +29 -32
- package/app/packages/fields/index.html +29 -32
- package/app/packages/human-resources/index.html +29 -32
- package/app/packages/images/index.html +29 -32
- package/app/packages/index.html +28 -31
- package/app/packages/ingestion/index.html +29 -32
- package/app/packages/inventory/index.html +29 -32
- package/app/packages/ledgers/index.html +29 -32
- package/app/packages/manufacturing/index.html +29 -32
- package/app/packages/marketing/index.html +29 -32
- package/app/packages/messages/index.html +29 -32
- package/app/packages/places/index.html +29 -32
- package/app/packages/preferences/index.html +29 -32
- package/app/packages/products/index.html +29 -32
- package/app/packages/profiles/index.html +29 -32
- package/app/packages/projects/index.html +29 -32
- package/app/packages/properties/index.html +29 -32
- package/app/packages/reports/index.html +29 -32
- package/app/packages/sales/index.html +29 -32
- package/app/packages/sites/index.html +29 -32
- package/app/packages/social/index.html +29 -32
- package/app/packages/subscriptions/index.html +29 -32
- package/app/packages/support/index.html +29 -32
- package/app/packages/tags/index.html +29 -32
- package/app/packages/timesheets/index.html +29 -32
- package/app/packages/users/index.html +29 -32
- package/app/packages/video/index.html +29 -32
- package/app/packages/voice/index.html +29 -32
- package/app/packages/webhooks/index.html +29 -32
- package/app/recipes/account-access/help/index.html +30 -33
- package/app/recipes/account-access/index.html +34 -36
- package/app/recipes/activity/help/index.html +30 -33
- package/app/recipes/activity/index.html +34 -36
- package/app/recipes/agreements/help/index.html +30 -33
- package/app/recipes/agreements/index.html +34 -36
- package/app/recipes/analytics.reports/help/index.html +30 -33
- package/app/recipes/analytics.reports/index.html +34 -36
- package/app/recipes/assets/help/index.html +30 -33
- package/app/recipes/assets/index.html +34 -36
- package/app/recipes/billing/help/index.html +30 -33
- package/app/recipes/billing/index.html +34 -36
- package/app/recipes/chat.assistant/help/index.html +30 -33
- package/app/recipes/chat.assistant/index.html +34 -36
- package/app/recipes/comments/help/index.html +30 -33
- package/app/recipes/comments/index.html +34 -36
- package/app/recipes/commerce.customers/help/index.html +30 -33
- package/app/recipes/commerce.customers/index.html +34 -36
- package/app/recipes/commerce.estimates/help/index.html +30 -33
- package/app/recipes/commerce.estimates/index.html +34 -36
- package/app/recipes/commerce.purchases/help/index.html +30 -33
- package/app/recipes/commerce.purchases/index.html +34 -36
- package/app/recipes/commerce.sales/help/index.html +30 -33
- package/app/recipes/commerce.sales/index.html +34 -36
- package/app/recipes/commerce.vendors/help/index.html +30 -33
- package/app/recipes/commerce.vendors/index.html +34 -36
- package/app/recipes/commerce.wholesale/help/index.html +30 -33
- package/app/recipes/commerce.wholesale/index.html +34 -36
- package/app/recipes/events.calendar/help/index.html +30 -33
- package/app/recipes/events.calendar/index.html +34 -36
- package/app/recipes/expenses.tracking/help/index.html +30 -33
- package/app/recipes/expenses.tracking/index.html +34 -36
- package/app/recipes/fields.form-customization/help/index.html +30 -33
- package/app/recipes/fields.form-customization/index.html +34 -36
- package/app/recipes/ingestion/help/index.html +30 -33
- package/app/recipes/ingestion/index.html +34 -36
- package/app/recipes/integrations/help/index.html +30 -33
- package/app/recipes/integrations/index.html +34 -36
- package/app/recipes/inventory.stock/help/index.html +30 -33
- package/app/recipes/inventory.stock/index.html +34 -36
- package/app/recipes/ledgers.bookkeeping/help/index.html +30 -33
- package/app/recipes/ledgers.bookkeeping/index.html +34 -36
- package/app/recipes/messages/help/index.html +30 -33
- package/app/recipes/messages/index.html +34 -36
- package/app/recipes/products/help/index.html +30 -33
- package/app/recipes/products/index.html +34 -36
- package/app/recipes/projects.tracker/help/index.html +30 -33
- package/app/recipes/projects.tracker/index.html +34 -36
- package/app/recipes/reports.materialized/help/index.html +30 -33
- package/app/recipes/reports.materialized/index.html +34 -36
- package/app/recipes/reports.runtime/help/index.html +31 -34
- package/app/recipes/reports.runtime/index.html +35 -37
- package/app/recipes/sales.pipeline/help/index.html +30 -33
- package/app/recipes/sales.pipeline/index.html +34 -36
- package/app/recipes/tags.taxonomy/help/index.html +30 -33
- package/app/recipes/tags.taxonomy/index.html +34 -36
- package/app/recipes/team-collaboration/help/index.html +30 -33
- package/app/recipes/team-collaboration/index.html +34 -36
- package/app/recipes/timesheets.time-entries/help/index.html +30 -33
- package/app/recipes/timesheets.time-entries/index.html +34 -36
- package/app/s/custom-accounts/index.html +27 -30
- package/app/s/custom-appointments/index.html +27 -30
- package/app/s/custom-billing/index.html +27 -30
- package/app/s/custom-bookings/index.html +27 -30
- package/app/s/custom-buying/index.html +27 -30
- package/app/s/custom-checkout/index.html +27 -30
- package/app/s/custom-classes/index.html +27 -30
- package/app/s/custom-clients/index.html +27 -30
- package/app/s/custom-events/index.html +27 -30
- package/app/s/custom-fleet/index.html +27 -30
- package/app/s/custom-gigs/index.html +27 -30
- package/app/s/custom-giving/index.html +27 -30
- package/app/s/custom-instruments/index.html +27 -30
- package/app/s/custom-jobs/index.html +27 -30
- package/app/s/custom-kitchen/index.html +27 -30
- package/app/s/custom-lessons/index.html +27 -30
- package/app/s/custom-material/index.html +27 -30
- package/app/s/custom-meetings/index.html +27 -30
- package/app/s/custom-members/index.html +27 -30
- package/app/s/custom-merch/index.html +27 -30
- package/app/s/custom-money/index.html +27 -30
- package/app/s/custom-paint/index.html +27 -30
- package/app/s/custom-parts/index.html +27 -30
- package/app/s/custom-rentals/index.html +27 -30
- package/app/s/custom-roastery/index.html +27 -30
- package/app/s/custom-schedule/index.html +27 -30
- package/app/s/custom-shelf/index.html +27 -30
- package/app/s/custom-shop/index.html +27 -30
- package/app/s/custom-students/index.html +27 -30
- package/app/s/custom-wholesale/index.html +27 -30
- package/app/s/custom-work/index.html +27 -30
- package/app/s/custom-yard/index.html +27 -30
- package/app/s/section-access-control/index.html +27 -30
- package/app/s/section-account-security/index.html +27 -30
- package/app/s/section-accounting/index.html +27 -30
- package/app/s/section-activity/index.html +27 -30
- package/app/s/section-agreements/index.html +27 -30
- package/app/s/section-analytics/index.html +27 -30
- package/app/s/section-assets/index.html +27 -30
- package/app/s/section-billing/index.html +27 -30
- package/app/s/section-calendar/index.html +27 -30
- package/app/s/section-catalog/index.html +27 -30
- package/app/s/section-chat.assistant/index.html +27 -30
- package/app/s/section-comments/index.html +27 -30
- package/app/s/section-fields.form-customization/index.html +27 -30
- package/app/s/section-ingestion/index.html +27 -30
- package/app/s/section-integrations/index.html +27 -30
- package/app/s/section-messages/index.html +27 -30
- package/app/s/section-more/index.html +27 -30
- package/app/s/section-operations/index.html +27 -30
- package/app/s/section-projects/index.html +27 -30
- package/app/s/section-purchasing/index.html +27 -30
- package/app/s/section-reports.materialized/index.html +27 -30
- package/app/s/section-reports.runtime/index.html +27 -30
- package/app/s/section-sales/index.html +27 -30
- package/app/s/section-taxonomy/index.html +27 -30
- package/app/s/section-team/index.html +27 -30
- package/app/s/section-work/index.html +27 -30
- package/dist/assistant/change.d.ts +10 -1
- package/dist/assistant/change.d.ts.map +1 -1
- package/dist/assistant/change.js +6 -1
- package/dist/assistant/prompt.d.ts +10 -1
- package/dist/assistant/prompt.d.ts.map +1 -1
- package/dist/assistant/prompt.js +42 -7
- package/dist/assistant/session.svelte.d.ts +6 -0
- package/dist/assistant/session.svelte.d.ts.map +1 -1
- package/dist/assistant/session.svelte.js +6 -0
- package/dist/assistant/transport.d.ts +23 -3
- package/dist/assistant/transport.d.ts.map +1 -1
- package/dist/assistant/transport.js +356 -13
- package/dist/catalog/catalog.json +107 -41
- package/dist/components/BrowserAssistant.svelte +26 -0
- package/dist/components/BrowserAssistant.svelte.d.ts.map +1 -1
- package/dist/components/ModelPage.svelte +36 -4
- package/dist/components/ModelPage.svelte.d.ts.map +1 -1
- package/dist/components/PlannerReports.svelte +61 -0
- package/dist/components/PlannerReports.svelte.d.ts +10 -0
- package/dist/components/PlannerReports.svelte.d.ts.map +1 -0
- package/dist/components/PlannerSavedReportsWidget.svelte +38 -0
- package/dist/components/PlannerSavedReportsWidget.svelte.d.ts +9 -0
- package/dist/components/PlannerSavedReportsWidget.svelte.d.ts.map +1 -0
- package/dist/components/ReportProposalCard.svelte +63 -0
- package/dist/components/ReportProposalCard.svelte.d.ts +17 -0
- package/dist/components/ReportProposalCard.svelte.d.ts.map +1 -0
- package/dist/components/SectionOverview.svelte +22 -1
- package/dist/components/SectionOverview.svelte.d.ts.map +1 -1
- package/dist/core/prompt.d.ts.map +1 -1
- package/dist/core/prompt.js +7 -1
- package/dist/core/reply.d.ts +6 -0
- package/dist/core/reply.d.ts.map +1 -1
- package/dist/core/reply.js +11 -0
- package/dist/data/planner-source.d.ts.map +1 -1
- package/dist/data/planner-source.js +4 -1
- package/dist/overviews/definitions.d.ts.map +1 -1
- package/dist/overviews/definitions.js +6 -1
- package/dist/overviews/registry.d.ts.map +1 -1
- package/dist/overviews/registry.js +9 -5
- package/dist/planner/Planner.svelte +17 -0
- package/dist/planner/Planner.svelte.d.ts.map +1 -1
- package/dist/planner/instance.d.ts +6 -0
- package/dist/planner/instance.d.ts.map +1 -1
- package/dist/planner/shell.d.ts.map +1 -1
- package/dist/planner/shell.js +3 -1
- package/dist/reports/intent.d.ts +106 -0
- package/dist/reports/intent.d.ts.map +1 -0
- package/dist/reports/intent.js +667 -0
- package/dist/reports/planner.d.ts +33 -0
- package/dist/reports/planner.d.ts.map +1 -0
- package/dist/reports/planner.js +60 -0
- package/dist/reports/sources.d.ts +36 -0
- package/dist/reports/sources.d.ts.map +1 -0
- package/dist/reports/sources.js +125 -0
- package/dist/reports/store.d.ts +64 -0
- package/dist/reports/store.d.ts.map +1 -0
- package/dist/reports/store.js +214 -0
- package/dist/smrt-version.d.ts +1 -1
- package/dist/smrt-version.js +1 -1
- package/package.json +54 -54
- package/app/_app/immutable/assets/0.C8CwV4Fz.css +0 -1
- package/app/_app/immutable/assets/AppShell.DrL8T9I9.css +0 -1
- package/app/_app/immutable/assets/app.4ScdTV_S.css +0 -1
- package/app/_app/immutable/assets/labels.AVmg4hBz.css +0 -1
- package/app/_app/immutable/assets/state.CkVocVUJ.css +0 -1
- package/app/_app/immutable/chunks/B7d7dGCP.js +0 -1
- package/app/_app/immutable/chunks/C3nKLKKd.js +0 -1
- package/app/_app/immutable/chunks/CXKa_xdk.js +0 -4
- package/app/_app/immutable/chunks/CdBvpdnq.js +0 -4
- package/app/_app/immutable/chunks/DBp81N-t.js +0 -24
- package/app/_app/immutable/chunks/DzRTyOpH.js +0 -1
- package/app/_app/immutable/chunks/GVQNHDp5.js +0 -1
- package/app/_app/immutable/chunks/M-CuJzQN.js +0 -1
- package/app/_app/immutable/chunks/ORlJSBRk.js +0 -1
- package/app/_app/immutable/chunks/gMSmS-_y.js +0 -2
- package/app/_app/immutable/chunks/jol435F5.js +0 -11
- package/app/_app/immutable/entry/app.C_C191oT.js +0 -2
- package/app/_app/immutable/entry/start.C01jT-Kj.js +0 -1
- package/app/_app/immutable/nodes/0._tbUx7Ky.js +0 -9
- package/app/_app/immutable/nodes/14.sDZ3z4Zh.js +0 -1
- package/app/_app/immutable/nodes/2.GYBHfqdI.js +0 -24
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A report request as the assistant's model states it, and the spec the
|
|
3
|
+
* planner's code builds from it (#4100).
|
|
4
|
+
*
|
|
5
|
+
* The on-device model is small, so it never writes a `RuntimeReportSpec`. It
|
|
6
|
+
* names an intent in its structured-JSON answer (which records, what to
|
|
7
|
+
* measure, how to group, which period); this module resolves the names
|
|
8
|
+
* against the source's fields, fills the gaps a person leaves ("a summary" is
|
|
9
|
+
* a count and the main amount; a period uses the business date) and builds the
|
|
10
|
+
* spec, which `parseRuntimeReportSpec` then validates. Anything it cannot
|
|
11
|
+
* resolve becomes a follow-up question, never a guess.
|
|
12
|
+
*/
|
|
13
|
+
import { describeRuntimeReportMemorySource, parseRuntimeReportSpec, parseRuntimeReportTime, RUNTIME_REPORT_LIMITS, RuntimeReportError, resolveRuntimeReportTimeRange, } from '@happyvertical/smrt-reports/spec';
|
|
14
|
+
export const REPORT_PERIODS = [
|
|
15
|
+
'today',
|
|
16
|
+
'yesterday',
|
|
17
|
+
'this_week',
|
|
18
|
+
'last_week',
|
|
19
|
+
'this_month',
|
|
20
|
+
'last_month',
|
|
21
|
+
'this_quarter',
|
|
22
|
+
'last_quarter',
|
|
23
|
+
'this_year',
|
|
24
|
+
'last_year',
|
|
25
|
+
'all_time',
|
|
26
|
+
];
|
|
27
|
+
export const REPORT_BUCKETS = [
|
|
28
|
+
'minute',
|
|
29
|
+
'hour',
|
|
30
|
+
'day',
|
|
31
|
+
'week',
|
|
32
|
+
'month',
|
|
33
|
+
'quarter',
|
|
34
|
+
'year',
|
|
35
|
+
];
|
|
36
|
+
export const REPORT_MEASURES = ['count', 'sum', 'avg', 'min', 'max'];
|
|
37
|
+
export const REPORT_FILTER_OPS = [
|
|
38
|
+
'eq',
|
|
39
|
+
'ne',
|
|
40
|
+
'gt',
|
|
41
|
+
'gte',
|
|
42
|
+
'lt',
|
|
43
|
+
'lte',
|
|
44
|
+
'contains',
|
|
45
|
+
];
|
|
46
|
+
/** Most measures and filters an intent may name; the schema caps them too. */
|
|
47
|
+
const MAX_MEASURES = 4;
|
|
48
|
+
const MAX_FILTERS = 3;
|
|
49
|
+
const MAX_NAME = 64;
|
|
50
|
+
const MAX_UNREADABLE = 8;
|
|
51
|
+
/** The JSON Schema of the `report` property of one assistant turn. */
|
|
52
|
+
export function reportIntentSchema(sourceIds) {
|
|
53
|
+
const name = { type: 'string', maxLength: MAX_NAME };
|
|
54
|
+
return {
|
|
55
|
+
type: ['object', 'null'],
|
|
56
|
+
properties: {
|
|
57
|
+
// A host builds its schema before it knows the plan: any id, checked
|
|
58
|
+
// against the offered sources when the planner builds the spec.
|
|
59
|
+
source: sourceIds.length
|
|
60
|
+
? { enum: [...sourceIds] }
|
|
61
|
+
: {
|
|
62
|
+
type: 'string',
|
|
63
|
+
maxLength: RUNTIME_REPORT_LIMITS.maxSourceIdLength,
|
|
64
|
+
},
|
|
65
|
+
measures: {
|
|
66
|
+
type: 'array',
|
|
67
|
+
maxItems: MAX_MEASURES,
|
|
68
|
+
items: {
|
|
69
|
+
type: 'object',
|
|
70
|
+
properties: { fn: { enum: [...REPORT_MEASURES] }, field: name },
|
|
71
|
+
required: ['fn'],
|
|
72
|
+
additionalProperties: false,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
groupBy: name,
|
|
76
|
+
bucket: { enum: [...REPORT_BUCKETS] },
|
|
77
|
+
period: { enum: [...REPORT_PERIODS] },
|
|
78
|
+
range: { type: 'string', maxLength: 40 },
|
|
79
|
+
dateField: name,
|
|
80
|
+
filters: {
|
|
81
|
+
type: 'array',
|
|
82
|
+
maxItems: MAX_FILTERS,
|
|
83
|
+
items: {
|
|
84
|
+
type: 'object',
|
|
85
|
+
properties: {
|
|
86
|
+
field: name,
|
|
87
|
+
op: { enum: [...REPORT_FILTER_OPS] },
|
|
88
|
+
value: { type: ['string', 'number', 'boolean'] },
|
|
89
|
+
},
|
|
90
|
+
required: ['field', 'op', 'value'],
|
|
91
|
+
additionalProperties: false,
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
title: {
|
|
95
|
+
type: 'string',
|
|
96
|
+
maxLength: RUNTIME_REPORT_LIMITS.maxTitleLength,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
required: ['source'],
|
|
100
|
+
additionalProperties: false,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const oneOf = (value, list) => typeof value === 'string' && list.includes(value)
|
|
104
|
+
? value
|
|
105
|
+
: undefined;
|
|
106
|
+
const shortText = (value, max = MAX_NAME) => typeof value === 'string' && value.trim() && value.trim().length <= max
|
|
107
|
+
? value.trim()
|
|
108
|
+
: undefined;
|
|
109
|
+
/**
|
|
110
|
+
* Read the model's `report` property. Structure only: names are kept as the
|
|
111
|
+
* model wrote them and resolved by `specFromIntent`. A `source` outside the
|
|
112
|
+
* offered ids is kept too, so the follow-up can say what is offered.
|
|
113
|
+
*/
|
|
114
|
+
export function parseReportIntent(value) {
|
|
115
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
116
|
+
return null;
|
|
117
|
+
const raw = value;
|
|
118
|
+
const source = shortText(raw.source, RUNTIME_REPORT_LIMITS.maxSourceIdLength);
|
|
119
|
+
if (!source)
|
|
120
|
+
return null;
|
|
121
|
+
// A part the model stated but that cannot be read is recorded, never
|
|
122
|
+
// dropped: dropping a filter or a period would silently broaden the report.
|
|
123
|
+
const stated = (key) => raw[key] !== undefined && raw[key] !== null;
|
|
124
|
+
// A host server parses the model's reply first and passes on what it could
|
|
125
|
+
// not read; keep that (bounded) so the browser still asks about it.
|
|
126
|
+
const unreadable = Array.isArray(raw.unreadable)
|
|
127
|
+
? raw.unreadable
|
|
128
|
+
.filter((entry) => typeof entry === 'string' && entry.trim().length > 0)
|
|
129
|
+
.slice(0, MAX_UNREADABLE)
|
|
130
|
+
.map((entry) => entry.trim().slice(0, MAX_NAME))
|
|
131
|
+
: [];
|
|
132
|
+
if (stated('measures') && !Array.isArray(raw.measures)) {
|
|
133
|
+
unreadable.push('the measures');
|
|
134
|
+
}
|
|
135
|
+
if (stated('filters') && !Array.isArray(raw.filters)) {
|
|
136
|
+
unreadable.push('the filters');
|
|
137
|
+
}
|
|
138
|
+
const measures = Array.isArray(raw.measures)
|
|
139
|
+
? raw.measures.slice(0, MAX_MEASURES).flatMap((entry) => {
|
|
140
|
+
const m = (entry && typeof entry === 'object' ? entry : {});
|
|
141
|
+
const fn = oneOf(m.fn, REPORT_MEASURES);
|
|
142
|
+
if (!fn) {
|
|
143
|
+
unreadable.push('a measure');
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
const field = shortText(m.field);
|
|
147
|
+
return [{ fn, ...(field ? { field } : {}) }];
|
|
148
|
+
})
|
|
149
|
+
: [];
|
|
150
|
+
const filters = Array.isArray(raw.filters)
|
|
151
|
+
? raw.filters.slice(0, MAX_FILTERS).flatMap((entry) => {
|
|
152
|
+
const f = (entry && typeof entry === 'object' ? entry : {});
|
|
153
|
+
const field = shortText(f.field);
|
|
154
|
+
const op = oneOf(f.op, REPORT_FILTER_OPS);
|
|
155
|
+
const v = f.value;
|
|
156
|
+
if (!field ||
|
|
157
|
+
!op ||
|
|
158
|
+
!((typeof v === 'string' && v.length <= 256) ||
|
|
159
|
+
(typeof v === 'number' && Number.isFinite(v)) ||
|
|
160
|
+
typeof v === 'boolean')) {
|
|
161
|
+
unreadable.push(field ? `the ${field} filter` : 'a filter');
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
return [{ field, op, value: v }];
|
|
165
|
+
})
|
|
166
|
+
: [];
|
|
167
|
+
if (Array.isArray(raw.measures) && raw.measures.length > MAX_MEASURES) {
|
|
168
|
+
unreadable.push(`more than ${MAX_MEASURES} measures`);
|
|
169
|
+
}
|
|
170
|
+
if (Array.isArray(raw.filters) && raw.filters.length > MAX_FILTERS) {
|
|
171
|
+
unreadable.push(`more than ${MAX_FILTERS} filters`);
|
|
172
|
+
}
|
|
173
|
+
const groupBy = shortText(raw.groupBy);
|
|
174
|
+
const bucket = oneOf(raw.bucket, REPORT_BUCKETS);
|
|
175
|
+
const period = oneOf(raw.period, REPORT_PERIODS);
|
|
176
|
+
const dateField = shortText(raw.dateField);
|
|
177
|
+
const range = shortText(raw.range, 40);
|
|
178
|
+
const title = shortText(raw.title, RUNTIME_REPORT_LIMITS.maxTitleLength);
|
|
179
|
+
if (stated('range') && !range)
|
|
180
|
+
unreadable.push('the period');
|
|
181
|
+
if (stated('groupBy') && !groupBy)
|
|
182
|
+
unreadable.push('the grouping');
|
|
183
|
+
if (stated('bucket') && !bucket)
|
|
184
|
+
unreadable.push('the time grouping');
|
|
185
|
+
if (stated('period') && !period)
|
|
186
|
+
unreadable.push('the period');
|
|
187
|
+
if (stated('dateField') && !dateField)
|
|
188
|
+
unreadable.push('the date');
|
|
189
|
+
return {
|
|
190
|
+
source,
|
|
191
|
+
...(measures.length ? { measures } : {}),
|
|
192
|
+
...(groupBy ? { groupBy } : {}),
|
|
193
|
+
...(bucket ? { bucket } : {}),
|
|
194
|
+
...(period ? { period } : {}),
|
|
195
|
+
...(range ? { range } : {}),
|
|
196
|
+
...(dateField ? { dateField } : {}),
|
|
197
|
+
...(filters.length ? { filters } : {}),
|
|
198
|
+
...(title ? { title } : {}),
|
|
199
|
+
...(unreadable.length ? { unreadable } : {}),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// Names. A small model writes "total", "Total amount" or "totalAmount".
|
|
204
|
+
const squash = (text) => text.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
205
|
+
function resolveField(name, fields, accept = () => true) {
|
|
206
|
+
const wanted = squash(name);
|
|
207
|
+
const candidates = fields.filter(accept);
|
|
208
|
+
return (candidates.find((f) => f.name === name) ??
|
|
209
|
+
candidates.find((f) => squash(f.name) === wanted) ??
|
|
210
|
+
candidates.find((f) => squash(f.label) === wanted));
|
|
211
|
+
}
|
|
212
|
+
/** "A, B or C" for a question. At most six, the rest elided. */
|
|
213
|
+
function choices(labels) {
|
|
214
|
+
const shown = labels.slice(0, 6);
|
|
215
|
+
const more = labels.length > shown.length ? ', …' : '';
|
|
216
|
+
if (shown.length <= 1)
|
|
217
|
+
return `${shown.join('')}${more}`;
|
|
218
|
+
return `${shown.slice(0, -1).join(', ')}${more} or ${shown[shown.length - 1]}`;
|
|
219
|
+
}
|
|
220
|
+
const ask = (question) => ({ ok: false, question });
|
|
221
|
+
/** A follow-up with options; with none to offer, a plain question. */
|
|
222
|
+
const follow = (followUp) => followUp.options.length
|
|
223
|
+
? {
|
|
224
|
+
ok: false,
|
|
225
|
+
question: followUp.prompt,
|
|
226
|
+
followUp: { ...followUp, default: followUp.options[0].value },
|
|
227
|
+
}
|
|
228
|
+
: { ok: false, question: followUp.prompt.replace(/…$/, ' what?') };
|
|
229
|
+
/** Most options a follow-up offers. */
|
|
230
|
+
const MAX_OPTIONS = 8;
|
|
231
|
+
/** Records with amounts and dates (invoices, orders, payments) first. */
|
|
232
|
+
function rankSources(sources) {
|
|
233
|
+
const weight = (s) => (s.amountField ? 2 : 0) + (s.dateField ? 1 : 0);
|
|
234
|
+
return [...sources].sort((a, b) => weight(b) - weight(a));
|
|
235
|
+
}
|
|
236
|
+
/** "Which records should the report cover?", with the sources as options. */
|
|
237
|
+
export function sourceFollowUp(sources) {
|
|
238
|
+
const ranked = rankSources(sources).slice(0, MAX_OPTIONS);
|
|
239
|
+
if (!ranked.length)
|
|
240
|
+
return null;
|
|
241
|
+
return {
|
|
242
|
+
field: 'source',
|
|
243
|
+
label: 'Records',
|
|
244
|
+
prompt: 'Which records should the report cover?',
|
|
245
|
+
options: ranked.map((source) => ({
|
|
246
|
+
value: source.id,
|
|
247
|
+
label: source.label,
|
|
248
|
+
apply: { source: source.id },
|
|
249
|
+
})),
|
|
250
|
+
default: ranked[0].id,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
/** The question for records no source offers. */
|
|
254
|
+
export function whichSourceQuestion(sources) {
|
|
255
|
+
return sources.length
|
|
256
|
+
? 'Which records should the report cover?'
|
|
257
|
+
: 'Which records should the report cover? Add a recipe with records first, such as invoicing?';
|
|
258
|
+
}
|
|
259
|
+
const BUCKET_WORDS = {
|
|
260
|
+
minute: 'minute',
|
|
261
|
+
minutely: 'minute',
|
|
262
|
+
hour: 'hour',
|
|
263
|
+
hourly: 'hour',
|
|
264
|
+
day: 'day',
|
|
265
|
+
daily: 'day',
|
|
266
|
+
week: 'week',
|
|
267
|
+
weekly: 'week',
|
|
268
|
+
month: 'month',
|
|
269
|
+
monthly: 'month',
|
|
270
|
+
quarter: 'quarter',
|
|
271
|
+
quarterly: 'quarter',
|
|
272
|
+
year: 'year',
|
|
273
|
+
yearly: 'year',
|
|
274
|
+
annual: 'year',
|
|
275
|
+
annually: 'year',
|
|
276
|
+
};
|
|
277
|
+
const TIME_FILLER = new Set([
|
|
278
|
+
'by',
|
|
279
|
+
'per',
|
|
280
|
+
'the',
|
|
281
|
+
'each',
|
|
282
|
+
'every',
|
|
283
|
+
'a',
|
|
284
|
+
'over',
|
|
285
|
+
'time',
|
|
286
|
+
'date',
|
|
287
|
+
'dates',
|
|
288
|
+
'in',
|
|
289
|
+
]);
|
|
290
|
+
/**
|
|
291
|
+
* A grouping the model wrote as time ("month", "by month", "the date month",
|
|
292
|
+
* "issue date monthly"): its bucket, and what is left once the time words go
|
|
293
|
+
* (a date field's name, or nothing).
|
|
294
|
+
*/
|
|
295
|
+
function timeGrouping(text) {
|
|
296
|
+
const words = text
|
|
297
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
298
|
+
.toLowerCase()
|
|
299
|
+
.split(/[^a-z0-9]+/)
|
|
300
|
+
.filter(Boolean);
|
|
301
|
+
let bucket;
|
|
302
|
+
let time = false;
|
|
303
|
+
const rest = [];
|
|
304
|
+
for (const word of words) {
|
|
305
|
+
const singular = word.endsWith('s') ? word.slice(0, -1) : word;
|
|
306
|
+
const found = BUCKET_WORDS[word] ?? BUCKET_WORDS[singular];
|
|
307
|
+
if (found) {
|
|
308
|
+
bucket ??= found;
|
|
309
|
+
time = true;
|
|
310
|
+
}
|
|
311
|
+
else if (TIME_FILLER.has(word)) {
|
|
312
|
+
if (word === 'date' || word === 'dates' || word === 'time')
|
|
313
|
+
time = true;
|
|
314
|
+
}
|
|
315
|
+
else
|
|
316
|
+
rest.push(word);
|
|
317
|
+
}
|
|
318
|
+
return { ...(bucket ? { bucket } : {}), rest: rest.join(' '), time };
|
|
319
|
+
}
|
|
320
|
+
const PERIOD_OPTIONS = [
|
|
321
|
+
{ value: 'this month', label: 'This month' },
|
|
322
|
+
{ value: 'last month', label: 'Last month' },
|
|
323
|
+
{ value: 'this quarter', label: 'This quarter' },
|
|
324
|
+
{ value: 'last quarter', label: 'Last quarter' },
|
|
325
|
+
{ value: 'this year', label: 'This year' },
|
|
326
|
+
{ value: 'last year', label: 'Last year' },
|
|
327
|
+
{ value: 'all time', label: 'All time' },
|
|
328
|
+
];
|
|
329
|
+
/** A readable output alias: `number_of_invoices`, `total_amount`, `average_subtotal`. */
|
|
330
|
+
function measureAlias(fn, field, sourceId) {
|
|
331
|
+
const snake = (text) => text
|
|
332
|
+
.replace(/([a-z\d])([A-Z])/g, '$1_$2')
|
|
333
|
+
.toLowerCase()
|
|
334
|
+
.replace(/[^a-z0-9_]+/g, '_');
|
|
335
|
+
const alias = fn === 'count' || !field
|
|
336
|
+
? `number_of_${snake(sourceId)}`
|
|
337
|
+
: fn === 'sum'
|
|
338
|
+
? snake(field).startsWith('total')
|
|
339
|
+
? snake(field)
|
|
340
|
+
: `total_${snake(field)}`
|
|
341
|
+
: `${{ avg: 'average', min: 'lowest', max: 'highest' }[fn]}_${snake(field)}`;
|
|
342
|
+
return alias
|
|
343
|
+
.slice(0, RUNTIME_REPORT_LIMITS.maxAliasLength)
|
|
344
|
+
.replace(/_+$/, '');
|
|
345
|
+
}
|
|
346
|
+
const OP_WORDS = {
|
|
347
|
+
eq: 'is',
|
|
348
|
+
ne: 'is not',
|
|
349
|
+
gt: 'over',
|
|
350
|
+
gte: 'at least',
|
|
351
|
+
lt: 'under',
|
|
352
|
+
lte: 'at most',
|
|
353
|
+
contains: 'contains',
|
|
354
|
+
};
|
|
355
|
+
/**
|
|
356
|
+
* One amount in currency units, written plainly ("500", "$1,250.50", "-20",
|
|
357
|
+
* 500), as integer minor units. Anything else ("$1k", "1e3", "500 or 600") is
|
|
358
|
+
* not read at all: a guessed threshold would save the wrong report.
|
|
359
|
+
*/
|
|
360
|
+
const MONEY_TEXT = /^-?\s*[$€£¥]?\s*-?(\d{1,3}(,\d{3})+|\d+)(\.\d{1,2})?$/;
|
|
361
|
+
function moneyAmount(value) {
|
|
362
|
+
if (typeof value === 'number') {
|
|
363
|
+
return Number.isFinite(value) ? Math.round(value * 100) : null;
|
|
364
|
+
}
|
|
365
|
+
if (typeof value !== 'string')
|
|
366
|
+
return null;
|
|
367
|
+
const text = value.trim();
|
|
368
|
+
if (!MONEY_TEXT.test(text))
|
|
369
|
+
return null;
|
|
370
|
+
const units = Number(text.replace(/[^0-9.-]/g, ''));
|
|
371
|
+
return Number.isFinite(units) ? Math.round(units * 100) : null;
|
|
372
|
+
}
|
|
373
|
+
const isNumeric = (field) => field.type === 'integer' || field.type === 'decimal';
|
|
374
|
+
/**
|
|
375
|
+
* Build a spec from an intent over the offered sources. `now` is the clock a
|
|
376
|
+
* period is relative to (the planner passes its sample-data clock); `text` is
|
|
377
|
+
* what the person said, whose time words ("by month", "last quarter", "over
|
|
378
|
+
* time") fill what the model left out. Anything else unresolved is a
|
|
379
|
+
* follow-up built from the source descriptor.
|
|
380
|
+
*/
|
|
381
|
+
export function specFromIntent(intent, sources, now, text = '') {
|
|
382
|
+
const source = sources.find((s) => s.id === intent.source);
|
|
383
|
+
if (!source) {
|
|
384
|
+
const followUp = sourceFollowUp(sources);
|
|
385
|
+
return followUp
|
|
386
|
+
? { ok: false, question: followUp.prompt, followUp }
|
|
387
|
+
: ask(whichSourceQuestion(sources));
|
|
388
|
+
}
|
|
389
|
+
const described = describeRuntimeReportMemorySource(sources, source.id);
|
|
390
|
+
const { fields } = described;
|
|
391
|
+
const noun = source.label;
|
|
392
|
+
const labelOf = (name) => fields.find((f) => f.name === name)?.label ?? name;
|
|
393
|
+
if (intent.unreadable?.length) {
|
|
394
|
+
return ask(`I could not read ${choices(intent.unreadable).replace(/ or ([^,]+)$/, ' and $1')} for the ${noun} report. Can you say that again?`);
|
|
395
|
+
}
|
|
396
|
+
const said = parseRuntimeReportTime(text, now);
|
|
397
|
+
// Measures: "a summary" is how many and how much.
|
|
398
|
+
const requested = intent.measures?.length
|
|
399
|
+
? intent.measures
|
|
400
|
+
: [
|
|
401
|
+
{ fn: 'count' },
|
|
402
|
+
...(source.amountField
|
|
403
|
+
? [{ fn: 'sum', field: source.amountField }]
|
|
404
|
+
: []),
|
|
405
|
+
];
|
|
406
|
+
const measures = [];
|
|
407
|
+
for (const [index, measure] of requested.entries()) {
|
|
408
|
+
if (measure.fn === 'count') {
|
|
409
|
+
if (!measures.some((m) => m.fn === 'count')) {
|
|
410
|
+
measures.push({
|
|
411
|
+
fn: 'count',
|
|
412
|
+
as: measureAlias('count', undefined, source.id),
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
const named = measure.field ?? source.amountField;
|
|
418
|
+
const accept = (f) => measure.fn === 'min' || measure.fn === 'max'
|
|
419
|
+
? isNumeric(f) || f.type === 'datetime'
|
|
420
|
+
: isNumeric(f);
|
|
421
|
+
const field = named ? resolveField(named, fields, accept) : undefined;
|
|
422
|
+
if (!field) {
|
|
423
|
+
const numeric = fields.filter((f) => isNumeric(f) && f.name !== 'id');
|
|
424
|
+
if (!numeric.length) {
|
|
425
|
+
return ask(`${noun} have no amounts to total. Should I count them instead?`);
|
|
426
|
+
}
|
|
427
|
+
// The document total first, then money: "how much" is an amount.
|
|
428
|
+
const ranked = [
|
|
429
|
+
...numeric.filter((f) => f.name === source.amountField),
|
|
430
|
+
...numeric.filter((f) => f.format === 'money' && f.name !== source.amountField),
|
|
431
|
+
...numeric.filter((f) => f.format !== 'money'),
|
|
432
|
+
].slice(0, MAX_OPTIONS);
|
|
433
|
+
return follow({
|
|
434
|
+
field: 'measure',
|
|
435
|
+
label: measure.fn === 'sum' ? 'Total' : 'Amount',
|
|
436
|
+
prompt: `Which amount on ${noun} should I ${measure.fn === 'sum' ? 'total' : `take the ${measure.fn} of`}?`,
|
|
437
|
+
options: ranked.map((f) => ({
|
|
438
|
+
value: f.name,
|
|
439
|
+
label: f.label,
|
|
440
|
+
// Only the unresolved measure changes; the others stay.
|
|
441
|
+
apply: {
|
|
442
|
+
measures: requested.map((m, k) => k === index ? { fn: measure.fn, field: f.name } : m),
|
|
443
|
+
},
|
|
444
|
+
})),
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
measures.push({
|
|
448
|
+
fn: measure.fn,
|
|
449
|
+
field: field.name,
|
|
450
|
+
as: measureAlias(measure.fn, field.name, source.id),
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
// Time: a grouping the model wrote as time words, else the person's words.
|
|
454
|
+
let groupBy = intent.groupBy;
|
|
455
|
+
let dateName = intent.dateField;
|
|
456
|
+
let bucket = intent.bucket;
|
|
457
|
+
if (groupBy) {
|
|
458
|
+
const timed = timeGrouping(groupBy);
|
|
459
|
+
const asDate = resolveField(groupBy, fields, (f) => f.type === 'datetime');
|
|
460
|
+
if (asDate) {
|
|
461
|
+
// "Group by issue date": its months.
|
|
462
|
+
dateName ??= asDate.name;
|
|
463
|
+
bucket ??= 'month';
|
|
464
|
+
groupBy = undefined;
|
|
465
|
+
}
|
|
466
|
+
else if (timed.time) {
|
|
467
|
+
const restDate = timed.rest
|
|
468
|
+
? resolveField(timed.rest, fields, (f) => f.type === 'datetime')
|
|
469
|
+
: undefined;
|
|
470
|
+
if (!timed.rest || restDate) {
|
|
471
|
+
if (restDate)
|
|
472
|
+
dateName ??= restDate.name;
|
|
473
|
+
bucket ??= timed.bucket ?? said.bucket ?? 'month';
|
|
474
|
+
groupBy = undefined;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
bucket ??= said.bucket;
|
|
479
|
+
let range = null;
|
|
480
|
+
if (intent.range) {
|
|
481
|
+
range = resolveRuntimeReportTimeRange(intent.range, now);
|
|
482
|
+
if (!range && !/\ball\s*time\b|\bever\b/i.test(intent.range)) {
|
|
483
|
+
return follow({
|
|
484
|
+
field: 'period',
|
|
485
|
+
label: 'Period',
|
|
486
|
+
prompt: `Which period should the ${noun} report cover?`,
|
|
487
|
+
options: PERIOD_OPTIONS.map((p) => ({
|
|
488
|
+
...p,
|
|
489
|
+
apply: { range: p.value, period: undefined },
|
|
490
|
+
})),
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
else if (intent.period && intent.period !== 'all_time') {
|
|
495
|
+
range = resolveRuntimeReportTimeRange(intent.period, now);
|
|
496
|
+
}
|
|
497
|
+
else if (!intent.period) {
|
|
498
|
+
range = said.range ?? null;
|
|
499
|
+
}
|
|
500
|
+
// The natural date (the descriptor's default) for a period or time grouping.
|
|
501
|
+
let dateField;
|
|
502
|
+
if (bucket || range) {
|
|
503
|
+
dateField = dateName
|
|
504
|
+
? resolveField(dateName, fields, (f) => f.type === 'datetime')
|
|
505
|
+
: described.defaultTimeField
|
|
506
|
+
? fields.find((f) => f.name === described.defaultTimeField)
|
|
507
|
+
: undefined;
|
|
508
|
+
if (!dateField) {
|
|
509
|
+
const dates = described.timeFields.slice(0, MAX_OPTIONS);
|
|
510
|
+
if (!dates.length) {
|
|
511
|
+
return ask(`${noun} have no date to group or filter by. Should the report cover all of them?`);
|
|
512
|
+
}
|
|
513
|
+
return follow({
|
|
514
|
+
field: 'dateField',
|
|
515
|
+
label: 'Date',
|
|
516
|
+
prompt: `Which date on ${noun} should the report use?`,
|
|
517
|
+
options: dates.map((name) => ({
|
|
518
|
+
value: name,
|
|
519
|
+
label: labelOf(name),
|
|
520
|
+
apply: { dateField: name },
|
|
521
|
+
})),
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
const dimensions = [];
|
|
526
|
+
if (groupBy) {
|
|
527
|
+
const groupable = new Set(described.dimensions);
|
|
528
|
+
const field = resolveField(groupBy, fields, (f) => groupable.has(f.name)) ??
|
|
529
|
+
resolveField(groupBy, fields, (f) => f.type !== 'datetime');
|
|
530
|
+
if (field)
|
|
531
|
+
dimensions.push({ field: field.name });
|
|
532
|
+
// A word the time grouping already covers ("sales by month" read as
|
|
533
|
+
// groupBy "sales"): the bucket answers it. Otherwise ask, from the
|
|
534
|
+
// descriptor's human groupings.
|
|
535
|
+
else if (!(bucket && dateField)) {
|
|
536
|
+
return follow(groupingFollowUp(described, noun, labelOf, said.mentionsTime));
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
if (bucket && dateField) {
|
|
540
|
+
dimensions.push({ field: dateField.name, bucket });
|
|
541
|
+
}
|
|
542
|
+
const filters = [];
|
|
543
|
+
if (range && dateField) {
|
|
544
|
+
filters.push({ field: dateField.name, op: 'gte', value: range.from });
|
|
545
|
+
filters.push({ field: dateField.name, op: 'lt', value: range.to });
|
|
546
|
+
}
|
|
547
|
+
const stated = [];
|
|
548
|
+
for (const filter of intent.filters ?? []) {
|
|
549
|
+
const field = resolveField(filter.field, fields);
|
|
550
|
+
if (!field) {
|
|
551
|
+
return ask(`Which field on ${noun} should "${filter.field}" filter: ${choices(fields.map((f) => f.label))}?`);
|
|
552
|
+
}
|
|
553
|
+
if (field.format === 'money') {
|
|
554
|
+
// People state amounts in currency units ("over $500"); money is stored
|
|
555
|
+
// in integer minor units, so the threshold is scaled, never guessed.
|
|
556
|
+
const amount = moneyAmount(filter.value);
|
|
557
|
+
if (amount === null) {
|
|
558
|
+
return ask(`What amount should ${field.label} be compared with?`);
|
|
559
|
+
}
|
|
560
|
+
filters.push({ field: field.name, op: filter.op, value: amount });
|
|
561
|
+
stated.push(`${field.label} ${OP_WORDS[filter.op]} ${(amount / 100).toFixed(2)}`);
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
filters.push({ field: field.name, op: filter.op, value: filter.value });
|
|
565
|
+
stated.push(`${field.label} ${OP_WORDS[filter.op]} ${String(filter.value)}`);
|
|
566
|
+
}
|
|
567
|
+
const title = intent.title ??
|
|
568
|
+
[
|
|
569
|
+
[
|
|
570
|
+
noun,
|
|
571
|
+
...dimensions.map((d) => d.bucket ? `by ${d.bucket}` : `by ${labelOf(d.field).toLowerCase()}`),
|
|
572
|
+
].join(' '),
|
|
573
|
+
range?.label ?? '',
|
|
574
|
+
]
|
|
575
|
+
.filter(Boolean)
|
|
576
|
+
.join(', ');
|
|
577
|
+
// The description states the conditions beyond the period (a threshold, a
|
|
578
|
+
// status), so a saved report says what it counts; the report screens show
|
|
579
|
+
// the period itself, with its dates, from the spec's filters.
|
|
580
|
+
const description = stated.join('; ') || undefined;
|
|
581
|
+
const measureAliases = (spec) => spec.measures.map((m) => m.as);
|
|
582
|
+
try {
|
|
583
|
+
const draft = parseRuntimeReportSpec({
|
|
584
|
+
title,
|
|
585
|
+
...(description ? { description } : {}),
|
|
586
|
+
source: source.id,
|
|
587
|
+
dimensions,
|
|
588
|
+
measures,
|
|
589
|
+
filters,
|
|
590
|
+
});
|
|
591
|
+
const amounts = draft.measures.filter((m) => m.fn !== 'count');
|
|
592
|
+
const lead = (amounts[0] ?? draft.measures[0]).as;
|
|
593
|
+
const time = draft.dimensions.find((d) => d.bucket);
|
|
594
|
+
const category = draft.dimensions.find((d) => !d.bucket);
|
|
595
|
+
const spec = parseRuntimeReportSpec({
|
|
596
|
+
...draft,
|
|
597
|
+
// A category reads best largest first; time reads in order.
|
|
598
|
+
sort: category && !time ? [{ by: lead, direction: 'desc' }] : [],
|
|
599
|
+
chart: draft.dimensions.length
|
|
600
|
+
? {
|
|
601
|
+
type: time && !category ? 'line' : 'bar',
|
|
602
|
+
x: (category ?? time)?.as,
|
|
603
|
+
y: amounts.length
|
|
604
|
+
? amounts.map((m) => m.as)
|
|
605
|
+
: measureAliases(draft),
|
|
606
|
+
}
|
|
607
|
+
: {
|
|
608
|
+
type: 'stat',
|
|
609
|
+
y: measureAliases(draft),
|
|
610
|
+
},
|
|
611
|
+
});
|
|
612
|
+
return { ok: true, spec, source };
|
|
613
|
+
}
|
|
614
|
+
catch (error) {
|
|
615
|
+
if (error instanceof RuntimeReportError) {
|
|
616
|
+
return ask(`I could not build that report (${error.message}). What should it show?`);
|
|
617
|
+
}
|
|
618
|
+
throw error;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* "Group Invoices by…": the descriptor's human groupings, never ids, hashes
|
|
623
|
+
* or plumbing. When time came up, the natural date by month leads.
|
|
624
|
+
*/
|
|
625
|
+
function groupingFollowUp(described, noun, labelOf, timeFirst) {
|
|
626
|
+
const date = described.defaultTimeField;
|
|
627
|
+
const time = date
|
|
628
|
+
? ['month', 'week', 'quarter'].map((bucket) => ({
|
|
629
|
+
value: `${date}:${bucket}`,
|
|
630
|
+
label: `${labelOf(date)} by ${bucket}`,
|
|
631
|
+
keywords: [`${bucket}ly`, 'date', 'time', 'per', bucket],
|
|
632
|
+
apply: { groupBy: undefined, bucket, dateField: date },
|
|
633
|
+
}))
|
|
634
|
+
: [];
|
|
635
|
+
const categories = described.dimensions.map((name) => ({
|
|
636
|
+
value: name,
|
|
637
|
+
label: labelOf(name),
|
|
638
|
+
apply: { groupBy: name },
|
|
639
|
+
}));
|
|
640
|
+
const options = timeFirst
|
|
641
|
+
? [...time, ...categories]
|
|
642
|
+
: [
|
|
643
|
+
...categories.slice(0, 4),
|
|
644
|
+
...time.slice(0, 1),
|
|
645
|
+
...categories.slice(4),
|
|
646
|
+
...time.slice(1),
|
|
647
|
+
];
|
|
648
|
+
return {
|
|
649
|
+
field: 'groupBy',
|
|
650
|
+
label: 'Group by',
|
|
651
|
+
prompt: `Group ${noun} by…`,
|
|
652
|
+
options: options.slice(0, MAX_OPTIONS),
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* A follow-up for a spec the runner rejected (a filter value of the wrong
|
|
657
|
+
* type, an operator a field does not take): what to change, as a question.
|
|
658
|
+
*/
|
|
659
|
+
export function questionForError(error, source) {
|
|
660
|
+
if (error.code === 'invalid_value') {
|
|
661
|
+
return `That value does not fit ${source.label} (${error.message}). What value did you mean?`;
|
|
662
|
+
}
|
|
663
|
+
if (error.code === 'invalid_operation') {
|
|
664
|
+
return `I cannot use that on ${source.label} (${error.message}). What should the report show instead?`;
|
|
665
|
+
}
|
|
666
|
+
return `I could not build that report (${error.message}). What should it show?`;
|
|
667
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The app's runtime reports: `createPlannerReports` over the planner's sample
|
|
3
|
+
* data, the recipes that are on and the sample clock. The assistant, the
|
|
4
|
+
* Saved reports page and a saved report's page all use one of these per
|
|
5
|
+
* data source.
|
|
6
|
+
*/
|
|
7
|
+
import type { DataSource } from '../data/source.ts';
|
|
8
|
+
import { type PlannerReports } from './store.ts';
|
|
9
|
+
/**
|
|
10
|
+
* Reports over `data`. Periods ("last month") are relative to the sample
|
|
11
|
+
* data's clock (`EPOCH`), as the overview charts are, so they find the rows
|
|
12
|
+
* the sample records carry.
|
|
13
|
+
*/
|
|
14
|
+
export declare function plannerReportsFor(data: DataSource): PlannerReports;
|
|
15
|
+
/**
|
|
16
|
+
* After the person approved a report: open it under Saved reports, through
|
|
17
|
+
* the running app's own navigation (the static layout's router or a mounted
|
|
18
|
+
* planner's fragment). Unset, the confirmation card's link still reaches it.
|
|
19
|
+
*/
|
|
20
|
+
export declare function openSavedReport(report: {
|
|
21
|
+
id: string;
|
|
22
|
+
}): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Open a saved report, then announce the navigation from `from` (inside the
|
|
25
|
+
* dock) so the shell closes an overlaying dock and focuses the report's own
|
|
26
|
+
* heading. Announced only once the app's navigation has finished: a router
|
|
27
|
+
* that resets focus when it lands (SvelteKit's `goto`) would otherwise undo
|
|
28
|
+
* the shell's focus, most visibly when the report is already open.
|
|
29
|
+
*/
|
|
30
|
+
export declare function openSavedReportFrom(from: Element | null, report: {
|
|
31
|
+
id: string;
|
|
32
|
+
}): Promise<void>;
|
|
33
|
+
//# sourceMappingURL=planner.d.ts.map
|