1 line
27 KiB
JSON
1 line
27 KiB
JSON
{"path":"NexusReader.Infrastructure/Services/EpubReaderService.cs","purpose":"Service that locates an EPUB file for an ebook in the DB, reads and parses a chapter into content blocks (text segments and AI-trigger blocks) for the reader UI.","classification":{"role":"service","layer":"infrastructure","confidence":0.9,"evidence":["Service naming pattern","Application/service path heuristic","namespace NexusReader.Infrastructure.Services","implements IEpubReader and reads from AppDbContext","uses EPUB parsing and builds ReaderPageViewModel"]},"className":"EpubReaderService","methods":[{"name":"EpubReaderService","line":21,"endLine":27,"signature":"(dbContextFactory: IDbContextFactory<AppDbContext>, logger: ILogger<EpubReaderService>) -> EpubReaderService","purpose":"Constructor that injects the DB context factory and logger.","calls":[],"actions":[{"id":"dependency-injection_25","kind":"mapping","label":"stores dependencies","line":25,"detail":"_dbContextFactory and _logger assigned","visibility":"detail-only","confidence":0.7}]},{"name":"GetEpubContentAsync","line":30,"endLine":118,"signature":"(ebookId: Guid, chapterIndex: int, userId: string?) -> Task<Result<ReaderPageViewModel>>","purpose":"Main runtime method: resolves the ebook file via DB, parses an EPUB, extracts chapter text into content blocks, inserts AI triggers based on word threshold, and returns a ReaderPageViewModel or a failure result.","calls":[{"targetFile":"self","targetMethod":"ResolvePath","callLine":54,"paramSummary":"ebook.FilePath (web-relative path)"},{"targetFile":"self","targetMethod":"FindTitleInNavigation","callLine":77,"paramSummary":"navigation, chapterRef.FilePath"},{"targetFile":"self","targetMethod":"ExtractParagraphs","callLine":88,"paramSummary":"chapterContent (HTML string)"},{"targetFile":"self","targetMethod":"SanitizeParagraph","callLine":91,"paramSummary":"paragraph HTML"},{"targetFile":"self","targetMethod":"CountWords","callLine":96,"paramSummary":"sanitized paragraph text"},{"targetFile":"self","targetMethod":"CreateAiTrigger","callLine":101,"paramSummary":"generated trigger id"},{"targetFile":"self","targetMethod":"CreateAiTrigger","callLine":108,"paramSummary":"generated trigger id"}],"actions":[{"id":"getepubcontentasync_try_36_0","kind":"try","label":"Begins protected execution","line":36,"detail":"try","visibility":"primary-visible","confidence":0.84},{"id":"try-catch_36","kind":"mapping","label":"overall error handling","line":36,"detail":"wraps the full operation and on exception logs and returns Result.Fail","visibility":"detail-only","confidence":0.7},{"id":"persistence-read_39","kind":"mapping","label":"resolve ebook row","line":39,"detail":"CreateDbContextAsync and query context.Ebooks.FirstOrDefaultAsync to find ebook by id and optional userId","visibility":"detail-only","confidence":0.7},{"id":"getepubcontentasync_await_39_1","kind":"await","label":"Waits for async work","line":39,"detail":"using var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken);","visibility":"secondary-visible","confidence":0.81},{"id":"getepubcontentasync_repository-read_41_2","kind":"repository-read","label":"Reads repository or persistence state","line":41,"detail":"var ebook = await context.Ebooks","visibility":"secondary-visible","confidence":0.86},{"id":"getepubcontentasync_await_41_3","kind":"await","label":"Waits for async work","line":41,"detail":"var ebook = await context.Ebooks","visibility":"secondary-visible","confidence":0.81},{"id":"guard-clause_47","kind":"guard-clause","label":"ebook not found","line":47,"detail":"returns Result.Fail with message","conditionSummary":"ebook == null","outcomeLabels":["fail-return"],"visibility":"detail-only","confidence":0.7},{"id":"getepubcontentasync_branch_47_4","kind":"branch","label":"Evaluates branch condition","line":47,"detail":"if (ebook == null)","conditionSummary":"ebook == null","outcomeLabels":["true","false"],"visibility":"secondary-visible","confidence":0.78},{"id":"getepubcontentasync_return_49_5","kind":"return","label":"Returns result","line":49,"detail":"return Result.Fail($\"Ebook '{ebookId}' not found for user '{userId}'.\");","visibility":"detail-only","confidence":0.7},{"id":"external-file-check_54","kind":"mapping","label":"resolve filesystem path and verify file exists","line":54,"detail":"_logger.LogError and Result.Fail when file missing","conditionSummary":"ResolvePath returned null or file missing","outcomeLabels":["log-error","fail-return"],"visibility":"detail-only","confidence":0.7},{"id":"getepubcontentasync_branch_55_6","kind":"branch","label":"Evaluates branch condition","line":55,"detail":"if (fullPath == null || !File.Exists(fullPath))","conditionSummary":"fullPath == null || !File.Exists(fullPath)","outcomeLabels":["true","false"],"visibility":"secondary-visible","confidence":0.78},{"id":"getepubcontentasync_log_57_7","kind":"log","label":"Logs runtime state","line":57,"detail":"_logger.LogError(\"EPUB file for ebook {EbookId} not found at path '{FilePath}'.\", ebookId, ebook.FilePath);","visibility":"secondary-visible","confidence":0.92},{"id":"getepubcontentasync_return_58_8","kind":"return","label":"Returns result","line":58,"detail":"return Result.Fail($\"The EPUB file for this book could not be found on the server.\");","visibility":"detail-only","confidence":0.7},{"id":"external-call_62","kind":"external-call","label":"open EPUB","line":62,"detail":"Calls VersOne.Epub.EpubReader.OpenBookAsync to open the EPUB (external library)","visibility":"detail-only","confidence":0.7},{"id":"getepubcontentasync_await_62_9","kind":"await","label":"Waits for async work","line":62,"detail":"using var bookRef = await EpubReader.OpenBookAsync(fullPath);","visibility":"secondary-visible","confidence":0.81},{"id":"getepubcontentasync_branch_65_10","kind":"branch","label":"Evaluates branch condition","line":65,"detail":"if (readingOrder == null || !readingOrder.Any())","conditionSummary":"readingOrder == null || !readingOrder.Any()","outcomeLabels":["true","false"],"visibility":"secondary-visible","confidence":0.78},{"id":"guard-clause_65","kind":"guard-clause","label":"no reading order","line":65,"detail":"returns Result.Fail if EPUB has no readable content","conditionSummary":"readingOrder == null || !readingOrder.Any()","outcomeLabels":["fail-return"],"visibility":"detail-only","confidence":0.7},{"id":"getepubcontentasync_return_67_11","kind":"return","label":"Returns result","line":67,"detail":"return Result.Fail(\"The EPUB has no readable content files in ReadingOrder.\");","visibility":"detail-only","confidence":0.7},{"id":"getepubcontentasync_branch_70_12","kind":"branch","label":"Evaluates branch condition","line":70,"detail":"if (chapterIndex < 0 || chapterIndex >= readingOrder.Count)","conditionSummary":"chapterIndex < 0 || chapterIndex >= readingOrder.Count","outcomeLabels":["true","false"],"visibility":"secondary-visible","confidence":0.78},{"id":"branch_70","kind":"branch","label":"normalize chapterIndex","line":70,"detail":"sets chapterIndex = 0 when invalid","conditionSummary":"chapterIndex out of bounds","outcomeLabels":["reset-to-0","continue"],"visibility":"detail-only","confidence":0.7},{"id":"getepubcontentasync_repository-read_77_13","kind":"repository-read","label":"Reads repository or persistence state","line":77,"detail":"var chapterTitle = FindTitleInNavigation(navigation, chapterRef.FilePath)","visibility":"secondary-visible","confidence":0.86},{"id":"external-call_81","kind":"external-call","label":"read chapter text","line":81,"detail":"chapterRef.ReadContentAsTextAsync() (external EPUB object)","visibility":"detail-only","confidence":0.7},{"id":"getepubcontentasync_await_81_14","kind":"await","label":"Waits for async work","line":81,"detail":"var chapterContent = await chapterRef.ReadContentAsTextAsync();","visibility":"secondary-visible","confidence":0.81},{"id":"loop_89","kind":"loop","label":"iterate paragraphs","line":89,"detail":"foreach paragraphs: sanitize, skip empties, add TextSegmentBlock, count words, insert AI trigger when threshold reached","visibility":"detail-only","confidence":0.7},{"id":"getepubcontentasync_loop_89_15","kind":"loop","label":"Repeats work over a collection or condition","line":89,"detail":"foreach (var p in paragraphs)","conditionSummary":"var p in paragraphs","loopTargetLine":89,"loopExitSummary":"Leaves the loop when the condition no longer holds.","visibility":"primary-visible","confidence":0.86},{"id":"getepubcontentasync_guard-clause_92_16","kind":"guard-clause","label":"Guards early exit or rejection path","line":92,"detail":"if (string.IsNullOrWhiteSpace(sanitizedContent)) continue;","conditionSummary":"string.IsNullOrWhiteSpace(sanitizedContent)","outcomeLabels":["exit","continue"],"visibility":"primary-visible","confidence":0.9},{"id":"getepubcontentasync_branch_99_17","kind":"branch","label":"Evaluates branch condition","line":99,"detail":"if (totalWordCount >= WordThreshold)","conditionSummary":"totalWordCount >= WordThreshold","outcomeLabels":["true","false"],"visibility":"secondary-visible","confidence":0.78},{"id":"threshold_99","kind":"mapping","label":"insert AI trigger after WordThreshold words","line":99,"detail":"totalWordCount compared to WordThreshold and resets after inserting trigger block","visibility":"detail-only","confidence":0.7},{"id":"post-condition_106","kind":"mapping","label":"ensure trailing AI trigger","line":106,"detail":"adds trigger if last block is not an AiActionTriggerBlock","visibility":"detail-only","confidence":0.7},{"id":"getepubcontentasync_branch_106_18","kind":"branch","label":"Evaluates branch condition","line":106,"detail":"if (blocks.Any() && blocks.Last() is not AiActionTriggerBlock)","conditionSummary":"blocks.Any() && blocks.Last() is not AiActionTriggerBlock","outcomeLabels":["true","false"],"visibility":"secondary-visible","confidence":0.78},{"id":"getepubcontentasync_return_111_19","kind":"return","label":"Returns result","line":111,"detail":"return Result.Ok(new ReaderPageViewModel(blocks, chapterIndex, readingOrder.Count, chapterTitle, ebook.Id));","visibility":"detail-only","confidence":0.7},{"id":"return_111","kind":"return","label":"successful result","line":111,"detail":"returns Result.Ok with ReaderPageViewModel","visibility":"detail-only","confidence":0.7},{"id":"getepubcontentasync_catch_113_20","kind":"catch","label":"Handles exception path","line":113,"detail":"catch (Exception ex)","conditionSummary":"Exception ex","outcomeLabels":["handled exception"],"visibility":"primary-visible","confidence":0.86},{"id":"log_115","kind":"log","label":"log error on exception","line":115,"detail":"_logger.LogError in catch, returns failure containing exception","visibility":"detail-only","confidence":0.7},{"id":"getepubcontentasync_log_115_21","kind":"log","label":"Logs runtime state","line":115,"detail":"_logger.LogError(ex, \"Failed to process EPUB for ebook {EbookId}.\", ebookId);","visibility":"secondary-visible","confidence":0.92},{"id":"getepubcontentasync_return_116_22","kind":"return","label":"Returns result","line":116,"detail":"return Result.Fail(new Error($\"Failed to process EPUB: {ex.Message}\").CausedBy(ex));","visibility":"detail-only","confidence":0.7}]},{"name":"ResolvePath","line":124,"endLine":143,"signature":"(relativePath: string) -> string?","purpose":"Resolves a web-relative storage path to an absolute filesystem path by searching up the app base directory for wwwroot (and a development src path).","calls":[],"actions":[{"id":"normalization_127","kind":"mapping","label":"normalize path separators","line":127,"detail":"converts '/' to OS directory separator","visibility":"detail-only","confidence":0.7},{"id":"resolvepath_loop_130_0","kind":"loop","label":"Repeats work over a collection or condition","line":130,"detail":"while (currentDir != null)","conditionSummary":"currentDir != null","loopTargetLine":130,"loopExitSummary":"Leaves the loop when the condition no longer holds.","visibility":"primary-visible","confidence":0.86},{"id":"loop_130","kind":"loop","label":"walk parent directories","line":130,"detail":"iteratively checks currentDir and its parents for candidate and devCandidate locations","visibility":"detail-only","confidence":0.7},{"id":"file-exists-check_132","kind":"mapping","label":"check candidate paths","line":132,"detail":"returns candidate or devCandidate when File.Exists","visibility":"detail-only","confidence":0.7},{"id":"resolvepath_guard-clause_133_1","kind":"guard-clause","label":"Guards early exit or rejection path","line":133,"detail":"if (File.Exists(candidate)) return candidate;","conditionSummary":"File.Exists(candidate)","outcomeLabels":["exit","continue"],"visibility":"primary-visible","confidence":0.9},{"id":"resolvepath_guard-clause_137_2","kind":"guard-clause","label":"Guards early exit or rejection path","line":137,"detail":"if (File.Exists(devCandidate)) return devCandidate;","conditionSummary":"File.Exists(devCandidate)","outcomeLabels":["exit","continue"],"visibility":"primary-visible","confidence":0.9},{"id":"return_142","kind":"return","label":"not found","line":142,"detail":"returns null if no candidate found","visibility":"detail-only","confidence":0.7},{"id":"resolvepath_return_142_3","kind":"return","label":"Returns result","line":142,"detail":"return null;","visibility":"detail-only","confidence":0.7}]},{"name":"ExtractParagraphs","line":145,"endLine":164,"signature":"(html: string) -> List<string>","purpose":"Extracts paragraph-like blocks from HTML by selecting <p>, headings, lists, blockquote, pre, hr or splitting on breaks when none found.","calls":[],"actions":[{"id":"regex-parse_147","kind":"mapping","label":"extract body content","line":147,"detail":"Regex.Match to capture <body> contents or fallback to full html","visibility":"detail-only","confidence":0.7},{"id":"regex-parse_151","kind":"mapping","label":"find paragraph-like tags","line":151,"detail":"Regex.Matches selects p, h1-6, ul, ol, blockquote, pre, hr","visibility":"detail-only","confidence":0.7},{"id":"extractparagraphs_loop_153_0","kind":"loop","label":"Repeats work over a collection or condition","line":153,"detail":"foreach (Match match in matches)","conditionSummary":"Match match in matches","loopTargetLine":153,"loopExitSummary":"Leaves the loop when the condition no longer holds.","visibility":"primary-visible","confidence":0.86},{"id":"extractparagraphs_branch_158_1","kind":"branch","label":"Evaluates branch condition","line":158,"detail":"if (paragraphs.Count == 0)","conditionSummary":"paragraphs.Count == 0","outcomeLabels":["true","false"],"visibility":"secondary-visible","confidence":0.78},{"id":"fallback-split_158","kind":"mapping","label":"split by breaks when no matches","line":158,"detail":"splits on <br />, <br>, double newlines","visibility":"detail-only","confidence":0.7},{"id":"extractparagraphs_return_163_2","kind":"return","label":"Returns result","line":163,"detail":"return paragraphs;","visibility":"detail-only","confidence":0.7}]},{"name":"SanitizeParagraph","line":166,"endLine":173,"signature":"(html: string) -> string","purpose":"Sanitizes HTML paragraph content by removing scripts/styles, stripping disallowed tags, normalizing allowed tags, HTML-decoding and trimming.","calls":[],"actions":[{"id":"regex-clean_168","kind":"mapping","label":"remove script/style blocks","line":168,"detail":"Regex.Replace to remove <script> and <style> contents","visibility":"detail-only","confidence":0.7},{"id":"regex-clean_169","kind":"mapping","label":"strip disallowed tags","line":169,"detail":"Regex.Replace removes tags not in allow-list","visibility":"detail-only","confidence":0.7},{"id":"regex-normalize_170","kind":"mapping","label":"normalize allowed tags","line":170,"detail":"Regex.Replace to simplify allowed tags to base form","visibility":"detail-only","confidence":0.7},{"id":"html-decode_171","kind":"mapping","label":"decode entities","line":171,"detail":"System.Net.WebUtility.HtmlDecode","visibility":"detail-only","confidence":0.7},{"id":"sanitizeparagraph_return_172_0","kind":"return","label":"Returns result","line":172,"detail":"return clean.Trim();","visibility":"detail-only","confidence":0.7}]},{"name":"CountWords","line":175,"endLine":179,"signature":"(text: string) -> int","purpose":"Counts words in a text by splitting on whitespace characters; returns 0 for null/whitespace input.","calls":[],"actions":[{"id":"guard-clause_177","kind":"guard-clause","label":"empty check","line":177,"detail":"returns 0 if string.IsNullOrWhiteSpace","visibility":"detail-only","confidence":0.7},{"id":"countwords_guard-clause_177_0","kind":"guard-clause","label":"Guards early exit or rejection path","line":177,"detail":"if (string.IsNullOrWhiteSpace(text)) return 0;","conditionSummary":"string.IsNullOrWhiteSpace(text)","outcomeLabels":["exit","continue"],"visibility":"primary-visible","confidence":0.9},{"id":"countwords_return_178_1","kind":"return","label":"Returns result","line":178,"detail":"return text.Split(new[] { ' ', '\\r', '\\n', '\\t' }, StringSplitOptions.RemoveEmptyEntries).Length;","visibility":"detail-only","confidence":0.7},{"id":"split-count_178","kind":"mapping","label":"split on whitespace and count","line":178,"detail":"text.Split with separators [' ', '\\r', '\\n', '\\t']","visibility":"detail-only","confidence":0.7}]},{"name":"CreateAiTrigger","line":181,"endLine":184,"signature":"(id: string) -> AiActionTriggerBlock","purpose":"Factory that creates an AiActionTriggerBlock with localized prompt and action labels.","calls":[],"actions":[{"id":"factory_181","kind":"mapping","label":"instantiate AiActionTriggerBlock","line":181,"detail":"returns new AiActionTriggerBlock with id, Polish prompt and choices","visibility":"detail-only","confidence":0.7}]},{"name":"FindTitleInNavigation","line":186,"endLine":204,"signature":"(navigation: IEnumerable<EpubNavigationItemRef>, filePath: string?) -> string?","purpose":"Recursively searches EPUB navigation items for a title that matches a given file path or file name.","calls":[{"targetFile":"self","targetMethod":"FindTitleInNavigation","callLine":198,"paramSummary":"item.NestedItems, filePath (recursive search)"}],"actions":[{"id":"findtitleinnavigation_repository-read_186_0","kind":"repository-read","label":"Reads repository or persistence state","line":186,"detail":"private static string? FindTitleInNavigation(IEnumerable<EpubNavigationItemRef> navigation, string? filePath)","visibility":"secondary-visible","confidence":0.86},{"id":"findtitleinnavigation_guard-clause_188_1","kind":"guard-clause","label":"Guards early exit or rejection path","line":188,"detail":"if (string.IsNullOrEmpty(filePath)) return null;","conditionSummary":"string.IsNullOrEmpty(filePath)","outcomeLabels":["exit","continue"],"visibility":"primary-visible","confidence":0.9},{"id":"guard-clause_188","kind":"guard-clause","label":"null/empty filePath","line":188,"detail":"returns null when filePath is null or empty","visibility":"detail-only","confidence":0.7},{"id":"loop_191","kind":"loop","label":"iterate navigation items","line":191,"detail":"for each navigation item compare Link.ContentFilePath or fileName and return item.Title if match","visibility":"detail-only","confidence":0.7},{"id":"findtitleinnavigation_loop_191_2","kind":"loop","label":"Repeats work over a collection or condition","line":191,"detail":"foreach (var item in navigation)","conditionSummary":"var item in navigation","loopTargetLine":191,"loopExitSummary":"Leaves the loop when the condition no longer holds.","visibility":"primary-visible","confidence":0.86},{"id":"findtitleinnavigation_branch_193_3","kind":"branch","label":"Evaluates branch condition","line":193,"detail":"if (item.Link?.ContentFilePath == filePath || item.Link?.ContentFilePath == fileName)","conditionSummary":"item.Link?.ContentFilePath == filePath || item.Link?.ContentFilePath == fileName","outcomeLabels":["true","false"],"visibility":"secondary-visible","confidence":0.78},{"id":"findtitleinnavigation_return_194_4","kind":"return","label":"Returns result","line":194,"detail":"return item.Title;","visibility":"detail-only","confidence":0.7},{"id":"findtitleinnavigation_branch_196_5","kind":"branch","label":"Evaluates branch condition","line":196,"detail":"if (item.NestedItems?.Any() == true)","conditionSummary":"item.NestedItems?.Any() == true","outcomeLabels":["true","false"],"visibility":"secondary-visible","confidence":0.78},{"id":"recursion_196","kind":"mapping","label":"search nested items","line":196,"detail":"recursively calls itself for nested navigation items and returns childTitle when found","visibility":"detail-only","confidence":0.7},{"id":"findtitleinnavigation_repository-read_198_6","kind":"repository-read","label":"Reads repository or persistence state","line":198,"detail":"var childTitle = FindTitleInNavigation(item.NestedItems, filePath);","visibility":"secondary-visible","confidence":0.86},{"id":"findtitleinnavigation_guard-clause_199_7","kind":"guard-clause","label":"Guards early exit or rejection path","line":199,"detail":"if (childTitle != null) return childTitle;","conditionSummary":"childTitle != null","outcomeLabels":["exit","continue"],"visibility":"primary-visible","confidence":0.9},{"id":"return_203","kind":"return","label":"not found","line":203,"detail":"returns null if no matching title found","visibility":"detail-only","confidence":0.7},{"id":"findtitleinnavigation_return_203_8","kind":"return","label":"Returns result","line":203,"detail":"return null;","visibility":"detail-only","confidence":0.7}]}],"types":[],"serviceRegistrations":[],"startupActions":[],"dependencies":["NexusReader.Data.Persistence (AppDbContext/Ebooks)","VersOne.Epub (EpubReader, EpubNavigationItemRef)","FluentResults (Result)"],"patterns":["Service","Factory (CreateAiTrigger)"],"domainConcepts":["Ebook","Chapter","ReaderPageViewModel","ContentBlock","AI Trigger"],"keyDetails":"Implements a word-threshold-driven AI trigger insertion (WordThreshold=1000). Uses DB to resolve filesystem path; searches upward for wwwroot to handle dev/prod layouts; sanitizes HTML and extracts paragraph-like blocks; robust guard clauses and try/catch with logging.","orchestrationMethods":[{"name":"GetEpubContentAsync","line":30,"confidence":0.98,"reason":"Coordinates 7 downstream calls with 13 architectural actions.","actionKinds":["try","mapping","await","repository-read","guard-clause","branch","return","log","external-call","loop","catch"],"evidencePaths":["NexusReader.Infrastructure/Services/EpubReaderService.cs","self","self","self","self","self"]},{"name":"FindTitleInNavigation","line":186,"confidence":0.98,"reason":"Contains 6 architectural actions relevant to business execution.","actionKinds":["repository-read","guard-clause","loop","branch","return","mapping"],"evidencePaths":["NexusReader.Infrastructure/Services/EpubReaderService.cs","self"]},{"name":"ResolvePath","line":124,"confidence":0.69,"reason":"Contains 2 architectural actions relevant to business execution.","actionKinds":["mapping","loop","guard-clause","return"],"evidencePaths":["NexusReader.Infrastructure/Services/EpubReaderService.cs"]},{"name":"ExtractParagraphs","line":145,"confidence":0.69,"reason":"Contains 2 architectural actions relevant to business execution.","actionKinds":["mapping","loop","branch","return"],"evidencePaths":["NexusReader.Infrastructure/Services/EpubReaderService.cs"]}],"typedContracts":[],"persistenceInteractions":[{"methodName":"GetEpubContentAsync","line":41,"kind":"persistence-read","detail":"var ebook = await context.Ebooks","evidencePaths":["NexusReader.Infrastructure/Services/EpubReaderService.cs"]},{"methodName":"GetEpubContentAsync","line":77,"kind":"persistence-read","detail":"var chapterTitle = FindTitleInNavigation(navigation, chapterRef.FilePath)","evidencePaths":["NexusReader.Infrastructure/Services/EpubReaderService.cs"]},{"methodName":"FindTitleInNavigation","line":186,"kind":"persistence-read","detail":"private static string? FindTitleInNavigation(IEnumerable<EpubNavigationItemRef> navigation, string? filePath)","evidencePaths":["NexusReader.Infrastructure/Services/EpubReaderService.cs"]},{"methodName":"FindTitleInNavigation","line":198,"kind":"persistence-read","detail":"var childTitle = FindTitleInNavigation(item.NestedItems, filePath);","evidencePaths":["NexusReader.Infrastructure/Services/EpubReaderService.cs"]}],"externalInteractions":[{"methodName":"GetEpubContentAsync","line":62,"kind":"external-call","detail":"Calls VersOne.Epub.EpubReader.OpenBookAsync to open the EPUB (external library)","evidencePaths":["NexusReader.Infrastructure/Services/EpubReaderService.cs"]},{"methodName":"GetEpubContentAsync","line":81,"kind":"external-call","detail":"chapterRef.ReadContentAsTextAsync() (external EPUB object)","evidencePaths":["NexusReader.Infrastructure/Services/EpubReaderService.cs"]}],"evidenceAnchors":[{"kind":"orchestration-method","label":"GetEpubContentAsync","line":30,"summary":"Coordinates 7 downstream calls with 13 architectural actions.","confidence":0.98,"evidencePaths":["NexusReader.Infrastructure/Services/EpubReaderService.cs","self","self","self","self","self"]},{"kind":"orchestration-method","label":"FindTitleInNavigation","line":186,"summary":"Contains 6 architectural actions relevant to business execution.","confidence":0.98,"evidencePaths":["NexusReader.Infrastructure/Services/EpubReaderService.cs","self"]},{"kind":"orchestration-method","label":"ResolvePath","line":124,"summary":"Contains 2 architectural actions relevant to business execution.","confidence":0.69,"evidencePaths":["NexusReader.Infrastructure/Services/EpubReaderService.cs"]},{"kind":"orchestration-method","label":"ExtractParagraphs","line":145,"summary":"Contains 2 architectural actions relevant to business execution.","confidence":0.69,"evidencePaths":["NexusReader.Infrastructure/Services/EpubReaderService.cs"]},{"kind":"persistence","label":"GetEpubContentAsync","line":41,"summary":"var ebook = await context.Ebooks","confidence":0.82,"evidencePaths":["NexusReader.Infrastructure/Services/EpubReaderService.cs"]},{"kind":"persistence","label":"GetEpubContentAsync","line":77,"summary":"var chapterTitle = FindTitleInNavigation(navigation, chapterRef.FilePath)","confidence":0.82,"evidencePaths":["NexusReader.Infrastructure/Services/EpubReaderService.cs"]},{"kind":"persistence","label":"FindTitleInNavigation","line":186,"summary":"private static string? FindTitleInNavigation(IEnumerable<EpubNavigationItemRef> navigation, string? filePath)","confidence":0.82,"evidencePaths":["NexusReader.Infrastructure/Services/EpubReaderService.cs"]},{"kind":"external-call","label":"GetEpubContentAsync","line":62,"summary":"Calls VersOne.Epub.EpubReader.OpenBookAsync to open the EPUB (external library)","confidence":0.8,"evidencePaths":["NexusReader.Infrastructure/Services/EpubReaderService.cs"]},{"kind":"external-call","label":"GetEpubContentAsync","line":81,"summary":"chapterRef.ReadContentAsTextAsync() (external EPUB object)","confidence":0.8,"evidencePaths":["NexusReader.Infrastructure/Services/EpubReaderService.cs"]}],"cacheMetadata":{"schemaVersion":2,"analysisVersion":"2026-05-23.cache-v1","contentChecksum":"e4aab8ba43c1b837895858f110b6aca349048148460070cc9f0050f2b9d3c112","sourceByteSize":8350,"analyzedAt":"2026-05-23T16:19:06.080Z","technology":"dotnet"}} |