Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: update vaa-shared and vaa-matching #602

Merged
merged 4 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions frontend/src/lib/utils/matching/LikertQuestion.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
import {MultipleChoiceQuestion} from '$voter/vaa-matching';
import type {CoordinateOrMissing, Id} from 'vaa-shared';
import {OrdinalQuestion} from '$voter/vaa-matching';

/**
* A dummy question object for matching.
*/
export class LikertQuestion extends MultipleChoiceQuestion {
export class LikertQuestion extends OrdinalQuestion {
public readonly category: QuestionCategoryProps;
constructor({id, values, category}: LikertQuestionOptions) {
super(id, values);
super({id, values});
this.category = category;
}

normalizeValue(value: number): CoordinateOrMissing {
// The current frontend implemenation of questions uses numbers for choice keys
return super.normalizeValue(`${value}`);
}
}
/**
* Options for a dummy question object for matching.
*/
interface LikertQuestionOptions {
id: ConstructorParameters<typeof MultipleChoiceQuestion>[0];
values: ConstructorParameters<typeof MultipleChoiceQuestion>[1];
id: Id;
values: ConstructorParameters<typeof OrdinalQuestion>[0]['values'];
category: QuestionCategoryProps;
}
2 changes: 1 addition & 1 deletion frontend/src/lib/utils/matching/imputePartyAnswers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {error} from '@sveltejs/kit';
import {MISSING_VALUE} from '$voter/vaa-matching';
import {MISSING_VALUE} from 'vaa-shared';
import {logDebugError} from '$lib/utils/logger';
import {mean} from './mean';
import {median} from './median';
Expand Down
17 changes: 11 additions & 6 deletions frontend/src/lib/utils/matching/match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import {error} from '@sveltejs/kit';
import {
type MatchingOptions,
MatchingAlgorithm,
DistanceMetric,
MissingValueDistanceMethod,
DISTANCE_METRIC,
MISSING_VALUE_METHOD,
type MatchableQuestionGroup
} from '$voter/vaa-matching';
import {LikertQuestion} from './LikertQuestion';
Expand All @@ -33,9 +33,9 @@ export async function match<E extends EntityProps>(
): Promise<RankingProps<E>[]> {
// Create the algorithm instance
const algorithm = new MatchingAlgorithm({
distanceMetric: DistanceMetric.Manhattan,
distanceMetric: DISTANCE_METRIC.Manhattan,
missingValueOptions: {
missingValueMethod: MissingValueDistanceMethod.AbsoluteMaximum
method: MISSING_VALUE_METHOD.RelativeMaximum
}
});

Expand All @@ -51,7 +51,7 @@ export async function match<E extends EntityProps>(
questions.push(
new LikertQuestion({
id: q.id,
values: q.values.map((o) => ({value: o.key})),
values: q.values.map((o) => ({id: `${o.key}`, value: o.key})),
category: q.category
})
);
Expand Down Expand Up @@ -85,7 +85,12 @@ export async function match<E extends EntityProps>(
}

// Perform the matching
return algorithm.match(questions, voter, entities, matchingOptions);
return algorithm.match({
questions,
reference: voter,
targets: entities,
options: matchingOptions
});
}

/**
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/lib/utils/tests/matching.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {expect, test} from 'vitest';
import {imputePartyAnswers, mean, median} from '../matching';
import {MockCandidate, MockParty} from './mock-objects';
import {MISSING_VALUE} from '$voter/vaa-matching';
import {MISSING_VALUE} from 'vaa-shared';

test('Mean and median', () => {
expect(mean([1, 2, 2, 2, 10]), 'Mean').toEqual((1 + 2 + 2 + 2 + 10) / 5);
Expand Down
17 changes: 17 additions & 0 deletions frontend/src/lib/voter/vaa-filters/src/entity/entityWithAnswers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type {HasAnswers} from 'vaa-shared';
import type {FilterableEntity} from './filterableEntity';

/**
* An entity that has answers and ca be filred with the question filters, e.g. a candidate.
* NB. This interface should align with the `HasAnswers` interface of the `vaa-matching` module.
*/
export type EntityWithAnswers = FilterableEntity & HasAnswers;

/**
* Check whether entity implements the `HasAnswers` interface.
* @param entity An entity.
* @returns true if entity implements `HasAnswers`
*/
export function isEntityWithAnswers(entity: object): entity is EntityWithAnswers {
return 'answers' in entity && typeof entity.answers === 'object';
}
38 changes: 0 additions & 38 deletions frontend/src/lib/voter/vaa-filters/src/entity/hasAnswers.ts

This file was deleted.

2 changes: 1 addition & 1 deletion frontend/src/lib/voter/vaa-filters/src/entity/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './entityWithAnswers';
export * from './filterableEntity';
export * from './hasAnswers';
4 changes: 2 additions & 2 deletions frontend/src/lib/voter/vaa-filters/src/filter/base/filter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {getEntity, type MaybeWrapped, hasAnswers, type ExtractEntity} from '../../entity';
import {getEntity, type MaybeWrapped, isEntityWithAnswers, type ExtractEntity} from '../../entity';
import {MISSING_VALUE, type MaybeMissing} from '../../missingValue';
import {ruleIsActive, matchRules, copyRules, type Rules, type Rule} from '../rules';
import {castValue} from './castValue';
Expand Down Expand Up @@ -53,7 +53,7 @@ export abstract class Filter<T extends MaybeWrapped, V> {
getValue(entity: ExtractEntity<T>): MaybeMissing<V> | MaybeMissing<V>[] {
let value: unknown;
if (this.options.question) {
if (!hasAnswers(entity)) throw new Error('Entity does not have answers.');
if (!isEntityWithAnswers(entity)) throw new Error('Entity does not have answers.');
value = entity.answers[this.options.question.id]?.value;
} else if (this.options.property != null) {
value = entity[this.options.property as keyof typeof entity];
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lib/voter/vaa-filters/src/question/choice.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type {FilterOptions} from '../filter';

// TODO: Update these when vaa-data module questions are adopted

/**
* The property name of a multiple choice key.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type {Id} from 'vaa-shared';
import type {Choice} from './choice';

/**
Expand Down Expand Up @@ -34,5 +35,5 @@ interface QuestionBase {
/**
* The entities' answers to questions are matched using the question id
*/
id: string;
id: Id;
}
3 changes: 1 addition & 2 deletions frontend/src/lib/voter/vaa-filters/tests/filter.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type {AnswerDict, AnswerValue} from 'vaa-shared';
import {
castValue,
FilterGroup,
Expand All @@ -7,8 +8,6 @@ import {
ObjectFilter,
TextPropertyFilter,
TextQuestionFilter,
type AnswerDict,
type AnswerValue,
type Choice,
type EntityWithAnswers,
type FilterableEntity,
Expand Down
Loading
Loading