Coverage for sources/librovore/results.py: 28%

293 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-09-03 21:59 +0000

1# vim: set filetype=python fileencoding=utf-8: 

2# -*- coding: utf-8 -*- 

3 

4#============================================================================# 

5# # 

6# Licensed under the Apache License, Version 2.0 (the "License"); # 

7# you may not use this file except in compliance with the License. # 

8# You may obtain a copy of the License at # 

9# # 

10# http://www.apache.org/licenses/LICENSE-2.0 # 

11# # 

12# Unless required by applicable law or agreed to in writing, software # 

13# distributed under the License is distributed on an "AS IS" BASIS, # 

14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

15# See the License for the specific language governing permissions and # 

16# limitations under the License. # 

17# # 

18#============================================================================# 

19 

20 

21''' Results structures. 

22 

23 Search results, inventory objects, content documents, etc.... 

24''' 

25 

26 

27from . import __ 

28from . import exceptions as _exceptions 

29 

30 

31_CONTENT_PREVIEW_LIMIT = 100 

32 

33 

34class ResultBase( __.immut.DataclassProtocol, __.typx.Protocol ): 

35 ''' Base protocol for all result objects with rendering methods. ''' 

36 

37 @__.abc.abstractmethod 

38 def render_as_json( self ) -> __.immut.Dictionary[ str, __.typx.Any ]: 

39 ''' Renders result as JSON-compatible dictionary. ''' 

40 raise NotImplementedError 

41 

42 @__.abc.abstractmethod 

43 def render_as_markdown( self ) -> tuple[ str, ... ]: 

44 ''' Renders result as Markdown lines for display. ''' 

45 raise NotImplementedError 

46 

47 

48class InventoryObject( ResultBase ): 

49 ''' Universal inventory object with complete source attribution. 

50 

51 Represents a single documentation object from any inventory source 

52 with standardized fields and format-specific metadata container. 

53 ''' 

54 

55 name: __.typx.Annotated[ 

56 str, 

57 __.ddoc.Doc( "Primary object identifier from inventory source." ), 

58 ] 

59 uri: __.typx.Annotated[ 

60 str, 

61 __.ddoc.Doc( "Relative URI to object documentation content." ), 

62 ] 

63 inventory_type: __.typx.Annotated[ 

64 str, 

65 __.ddoc.Doc( 

66 "Inventory format identifier (e.g., sphinx_objects_inv)." ), 

67 ] 

68 location_url: __.typx.Annotated[ 

69 str, __.ddoc.Doc( 

70 "Complete URL to inventory location for attribution." ) 

71 ] 

72 display_name: __.typx.Annotated[ 

73 __.typx.Optional[ str ], 

74 __.ddoc.Doc( "Human-readable name if different from name." ), 

75 ] = None 

76 specifics: __.typx.Annotated[ 

77 __.immut.Dictionary[ str, __.typx.Any ], 

78 __.ddoc.Doc( 

79 "Format-specific metadata (domain, role, priority, etc.)." ), 

80 ] = __.dcls.field( default_factory = lambda: __.immut.Dictionary( ) ) 

81 

82 

83 @property 

84 def effective_display_name( self ) -> str: 

85 ''' Effective display name. Might be same as name. ''' 

86 if self.display_name is not None: 

87 return self.display_name 

88 return self.name 

89 

90 @__.abc.abstractmethod 

91 def render_specifics_json( 

92 self 

93 ) -> __.immut.Dictionary[ str, __.typx.Any ]: 

94 ''' Renders specifics for JSON output. ''' 

95 raise NotImplementedError 

96 

97 @__.abc.abstractmethod 

98 def render_specifics_markdown( 

99 self, /, *, 

100 reveal_internals: __.typx.Annotated[ 

101 bool, 

102 __.ddoc.Doc( ''' 

103 Controls whether implementation-specific details (internal 

104 field names, version numbers, priority scores) are included. 

105 When False, only user-facing information is shown. 

106 ''' ), 

107 ] = True, 

108 ) -> tuple[ str, ... ]: 

109 ''' Renders specifics as Markdown lines for CLI display. ''' 

110 raise NotImplementedError 

111 

112 def render_as_json( self ) -> __.immut.Dictionary[ str, __.typx.Any ]: 

113 ''' Renders complete object as JSON-compatible dictionary. ''' 

114 base = __.immut.Dictionary[ 

115 str, __.typx.Any 

116 ]( 

117 name = self.name, 

118 uri = self.uri, 

119 inventory_type = self.inventory_type, 

120 location_url = self.location_url, 

121 display_name = self.display_name, 

122 effective_display_name = self.effective_display_name, 

123 ) 

124 formatted_specifics = self.render_specifics_json( ) 

125 result_dict = dict( base ) 

126 result_dict.update( dict( formatted_specifics ) ) 

127 return __.immut.Dictionary[ str, __.typx.Any ]( result_dict ) 

128 

129 def render_as_markdown( 

130 self, /, *, 

131 reveal_internals: __.typx.Annotated[ 

132 bool, 

133 __.ddoc.Doc( "Controls whether internal details are shown." ), 

134 ] = True, 

135 ) -> tuple[ str, ... ]: 

136 ''' Renders complete object as Markdown lines for display. ''' 

137 lines = [ f"### `{self.effective_display_name}`" ] 

138 if reveal_internals: 

139 lines.append( f"**URI:** {self.uri}" ) 

140 lines.append( f"**Type:** {self.inventory_type}" ) 

141 lines.append( f"**Location:** {self.location_url}" ) 

142 specifics_lines = self.render_specifics_markdown( 

143 reveal_internals = reveal_internals ) 

144 lines.extend( specifics_lines ) 

145 return tuple( lines ) 

146 

147 

148class ContentDocument( ResultBase ): 

149 ''' Documentation content with extracted metadata and content ID. ''' 

150 

151 inventory_object: __.typx.Annotated[ 

152 InventoryObject, 

153 __.ddoc.Doc( "Location inventory object for this content." ), 

154 ] 

155 content_id: __.typx.Annotated[ 

156 str, 

157 __.ddoc.Doc( "Deterministic identifier for content retrieval." ), 

158 ] 

159 description: __.typx.Annotated[ 

160 str, 

161 __.ddoc.Doc( "Extracted object description or summary." ), 

162 ] = '' 

163 documentation_url: __.typx.Annotated[ 

164 str, 

165 __.ddoc.Doc( "Complete URL to full documentation page." ), 

166 ] = '' 

167 extraction_metadata: __.typx.Annotated[ 

168 __.immut.Dictionary[ str, __.typx.Any ], 

169 __.ddoc.Doc( "Metadata from structure processor extraction." ), 

170 ] = __.dcls.field( default_factory = lambda: __.immut.Dictionary( ) ) 

171 

172 @property 

173 def has_meaningful_content( self ) -> bool: 

174 ''' Returns True if document contains useful extracted content. ''' 

175 return bool( self.description ) 

176 

177 def render_as_json( 

178 self, /, *, 

179 lines_max: __.typx.Optional[ int ] = None, 

180 ) -> __.immut.Dictionary[ str, __.typx.Any ]: 

181 ''' Renders complete document as JSON-compatible dictionary. ''' 

182 description = self.description 

183 if lines_max is not None: 

184 desc_lines = description.split( '\n' ) 

185 if len( desc_lines ) > lines_max: 

186 desc_lines = desc_lines[ :lines_max ] 

187 desc_lines.append( "..." ) 

188 description = '\n'.join( desc_lines ) 

189 return __.immut.Dictionary[ 

190 str, __.typx.Any 

191 ]( 

192 inventory_object = dict( self.inventory_object.render_as_json( ) ), 

193 content_id = self.content_id, 

194 description = description, 

195 documentation_url = self.documentation_url, 

196 extraction_metadata = dict( self.extraction_metadata ), 

197 has_meaningful_content = self.has_meaningful_content, 

198 ) 

199 

200 def render_as_markdown( 

201 self, /, *, 

202 reveal_internals: __.typx.Annotated[ 

203 bool, 

204 __.ddoc.Doc( "Controls whether internal details are shown." ), 

205 ] = True, 

206 lines_max: __.typx.Annotated[ 

207 __.typx.Optional[ int ], 

208 __.ddoc.Doc( "Maximum lines to display for description." ), 

209 ] = None, 

210 include_title: __.typx.Annotated[ 

211 bool, 

212 __.ddoc.Doc( "Whether to include document title header." ), 

213 ] = True, 

214 ) -> tuple[ str, ... ]: 

215 ''' Renders complete document as Markdown lines for display. ''' 

216 lines: list[ str ] = [ ] 

217 if include_title: 

218 lines.append( 

219 f"### `{self.inventory_object.effective_display_name}`" ) 

220 if self.description: 

221 description = self.description 

222 if lines_max is not None: 

223 desc_lines = description.split( '\n' ) 

224 if len( desc_lines ) > lines_max: 

225 desc_lines = desc_lines[ :lines_max ] 

226 desc_lines.append( "..." ) 

227 description = '\n'.join( desc_lines ) 

228 lines.append( f"**Description:** {description}" ) 

229 if self.documentation_url: 

230 lines.append( f"**URL:** {self.documentation_url}" ) 

231 lines.append( f"**Content ID:** `{self.content_id}`" ) 

232 if reveal_internals: 

233 inventory_lines = self.inventory_object.render_specifics_markdown( 

234 reveal_internals = True ) 

235 lines.extend( inventory_lines ) 

236 return tuple( lines ) 

237 

238 

239class InventoryLocationInfo( __.immut.DataclassObject ): 

240 ''' Information about detected inventory location and processor. ''' 

241 

242 inventory_type: __.typx.Annotated[ 

243 str, 

244 __.ddoc.Doc( "Inventory format type identifier." ), 

245 ] 

246 location_url: __.typx.Annotated[ 

247 str, 

248 __.ddoc.Doc( "Complete URL to inventory location." ), 

249 ] 

250 processor_name: __.typx.Annotated[ 

251 str, 

252 __.ddoc.Doc( "Name of processor handling this location." ), 

253 ] 

254 confidence: __.typx.Annotated[ 

255 float, 

256 __.ddoc.Doc( "Detection confidence score (0.0-1.0)." ), 

257 ] 

258 object_count: __.typx.Annotated[ 

259 int, 

260 __.ddoc.Doc( "Total objects available in this inventory." ), 

261 ] 

262 

263 def render_as_json( self ) -> __.immut.Dictionary[ str, __.typx.Any ]: 

264 ''' Renders location info as JSON-compatible dictionary. ''' 

265 return __.immut.Dictionary( 

266 inventory_type = self.inventory_type, 

267 location_url = self.location_url, 

268 processor_name = self.processor_name, 

269 confidence = self.confidence, 

270 object_count = self.object_count, 

271 ) 

272 

273 

274class SearchMetadata( __.immut.DataclassObject ): 

275 ''' Search operation metadata and performance statistics. ''' 

276 

277 results_count: __.typx.Annotated[ 

278 int, 

279 __.ddoc.Doc( "Number of results returned to user." ), 

280 ] 

281 results_max: __.typx.Annotated[ 

282 int, 

283 __.ddoc.Doc( "Maximum results requested by user." ), 

284 ] 

285 matches_total: __.typx.Annotated[ 

286 __.typx.Optional[ int ], 

287 __.ddoc.Doc( "Total matching objects before limit applied." ), 

288 ] = None 

289 search_time_ms: __.typx.Annotated[ 

290 __.typx.Optional[ int ], 

291 __.ddoc.Doc( "Search execution time in milliseconds." ), 

292 ] = None 

293 

294 @property 

295 def results_truncated( self ) -> bool: 

296 ''' Returns True if results were limited by results_max. ''' 

297 if self.matches_total is None: 

298 return False 

299 return self.results_count < self.matches_total 

300 

301 def render_as_json( self ) -> __.immut.Dictionary[ str, __.typx.Any ]: 

302 ''' Renders search metadata as JSON-compatible dictionary. ''' 

303 return __.immut.Dictionary( 

304 results_count = self.results_count, 

305 results_max = self.results_max, 

306 matches_total = self.matches_total, 

307 search_time_ms = self.search_time_ms, 

308 results_truncated = self.results_truncated, 

309 ) 

310 

311 

312class SearchResult( ResultBase ): 

313 ''' Search result with inventory object and match metadata. ''' 

314 

315 inventory_object: __.typx.Annotated[ 

316 InventoryObject, 

317 __.ddoc.Doc( "Matched inventory object with metadata." ), 

318 ] 

319 score: __.typx.Annotated[ 

320 float, 

321 __.ddoc.Doc( "Search relevance score (0.0-1.0)." ), 

322 ] 

323 match_reasons: __.typx.Annotated[ 

324 tuple[ str, ... ], 

325 __.ddoc.Doc( "Detailed reasons for search match." ), 

326 ] 

327 

328 @classmethod 

329 def from_inventory_object( 

330 cls, 

331 inventory_object: InventoryObject, *, 

332 score: float, 

333 match_reasons: __.cabc.Sequence[ str ], 

334 ) -> __.typx.Self: 

335 ''' Produces search result from inventory object with scoring. ''' 

336 return cls( 

337 inventory_object = inventory_object, 

338 score = score, 

339 match_reasons = tuple( match_reasons ) ) 

340 

341 def render_as_json( self ) -> __.immut.Dictionary[ str, __.typx.Any ]: 

342 ''' Renders search result as JSON-compatible dictionary. ''' 

343 return __.immut.Dictionary[ 

344 str, __.typx.Any 

345 ]( 

346 inventory_object = dict( self.inventory_object.render_as_json( ) ), 

347 score = self.score, 

348 match_reasons = list( self.match_reasons ), 

349 ) 

350 

351 def render_as_markdown( 

352 self, /, *, 

353 reveal_internals: __.typx.Annotated[ 

354 bool, 

355 __.ddoc.Doc( "Controls whether internal details are shown." ), 

356 ] = True, 

357 ) -> tuple[ str, ... ]: 

358 ''' Renders search result as Markdown lines for display. ''' 

359 title = "### `{name}` (Score: {score:.2f})".format( 

360 name = self.inventory_object.effective_display_name, 

361 score = self.score ) 

362 lines = [ title ] 

363 if reveal_internals and self.match_reasons: 

364 reasons = ', '.join( self.match_reasons ) 

365 lines.append( "**Match reasons:** {reasons}".format( 

366 reasons = reasons ) ) 

367 inventory_lines = self.inventory_object.render_as_markdown( 

368 reveal_internals = reveal_internals ) 

369 lines.extend( inventory_lines[ 1: ] ) # Skip duplicate title line 

370 return tuple( lines ) 

371 

372 

373class ContentQueryResult( ResultBase ): 

374 ''' Complete result structure for content queries. ''' 

375 

376 location: __.typx.Annotated[ 

377 str, 

378 __.ddoc.Doc( "Primary location URL for this query." ), 

379 ] 

380 query: __.typx.Annotated[ 

381 str, 

382 __.ddoc.Doc( "Search term or query string used." ), 

383 ] 

384 documents: __.typx.Annotated[ 

385 tuple[ ContentDocument, ... ], 

386 __.ddoc.Doc( "Documentation content for matching objects." ) ] 

387 search_metadata: __.typx.Annotated[ 

388 SearchMetadata, 

389 __.ddoc.Doc( "Search execution and result metadata." ), 

390 ] 

391 inventory_locations: __.typx.Annotated[ 

392 tuple[ InventoryLocationInfo, ... ], 

393 __.ddoc.Doc( "Information about inventory locations used." ), 

394 ] 

395 

396 def render_as_json( 

397 self, /, *, 

398 lines_max: __.typx.Optional[ int ] = None, 

399 ) -> __.immut.Dictionary[ str, __.typx.Any ]: 

400 ''' Renders content query result as JSON-compatible dictionary. ''' 

401 documents_json = [ 

402 dict( doc.render_as_json( lines_max = lines_max ) ) 

403 for doc in self.documents ] 

404 locations_json = [ 

405 dict( loc.render_as_json( ) ) for loc in self.inventory_locations ] 

406 return __.immut.Dictionary[ 

407 str, __.typx.Any 

408 ]( 

409 location = self.location, 

410 query = self.query, 

411 documents = documents_json, 

412 search_metadata = dict( self.search_metadata.render_as_json( ) ), 

413 inventory_locations = locations_json, 

414 ) 

415 

416 def render_as_markdown( 

417 self, /, *, 

418 reveal_internals: __.typx.Annotated[ 

419 bool, 

420 __.ddoc.Doc( "Controls whether internal details are shown." ), 

421 ] = True, 

422 lines_max: __.typx.Annotated[ 

423 __.typx.Optional[ int ], 

424 __.ddoc.Doc( "Maximum lines to display per content result." ), 

425 ] = None, 

426 ) -> tuple[ str, ... ]: 

427 ''' Renders content query result as Markdown lines for display. ''' 

428 title = "# Content Query Results" 

429 if lines_max is not None: 

430 title += " (truncated)" 

431 lines = [ title ] 

432 lines.append( "**Query:** {query}".format( query = self.query ) ) 

433 if reveal_internals: 

434 lines.append( "**Location:** {location}".format( 

435 location = self.location ) ) 

436 lines.append( "**Results:** {count} of {max}".format( 

437 count = self.search_metadata.results_count, 

438 max = self.search_metadata.results_max ) ) 

439 if self.documents: 

440 lines.append( "" ) 

441 lines.append( "## Documents" ) 

442 for index, doc in enumerate( self.documents, 1 ): 

443 separator = "\n📄 ── Document {} ──────────────────── 📄\n" 

444 lines.append( separator.format( index ) ) 

445 doc_lines = doc.render_as_markdown( 

446 reveal_internals = reveal_internals, 

447 lines_max = lines_max, 

448 include_title = False ) 

449 lines.extend( doc_lines ) 

450 return tuple( lines ) 

451 

452 

453class InventoryQueryResult( ResultBase ): 

454 ''' Complete result structure for inventory queries. ''' 

455 

456 location: __.typx.Annotated[ 

457 str, 

458 __.ddoc.Doc( "Primary location URL for this query." ), 

459 ] 

460 query: __.typx.Annotated[ 

461 str, 

462 __.ddoc.Doc( "Search term or query string used." ), 

463 ] 

464 objects: __.typx.Annotated[ 

465 tuple[ InventoryObject, ... ], 

466 __.ddoc.Doc( "Inventory objects matching search criteria." ), 

467 ] 

468 search_metadata: __.typx.Annotated[ 

469 SearchMetadata, 

470 __.ddoc.Doc( "Search execution and result metadata." ), 

471 ] 

472 inventory_locations: __.typx.Annotated[ 

473 tuple[ InventoryLocationInfo, ... ], 

474 __.ddoc.Doc( "Information about inventory locations used." ), 

475 ] 

476 

477 def render_as_json( self ) -> __.immut.Dictionary[ str, __.typx.Any ]: 

478 ''' Renders inventory query result as JSON-compatible dictionary. ''' 

479 objects_json = [ 

480 dict( obj.render_as_json( ) ) for obj in self.objects ] 

481 locations_json = [ 

482 dict( loc.render_as_json( ) ) for loc in self.inventory_locations ] 

483 return __.immut.Dictionary[ 

484 str, __.typx.Any 

485 ]( 

486 location = self.location, 

487 query = self.query, 

488 objects = objects_json, 

489 search_metadata = dict( self.search_metadata.render_as_json( ) ), 

490 inventory_locations = locations_json, 

491 ) 

492 

493 def render_as_markdown( 

494 self, /, *, 

495 reveal_internals: __.typx.Annotated[ 

496 bool, 

497 __.ddoc.Doc( "Controls whether internal details are shown." ), 

498 ] = True, 

499 ) -> tuple[ str, ... ]: 

500 ''' Renders inventory query result as Markdown lines for display. ''' 

501 lines = [ "# Inventory Query Results" ] 

502 lines.append( "**Query:** {query}".format( query = self.query ) ) 

503 if reveal_internals: 

504 lines.append( "**Location:** {location}".format( 

505 location = self.location ) ) 

506 lines.append( "**Results:** {count} of {max}".format( 

507 count = self.search_metadata.results_count, 

508 max = self.search_metadata.results_max ) ) 

509 if self.objects: 

510 lines.append( "" ) 

511 lines.append( "## Objects" ) 

512 for index, obj in enumerate( self.objects, 1 ): 

513 separator = "\n📦 ── Object {} ─────────────────────── 📦\n" 

514 lines.append( separator.format( index ) ) 

515 obj_lines = obj.render_as_markdown( 

516 reveal_internals = reveal_internals ) 

517 lines.extend( obj_lines ) 

518 return tuple( lines ) 

519 

520 

521class Detection( __.immut.DataclassObject ): 

522 ''' Processor detection information with confidence scoring. ''' 

523 

524 processor_name: __.typx.Annotated[ 

525 str, 

526 __.ddoc.Doc( "Name of the processor that can handle this location." ), 

527 ] 

528 confidence: __.typx.Annotated[ 

529 float, 

530 __.ddoc.Doc( "Detection confidence score (0.0-1.0)." ), 

531 ] 

532 processor_type: __.typx.Annotated[ 

533 str, 

534 __.ddoc.Doc( "Type of processor (inventory, structure)." ), 

535 ] 

536 detection_metadata: __.typx.Annotated[ 

537 __.immut.Dictionary[ str, __.typx.Any ], 

538 __.ddoc.Doc( "Processor-specific detection metadata." ), 

539 ] = __.dcls.field( default_factory = lambda: __.immut.Dictionary( ) ) 

540 

541 def render_as_json( self ) -> __.immut.Dictionary[ str, __.typx.Any ]: 

542 ''' Renders detection as JSON-compatible dictionary. ''' 

543 return __.immut.Dictionary[ 

544 str, __.typx.Any 

545 ]( 

546 processor_name = self.processor_name, 

547 confidence = self.confidence, 

548 processor_type = self.processor_type, 

549 detection_metadata = dict( self.detection_metadata ), 

550 ) 

551 

552 

553class DetectionsResult( ResultBase ): 

554 ''' Detection results with processor selection and timing metadata. ''' 

555 

556 source: __.typx.Annotated[ 

557 str, 

558 __.ddoc.Doc( "Primary location URL for detection operation." ), 

559 ] 

560 detections: __.typx.Annotated[ 

561 tuple[ Detection, ... ], 

562 __.ddoc.Doc( "All processor detections found for location." ), 

563 ] 

564 detection_optimal: __.typx.Annotated[ 

565 __.typx.Optional[ Detection ], 

566 __.ddoc.Doc( "Best detection result based on confidence scoring." ), 

567 ] 

568 time_detection_ms: __.typx.Annotated[ 

569 int, 

570 __.ddoc.Doc( "Detection operation time in milliseconds." ), 

571 ] 

572 

573 

574 def render_as_json( self ) -> __.immut.Dictionary[ str, __.typx.Any ]: 

575 ''' Renders detection results as JSON-compatible dictionary. ''' 

576 detections_json = [ 

577 dict( detection.render_as_json( ) ) 

578 for detection in self.detections ] 

579 return __.immut.Dictionary[ 

580 str, __.typx.Any 

581 ]( 

582 source = self.source, 

583 detections = detections_json, 

584 detection_optimal = ( 

585 dict( self.detection_optimal.render_as_json( ) ) 

586 if self.detection_optimal else None ), 

587 time_detection_ms = self.time_detection_ms, 

588 ) 

589 

590 def render_as_markdown( 

591 self, /, *, 

592 reveal_internals: __.typx.Annotated[ 

593 bool, 

594 __.ddoc.Doc( "Controls whether internal details are shown." ), 

595 ] = True, 

596 ) -> tuple[ str, ... ]: 

597 ''' Renders detection results as Markdown lines for display. ''' 

598 lines = [ "# Detection Results" ] 

599 if reveal_internals: 

600 lines.append( "**Source:** {source}".format( 

601 source = self.source ) ) 

602 lines.append( "**Detection time:** {time}ms".format( 

603 time = self.time_detection_ms ) ) 

604 if self.detection_optimal: 

605 lines.append( "**Optimal processor:** {name} ({type})".format( 

606 name = self.detection_optimal.processor_name, 

607 type = self.detection_optimal.processor_type ) ) 

608 lines.append( "**Confidence:** {conf:.2f}".format( 

609 conf = self.detection_optimal.confidence ) ) 

610 else: 

611 lines.append( "**No optimal processor found**" ) 

612 if reveal_internals and self.detections: 

613 lines.append( "" ) 

614 lines.append( "## All Detections" ) 

615 detection_lines = [ 

616 "- **{name}** ({type}): {conf:.2f}".format( 

617 name = detection.processor_name, 

618 type = detection.processor_type, 

619 conf = detection.confidence ) 

620 for detection in self.detections ] 

621 lines.extend( detection_lines ) 

622 return tuple( lines ) 

623 

624 

625class ProcessorInfo( ResultBase ): 

626 ''' Information about a processor and its capabilities. ''' 

627 

628 processor_name: __.typx.Annotated[ 

629 str, 

630 __.ddoc.Doc( "Name of the processor for identification." ), 

631 ] 

632 processor_type: __.typx.Annotated[ 

633 str, 

634 __.ddoc.Doc( "Type of processor (inventory, structure)." ), 

635 ] 

636 capabilities: __.typx.Annotated[ 

637 __.typx.Any, # Will be _interfaces.ProcessorCapabilities after import 

638 __.ddoc.Doc( "Complete capability description for processor." ), 

639 ] 

640 

641 def render_as_json( self ) -> __.immut.Dictionary[ str, __.typx.Any ]: 

642 ''' Renders processor info as JSON-compatible dictionary. ''' 

643 return __.immut.Dictionary[ 

644 str, __.typx.Any 

645 ]( 

646 processor_name = self.processor_name, 

647 processor_type = self.processor_type, 

648 capabilities = serialize_for_json( self.capabilities ), 

649 ) 

650 

651 def render_as_markdown( 

652 self, /, *, 

653 reveal_internals: __.typx.Annotated[ 

654 bool, 

655 __.ddoc.Doc( "Controls whether internal details are shown." ), 

656 ] = True, 

657 ) -> tuple[ str, ... ]: 

658 ''' Renders processor info as Markdown lines for display. ''' 

659 lines = [ f"### `{self.processor_name}` ({self.processor_type})" ] 

660 if reveal_internals: 

661 lines.append( f"**Version:** {self.capabilities.version}" ) 

662 if self.capabilities.results_limit_max: 

663 lines.append( 

664 f"**Max results:** {self.capabilities.results_limit_max}" ) 

665 if self.capabilities.response_time_typical: 

666 lines.append( 

667 f"**Response time:** " 

668 f"{self.capabilities.response_time_typical}" ) 

669 if self.capabilities.notes: 

670 lines.append( f"**Notes:** {self.capabilities.notes}" ) 

671 if self.capabilities.supported_filters: 

672 lines.append( "" ) 

673 lines.append( "**Supported filters:**" ) 

674 for filter_cap in self.capabilities.supported_filters: 

675 filter_line = f"- `{filter_cap.name}` ({filter_cap.type})" 

676 if filter_cap.required: 

677 filter_line += " *required*" 

678 lines.append( filter_line ) 

679 if filter_cap.description: 

680 lines.append( f" {filter_cap.description}" ) 

681 if filter_cap.values: 

682 values_str = ', '.join( filter_cap.values ) 

683 lines.append( f" Values: {values_str}" ) 

684 return tuple( lines ) 

685 

686 

687class ProcessorsSurveyResult( ResultBase ): 

688 ''' Survey results listing available processors and capabilities. ''' 

689 

690 genus: __.typx.Annotated[ 

691 __.typx.Any, # Will be _interfaces.ProcessorGenera after import 

692 __.ddoc.Doc( 

693 "Processor genus that was surveyed (inventory or structure)." ), 

694 ] 

695 filter_name: __.typx.Annotated[ 

696 __.typx.Optional[ str ], 

697 __.ddoc.Doc( "Optional processor name filter applied to survey." ), 

698 ] = None 

699 processors: __.typx.Annotated[ 

700 tuple[ ProcessorInfo, ... ], 

701 __.ddoc.Doc( "Available processors matching survey criteria." ), 

702 ] 

703 survey_time_ms: __.typx.Annotated[ 

704 int, 

705 __.ddoc.Doc( "Survey operation time in milliseconds." ), 

706 ] 

707 

708 def render_as_json( self ) -> __.immut.Dictionary[ str, __.typx.Any ]: 

709 ''' Renders survey results as JSON-compatible dictionary. ''' 

710 processors_json = [ 

711 dict( processor.render_as_json( ) ) 

712 for processor in self.processors ] 

713 return __.immut.Dictionary[ 

714 str, __.typx.Any 

715 ]( 

716 genus = ( 

717 self.genus.value if hasattr( self.genus, 'value' ) 

718 else str( self.genus ) ), 

719 filter_name = self.filter_name, 

720 processors = processors_json, 

721 survey_time_ms = self.survey_time_ms, 

722 ) 

723 

724 def render_as_markdown( 

725 self, /, *, 

726 reveal_internals: __.typx.Annotated[ 

727 bool, 

728 __.ddoc.Doc( "Controls whether internal details are shown." ), 

729 ] = True, 

730 ) -> tuple[ str, ... ]: 

731 ''' Renders survey results as Markdown lines for display. ''' 

732 genus_name = ( 

733 self.genus.value if hasattr( self.genus, 'value' ) 

734 else str( self.genus ) ) 

735 title = f"# Processor Survey Results ({genus_name})" 

736 lines = [ title ] 

737 if reveal_internals: 

738 lines.append( f"**Survey time:** {self.survey_time_ms}ms" ) 

739 if self.filter_name: 

740 lines.append( f"**Filter:** {self.filter_name}" ) 

741 lines.append( f"**Processors found:** {len( self.processors )}" ) 

742 if self.processors: 

743 lines.append( "" ) 

744 for i, processor in enumerate( self.processors, 1 ): 

745 lines.append( f"📦 ── Processor {i} ──────────" ) 

746 processor_lines = processor.render_as_markdown( 

747 reveal_internals = reveal_internals ) 

748 lines.extend( processor_lines ) 

749 if i < len( self.processors ): 

750 lines.append( "" ) 

751 return tuple( lines ) 

752 

753 

754def parse_content_id( content_id: str ) -> tuple[ str, str ]: 

755 ''' Parses content identifier back to location and name components. 

756  

757 Returns tuple of (location, name) extracted from content_id. 

758 Raises ContentIdInvalidity if content_id is malformed or cannot be  

759 decoded. 

760 ''' 

761 try: 

762 identifier_source = __.base64.b64decode( 

763 content_id.encode( 'ascii' ) ).decode( 'utf-8' ) 

764 except Exception as exc: 

765 raise _exceptions.ContentIdInvalidity( 

766 content_id, "Base64 decoding failed" ) from exc 

767 if ':' not in identifier_source: 

768 raise _exceptions.ContentIdInvalidity( 

769 content_id, "Missing location:object separator" ) 

770 location, name = identifier_source.rsplit( ':', 1 ) 

771 return location, name 

772 

773 

774def produce_content_id( location: str, name: str ) -> str: 

775 ''' Produces deterministic content identifier for browse-then-extract. 

776  

777 Uses base64 encoding of location + ":" + name to create stable, 

778 debuggable identifiers that maintain stateless operation. 

779 ''' 

780 identifier_source = f"{location}:{name}" 

781 return __.base64.b64encode( 

782 identifier_source.encode( 'utf-8' ) ).decode( 'ascii' ) 

783 

784 

785def serialize_for_json( objct: __.typx.Any ) -> __.typx.Any: 

786 ''' Legacy serialization for non-structured objects (dicts). ''' 

787 if __.dcls.is_dataclass( objct ): 

788 return _serialize_dataclass_for_json( objct ) 

789 if isinstance( objct, ( list, tuple, set, frozenset ) ): 

790 return _serialize_sequence_for_json( objct ) 

791 if isinstance( objct, ( dict, __.immut.Dictionary ) ): 

792 return _serialize_mapping_for_json( objct ) 

793 return objct 

794 

795 

796 

797def _serialize_dataclass_for_json( 

798 obj: __.typx.Any, 

799) -> dict[ str, __.typx.Any ]: 

800 ''' Serializes dataclass objects to JSON-compatible dictionaries. ''' 

801 result: dict[ str, __.typx.Any ] = { } 

802 for field in __.dcls.fields( obj ): 

803 if field.name.startswith( '_' ): 

804 continue 

805 value = getattr( obj, field.name ) 

806 result[ field.name ] = serialize_for_json( value ) 

807 return result 

808 

809 

810def _serialize_mapping_for_json( 

811 obj: __.typx.Any 

812) -> dict[ __.typx.Any, __.typx.Any ]: 

813 ''' Serializes mapping-like objects to JSON-compatible dictionaries. ''' 

814 return { 

815 key: serialize_for_json( value ) 

816 for key, value in obj.items( ) 

817 } 

818 

819 

820def _serialize_sequence_for_json( obj: __.typx.Any ) -> list[ __.typx.Any ]: 

821 ''' Serializes sequence-like objects to JSON-compatible lists. ''' 

822 return [ serialize_for_json( item ) for item in obj ] 

823 

824 

825ContentDocuments: __.typx.TypeAlias = __.cabc.Sequence[ ContentDocument ] 

826InventoryObjects: __.typx.TypeAlias = __.cabc.Sequence[ InventoryObject ] 

827SearchResults: __.typx.TypeAlias = __.cabc.Sequence[ SearchResult ] 

828