mulmoclaude 0.1.0
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/README.md +44 -0
- package/bin/mulmoclaude.js +202 -0
- package/bin/prepare-dist.js +93 -0
- package/client/assets/chunk-vKJrgz-R-C_I3GbVV.js +1 -0
- package/client/assets/html2canvas-Cx501zZr-BF5dYYkY.js +5 -0
- package/client/assets/index-D8rhwXLq.js +4906 -0
- package/client/assets/index-KNLBjwuh.css +1 -0
- package/client/assets/index.es-D4YyL_Dg-BfRHLTZV.js +5 -0
- package/client/assets/material-icons-Dr0goTwe.woff +0 -0
- package/client/assets/material-icons-kAwBdRge.woff2 +0 -0
- package/client/assets/material-icons-outlined-BpWbwl2n.woff +0 -0
- package/client/assets/material-icons-outlined-DZhiGvEA.woff2 +0 -0
- package/client/assets/material-icons-round-BDlwx-sv.woff +0 -0
- package/client/assets/material-icons-round-DrirKXBx.woff2 +0 -0
- package/client/assets/material-icons-sharp-CH1KkVu7.woff +0 -0
- package/client/assets/material-icons-sharp-gidztirS.woff2 +0 -0
- package/client/assets/material-icons-two-tone-B7wz7mED.woff +0 -0
- package/client/assets/material-icons-two-tone-DuNIpaEj.woff2 +0 -0
- package/client/assets/mulmo_bw-ERmkSv0a.png +0 -0
- package/client/assets/purify.es-Fx1Nqyry-PeS5RUhs.js +2 -0
- package/client/assets/typeof-DBp4T-Ny-BC0P-2DM.js +1 -0
- package/client/index.html +28 -0
- package/package.json +66 -0
- package/server/agent/attachmentConverter.ts +270 -0
- package/server/agent/config.ts +414 -0
- package/server/agent/index.ts +260 -0
- package/server/agent/mcp-server.ts +412 -0
- package/server/agent/mcp-tools/index.ts +63 -0
- package/server/agent/mcp-tools/x.ts +188 -0
- package/server/agent/plugin-names.ts +75 -0
- package/server/agent/prompt.ts +349 -0
- package/server/agent/resumeFailover.ts +129 -0
- package/server/agent/sandboxMounts.ts +329 -0
- package/server/agent/stream.ts +194 -0
- package/server/api/auth/bearerAuth.ts +61 -0
- package/server/api/auth/token.ts +98 -0
- package/server/api/csrfGuard.ts +85 -0
- package/server/api/routes/agent.ts +478 -0
- package/server/api/routes/chart.ts +98 -0
- package/server/api/routes/chat-index.ts +46 -0
- package/server/api/routes/config.ts +258 -0
- package/server/api/routes/dispatchResponse.ts +79 -0
- package/server/api/routes/files.ts +812 -0
- package/server/api/routes/html.ts +101 -0
- package/server/api/routes/image.ts +169 -0
- package/server/api/routes/mulmo-script.ts +712 -0
- package/server/api/routes/mulmoScriptValidate.ts +101 -0
- package/server/api/routes/notifications.ts +69 -0
- package/server/api/routes/pdf.ts +163 -0
- package/server/api/routes/plugins.ts +276 -0
- package/server/api/routes/presentHtml.ts +48 -0
- package/server/api/routes/roles.ts +125 -0
- package/server/api/routes/scheduler.ts +153 -0
- package/server/api/routes/schedulerHandlers.ts +151 -0
- package/server/api/routes/schedulerTasks.ts +163 -0
- package/server/api/routes/sessions.ts +294 -0
- package/server/api/routes/sessionsCursor.ts +59 -0
- package/server/api/routes/skills.ts +195 -0
- package/server/api/routes/sources.ts +540 -0
- package/server/api/routes/todos.ts +263 -0
- package/server/api/routes/todosColumnsHandlers.ts +347 -0
- package/server/api/routes/todosHandlers.ts +274 -0
- package/server/api/routes/todosItemsHandlers.ts +386 -0
- package/server/api/routes/wiki/pageIndex.ts +53 -0
- package/server/api/routes/wiki.ts +363 -0
- package/server/api/sandboxStatus.ts +64 -0
- package/server/events/notifications.ts +160 -0
- package/server/events/pub-sub/index.ts +45 -0
- package/server/events/relay-client.ts +288 -0
- package/server/events/scheduler-adapter.ts +302 -0
- package/server/events/session-store/index.ts +492 -0
- package/server/events/task-manager/index.ts +181 -0
- package/server/index.ts +572 -0
- package/server/system/config.ts +243 -0
- package/server/system/credentials.ts +220 -0
- package/server/system/docker.ts +97 -0
- package/server/system/env.ts +109 -0
- package/server/system/logger/config.ts +112 -0
- package/server/system/logger/formatters.ts +40 -0
- package/server/system/logger/index.ts +53 -0
- package/server/system/logger/rotation.ts +37 -0
- package/server/system/logger/sinks.ts +101 -0
- package/server/system/logger/types.ts +29 -0
- package/server/utils/date.ts +57 -0
- package/server/utils/errors.ts +7 -0
- package/server/utils/fetch.ts +27 -0
- package/server/utils/files/atomic.ts +125 -0
- package/server/utils/files/html-io.ts +20 -0
- package/server/utils/files/image-store.ts +66 -0
- package/server/utils/files/index.ts +45 -0
- package/server/utils/files/journal-io.ts +213 -0
- package/server/utils/files/json.ts +69 -0
- package/server/utils/files/markdown-store.ts +33 -0
- package/server/utils/files/naming.ts +50 -0
- package/server/utils/files/reference-dirs-io.ts +45 -0
- package/server/utils/files/roles-io.ts +45 -0
- package/server/utils/files/safe.ts +106 -0
- package/server/utils/files/scheduler-io.ts +20 -0
- package/server/utils/files/scheduler-overrides-io.ts +64 -0
- package/server/utils/files/session-io.ts +136 -0
- package/server/utils/files/spreadsheet-store.ts +63 -0
- package/server/utils/files/todos-io.ts +29 -0
- package/server/utils/files/user-tasks-io.ts +25 -0
- package/server/utils/files/workspace-io.ts +221 -0
- package/server/utils/gemini.ts +59 -0
- package/server/utils/gitignore.ts +69 -0
- package/server/utils/http.ts +15 -0
- package/server/utils/httpError.ts +61 -0
- package/server/utils/id.ts +16 -0
- package/server/utils/json.ts +83 -0
- package/server/utils/logBackgroundError.ts +22 -0
- package/server/utils/markdown.ts +82 -0
- package/server/utils/request.ts +29 -0
- package/server/utils/slug.ts +50 -0
- package/server/utils/spawn.ts +62 -0
- package/server/utils/time.ts +34 -0
- package/server/utils/types.ts +47 -0
- package/server/workspace/chat-index/index.ts +153 -0
- package/server/workspace/chat-index/indexer.ts +209 -0
- package/server/workspace/chat-index/paths.ts +34 -0
- package/server/workspace/chat-index/summarizer.ts +247 -0
- package/server/workspace/chat-index/types.ts +38 -0
- package/server/workspace/custom-dirs.ts +220 -0
- package/server/workspace/helps/business.md +104 -0
- package/server/workspace/helps/github.md +23 -0
- package/server/workspace/helps/index.md +60 -0
- package/server/workspace/helps/mulmoscript.md +249 -0
- package/server/workspace/helps/sandbox.md +90 -0
- package/server/workspace/helps/spreadsheet.md +43 -0
- package/server/workspace/helps/telegram.md +135 -0
- package/server/workspace/helps/wiki.md +131 -0
- package/server/workspace/journal/archivist.ts +386 -0
- package/server/workspace/journal/dailyPass.ts +743 -0
- package/server/workspace/journal/diff.ts +71 -0
- package/server/workspace/journal/index.ts +185 -0
- package/server/workspace/journal/indexFile.ts +136 -0
- package/server/workspace/journal/linkRewrite.ts +4 -0
- package/server/workspace/journal/memoryExtractor.ts +130 -0
- package/server/workspace/journal/optimizationPass.ts +160 -0
- package/server/workspace/journal/paths.ts +76 -0
- package/server/workspace/journal/state.ts +125 -0
- package/server/workspace/paths.ts +158 -0
- package/server/workspace/reference-dirs.ts +252 -0
- package/server/workspace/roles.ts +37 -0
- package/server/workspace/skills/discovery.ts +125 -0
- package/server/workspace/skills/index.ts +10 -0
- package/server/workspace/skills/parser.ts +144 -0
- package/server/workspace/skills/paths.ts +41 -0
- package/server/workspace/skills/scheduler.ts +149 -0
- package/server/workspace/skills/types.ts +30 -0
- package/server/workspace/skills/user-tasks.ts +257 -0
- package/server/workspace/skills/writer.ts +189 -0
- package/server/workspace/sources/arxivDiscovery.ts +182 -0
- package/server/workspace/sources/classifier.ts +268 -0
- package/server/workspace/sources/fetchers/arxiv.ts +170 -0
- package/server/workspace/sources/fetchers/github.ts +106 -0
- package/server/workspace/sources/fetchers/githubIssues.ts +208 -0
- package/server/workspace/sources/fetchers/githubReleases.ts +186 -0
- package/server/workspace/sources/fetchers/index.ts +71 -0
- package/server/workspace/sources/fetchers/registerAll.ts +15 -0
- package/server/workspace/sources/fetchers/rss.ts +141 -0
- package/server/workspace/sources/fetchers/rssParser.ts +295 -0
- package/server/workspace/sources/httpFetcher.ts +230 -0
- package/server/workspace/sources/interests.ts +120 -0
- package/server/workspace/sources/paths.ts +110 -0
- package/server/workspace/sources/pipeline/dedup.ts +60 -0
- package/server/workspace/sources/pipeline/fetch.ts +136 -0
- package/server/workspace/sources/pipeline/index.ts +249 -0
- package/server/workspace/sources/pipeline/notify.ts +72 -0
- package/server/workspace/sources/pipeline/plan.ts +66 -0
- package/server/workspace/sources/pipeline/summarize.ts +189 -0
- package/server/workspace/sources/pipeline/write.ts +185 -0
- package/server/workspace/sources/rateLimiter.ts +148 -0
- package/server/workspace/sources/registry.ts +326 -0
- package/server/workspace/sources/robots.ts +271 -0
- package/server/workspace/sources/sourceState.ts +135 -0
- package/server/workspace/sources/taxonomy.ts +74 -0
- package/server/workspace/sources/types.ts +144 -0
- package/server/workspace/sources/urls.ts +112 -0
- package/server/workspace/tool-trace/classify.ts +114 -0
- package/server/workspace/tool-trace/index.ts +250 -0
- package/server/workspace/tool-trace/writeSearch.ts +98 -0
- package/server/workspace/wiki-backlinks/index.ts +107 -0
- package/server/workspace/wiki-backlinks/sessionBacklinks.ts +144 -0
- package/server/workspace/workspace.ts +66 -0
- package/src/App.vue +720 -0
- package/src/assets/mulmo_bw.png +0 -0
- package/src/components/CanvasViewToggle.vue +27 -0
- package/src/components/ChatAttachmentPreview.vue +45 -0
- package/src/components/ChatImagePreview.vue +17 -0
- package/src/components/ChatInput.vue +208 -0
- package/src/components/FileContentHeader.vue +49 -0
- package/src/components/FileContentRenderer.vue +162 -0
- package/src/components/FileTree.vue +115 -0
- package/src/components/FileTreePane.vue +85 -0
- package/src/components/FilesView.vue +206 -0
- package/src/components/LockStatusPopup.vue +111 -0
- package/src/components/NotificationBell.vue +131 -0
- package/src/components/NotificationToast.vue +72 -0
- package/src/components/PluginLauncher.vue +138 -0
- package/src/components/RightSidebar.vue +113 -0
- package/src/components/RoleSelector.vue +64 -0
- package/src/components/SessionHistoryPanel.vue +176 -0
- package/src/components/SessionTabBar.vue +81 -0
- package/src/components/SettingsMcpTab.vue +350 -0
- package/src/components/SettingsModal.vue +275 -0
- package/src/components/SettingsReferenceDirsTab.vue +173 -0
- package/src/components/SettingsWorkspaceDirsTab.vue +174 -0
- package/src/components/SidebarHeader.vue +69 -0
- package/src/components/StackView.vue +360 -0
- package/src/components/SuggestionsPanel.vue +65 -0
- package/src/components/TodoExplorer.vue +358 -0
- package/src/components/ToolResultsPanel.vue +77 -0
- package/src/components/todo/TodoAddDialog.vue +131 -0
- package/src/components/todo/TodoEditDialog.vue +47 -0
- package/src/components/todo/TodoEditPanel.vue +113 -0
- package/src/components/todo/TodoKanbanView.vue +249 -0
- package/src/components/todo/TodoListView.vue +79 -0
- package/src/components/todo/TodoTableView.vue +177 -0
- package/src/composables/useActiveSession.ts +40 -0
- package/src/composables/useAppApi.ts +45 -0
- package/src/composables/useCanvasViewMode.ts +121 -0
- package/src/composables/useChatScroll.ts +47 -0
- package/src/composables/useClickOutside.ts +26 -0
- package/src/composables/useClipboardCopy.ts +44 -0
- package/src/composables/useContentDisplay.ts +52 -0
- package/src/composables/useDebugBeat.ts +23 -0
- package/src/composables/useDynamicFavicon.ts +115 -0
- package/src/composables/useEventListeners.ts +42 -0
- package/src/composables/useExpandedDirs.ts +64 -0
- package/src/composables/useFaviconState.ts +30 -0
- package/src/composables/useFileSelection.ts +115 -0
- package/src/composables/useFileSortMode.ts +24 -0
- package/src/composables/useFileTree.ts +85 -0
- package/src/composables/useFreshPluginData.ts +89 -0
- package/src/composables/useHealth.ts +38 -0
- package/src/composables/useImeAwareEnter.ts +57 -0
- package/src/composables/useKeyNavigation.ts +60 -0
- package/src/composables/useMarkdownLinkHandler.ts +46 -0
- package/src/composables/useMarkdownMode.ts +17 -0
- package/src/composables/useMcpTools.ts +71 -0
- package/src/composables/useMergedSessions.ts +27 -0
- package/src/composables/useNotifications.ts +90 -0
- package/src/composables/usePdfDownload.ts +60 -0
- package/src/composables/usePendingCalls.ts +77 -0
- package/src/composables/usePubSub.ts +85 -0
- package/src/composables/useRightSidebar.ts +23 -0
- package/src/composables/useRoles.ts +34 -0
- package/src/composables/useSandboxStatus.ts +67 -0
- package/src/composables/useSelectedResult.ts +49 -0
- package/src/composables/useSessionDerived.ts +51 -0
- package/src/composables/useSessionHistory.ts +81 -0
- package/src/composables/useSessionSync.ts +57 -0
- package/src/composables/useViewLayout.ts +55 -0
- package/src/config/apiRoutes.ts +173 -0
- package/src/config/pubsubChannels.ts +45 -0
- package/src/config/roles.ts +335 -0
- package/src/config/schedulerActions.ts +25 -0
- package/src/config/toolNames.ts +71 -0
- package/src/config/workspacePaths.ts +24 -0
- package/src/index.css +107 -0
- package/src/main.ts +25 -0
- package/src/plugins/canvas/Preview.vue +13 -0
- package/src/plugins/canvas/View.vue +333 -0
- package/src/plugins/canvas/definition.ts +38 -0
- package/src/plugins/canvas/index.ts +36 -0
- package/src/plugins/chart/Preview.vue +49 -0
- package/src/plugins/chart/View.vue +143 -0
- package/src/plugins/chart/definition.ts +58 -0
- package/src/plugins/chart/index.ts +52 -0
- package/src/plugins/editImage/Preview.vue +13 -0
- package/src/plugins/editImage/View.vue +13 -0
- package/src/plugins/editImage/definition.ts +27 -0
- package/src/plugins/editImage/index.ts +36 -0
- package/src/plugins/generateImage/Preview.vue +13 -0
- package/src/plugins/generateImage/View.vue +33 -0
- package/src/plugins/generateImage/definition.ts +32 -0
- package/src/plugins/generateImage/index.ts +56 -0
- package/src/plugins/manageRoles/Preview.vue +49 -0
- package/src/plugins/manageRoles/View.vue +525 -0
- package/src/plugins/manageRoles/definition.ts +43 -0
- package/src/plugins/manageRoles/index.ts +47 -0
- package/src/plugins/manageSkills/Preview.vue +21 -0
- package/src/plugins/manageSkills/View.vue +321 -0
- package/src/plugins/manageSkills/definition.ts +49 -0
- package/src/plugins/manageSkills/index.ts +49 -0
- package/src/plugins/manageSource/Preview.vue +33 -0
- package/src/plugins/manageSource/View.vue +697 -0
- package/src/plugins/manageSource/definition.ts +63 -0
- package/src/plugins/manageSource/index.ts +66 -0
- package/src/plugins/markdown/Preview.vue +77 -0
- package/src/plugins/markdown/View.vue +476 -0
- package/src/plugins/markdown/definition.ts +50 -0
- package/src/plugins/markdown/index.ts +36 -0
- package/src/plugins/presentHtml/Preview.vue +25 -0
- package/src/plugins/presentHtml/View.vue +52 -0
- package/src/plugins/presentHtml/definition.ts +27 -0
- package/src/plugins/presentHtml/helpers.ts +72 -0
- package/src/plugins/presentHtml/index.ts +41 -0
- package/src/plugins/presentMulmoScript/Preview.vue +23 -0
- package/src/plugins/presentMulmoScript/View.vue +1166 -0
- package/src/plugins/presentMulmoScript/definition.ts +95 -0
- package/src/plugins/presentMulmoScript/helpers.ts +162 -0
- package/src/plugins/presentMulmoScript/index.ts +40 -0
- package/src/plugins/scheduler/Preview.vue +67 -0
- package/src/plugins/scheduler/TasksTab.vue +205 -0
- package/src/plugins/scheduler/View.vue +565 -0
- package/src/plugins/scheduler/definition.ts +57 -0
- package/src/plugins/scheduler/index.ts +45 -0
- package/src/plugins/scheduler/viewModes.ts +26 -0
- package/src/plugins/spreadsheet/Preview.vue +29 -0
- package/src/plugins/spreadsheet/View.vue +997 -0
- package/src/plugins/spreadsheet/cellHighlights.ts +79 -0
- package/src/plugins/spreadsheet/definition.ts +121 -0
- package/src/plugins/spreadsheet/engine/calculator.ts +459 -0
- package/src/plugins/spreadsheet/engine/cellBuilder.ts +81 -0
- package/src/plugins/spreadsheet/engine/date-parser.ts +220 -0
- package/src/plugins/spreadsheet/engine/date-utils.ts +56 -0
- package/src/plugins/spreadsheet/engine/engine.ts +176 -0
- package/src/plugins/spreadsheet/engine/evaluator.ts +390 -0
- package/src/plugins/spreadsheet/engine/formatter.ts +172 -0
- package/src/plugins/spreadsheet/engine/formulaRefs.ts +101 -0
- package/src/plugins/spreadsheet/engine/functions/date.ts +299 -0
- package/src/plugins/spreadsheet/engine/functions/financial.ts +387 -0
- package/src/plugins/spreadsheet/engine/functions/index.ts +16 -0
- package/src/plugins/spreadsheet/engine/functions/logical.ts +262 -0
- package/src/plugins/spreadsheet/engine/functions/lookup.ts +400 -0
- package/src/plugins/spreadsheet/engine/functions/mathematical.ts +297 -0
- package/src/plugins/spreadsheet/engine/functions/statistical.ts +338 -0
- package/src/plugins/spreadsheet/engine/functions/text.ts +389 -0
- package/src/plugins/spreadsheet/engine/index.ts +27 -0
- package/src/plugins/spreadsheet/engine/jsonCellLocator.ts +111 -0
- package/src/plugins/spreadsheet/engine/parser.ts +143 -0
- package/src/plugins/spreadsheet/engine/registry.ts +150 -0
- package/src/plugins/spreadsheet/engine/responseDecoder.ts +67 -0
- package/src/plugins/spreadsheet/engine/types.ts +64 -0
- package/src/plugins/spreadsheet/index.ts +36 -0
- package/src/plugins/textResponse/Preview.vue +94 -0
- package/src/plugins/textResponse/View.vue +503 -0
- package/src/plugins/textResponse/definition.ts +34 -0
- package/src/plugins/textResponse/index.ts +27 -0
- package/src/plugins/textResponse/plugin.ts +29 -0
- package/src/plugins/textResponse/samples.ts +97 -0
- package/src/plugins/textResponse/types.ts +11 -0
- package/src/plugins/todo/Preview.vue +63 -0
- package/src/plugins/todo/View.vue +364 -0
- package/src/plugins/todo/composables/useTodos.ts +177 -0
- package/src/plugins/todo/definition.ts +45 -0
- package/src/plugins/todo/index.ts +61 -0
- package/src/plugins/todo/labels.ts +163 -0
- package/src/plugins/todo/priority.ts +98 -0
- package/src/plugins/todo/viewModes.ts +19 -0
- package/src/plugins/ui-image/ImagePreview.vue +23 -0
- package/src/plugins/ui-image/ImageView.vue +34 -0
- package/src/plugins/ui-image/index.ts +3 -0
- package/src/plugins/ui-image/types.ts +4 -0
- package/src/plugins/wiki/Preview.vue +65 -0
- package/src/plugins/wiki/View.vue +342 -0
- package/src/plugins/wiki/definition.ts +25 -0
- package/src/plugins/wiki/helpers.ts +59 -0
- package/src/plugins/wiki/index.ts +52 -0
- package/src/router/guards.ts +61 -0
- package/src/router/index.ts +50 -0
- package/src/tools/index.ts +52 -0
- package/src/tools/types.ts +27 -0
- package/src/types/events.ts +16 -0
- package/src/types/fileTree.ts +13 -0
- package/src/types/notification.ts +67 -0
- package/src/types/session.ts +116 -0
- package/src/types/sse.ts +90 -0
- package/src/types/toolCallHistory.ts +13 -0
- package/src/utils/agent/eventDispatch.ts +74 -0
- package/src/utils/agent/request.ts +55 -0
- package/src/utils/agent/toolCalls.ts +62 -0
- package/src/utils/api.ts +218 -0
- package/src/utils/canvas/viewMode.ts +46 -0
- package/src/utils/dom/authTokenMeta.ts +20 -0
- package/src/utils/dom/clickOutside.ts +11 -0
- package/src/utils/dom/externalLink.ts +57 -0
- package/src/utils/dom/scrollable.ts +24 -0
- package/src/utils/errors.ts +11 -0
- package/src/utils/files/expandedDirs.ts +25 -0
- package/src/utils/files/filename.ts +12 -0
- package/src/utils/files/sortChildren.ts +20 -0
- package/src/utils/filesPreview/schedulerPreview.ts +38 -0
- package/src/utils/filesPreview/todoPreview.ts +40 -0
- package/src/utils/format/date.ts +85 -0
- package/src/utils/format/frontmatter.ts +80 -0
- package/src/utils/format/jsonSyntax.ts +109 -0
- package/src/utils/html/previewCsp.ts +65 -0
- package/src/utils/image/resolve.ts +8 -0
- package/src/utils/image/rewriteMarkdownImageRefs.ts +182 -0
- package/src/utils/markdown/extractFirstH1.ts +39 -0
- package/src/utils/notification/dispatch.ts +22 -0
- package/src/utils/path/relativeLink.ts +130 -0
- package/src/utils/role/icon.ts +20 -0
- package/src/utils/role/merge.ts +10 -0
- package/src/utils/role/plugins.ts +12 -0
- package/src/utils/session/mergeSessions.ts +103 -0
- package/src/utils/session/seedRoleDefault.ts +35 -0
- package/src/utils/session/sessionEntries.ts +121 -0
- package/src/utils/session/sessionFactory.ts +22 -0
- package/src/utils/session/sessionHelpers.ts +99 -0
- package/src/utils/tools/dedup.ts +17 -0
- package/src/utils/tools/mcp.ts +33 -0
- package/src/utils/tools/pendingCalls.ts +16 -0
- package/src/utils/tools/result.ts +40 -0
- package/src/utils/types.ts +44 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Date and Time Functions
|
|
3
|
+
* Excel stores dates as sequential serial numbers so they can be used in calculations.
|
|
4
|
+
* January 1, 1900 is serial number 1.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { functionRegistry, toNumber, toString, type FunctionHandler } from "../registry";
|
|
8
|
+
import { dateToSerial, serialToDate } from "../date-utils";
|
|
9
|
+
|
|
10
|
+
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
11
|
+
|
|
12
|
+
const nowHandler: FunctionHandler = (args) => {
|
|
13
|
+
if (args.length !== 0) throw new Error("NOW requires 0 arguments");
|
|
14
|
+
// Return current date and time as serial number
|
|
15
|
+
// We need to adjust for timezone offset because Excel dates are "local" usually
|
|
16
|
+
// But for simplicity we'll use local time converted to serial
|
|
17
|
+
const now = new Date();
|
|
18
|
+
// Create a UTC date that matches the local time components
|
|
19
|
+
const localAsUtc = new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(), now.getMinutes(), now.getSeconds()));
|
|
20
|
+
return dateToSerial(localAsUtc);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const todayHandler: FunctionHandler = (args) => {
|
|
24
|
+
if (args.length !== 0) throw new Error("TODAY requires 0 arguments");
|
|
25
|
+
const now = new Date();
|
|
26
|
+
const today = new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()));
|
|
27
|
+
return dateToSerial(today);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const dateHandler: FunctionHandler = (args, context) => {
|
|
31
|
+
if (args.length !== 3) throw new Error("DATE requires 3 arguments");
|
|
32
|
+
|
|
33
|
+
const year = toNumber(context.evaluateFormula(args[0]));
|
|
34
|
+
const month = toNumber(context.evaluateFormula(args[1]));
|
|
35
|
+
const day = toNumber(context.evaluateFormula(args[2]));
|
|
36
|
+
|
|
37
|
+
// JS Date constructor handles overflow (e.g. month 13 becomes Jan of next year)
|
|
38
|
+
// Month is 0-indexed in JS, 1-indexed in Excel
|
|
39
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
40
|
+
return dateToSerial(date);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const timeHandler: FunctionHandler = (args, context) => {
|
|
44
|
+
if (args.length !== 3) throw new Error("TIME requires 3 arguments");
|
|
45
|
+
|
|
46
|
+
const hour = toNumber(context.evaluateFormula(args[0]));
|
|
47
|
+
const minute = toNumber(context.evaluateFormula(args[1]));
|
|
48
|
+
const second = toNumber(context.evaluateFormula(args[2]));
|
|
49
|
+
|
|
50
|
+
// Time is a fraction of a day
|
|
51
|
+
// 1 hour = 1/24
|
|
52
|
+
// 1 minute = 1/(24*60)
|
|
53
|
+
// 1 second = 1/(24*60*60)
|
|
54
|
+
|
|
55
|
+
// Normalize inputs (e.g. 25 hours)
|
|
56
|
+
const totalSeconds = hour * 3600 + minute * 60 + second;
|
|
57
|
+
const secondsPerDay = 86400;
|
|
58
|
+
|
|
59
|
+
return (totalSeconds % secondsPerDay) / secondsPerDay;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const yearHandler: FunctionHandler = (args, context) => {
|
|
63
|
+
if (args.length !== 1) throw new Error("YEAR requires 1 argument");
|
|
64
|
+
const serial = toNumber(context.evaluateFormula(args[0]));
|
|
65
|
+
const date = serialToDate(serial);
|
|
66
|
+
return date.getUTCFullYear();
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const monthHandler: FunctionHandler = (args, context) => {
|
|
70
|
+
if (args.length !== 1) throw new Error("MONTH requires 1 argument");
|
|
71
|
+
const serial = toNumber(context.evaluateFormula(args[0]));
|
|
72
|
+
const date = serialToDate(serial);
|
|
73
|
+
return date.getUTCMonth() + 1; // 1-indexed
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const dayHandler: FunctionHandler = (args, context) => {
|
|
77
|
+
if (args.length !== 1) throw new Error("DAY requires 1 argument");
|
|
78
|
+
const serial = toNumber(context.evaluateFormula(args[0]));
|
|
79
|
+
const date = serialToDate(serial);
|
|
80
|
+
return date.getUTCDate();
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const hourHandler: FunctionHandler = (args, context) => {
|
|
84
|
+
if (args.length !== 1) throw new Error("HOUR requires 1 argument");
|
|
85
|
+
const serial = toNumber(context.evaluateFormula(args[0]));
|
|
86
|
+
// Get fractional part
|
|
87
|
+
const timePart = serial - Math.floor(serial);
|
|
88
|
+
const totalSeconds = Math.round(timePart * 86400);
|
|
89
|
+
return Math.floor(totalSeconds / 3600);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const minuteHandler: FunctionHandler = (args, context) => {
|
|
93
|
+
if (args.length !== 1) throw new Error("MINUTE requires 1 argument");
|
|
94
|
+
const serial = toNumber(context.evaluateFormula(args[0]));
|
|
95
|
+
const timePart = serial - Math.floor(serial);
|
|
96
|
+
const totalSeconds = Math.round(timePart * 86400);
|
|
97
|
+
return Math.floor((totalSeconds % 3600) / 60);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const secondHandler: FunctionHandler = (args, context) => {
|
|
101
|
+
if (args.length !== 1) throw new Error("SECOND requires 1 argument");
|
|
102
|
+
const serial = toNumber(context.evaluateFormula(args[0]));
|
|
103
|
+
const timePart = serial - Math.floor(serial);
|
|
104
|
+
const totalSeconds = Math.round(timePart * 86400);
|
|
105
|
+
return totalSeconds % 60;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const datedifHandler: FunctionHandler = (args, context) => {
|
|
109
|
+
if (args.length !== 3) throw new Error("DATEDIF requires 3 arguments");
|
|
110
|
+
|
|
111
|
+
const startSerial = toNumber(context.evaluateFormula(args[0]));
|
|
112
|
+
const endSerial = toNumber(context.evaluateFormula(args[1]));
|
|
113
|
+
const unit = toString(context.evaluateFormula(args[2])).toUpperCase();
|
|
114
|
+
|
|
115
|
+
if (startSerial > endSerial) return "#NUM!";
|
|
116
|
+
|
|
117
|
+
const startDate = serialToDate(startSerial);
|
|
118
|
+
const endDate = serialToDate(endSerial);
|
|
119
|
+
|
|
120
|
+
const yearDiff = endDate.getUTCFullYear() - startDate.getUTCFullYear();
|
|
121
|
+
const monthDiff = endDate.getUTCMonth() - startDate.getUTCMonth();
|
|
122
|
+
const dayDiff = endDate.getUTCDate() - startDate.getUTCDate();
|
|
123
|
+
|
|
124
|
+
switch (unit) {
|
|
125
|
+
case "Y": {
|
|
126
|
+
// Complete years
|
|
127
|
+
let years = yearDiff;
|
|
128
|
+
if (monthDiff < 0 || (monthDiff === 0 && dayDiff < 0)) {
|
|
129
|
+
years--;
|
|
130
|
+
}
|
|
131
|
+
return years;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
case "M": {
|
|
135
|
+
// Complete months
|
|
136
|
+
let months = yearDiff * 12 + monthDiff;
|
|
137
|
+
if (dayDiff < 0) {
|
|
138
|
+
months--;
|
|
139
|
+
}
|
|
140
|
+
return months;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
case "D":
|
|
144
|
+
// Complete days
|
|
145
|
+
return Math.floor(endSerial - startSerial);
|
|
146
|
+
|
|
147
|
+
case "MD": {
|
|
148
|
+
// Difference in days, ignoring months and years
|
|
149
|
+
// This is tricky. It's basically day of month difference, but handling wrap around
|
|
150
|
+
// E.g. Jan 30 to Mar 1.
|
|
151
|
+
// Standard implementation:
|
|
152
|
+
const startD = startDate.getUTCDate();
|
|
153
|
+
const endD = endDate.getUTCDate();
|
|
154
|
+
|
|
155
|
+
if (endD >= startD) return endD - startD;
|
|
156
|
+
|
|
157
|
+
// Need to borrow days from previous month
|
|
158
|
+
const prevMonthDate = new Date(Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth(), 0));
|
|
159
|
+
return prevMonthDate.getUTCDate() - startD + endD;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
case "YM": {
|
|
163
|
+
// Difference in months, ignoring years
|
|
164
|
+
let ym = monthDiff;
|
|
165
|
+
if (dayDiff < 0) ym--;
|
|
166
|
+
if (ym < 0) ym += 12;
|
|
167
|
+
return ym;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
case "YD": {
|
|
171
|
+
// Difference in days, ignoring years
|
|
172
|
+
// Treat start date as being in the same year as end date
|
|
173
|
+
// If start > end (after adjusting year), move start to previous year
|
|
174
|
+
const startCopy = new Date(startDate);
|
|
175
|
+
startCopy.setUTCFullYear(endDate.getUTCFullYear());
|
|
176
|
+
|
|
177
|
+
const diff = (startCopy.getTime() - endDate.getTime()) / MS_PER_DAY;
|
|
178
|
+
if (diff > 0) {
|
|
179
|
+
startCopy.setUTCFullYear(endDate.getUTCFullYear() - 1);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return Math.floor((endDate.getTime() - startCopy.getTime()) / MS_PER_DAY);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
default:
|
|
186
|
+
return "#NUM!";
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Register functions
|
|
191
|
+
functionRegistry.register({
|
|
192
|
+
name: "NOW",
|
|
193
|
+
handler: nowHandler,
|
|
194
|
+
minArgs: 0,
|
|
195
|
+
maxArgs: 0,
|
|
196
|
+
description: "Returns the serial number of the current date and time",
|
|
197
|
+
examples: ["NOW()"],
|
|
198
|
+
category: "Date & Time",
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
functionRegistry.register({
|
|
202
|
+
name: "TODAY",
|
|
203
|
+
handler: todayHandler,
|
|
204
|
+
minArgs: 0,
|
|
205
|
+
maxArgs: 0,
|
|
206
|
+
description: "Returns the serial number of today's date",
|
|
207
|
+
examples: ["TODAY()"],
|
|
208
|
+
category: "Date & Time",
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
functionRegistry.register({
|
|
212
|
+
name: "DATE",
|
|
213
|
+
handler: dateHandler,
|
|
214
|
+
minArgs: 3,
|
|
215
|
+
maxArgs: 3,
|
|
216
|
+
description: "Returns the serial number that represents a particular date",
|
|
217
|
+
examples: ["DATE(2023, 11, 25)"],
|
|
218
|
+
category: "Date & Time",
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
functionRegistry.register({
|
|
222
|
+
name: "TIME",
|
|
223
|
+
handler: timeHandler,
|
|
224
|
+
minArgs: 3,
|
|
225
|
+
maxArgs: 3,
|
|
226
|
+
description: "Returns the serial number of a particular time",
|
|
227
|
+
examples: ["TIME(14, 30, 0)"],
|
|
228
|
+
category: "Date & Time",
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
functionRegistry.register({
|
|
232
|
+
name: "YEAR",
|
|
233
|
+
handler: yearHandler,
|
|
234
|
+
minArgs: 1,
|
|
235
|
+
maxArgs: 1,
|
|
236
|
+
description: "Converts a serial number to a year",
|
|
237
|
+
examples: ["YEAR(TODAY())", "YEAR(A1)"],
|
|
238
|
+
category: "Date & Time",
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
functionRegistry.register({
|
|
242
|
+
name: "MONTH",
|
|
243
|
+
handler: monthHandler,
|
|
244
|
+
minArgs: 1,
|
|
245
|
+
maxArgs: 1,
|
|
246
|
+
description: "Converts a serial number to a month",
|
|
247
|
+
examples: ["MONTH(TODAY())", "MONTH(A1)"],
|
|
248
|
+
category: "Date & Time",
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
functionRegistry.register({
|
|
252
|
+
name: "DAY",
|
|
253
|
+
handler: dayHandler,
|
|
254
|
+
minArgs: 1,
|
|
255
|
+
maxArgs: 1,
|
|
256
|
+
description: "Converts a serial number to a day of the month",
|
|
257
|
+
examples: ["DAY(TODAY())", "DAY(A1)"],
|
|
258
|
+
category: "Date & Time",
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
functionRegistry.register({
|
|
262
|
+
name: "HOUR",
|
|
263
|
+
handler: hourHandler,
|
|
264
|
+
minArgs: 1,
|
|
265
|
+
maxArgs: 1,
|
|
266
|
+
description: "Converts a serial number to an hour",
|
|
267
|
+
examples: ["HOUR(NOW())", "HOUR(A1)"],
|
|
268
|
+
category: "Date & Time",
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
functionRegistry.register({
|
|
272
|
+
name: "MINUTE",
|
|
273
|
+
handler: minuteHandler,
|
|
274
|
+
minArgs: 1,
|
|
275
|
+
maxArgs: 1,
|
|
276
|
+
description: "Converts a serial number to a minute",
|
|
277
|
+
examples: ["MINUTE(NOW())", "MINUTE(A1)"],
|
|
278
|
+
category: "Date & Time",
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
functionRegistry.register({
|
|
282
|
+
name: "SECOND",
|
|
283
|
+
handler: secondHandler,
|
|
284
|
+
minArgs: 1,
|
|
285
|
+
maxArgs: 1,
|
|
286
|
+
description: "Converts a serial number to a second",
|
|
287
|
+
examples: ["SECOND(NOW())", "SECOND(A1)"],
|
|
288
|
+
category: "Date & Time",
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
functionRegistry.register({
|
|
292
|
+
name: "DATEDIF",
|
|
293
|
+
handler: datedifHandler,
|
|
294
|
+
minArgs: 3,
|
|
295
|
+
maxArgs: 3,
|
|
296
|
+
description: "Calculates the number of days, months, or years between two dates",
|
|
297
|
+
examples: ['DATEDIF(A1, B1, "Y")', 'DATEDIF(DATE(2020,1,1), TODAY(), "D")'],
|
|
298
|
+
category: "Date & Time",
|
|
299
|
+
});
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Financial Functions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { functionRegistry, toNumber, type FunctionHandler } from "../registry";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* FV - Future Value
|
|
9
|
+
* Calculates the future value of an investment based on periodic, constant payments and a constant interest rate.
|
|
10
|
+
* FV(rate, nper, pmt, [pv], [type])
|
|
11
|
+
* - rate: Interest rate per period
|
|
12
|
+
* - nper: Total number of payment periods
|
|
13
|
+
* - pmt: Payment made each period (negative for outflows)
|
|
14
|
+
* - pv: Present value (optional, default 0)
|
|
15
|
+
* - type: 0 = end of period, 1 = beginning of period (optional, default 0)
|
|
16
|
+
*/
|
|
17
|
+
const fvHandler: FunctionHandler = (args, context) => {
|
|
18
|
+
if (args.length < 3 || args.length > 5) {
|
|
19
|
+
throw new Error("FV requires 3 to 5 arguments");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const rate = toNumber(context.evaluateFormula(args[0]));
|
|
23
|
+
const nper = toNumber(context.evaluateFormula(args[1]));
|
|
24
|
+
const pmt = toNumber(context.evaluateFormula(args[2]));
|
|
25
|
+
const pv = args.length >= 4 ? toNumber(context.evaluateFormula(args[3])) : 0;
|
|
26
|
+
const type = args.length >= 5 ? toNumber(context.evaluateFormula(args[4])) : 0;
|
|
27
|
+
|
|
28
|
+
if (rate === 0) {
|
|
29
|
+
return -(pv + pmt * nper);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const pvFactor = Math.pow(1 + rate, nper);
|
|
33
|
+
const fv = -pv * pvFactor - (pmt * (pvFactor - 1) * (1 + rate * type)) / rate;
|
|
34
|
+
|
|
35
|
+
return fv;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* PV - Present Value
|
|
40
|
+
* Calculates the present value of an investment based on periodic, constant payments and a constant interest rate.
|
|
41
|
+
* PV(rate, nper, pmt, [fv], [type])
|
|
42
|
+
*/
|
|
43
|
+
const pvHandler: FunctionHandler = (args, context) => {
|
|
44
|
+
if (args.length < 3 || args.length > 5) {
|
|
45
|
+
throw new Error("PV requires 3 to 5 arguments");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const rate = toNumber(context.evaluateFormula(args[0]));
|
|
49
|
+
const nper = toNumber(context.evaluateFormula(args[1]));
|
|
50
|
+
const pmt = toNumber(context.evaluateFormula(args[2]));
|
|
51
|
+
const fv = args.length >= 4 ? toNumber(context.evaluateFormula(args[3])) : 0;
|
|
52
|
+
const type = args.length >= 5 ? toNumber(context.evaluateFormula(args[4])) : 0;
|
|
53
|
+
|
|
54
|
+
if (rate === 0) {
|
|
55
|
+
return -(fv + pmt * nper);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const pvFactor = Math.pow(1 + rate, nper);
|
|
59
|
+
const pv = (-fv - (pmt * (pvFactor - 1) * (1 + rate * type)) / rate) / pvFactor;
|
|
60
|
+
|
|
61
|
+
return pv;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* PMT - Payment
|
|
66
|
+
* Calculates the payment for a loan based on constant payments and a constant interest rate.
|
|
67
|
+
* PMT(rate, nper, pv, [fv], [type])
|
|
68
|
+
*/
|
|
69
|
+
const pmtHandler: FunctionHandler = (args, context) => {
|
|
70
|
+
if (args.length < 3 || args.length > 5) {
|
|
71
|
+
throw new Error("PMT requires 3 to 5 arguments");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const rate = toNumber(context.evaluateFormula(args[0]));
|
|
75
|
+
const nper = toNumber(context.evaluateFormula(args[1]));
|
|
76
|
+
const pv = toNumber(context.evaluateFormula(args[2]));
|
|
77
|
+
const fv = args.length >= 4 ? toNumber(context.evaluateFormula(args[3])) : 0;
|
|
78
|
+
const type = args.length >= 5 ? toNumber(context.evaluateFormula(args[4])) : 0;
|
|
79
|
+
|
|
80
|
+
if (rate === 0) {
|
|
81
|
+
return -(fv + pv) / nper;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const pvFactor = Math.pow(1 + rate, nper);
|
|
85
|
+
const pmt = (-rate * (fv + pv * pvFactor)) / ((pvFactor - 1) * (1 + rate * type));
|
|
86
|
+
|
|
87
|
+
return pmt;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* NPER - Number of Periods
|
|
92
|
+
* Calculates the number of periods for an investment based on periodic, constant payments and a constant interest rate.
|
|
93
|
+
* NPER(rate, pmt, pv, [fv], [type])
|
|
94
|
+
*/
|
|
95
|
+
const nperHandler: FunctionHandler = (args, context) => {
|
|
96
|
+
if (args.length < 3 || args.length > 5) {
|
|
97
|
+
throw new Error("NPER requires 3 to 5 arguments");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const rate = toNumber(context.evaluateFormula(args[0]));
|
|
101
|
+
const pmt = toNumber(context.evaluateFormula(args[1]));
|
|
102
|
+
const pv = toNumber(context.evaluateFormula(args[2]));
|
|
103
|
+
const fv = args.length >= 4 ? toNumber(context.evaluateFormula(args[3])) : 0;
|
|
104
|
+
const type = args.length >= 5 ? toNumber(context.evaluateFormula(args[4])) : 0;
|
|
105
|
+
|
|
106
|
+
if (rate === 0) {
|
|
107
|
+
return -(fv + pv) / pmt;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const pmtWithType = pmt * (1 + rate * type);
|
|
111
|
+
const nper = Math.log((pmtWithType - fv * rate) / (pmtWithType + pv * rate)) / Math.log(1 + rate);
|
|
112
|
+
|
|
113
|
+
return nper;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* RATE - Interest Rate
|
|
118
|
+
* Calculates the interest rate per period of an annuity.
|
|
119
|
+
* RATE(nper, pmt, pv, [fv], [type], [guess])
|
|
120
|
+
* Uses Newton-Raphson method for iteration
|
|
121
|
+
*/
|
|
122
|
+
const rateHandler: FunctionHandler = (args, context) => {
|
|
123
|
+
if (args.length < 3 || args.length > 6) {
|
|
124
|
+
throw new Error("RATE requires 3 to 6 arguments");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const nper = toNumber(context.evaluateFormula(args[0]));
|
|
128
|
+
const pmt = toNumber(context.evaluateFormula(args[1]));
|
|
129
|
+
const pv = toNumber(context.evaluateFormula(args[2]));
|
|
130
|
+
const fv = args.length >= 4 ? toNumber(context.evaluateFormula(args[3])) : 0;
|
|
131
|
+
const type = args.length >= 5 ? toNumber(context.evaluateFormula(args[4])) : 0;
|
|
132
|
+
const guess = args.length >= 6 ? toNumber(context.evaluateFormula(args[5])) : 0.1;
|
|
133
|
+
|
|
134
|
+
// Use Newton-Raphson method to find rate
|
|
135
|
+
let rate = guess;
|
|
136
|
+
const maxIterations = 100;
|
|
137
|
+
const tolerance = 1e-7;
|
|
138
|
+
|
|
139
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
140
|
+
if (Math.abs(rate) < tolerance) {
|
|
141
|
+
rate = tolerance; // Avoid division by zero
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const y = Math.pow(1 + rate, nper);
|
|
145
|
+
const f = pv * y + pmt * ((y - 1) / rate) * (1 + rate * type) + fv;
|
|
146
|
+
|
|
147
|
+
const df =
|
|
148
|
+
nper * pv * Math.pow(1 + rate, nper - 1) +
|
|
149
|
+
(pmt * (1 + rate * type) * (nper * Math.pow(1 + rate, nper - 1) * rate - (Math.pow(1 + rate, nper) - 1))) / (rate * rate) +
|
|
150
|
+
pmt * type * ((Math.pow(1 + rate, nper) - 1) / rate);
|
|
151
|
+
|
|
152
|
+
const newRate = rate - f / df;
|
|
153
|
+
|
|
154
|
+
if (Math.abs(newRate - rate) < tolerance) {
|
|
155
|
+
return newRate;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
rate = newRate;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return rate;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* IPMT - Interest Payment
|
|
166
|
+
* Calculates the interest payment for a given period for an investment based on periodic, constant payments and a constant interest rate.
|
|
167
|
+
* IPMT(rate, per, nper, pv, [fv], [type])
|
|
168
|
+
*/
|
|
169
|
+
const ipmtHandler: FunctionHandler = (args, context) => {
|
|
170
|
+
if (args.length < 4 || args.length > 6) {
|
|
171
|
+
throw new Error("IPMT requires 4 to 6 arguments");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const rate = toNumber(context.evaluateFormula(args[0]));
|
|
175
|
+
const per = toNumber(context.evaluateFormula(args[1]));
|
|
176
|
+
const type = args.length >= 6 ? toNumber(context.evaluateFormula(args[5])) : 0;
|
|
177
|
+
|
|
178
|
+
// Calculate payment first
|
|
179
|
+
const pmt = pmtHandler([args[0], args[2], args[3], ...(args.length >= 5 ? [args[4]] : []), ...(args.length >= 6 ? [args[5]] : [])], context);
|
|
180
|
+
|
|
181
|
+
if (per === 1 && type === 1) {
|
|
182
|
+
return 0; // No interest in first period when payment is at beginning
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Calculate remaining balance at previous period
|
|
186
|
+
const fvPrevious = fvHandler([args[0], String(type === 1 ? per - 2 : per - 1), String(pmt), args[3], ...(args.length >= 6 ? [args[5]] : [])], context);
|
|
187
|
+
|
|
188
|
+
const ipmt = -fvPrevious * rate;
|
|
189
|
+
|
|
190
|
+
return type === 1 ? ipmt / (1 + rate) : ipmt;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* PPMT - Principal Payment
|
|
195
|
+
* Calculates the payment on the principal for a given period for an investment based on periodic, constant payments and a constant interest rate.
|
|
196
|
+
* PPMT(rate, per, nper, pv, [fv], [type])
|
|
197
|
+
*/
|
|
198
|
+
const ppmtHandler: FunctionHandler = (args, context) => {
|
|
199
|
+
if (args.length < 4 || args.length > 6) {
|
|
200
|
+
throw new Error("PPMT requires 4 to 6 arguments");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Calculate total payment
|
|
204
|
+
const pmt = pmtHandler([args[0], args[2], args[3], ...(args.length >= 5 ? [args[4]] : []), ...(args.length >= 6 ? [args[5]] : [])], context);
|
|
205
|
+
|
|
206
|
+
// Calculate interest payment
|
|
207
|
+
const ipmt = ipmtHandler(args, context);
|
|
208
|
+
|
|
209
|
+
// Principal payment = Total payment - Interest payment
|
|
210
|
+
return toNumber(pmt) - toNumber(ipmt);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* NPV - Net Present Value
|
|
215
|
+
* Calculates the net present value of an investment based on a discount rate and a series of future cash flows.
|
|
216
|
+
* NPV(rate, value1, [value2], ...)
|
|
217
|
+
*/
|
|
218
|
+
const npvHandler: FunctionHandler = (args, context) => {
|
|
219
|
+
if (args.length < 2) {
|
|
220
|
+
throw new Error("NPV requires at least 2 arguments");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const rate = toNumber(context.evaluateFormula(args[0]));
|
|
224
|
+
let npv = 0;
|
|
225
|
+
|
|
226
|
+
// Process each cash flow
|
|
227
|
+
for (let i = 1; i < args.length; i++) {
|
|
228
|
+
// Check if argument is a range
|
|
229
|
+
if (args[i].includes(":")) {
|
|
230
|
+
const values = context.getRangeValues(args[i]);
|
|
231
|
+
for (let j = 0; j < values.length; j++) {
|
|
232
|
+
const value = toNumber(values[j]);
|
|
233
|
+
// Period starts from i for first range element, then continues
|
|
234
|
+
const period = i + j;
|
|
235
|
+
npv += value / Math.pow(1 + rate, period);
|
|
236
|
+
}
|
|
237
|
+
} else {
|
|
238
|
+
const value = toNumber(context.evaluateFormula(args[i]));
|
|
239
|
+
npv += value / Math.pow(1 + rate, i);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return npv;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* IRR - Internal Rate of Return
|
|
248
|
+
* Calculates the internal rate of return for a series of cash flows.
|
|
249
|
+
* IRR(values, [guess])
|
|
250
|
+
* Uses Newton-Raphson method for iteration
|
|
251
|
+
*/
|
|
252
|
+
const irrHandler: FunctionHandler = (args, context) => {
|
|
253
|
+
if (args.length < 1 || args.length > 2) {
|
|
254
|
+
throw new Error("IRR requires 1 or 2 arguments");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const values = context.getRangeValues(args[0]).map(toNumber);
|
|
258
|
+
const guess = args.length === 2 ? toNumber(context.evaluateFormula(args[1])) : 0.1;
|
|
259
|
+
|
|
260
|
+
if (values.length === 0) {
|
|
261
|
+
throw new Error("IRR requires at least one value");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Use Newton-Raphson method
|
|
265
|
+
let rate = guess;
|
|
266
|
+
const maxIterations = 100;
|
|
267
|
+
const tolerance = 1e-7;
|
|
268
|
+
|
|
269
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
270
|
+
let npv = 0;
|
|
271
|
+
let dnpv = 0;
|
|
272
|
+
|
|
273
|
+
for (let j = 0; j < values.length; j++) {
|
|
274
|
+
const factor = Math.pow(1 + rate, j);
|
|
275
|
+
npv += values[j] / factor;
|
|
276
|
+
dnpv -= (j * values[j]) / (factor * (1 + rate));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (Math.abs(npv) < tolerance) {
|
|
280
|
+
return rate;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (Math.abs(dnpv) < tolerance) {
|
|
284
|
+
throw new Error("IRR cannot converge");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const newRate = rate - npv / dnpv;
|
|
288
|
+
|
|
289
|
+
if (Math.abs(newRate - rate) < tolerance) {
|
|
290
|
+
return newRate;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
rate = newRate;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return rate;
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
// Register all financial functions
|
|
300
|
+
functionRegistry.register({
|
|
301
|
+
name: "FV",
|
|
302
|
+
handler: fvHandler,
|
|
303
|
+
minArgs: 3,
|
|
304
|
+
maxArgs: 5,
|
|
305
|
+
description: "Calculates the future value of an investment based on periodic, constant payments and a constant interest rate",
|
|
306
|
+
examples: ["FV(0.06/12, 12, -100, -1000, 1)", "FV(A1, A2, A3)"],
|
|
307
|
+
category: "Financial",
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
functionRegistry.register({
|
|
311
|
+
name: "PV",
|
|
312
|
+
handler: pvHandler,
|
|
313
|
+
minArgs: 3,
|
|
314
|
+
maxArgs: 5,
|
|
315
|
+
description: "Calculates the present value of an investment based on periodic, constant payments and a constant interest rate",
|
|
316
|
+
examples: ["PV(0.08/12, 12*20, 500, 0, 0)", "PV(A1, A2, A3)"],
|
|
317
|
+
category: "Financial",
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
functionRegistry.register({
|
|
321
|
+
name: "PMT",
|
|
322
|
+
handler: pmtHandler,
|
|
323
|
+
minArgs: 3,
|
|
324
|
+
maxArgs: 5,
|
|
325
|
+
description: "Calculates the payment for a loan based on constant payments and a constant interest rate",
|
|
326
|
+
examples: ["PMT(0.06/12, 30*12, 250000)", "PMT(A1, A2, A3)"],
|
|
327
|
+
category: "Financial",
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
functionRegistry.register({
|
|
331
|
+
name: "NPER",
|
|
332
|
+
handler: nperHandler,
|
|
333
|
+
minArgs: 3,
|
|
334
|
+
maxArgs: 5,
|
|
335
|
+
description: "Calculates the number of periods for an investment based on periodic, constant payments and a constant interest rate",
|
|
336
|
+
examples: ["NPER(0.06/12, -1000, 50000, 0, 0)", "NPER(A1, A2, A3)"],
|
|
337
|
+
category: "Financial",
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
functionRegistry.register({
|
|
341
|
+
name: "RATE",
|
|
342
|
+
handler: rateHandler,
|
|
343
|
+
minArgs: 3,
|
|
344
|
+
maxArgs: 6,
|
|
345
|
+
description: "Calculates the interest rate per period of an annuity using iteration",
|
|
346
|
+
examples: ["RATE(12, -100, 1000, 0, 0, 0.1)", "RATE(A1, A2, A3)"],
|
|
347
|
+
category: "Financial",
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
functionRegistry.register({
|
|
351
|
+
name: "IPMT",
|
|
352
|
+
handler: ipmtHandler,
|
|
353
|
+
minArgs: 4,
|
|
354
|
+
maxArgs: 6,
|
|
355
|
+
description: "Calculates the interest payment for a given period for an investment",
|
|
356
|
+
examples: ["IPMT(0.06/12, 1, 30*12, 250000)", "IPMT(A1, A2, A3, A4)"],
|
|
357
|
+
category: "Financial",
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
functionRegistry.register({
|
|
361
|
+
name: "PPMT",
|
|
362
|
+
handler: ppmtHandler,
|
|
363
|
+
minArgs: 4,
|
|
364
|
+
maxArgs: 6,
|
|
365
|
+
description: "Calculates the payment on the principal for a given period for an investment",
|
|
366
|
+
examples: ["PPMT(0.06/12, 1, 30*12, 250000)", "PPMT(A1, A2, A3, A4)"],
|
|
367
|
+
category: "Financial",
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
functionRegistry.register({
|
|
371
|
+
name: "NPV",
|
|
372
|
+
handler: npvHandler,
|
|
373
|
+
minArgs: 2,
|
|
374
|
+
description: "Calculates the net present value of an investment based on a discount rate and a series of future cash flows",
|
|
375
|
+
examples: ["NPV(0.1, -10000, 3000, 4200, 6800)", "NPV(A1, B1:B10)"],
|
|
376
|
+
category: "Financial",
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
functionRegistry.register({
|
|
380
|
+
name: "IRR",
|
|
381
|
+
handler: irrHandler,
|
|
382
|
+
minArgs: 1,
|
|
383
|
+
maxArgs: 2,
|
|
384
|
+
description: "Calculates the internal rate of return for a series of cash flows",
|
|
385
|
+
examples: ["IRR(A1:A5, 0.1)", "IRR(B1:B10)"],
|
|
386
|
+
category: "Financial",
|
|
387
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spreadsheet Functions Index
|
|
3
|
+
*
|
|
4
|
+
* Imports all function categories to register them with the function registry.
|
|
5
|
+
* Import this file to load all available spreadsheet functions.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import "./statistical";
|
|
9
|
+
import "./mathematical";
|
|
10
|
+
import "./logical";
|
|
11
|
+
import "./text";
|
|
12
|
+
import "./financial";
|
|
13
|
+
import "./lookup";
|
|
14
|
+
import "./date";
|
|
15
|
+
|
|
16
|
+
export { functionRegistry } from "../registry";
|