"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
const developer_profile_1 = require("../../walls/03-knowledge/decisions/developer-profile");
const recommendation_engine_1 = require("../../walls/03-knowledge/decisions/recommendation-engine");
const recommendation_store_1 = require("../../walls/06-memory/stores/recommendation-store");
const path = __importStar(require("path"));
const fs = __importStar(require("fs"));
function tmpDir() {
    const dir = path.join(require('os').tmpdir(), `oc-ch4-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
    fs.mkdirSync(dir, { recursive: true });
    return dir;
}
function makePattern(id, category, language, opts) {
    const pos = opts?.pos ?? 10;
    const neg = opts?.neg ?? 2;
    const total = pos + neg;
    return {
        patternId: id,
        category,
        description: `Pattern ${id}`,
        from: 'old_way',
        to: 'new_way',
        conditions: [
            { type: 'language', value: language, matchScore: 1 },
        ],
        confidence: {
            distribution: 'beta',
            alpha: pos + 1,
            beta: neg + 1,
            mean: pos / Math.max(total, 1),
            ci95: [0.3, 0.9],
        },
        signals: {
            positive: pos,
            negative: neg,
            total,
            lastSignal: Date.now(),
        },
        attribution: {
            primaryCondition: '',
            conditionConfidence: 0,
            sourceFiles: [],
            note: '',
        },
        scope: 'global',
        survivedDays: 30,
        meta: {
            language,
            subcategory: 'general',
            complexity: 'low',
            risk: 'low',
            source: 'manual',
        },
        lifecycle: {
            firstSeen: Date.now() - 86400000,
            lastSeen: Date.now(),
            version: 1,
            history: [],
        },
        quality: {
            score: 80,
            reuseRate: 0.5,
            successRate: pos / Math.max(total, 1),
            failureRate: neg / Math.max(total, 1),
            acceptanceRate: pos / Math.max(total, 1),
            daysSinceLastSignal: 1,
            age: 30,
        },
        relationships: {
            related: [],
            conflicting: [],
            complementary: [],
            dependencies: [],
        },
        ranking: {
            score: 80,
            rank: 1,
            factors: { confidence: 70, quality: 80, usage: 60, recency: 90, acceptance: 80 },
        },
    };
}
function makeOutcome(patternId, successRate, usageCount = 10) {
    const successCount = Math.round(usageCount * successRate);
    const failureCount = usageCount - successCount;
    return {
        patternId,
        usageCount,
        successCount,
        failureCount,
        acceptanceCount: Math.round(usageCount * 0.8),
        rejectionCount: Math.round(usageCount * 0.2),
        lastOutcome: successRate > 0.5 ? 'PATTERN_SUCCESS' : 'PATTERN_FAILURE',
        lastSuccessTimestamp: successRate > 0.5 ? Date.now() : null,
        lastFailureTimestamp: successRate <= 0.5 ? Date.now() : null,
        successRate,
        failureRate: 1 - successRate,
        acceptanceRate: 0.8,
        confidence: 0.5 + successRate * 0.4,
        trend: successRate > 0.7 ? 'rising' : 'declining',
        health: Math.round(successRate * 100),
        firstSeen: Date.now() - 86400000,
        lastSeen: Date.now(),
    };
}
function makeProfile(id, langs, cats) {
    return {
        developerId: id,
        languages: langs,
        frameworks: {},
        projectTypes: [],
        patternsUsed: 10,
        patternAcceptanceRate: 0.85,
        successRate: 0.8,
        failureRate: 0.2,
        topCategories: cats,
        lastUpdated: Date.now(),
    };
}
describe('Chapter 4 — Recommendation Engine V2', () => {
    describe('Test 1: 3 developers → 3 different recommendation sets', () => {
        test('each developer gets distinct, language-matched recommendations', () => {
            const categories = ['testing', 'data', 'devops'];
            const languages = ['typescript', 'python', 'go'];
            const allPatterns = [];
            const outcomesMap = new Map();
            for (const cat of categories) {
                for (const lang of languages) {
                    for (let i = 0; i < 3; i++) {
                        const pid = `${lang}_${cat}_${i}`;
                        allPatterns.push(makePattern(pid, cat, lang));
                        outcomesMap.set(pid, makeOutcome(pid, 0.9));
                    }
                }
            }
            const devAData = allPatterns.filter((p) => p.meta.language === 'typescript' && p.category === 'testing');
            const devBData = allPatterns.filter((p) => p.meta.language === 'python' && p.category === 'data');
            const devCData = allPatterns.filter((p) => p.meta.language === 'go' && p.category === 'devops');
            const profileA = (0, developer_profile_1.buildProfile)('dev-a', devAData, outcomesMap);
            const profileB = (0, developer_profile_1.buildProfile)('dev-b', devBData, outcomesMap);
            const profileC = (0, developer_profile_1.buildProfile)('dev-c', devCData, outcomesMap);
            const allProfiles = new Map();
            allProfiles.set('dev-a', profileA);
            allProfiles.set('dev-b', profileB);
            allProfiles.set('dev-c', profileC);
            const resultA = (0, recommendation_engine_1.recommendPatterns)('dev-a', allPatterns, outcomesMap, allProfiles);
            const resultB = (0, recommendation_engine_1.recommendPatterns)('dev-b', allPatterns, outcomesMap, allProfiles);
            const resultC = (0, recommendation_engine_1.recommendPatterns)('dev-c', allPatterns, outcomesMap, allProfiles);
            const idsA = new Set(resultA.recommendations.map((r) => r.patternId));
            const idsB = new Set(resultB.recommendations.map((r) => r.patternId));
            const idsC = new Set(resultC.recommendations.map((r) => r.patternId));
            expect(idsA.size).toBeGreaterThan(0);
            expect(idsB.size).toBeGreaterThan(0);
            expect(idsC.size).toBeGreaterThan(0);
            const allSame = [...idsA].every((id) => idsB.has(id) && idsC.has(id));
            expect(allSame).toBe(false);
            const langCountA = countByLanguage(resultA);
            const langCountB = countByLanguage(resultB);
            const langCountC = countByLanguage(resultC);
            expect(langCountA.typescript).toBeGreaterThan(langCountA.python ?? 0);
            expect(langCountA.typescript).toBeGreaterThan(langCountA.go ?? 0);
            expect(langCountB.python).toBeGreaterThan(langCountB.typescript ?? 0);
            expect(langCountB.python).toBeGreaterThan(langCountB.go ?? 0);
            expect(langCountC.go).toBeGreaterThan(langCountC.typescript ?? 0);
            expect(langCountC.go).toBeGreaterThan(langCountC.python ?? 0);
        });
    });
    describe('Test 2: Outcome-based scoring', () => {
        test('changing outcomes changes recommendation ranking', () => {
            const patterns = [
                makePattern('p_a', 'testing', 'typescript'),
                makePattern('p_b', 'data', 'python'),
                makePattern('p_c', 'devops', 'go'),
            ];
            const outcomes = new Map();
            outcomes.set('p_a', makeOutcome('p_a', 0.9));
            outcomes.set('p_b', makeOutcome('p_b', 0.8));
            outcomes.set('p_c', makeOutcome('p_c', 0.7));
            const profile = (0, developer_profile_1.buildProfile)('dev', patterns, outcomes);
            const profiles = new Map();
            profiles.set('dev', profile);
            const first = (0, recommendation_engine_1.recommendPatterns)('dev', patterns, outcomes, profiles);
            const firstOrder = first.recommendations.map((r) => r.patternId);
            outcomes.set('p_a', makeOutcome('p_a', 0.2));
            const profile2 = (0, developer_profile_1.buildProfile)('dev', patterns, outcomes);
            profiles.set('dev', profile2);
            const second = (0, recommendation_engine_1.recommendPatterns)('dev', patterns, outcomes, profiles);
            const secondOrder = second.recommendations.map((r) => r.patternId);
            expect(firstOrder).not.toEqual(secondOrder);
            expect(second.recommendations.every((r) => r.patternId !== 'p_a')).toBe(true);
        });
    });
    describe('Test 3: Explanations exist', () => {
        test('every recommendation has at least 1 reason with description', () => {
            const categories = ['testing', 'data', 'devops'];
            const languages = ['typescript', 'python', 'go'];
            const allPatterns = [];
            const outcomesMap = new Map();
            for (const cat of categories) {
                for (const lang of languages) {
                    const pid = `${cat}_${lang}`;
                    allPatterns.push(makePattern(pid, cat, lang));
                    outcomesMap.set(pid, makeOutcome(pid, 0.85));
                }
            }
            const profile = (0, developer_profile_1.buildProfile)('dev-x', allPatterns, outcomesMap);
            const profiles = new Map();
            profiles.set('dev-x', profile);
            const result = (0, recommendation_engine_1.recommendPatterns)('dev-x', allPatterns, outcomesMap, profiles);
            for (const rec of result.recommendations) {
                expect(rec.reasons.length).toBeGreaterThanOrEqual(1);
                for (const reason of rec.reasons) {
                    expect(reason.description.length).toBeGreaterThan(0);
                    expect(typeof reason.value).toBe('number');
                }
            }
        });
    });
    describe('Test 4: Diversity', () => {
        test('recommendations span at least 3 categories', () => {
            const categories = ['testing', 'data', 'devops', 'security', 'performance'];
            const allPatterns = [];
            const outcomesMap = new Map();
            for (const cat of categories) {
                for (let i = 0; i < 5; i++) {
                    const pid = `${cat}_pat_${i}`;
                    allPatterns.push(makePattern(pid, cat, 'typescript'));
                    outcomesMap.set(pid, makeOutcome(pid, 0.85));
                }
            }
            const profile = (0, developer_profile_1.buildProfile)('dev-y', allPatterns, outcomesMap);
            const profiles = new Map();
            profiles.set('dev-y', profile);
            const result = (0, recommendation_engine_1.recommendPatterns)('dev-y', allPatterns, outcomesMap, profiles);
            const categoriesFound = new Set(result.recommendations.map((r) => r.category));
            expect(categoriesFound.size).toBeGreaterThanOrEqual(3);
            expect(result.recommendations.length).toBeLessThanOrEqual(20);
        });
    });
    describe('Test 5: Scale — 100 developers', () => {
        test('generates recommendations within 2000ms', () => {
            const patterns = [];
            const outcomesMap = new Map();
            for (let i = 0; i < 20; i++) {
                const pid = `scale_pat_${i}`;
                patterns.push(makePattern(pid, i % 2 === 0 ? 'testing' : 'data', i % 3 === 0 ? 'typescript' : 'python'));
                outcomesMap.set(pid, makeOutcome(pid, 0.8, 15));
            }
            const langs = ['typescript', 'python', 'go', 'rust', 'java'];
            const cats = ['testing', 'data', 'devops', 'security', 'performance'];
            const profiles = new Map();
            for (let i = 0; i < 100; i++) {
                const id = `dev_${i}`;
                profiles.set(id, makeProfile(id, { [langs[i % langs.length]]: 5 + (i % 10) }, [cats[i % cats.length]]));
            }
            const start = Date.now();
            const result = (0, recommendation_engine_1.recommendPatterns)('dev_0', patterns, outcomesMap, profiles);
            const elapsed = Date.now() - start;
            expect(elapsed).toBeLessThan(2000);
            expect(result.recommendations.length).toBeGreaterThan(0);
            expect(result.latencyMs).toBeLessThan(2000);
        });
    });
    describe('Test 6: Feedback loop', () => {
        test('records and stores feedback', () => {
            const tmp = tmpDir();
            try {
                const store = new recommendation_store_1.RecommendationStore(tmp);
                (0, recommendation_engine_1.recordFeedback)(store, 'dev-1', 'pat-1', 'shown');
                (0, recommendation_engine_1.recordFeedback)(store, 'dev-1', 'pat-2', 'accepted');
                (0, recommendation_engine_1.recordFeedback)(store, 'dev-2', 'pat-1', 'rejected');
                const allFeedback = store.getFeedback();
                expect(allFeedback.length).toBe(3);
                const dev1Feedback = store.getFeedbackForDeveloper('dev-1');
                expect(dev1Feedback.length).toBe(2);
                const pat1Feedback = store.getFeedbackForPattern('pat-1');
                expect(pat1Feedback.length).toBe(2);
                const shownItem = allFeedback.find((f) => f.action === 'shown');
                expect(shownItem).toBeDefined();
                expect(shownItem.developerId).toBe('dev-1');
                expect(shownItem.patternId).toBe('pat-1');
                expect(shownItem.id.length).toBeGreaterThan(0);
                const store2 = new recommendation_store_1.RecommendationStore(tmp);
                expect(store2.getFeedback().length).toBe(3);
            }
            finally {
                fs.rmSync(tmp, { recursive: true, force: true });
            }
        });
    });
    describe('Test 7: Failure avoidance', () => {
        test('pattern with high failure rate is excluded from recommendations', () => {
            const patterns = [
                makePattern('good_pat', 'testing', 'typescript', { pos: 18, neg: 2 }),
                makePattern('bad_pat', 'testing', 'typescript', { pos: 2, neg: 18 }),
            ];
            const outcomes = new Map();
            outcomes.set('good_pat', makeOutcome('good_pat', 0.9, 20));
            outcomes.set('bad_pat', makeOutcome('bad_pat', 0.1, 20));
            const profile = (0, developer_profile_1.buildProfile)('dev-z', patterns, outcomes);
            const profiles = new Map();
            profiles.set('dev-z', profile);
            const result = (0, recommendation_engine_1.recommendPatterns)('dev-z', patterns, outcomes, profiles);
            expect(result.recommendations.some((r) => r.patternId === 'good_pat')).toBe(true);
            expect(result.recommendations.some((r) => r.patternId === 'bad_pat')).toBe(false);
        });
    });
});
function countByLanguage(result) {
    const counts = {};
    for (const rec of result.recommendations) {
        const lang = rec.patternId.split('_')[0];
        counts[lang] = (counts[lang] || 0) + 1;
    }
    return counts;
}
//# sourceMappingURL=chapter4-verify.test.js.map