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

270 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-09-02 00:02 +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 __ 

28 

29 

30_CONTENT_PREVIEW_LIMIT = 100 

31 

32 

33class InventoryObject( __.immut.DataclassObject ): 

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

35 

36 Represents a single documentation object from any inventory source 

37 with standardized fields and format-specific metadata container. 

38 ''' 

39 

40 name: __.typx.Annotated[ 

41 str, 

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

43 ] 

44 uri: __.typx.Annotated[ 

45 str, 

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

47 ] 

48 inventory_type: __.typx.Annotated[ 

49 str, 

50 __.ddoc.Doc( 

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

52 ] 

53 location_url: __.typx.Annotated[ 

54 str, __.ddoc.Doc( 

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

56 ] 

57 display_name: __.typx.Annotated[ 

58 __.typx.Optional[ str ], 

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

60 ] = None 

61 specifics: __.typx.Annotated[ 

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

63 __.ddoc.Doc( 

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

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

66 

67 

68 @property 

69 def effective_display_name( self ) -> str: 

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

71 if self.display_name is not None: 

72 return self.display_name 

73 return self.name 

74 

75 @__.abc.abstractmethod 

76 def render_specifics_json( 

77 self 

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

79 ''' Renders specifics for JSON output. ''' 

80 raise NotImplementedError 

81 

82 @__.abc.abstractmethod 

83 def render_specifics_markdown( 

84 self, /, *, 

85 reveal_internals: __.typx.Annotated[ 

86 bool, 

87 __.ddoc.Doc( ''' 

88 Controls whether implementation-specific details (internal 

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

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

91 ''' ), 

92 ] = True, 

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

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

95 raise NotImplementedError 

96 

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

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

99 base = __.immut.Dictionary[ 

100 str, __.typx.Any 

101 ]( 

102 name = self.name, 

103 uri = self.uri, 

104 inventory_type = self.inventory_type, 

105 location_url = self.location_url, 

106 display_name = self.display_name, 

107 effective_display_name = self.effective_display_name, 

108 ) 

109 formatted_specifics = self.render_specifics_json( ) 

110 result_dict = dict( base ) 

111 result_dict.update( dict( formatted_specifics ) ) 

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

113 

114 def render_as_markdown( 

115 self, /, *, 

116 reveal_internals: __.typx.Annotated[ 

117 bool, 

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

119 ] = True, 

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

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

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

123 if reveal_internals: 

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

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

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

127 specifics_lines = self.render_specifics_markdown( 

128 reveal_internals = reveal_internals ) 

129 lines.extend( specifics_lines ) 

130 return tuple( lines ) 

131 

132 

133class ContentDocument( __.immut.DataclassObject ): 

134 ''' Documentation content with extracted metadata and snippets. ''' 

135 

136 inventory_object: __.typx.Annotated[ 

137 InventoryObject, 

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

139 ] 

140 description: __.typx.Annotated[ 

141 str, 

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

143 ] = '' 

144 documentation_url: __.typx.Annotated[ 

145 str, 

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

147 ] = '' 

148 extraction_metadata: __.typx.Annotated[ 

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

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

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

152 

153 @property 

154 def has_meaningful_content( self ) -> bool: 

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

156 return bool( self.description ) 

157 

158 def render_as_json( 

159 self, /, *, 

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

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

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

163 description = self.description 

164 if lines_max is not None: 

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

166 if len( desc_lines ) > lines_max: 

167 desc_lines = desc_lines[ :lines_max ] 

168 desc_lines.append( "..." ) 

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

170 return __.immut.Dictionary[ 

171 str, __.typx.Any 

172 ]( 

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

174 description = description, 

175 documentation_url = self.documentation_url, 

176 extraction_metadata = dict( self.extraction_metadata ), 

177 has_meaningful_content = self.has_meaningful_content, 

178 ) 

179 

180 def render_as_markdown( 

181 self, /, *, 

182 reveal_internals: __.typx.Annotated[ 

183 bool, 

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

185 ] = True, 

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

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

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

189 if self.description: 

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

191 if self.documentation_url: 

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

193 if reveal_internals: 

194 inventory_lines = self.inventory_object.render_specifics_markdown( 

195 reveal_internals = True ) 

196 lines.extend( inventory_lines ) 

197 return tuple( lines ) 

198 

199 

200class InventoryLocationInfo( __.immut.DataclassObject ): 

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

202 

203 inventory_type: __.typx.Annotated[ 

204 str, 

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

206 ] 

207 location_url: __.typx.Annotated[ 

208 str, 

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

210 ] 

211 processor_name: __.typx.Annotated[ 

212 str, 

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

214 ] 

215 confidence: __.typx.Annotated[ 

216 float, 

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

218 ] 

219 object_count: __.typx.Annotated[ 

220 int, 

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

222 ] 

223 

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

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

226 return __.immut.Dictionary( 

227 inventory_type = self.inventory_type, 

228 location_url = self.location_url, 

229 processor_name = self.processor_name, 

230 confidence = self.confidence, 

231 object_count = self.object_count, 

232 ) 

233 

234 

235class SearchMetadata( __.immut.DataclassObject ): 

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

237 

238 results_count: __.typx.Annotated[ 

239 int, 

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

241 ] 

242 results_max: __.typx.Annotated[ 

243 int, 

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

245 ] 

246 matches_total: __.typx.Annotated[ 

247 __.typx.Optional[ int ], 

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

249 ] = None 

250 search_time_ms: __.typx.Annotated[ 

251 __.typx.Optional[ int ], 

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

253 ] = None 

254 

255 @property 

256 def results_truncated( self ) -> bool: 

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

258 if self.matches_total is None: 

259 return False 

260 return self.results_count < self.matches_total 

261 

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

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

264 return __.immut.Dictionary( 

265 results_count = self.results_count, 

266 results_max = self.results_max, 

267 matches_total = self.matches_total, 

268 search_time_ms = self.search_time_ms, 

269 results_truncated = self.results_truncated, 

270 ) 

271 

272 

273class SearchResult( __.immut.DataclassObject ): 

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

275 

276 inventory_object: __.typx.Annotated[ 

277 InventoryObject, 

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

279 ] 

280 score: __.typx.Annotated[ 

281 float, 

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

283 ] 

284 match_reasons: __.typx.Annotated[ 

285 tuple[ str, ... ], 

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

287 ] 

288 

289 @classmethod 

290 def from_inventory_object( 

291 cls, 

292 inventory_object: InventoryObject, *, 

293 score: float, 

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

295 ) -> __.typx.Self: 

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

297 return cls( 

298 inventory_object = inventory_object, 

299 score = score, 

300 match_reasons = tuple( match_reasons ) ) 

301 

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

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

304 return __.immut.Dictionary[ 

305 str, __.typx.Any 

306 ]( 

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

308 score = self.score, 

309 match_reasons = list( self.match_reasons ), 

310 ) 

311 

312 def render_as_markdown( 

313 self, /, *, 

314 reveal_internals: __.typx.Annotated[ 

315 bool, 

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

317 ] = True, 

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

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

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

321 name = self.inventory_object.effective_display_name, 

322 score = self.score ) 

323 lines = [ title ] 

324 if reveal_internals and self.match_reasons: 

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

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

327 reasons = reasons ) ) 

328 inventory_lines = self.inventory_object.render_as_markdown( 

329 reveal_internals = reveal_internals ) 

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

331 return tuple( lines ) 

332 

333 

334class ContentQueryResult( __.immut.DataclassObject ): 

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

336 

337 location: __.typx.Annotated[ 

338 str, 

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

340 ] 

341 query: __.typx.Annotated[ 

342 str, 

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

344 ] 

345 documents: __.typx.Annotated[ 

346 tuple[ ContentDocument, ... ], 

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

348 search_metadata: __.typx.Annotated[ 

349 SearchMetadata, 

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

351 ] 

352 inventory_locations: __.typx.Annotated[ 

353 tuple[ InventoryLocationInfo, ... ], 

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

355 ] 

356 

357 def render_as_json( 

358 self, /, *, 

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

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

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

362 documents_json = [ 

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

364 for doc in self.documents ] 

365 locations_json = [ 

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

367 return __.immut.Dictionary[ 

368 str, __.typx.Any 

369 ]( 

370 location = self.location, 

371 query = self.query, 

372 documents = documents_json, 

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

374 inventory_locations = locations_json, 

375 ) 

376 

377 def render_as_markdown( 

378 self, /, *, 

379 reveal_internals: __.typx.Annotated[ 

380 bool, 

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

382 ] = True, 

383 lines_max: __.typx.Annotated[ 

384 __.typx.Optional[ int ], 

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

386 ] = None, 

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

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

389 title = "# Content Query Results" 

390 if lines_max is not None: 

391 title += " (truncated)" 

392 lines = [ title ] 

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

394 if reveal_internals: 

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

396 location = self.location ) ) 

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

398 count = self.search_metadata.results_count, 

399 max = self.search_metadata.results_max ) ) 

400 if self.documents: 

401 lines.append( "" ) 

402 lines.append( "## Documents" ) 

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

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

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

406 lines.append( "**URL:** {url}".format( 

407 url = doc.documentation_url ) ) 

408 if doc.description: 

409 description = doc.description 

410 if lines_max is not None: 

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

412 if len( desc_lines ) > lines_max: 

413 desc_lines = desc_lines[ :lines_max ] 

414 desc_lines.append( "..." ) 

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

416 lines.append( "**Description:** {description}".format( 

417 description = description ) ) 

418 return tuple( lines ) 

419 

420 

421class InventoryQueryResult( __.immut.DataclassObject ): 

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

423 

424 location: __.typx.Annotated[ 

425 str, 

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

427 ] 

428 query: __.typx.Annotated[ 

429 str, 

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

431 ] 

432 objects: __.typx.Annotated[ 

433 tuple[ InventoryObject, ... ], 

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

435 ] 

436 search_metadata: __.typx.Annotated[ 

437 SearchMetadata, 

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

439 ] 

440 inventory_locations: __.typx.Annotated[ 

441 tuple[ InventoryLocationInfo, ... ], 

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

443 ] 

444 

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

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

447 objects_json = [ 

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

449 locations_json = [ 

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

451 return __.immut.Dictionary[ 

452 str, __.typx.Any 

453 ]( 

454 location = self.location, 

455 query = self.query, 

456 objects = objects_json, 

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

458 inventory_locations = locations_json, 

459 ) 

460 

461 def render_as_markdown( 

462 self, /, *, 

463 reveal_internals: __.typx.Annotated[ 

464 bool, 

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

466 ] = True, 

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

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

469 lines = [ "# Inventory Query Results" ] 

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

471 if reveal_internals: 

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

473 location = self.location ) ) 

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

475 count = self.search_metadata.results_count, 

476 max = self.search_metadata.results_max ) ) 

477 if self.objects: 

478 lines.append( "" ) 

479 lines.append( "## Objects" ) 

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

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

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

483 obj_lines = obj.render_as_markdown( 

484 reveal_internals = reveal_internals ) 

485 lines.extend( obj_lines ) 

486 return tuple( lines ) 

487 

488 

489class Detection( __.immut.DataclassObject ): 

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

491 

492 processor_name: __.typx.Annotated[ 

493 str, 

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

495 ] 

496 confidence: __.typx.Annotated[ 

497 float, 

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

499 ] 

500 processor_type: __.typx.Annotated[ 

501 str, 

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

503 ] 

504 detection_metadata: __.typx.Annotated[ 

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

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

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

508 

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

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

511 return __.immut.Dictionary[ 

512 str, __.typx.Any 

513 ]( 

514 processor_name = self.processor_name, 

515 confidence = self.confidence, 

516 processor_type = self.processor_type, 

517 detection_metadata = dict( self.detection_metadata ), 

518 ) 

519 

520 

521class DetectionsResult( __.immut.DataclassObject ): 

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

523 

524 source: __.typx.Annotated[ 

525 str, 

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

527 ] 

528 detections: __.typx.Annotated[ 

529 tuple[ Detection, ... ], 

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

531 ] 

532 detection_optimal: __.typx.Annotated[ 

533 __.typx.Optional[ Detection ], 

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

535 ] 

536 time_detection_ms: __.typx.Annotated[ 

537 int, 

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

539 ] 

540 

541 

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

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

544 detections_json = [ 

545 dict( detection.render_as_json( ) ) 

546 for detection in self.detections ] 

547 return __.immut.Dictionary[ 

548 str, __.typx.Any 

549 ]( 

550 source = self.source, 

551 detections = detections_json, 

552 detection_optimal = ( 

553 dict( self.detection_optimal.render_as_json( ) ) 

554 if self.detection_optimal else None ), 

555 time_detection_ms = self.time_detection_ms, 

556 ) 

557 

558 def render_as_markdown( 

559 self, /, *, 

560 reveal_internals: __.typx.Annotated[ 

561 bool, 

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

563 ] = True, 

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

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

566 lines = [ "# Detection Results" ] 

567 if reveal_internals: 

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

569 source = self.source ) ) 

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

571 time = self.time_detection_ms ) ) 

572 if self.detection_optimal: 

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

574 name = self.detection_optimal.processor_name, 

575 type = self.detection_optimal.processor_type ) ) 

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

577 conf = self.detection_optimal.confidence ) ) 

578 else: 

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

580 if reveal_internals and self.detections: 

581 lines.append( "" ) 

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

583 detection_lines = [ 

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

585 name = detection.processor_name, 

586 type = detection.processor_type, 

587 conf = detection.confidence ) 

588 for detection in self.detections ] 

589 lines.extend( detection_lines ) 

590 return tuple( lines ) 

591 

592 

593class ProcessorInfo( __.immut.DataclassObject ): 

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

595 

596 processor_name: __.typx.Annotated[ 

597 str, 

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

599 ] 

600 processor_type: __.typx.Annotated[ 

601 str, 

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

603 ] 

604 capabilities: __.typx.Annotated[ 

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

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

607 ] 

608 

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

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

611 return __.immut.Dictionary[ 

612 str, __.typx.Any 

613 ]( 

614 processor_name = self.processor_name, 

615 processor_type = self.processor_type, 

616 capabilities = serialize_for_json( self.capabilities ), 

617 ) 

618 

619 def render_as_markdown( 

620 self, /, *, 

621 reveal_internals: __.typx.Annotated[ 

622 bool, 

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

624 ] = True, 

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

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

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

628 if reveal_internals: 

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

630 if self.capabilities.results_limit_max: 

631 lines.append( 

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

633 if self.capabilities.response_time_typical: 

634 lines.append( 

635 f"**Response time:** " 

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

637 if self.capabilities.notes: 

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

639 if self.capabilities.supported_filters: 

640 lines.append( "" ) 

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

642 for filter_cap in self.capabilities.supported_filters: 

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

644 if filter_cap.required: 

645 filter_line += " *required*" 

646 lines.append( filter_line ) 

647 if filter_cap.description: 

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

649 if filter_cap.values: 

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

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

652 return tuple( lines ) 

653 

654 

655class ProcessorsSurveyResult( __.immut.DataclassObject ): 

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

657 

658 genus: __.typx.Annotated[ 

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

660 __.ddoc.Doc( 

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

662 ] 

663 filter_name: __.typx.Annotated[ 

664 __.typx.Optional[ str ], 

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

666 ] = None 

667 processors: __.typx.Annotated[ 

668 tuple[ ProcessorInfo, ... ], 

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

670 ] 

671 survey_time_ms: __.typx.Annotated[ 

672 int, 

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

674 ] 

675 

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

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

678 processors_json = [ 

679 dict( processor.render_as_json( ) ) 

680 for processor in self.processors ] 

681 return __.immut.Dictionary[ 

682 str, __.typx.Any 

683 ]( 

684 genus = ( 

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

686 else str( self.genus ) ), 

687 filter_name = self.filter_name, 

688 processors = processors_json, 

689 survey_time_ms = self.survey_time_ms, 

690 ) 

691 

692 def render_as_markdown( 

693 self, /, *, 

694 reveal_internals: __.typx.Annotated[ 

695 bool, 

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

697 ] = True, 

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

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

700 genus_name = ( 

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

702 else str( self.genus ) ) 

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

704 lines = [ title ] 

705 if reveal_internals: 

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

707 if self.filter_name: 

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

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

710 if self.processors: 

711 lines.append( "" ) 

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

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

714 processor_lines = processor.render_as_markdown( 

715 reveal_internals = reveal_internals ) 

716 lines.extend( processor_lines ) 

717 if i < len( self.processors ): 

718 lines.append( "" ) 

719 return tuple( lines ) 

720 

721 

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

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

724 if __.dcls.is_dataclass( objct ): 

725 return _serialize_dataclass_for_json( objct ) 

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

727 return _serialize_sequence_for_json( objct ) 

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

729 return _serialize_mapping_for_json( objct ) 

730 return objct 

731 

732 

733 

734def _serialize_dataclass_for_json( 

735 obj: __.typx.Any, 

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

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

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

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

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

741 continue 

742 value = getattr( obj, field.name ) 

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

744 return result 

745 

746 

747def _serialize_mapping_for_json( 

748 obj: __.typx.Any 

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

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

751 return { 

752 key: serialize_for_json( value ) 

753 for key, value in obj.items( ) 

754 } 

755 

756 

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

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

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

760 

761 

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

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

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

765