def compute_actions_for_project( scoring_project: scoring.ScoringProject, ) -> Iterable[action_pb2.Action]: """Compute all actions possible for a project.""" action_templates = { action.action_template_id: action for action in _ACTION_TEMPLATES.get_collection( scoring_project.database) } if scoring_project.user.features_enabled.all_modules: scores: Mapping[str, float] = {key: 3 for key in action_templates} else: scores = scoring_project.score_and_explain_all( (key, action_template.trigger_scoring_model) for key, action_template in action_templates.items()).scores sorted_action_templates = sorted( action_templates.values(), key=lambda m: (scores.get(m.action_template_id, 0), m.action_template_id), reverse=True) deployment = os.getenv('BOB_DEPLOYMENT', 'fr') for action_template in sorted_action_templates: action_id = action_template.action_template_id if not (score := scores.get(action_id)) or score <= 0: break scoring_project.details.actions.add( action_id=action_id, title=scoring_project.translate_airtable_string( 'actionTemplates', action_id, 'title', hint=action_template.title, is_genderized=True), short_description=scoring_project.translate_airtable_string( 'actionTemplates', action_id, 'short_description', hint=action_template.short_description, is_genderized=True), tags=[ scoring_project.translate_airtable_string( 'actionTemplates', 'tags', tag) for tag in action_template.tags ], duration=action_template.duration, status=action_pb2.ACTION_UNREAD, advice_id=action_template.advice_id, resource_url=scoring_project.translate_airtable_string( 'actionTemplates', action_id, 'resource_url', hint=action_template.resource_url, context=deployment), )
def compute_available_methods( scoring_project: scoring.ScoringProject, method_modules: Iterable[advisor_pb2.AdviceModule], scoring_timeout_seconds: float = 3) \ -> Iterator[Tuple[project_pb2.Advice, frozenset[str]]]: """Advise on a user project. Args: scoring_project: the user's data. advice_modules: a list of modules, from which we want to derive the advices. scoring_timeout_seconds: how long we wait to compute each advice scoring model. Returns: an Iterator of recommendations, each with a list of fields that would help improve the process. """ ready_modules = { module.advice_id: module.trigger_scoring_model for module in method_modules if module.is_ready_for_prod or scoring_project.features_enabled.alpha } scores: Mapping[str, float] = {} reasons: Mapping[str, tuple[str, ...]] = {} missing_fields: Mapping[str, frozenset[str]] = {} if scoring_project.user.features_enabled.all_modules: scores = {key: 3 for key in ready_modules} else: scores, reasons, missing_fields = scoring_project.score_and_explain_all( ready_modules.items(), scoring_timeout_seconds=scoring_timeout_seconds) modules = sorted(method_modules, key=lambda m: (scores.get(m.advice_id, 0), m.advice_id), reverse=True) incompatible_modules: Set[str] = set() has_module = False for module in modules: score = scores.get(module.advice_id) if not score: # We can break as others will have 0 score as well. break if module.airtable_id in incompatible_modules and \ not scoring_project.user.features_enabled.all_modules: continue piece_of_advice = project_pb2.Advice( advice_id=module.advice_id, num_stars=score, is_for_alpha_only=not module.is_ready_for_prod) piece_of_advice.explanations.extend( scoring_project.populate_template(reason) for reason in reasons.get(module.advice_id, [])) incompatible_modules.update(module.incompatible_advice_ids) _maybe_override_advice_data(piece_of_advice, module, scoring_project) has_module = True yield piece_of_advice, missing_fields.get(module.advice_id, frozenset()) if not has_module and method_modules: logging.warning( 'We could not find *any* advice for a project:\nModules tried:\n"%s"\nProject:\n%s', '", "'.join(m.advice_id for m in method_modules), scoring_project)