Add scan flow MVP and local Axiom skill workspace
This snapshot establishes the camera-to-result recognition flow and related tests while checking in the project skill/docs assets required for the configured local tooling.
This commit is contained in:
@@ -7,13 +7,113 @@
|
||||
|
||||
import Testing
|
||||
@testable import StackDex
|
||||
import UIKit
|
||||
|
||||
struct StackDexTests {
|
||||
|
||||
@Test func example() async throws {
|
||||
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
|
||||
// Swift Testing Documentation
|
||||
// https://developer.apple.com/documentation/testing
|
||||
@Test func cameraSessionStateBlocksStartUntilConfigurationCommits() {
|
||||
var state = CameraSessionState()
|
||||
|
||||
#expect(state.canStartSession)
|
||||
|
||||
state.beginConfiguration()
|
||||
#expect(!state.canStartSession)
|
||||
|
||||
state.commitConfiguration()
|
||||
#expect(state.canStartSession)
|
||||
}
|
||||
|
||||
@Test func cameraSessionStateTracksNestedConfigurationDepth() {
|
||||
var state = CameraSessionState()
|
||||
|
||||
state.beginConfiguration()
|
||||
state.beginConfiguration()
|
||||
#expect(!state.canStartSession)
|
||||
|
||||
state.commitConfiguration()
|
||||
#expect(!state.canStartSession)
|
||||
|
||||
state.commitConfiguration()
|
||||
#expect(state.canStartSession)
|
||||
}
|
||||
|
||||
@Test func heuristicExtractorFindsStructuredFields() {
|
||||
let extractor = CardTextHeuristicExtractor()
|
||||
let payload = OCRTextPayload(
|
||||
rawText: "Charizard\n120 HP\n4/102 Base Set\nHolo Rare",
|
||||
lines: [
|
||||
RecognizedTextLine(text: "Charizard", confidence: 0.95, normalizedBounds: CGRect(x: 0.1, y: 0.82, width: 0.4, height: 0.08)),
|
||||
RecognizedTextLine(text: "120 HP", confidence: 0.81, normalizedBounds: CGRect(x: 0.75, y: 0.83, width: 0.12, height: 0.05)),
|
||||
RecognizedTextLine(text: "4/102 Base Set", confidence: 0.92, normalizedBounds: CGRect(x: 0.2, y: 0.12, width: 0.4, height: 0.05)),
|
||||
RecognizedTextLine(text: "Holo Rare", confidence: 0.88, normalizedBounds: CGRect(x: 0.7, y: 0.1, width: 0.2, height: 0.05)),
|
||||
],
|
||||
averageConfidence: 0.89
|
||||
)
|
||||
|
||||
let draft = extractor.extract(payload: payload, source: .onDeviceOffline)
|
||||
|
||||
#expect(draft.cardName == "Charizard")
|
||||
#expect(draft.cardNumber == "4/102")
|
||||
#expect(draft.setIdentifier == "Base Set")
|
||||
#expect(draft.rarity == CardRarity.holoRare.rawValue)
|
||||
#expect(draft.confidence == .high)
|
||||
}
|
||||
|
||||
@Test func pipelineFallsBackToVisionWhenCloudIsStubbed() async throws {
|
||||
let monitor = TestNetworkStatusProvider(isOnline: true)
|
||||
let pipeline = CardRecognitionPipeline(
|
||||
networkStatusProvider: monitor,
|
||||
cloudOCRClient: StubCloudOCRClient(),
|
||||
fallbackOCR: TestOCRService(payload: OCRTextPayload(
|
||||
rawText: "Pikachu\n25/165\nScarlet & Violet\nRare",
|
||||
lines: [
|
||||
RecognizedTextLine(text: "Pikachu", confidence: 0.95, normalizedBounds: CGRect(x: 0.1, y: 0.8, width: 0.3, height: 0.08)),
|
||||
RecognizedTextLine(text: "25/165", confidence: 0.91, normalizedBounds: CGRect(x: 0.3, y: 0.15, width: 0.18, height: 0.05)),
|
||||
RecognizedTextLine(text: "Scarlet & Violet", confidence: 0.87, normalizedBounds: CGRect(x: 0.25, y: 0.11, width: 0.4, height: 0.05)),
|
||||
RecognizedTextLine(text: "Rare", confidence: 0.75, normalizedBounds: CGRect(x: 0.76, y: 0.1, width: 0.12, height: 0.04)),
|
||||
],
|
||||
averageConfidence: 0.87
|
||||
))
|
||||
)
|
||||
|
||||
let session = try await pipeline.recognizeCard(in: TestImageFactory.makeImage())
|
||||
|
||||
#expect(session.draft.source == .onDeviceFallback)
|
||||
#expect(session.draft.cardName == "Pikachu")
|
||||
#expect(session.draft.cardNumber == "25/165")
|
||||
#expect(session.draft.notes.contains(where: { $0.contains("Cloud OCR") }))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final class TestNetworkStatusProvider: NetworkStatusProviding {
|
||||
var isOnline: Bool
|
||||
|
||||
init(isOnline: Bool) {
|
||||
self.isOnline = isOnline
|
||||
}
|
||||
|
||||
func startMonitoring() {}
|
||||
func stopMonitoring() {}
|
||||
}
|
||||
|
||||
private struct TestOCRService: CardTextRecognizing {
|
||||
let payload: OCRTextPayload
|
||||
|
||||
func recognizeText(in image: PreparedImage) async throws -> OCRTextPayload {
|
||||
payload
|
||||
}
|
||||
}
|
||||
|
||||
private enum TestImageFactory {
|
||||
static func makeImage() -> UIImage {
|
||||
let renderer = UIGraphicsImageRenderer(size: CGSize(width: 300, height: 420))
|
||||
return renderer.image { context in
|
||||
UIColor.white.setFill()
|
||||
context.fill(CGRect(x: 0, y: 0, width: 300, height: 420))
|
||||
UIColor.black.setStroke()
|
||||
context.cgContext.setLineWidth(6)
|
||||
context.cgContext.stroke(CGRect(x: 16, y: 16, width: 268, height: 388))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user