def makeSyntR(semR, checkSpecial=True): # checkSpecial is False when called from within specialConcept.checkSpecialConcept # to avoid infinite loop traceSyntR("makeSyntR", semR) concept = semR.concept if concept == None: # l'instance réfère à un autre AMR return instance2SyntR(semR) if checkSpecial: sConcept = specialConcept.checkSpecialConcept(semR) if sConcept != None: traceSyntR("special concept", concept) return sConcept # evaluate each role to build the environment dictInfo = getSyntR(concept) env = Env() opts = Options() roles = semR.get_roles() if isVerb(concept): #only for verbs # HACK for passive : seem to be too aggressive.... so we keep it only for top-level AMR # generate a passive sentence if concept has an :ARG0 and the actual roles does not have :ARG0 but has :ARG1 # %% do not passivate the special case of bear-02 because it is already passive if concept!="bear-02" and semR.parent==None and \ ":ARG0" in verbs[concept].args and ":ARG0" not in semR.roles and ":ARG1" in semR.roles: opts.add("typ", {"pas": True}) roleProcessing.processRoles(concept, roles, [], dictInfo, env, opts) ## patch the syntactic structures for frequent special cases if isVerb(concept): # HACK for changing nominative pronoun to accusative for :ARG1 when :ARG0 is also present if ":ARG1" in env and ":ARG0" in env and isinstance( env[":ARG1"], Pro) and env[":ARG1"].lemma == "I": env[":ARG1"].lemma = "me" elif isAdjective(concept): # adjective with :ARG0 and :ARG1 adj = dictInfo.lemma if ":ARG0" in env and ":ARG1" in env: if isinstance(env[":ARG0"], Pro) and env[":ARG0"].lemma == "me": env[":ARG0"].lemma = "I" if isinstance(env[":ARG1"], Pro) and env[":ARG1"].lemma == "I": env[":ARG0"].lemma = "me" dictInfo = LexSem( adj, "S", [":ARG0", ":ARG1"], lambda arg0, arg1: S( arg0, VP(V("be"), A(adj), pp("for", arg1)))) elif ":ARG1" in env and ":ARG2" in env: if isinstance(env[":ARG1"], Pro) and env[":ARG1"].lemma == "me": env[":ARG1"].lemma = "I" if isinstance(env[":ARG2"], Pro) and env[":ARG2"].lemma == "I": env[":ARG2"].lemma = "me" dictInfo = LexSem( adj, "S", [":ARG1", ":ARG2"], lambda arg1, arg2: S( arg1, VP(V("be"), A(adj), pp("for", arg2)))) elif ":ARG1" in env: if isinstance(env[":ARG1"], Pro) and env[":ARG1"].lemma == "me": env[":ARG1"].lemma = "I" dictInfo = LexSem(adj, "S", [":ARG1"], lambda arg1: S(arg1, VP(V("be"), A(adj)))) syntR = dictInfo.apply(env, opts) return syntR
def domainRole(semR, env, opts): traceSyntR("domainRole", semR) concept = semR.get_concept() if concept in determiners: env.put(":D", D(concept)) elif isPronoun(concept): env.unshift(SP(Pro(concept), V("be"))) else: env.push(VP(V("be"), makeSyntR(semR)))
def temp_trend(lang, trend, goalTemp, when): if lang == "en": return S( N("temperature"), VP(V(trend).t("pr"), PP(P("to"), jsrTemp(goalTemp, lang)), when)) else: return S( NP(N("température").n("p"), PP(P("à"), NP(D("le"), N(trend)))), PP(P("pour"), V("atteindre").t("b"), jsrTemp(goalTemp, lang), when))
def predicate(subject, attribute): if attribute == None: return S(subject, VP(V("be"))) if isinstance(subject, S): syntR = subject.add(attribute) else: syntR = S(subject, VP(V("be"), attribute)) if isinstance(attribute, Phrase) and "typ" in attribute.props: if "neg" in attribute.props[ "typ"] and attribute.props["typ"]["neg"] != False: if "typ" not in syntR.props: syntR.props["typ"] = {} syntR.props["typ"]["neg"] = attribute.props["typ"]["neg"] return syntR
def apply(self, env=None, opts=None): if env == None: env = Env() if opts == None: opts = Options() ## process args from dictInfo building the list of arguments or None argV = [env.get(arg) if arg in env else None for arg in self.args] syntR = self.lambda_(*argV) if all([arg == None for arg in argV]) and len(env) == 0: return opts.apply(syntR) if isinstance(syntR, Terminal): if isinstance(syntR, A): if ":ARG1" in env: syntR = S(env.get(":ARG1"), VP(V("be"), syntR)) else: syntR = AP(syntR) elif isinstance(syntR, (Pro, Q, NO)): syntR = SP(syntR) elif isinstance(syntR, Adv): syntR = AdvP(syntR) elif isinstance(syntR, P): syntR = PP(syntR) else: print("** apply: strange syntR:%s:%s" % (type(syntR), syntR)) syntR = SP(syntR) ## add unprocessed args if len(env) > 0: syntR.add(env.get(":start"), 0) syntR.add(env.get(".*")) return opts.apply(syntR)
def havePurpose91(concept,roles,env,opts): if ":ARG2" in roles: if roles[":ARG2"].get_concept()=="amr-unknown": env.put(":start",Q("For what ?")) return addRoles(concept, roles, [], LexSem("have-purpose","S",[":ARG1",":ARG2"], lambda arg1,arg2:S(arg1,VP(V("be"),PP(P("for"),arg2)))), env, opts) return None
def precipitation(wInfo,period,lang): jsrExprs=[] prob_terms=wInfo.get_precipitation_probabilities(period) type_terms=wInfo.get_precipitation_type(period) accum_terms=wInfo.get_precipitation_accumulation(period) for prob_term in prob_terms: prob_val=round(prob_term.infos[0]/10)*10 type_term=get_term_at(type_terms,prob_term.start) if type_term!=None and prob_val>=30: # interesting precipitation if prob_val <= 70 and prob_val!=50: # show probability if lang=="en": prob=NP(NO(prob_val),Q("percent"),N("chance").n("s"),P("of")) else: prob=NP(NO(prob_val),Q("pour cent"),P("de"),N("probabilité").n("s"),P("de")) timePeriod=None else: # probability >= 80% prob=None # indicate beginning or ending start=prob_term.start end=prob_term.end if wInfo.is_in_period(start,period): timePeriod=VP(V("begin" if lang=="en" else "débuter").t("pr"),jsrHour(start%24,lang)) elif wInfo.is_in_period(end,period): timePeriod=VP(V("end" if lang=="en" else "finir").t("pr"),jsrHour(end%24,lang)) else: timePeriod=None jsrExpr=NP(prob,precipitationTypes[type_term.infos[0]][lang],timePeriod) amount_term=get_term_at(accum_terms,prob_term.start) if amount_term!=None: # check for significant amount pcpnType=amount_term.infos[0] amount=amount_term.infos[1] jsrAmount=None if pcpnType=="rain" and amount>=25: jsrAmount=NP(NO(round(amount)),Q("mm")) elif pcpnType=="snow" and amount>=2: jsrAmount=NP(NO(round(amount)),Q("cm")) if jsrAmount!=None: if lang=="en": jsrAmount.add(N("amount"),0) else: jsrAmount.add(N("accumulation"),0).add(P("de"),1) jsrExpr=SP(jsrExpr.a(","),jsrAmount) jsrExprs.append(jsrExpr) return " ".join(realize(jsrExpr,lang) for jsrExpr in jsrExprs)
def haveRelRole91(concept,roles,env,opts): traceSyntR("haveRelRole91",concept) # syntR_A=makeSyntR(roles[":ARG0"]) if ":ARG0" in roles else None syntR_B=makeSyntR(roles[":ARG1"]) if ":ARG1" in roles else None if ":ARG2" in roles: relation=makeSyntR(roles[":ARG2"]) if isinstance(syntR_B,Pro) and isinstance(relation,NP): relation.elements[0]=makePoss(syntR_B.lemma) return addRoles(concept, roles, [":ARG1",":ARG2"], LexSem("relation","NP",[":ARG0"],lambda arg0:S(arg0,VP(V("be"),relation))), env, opts) if syntR_B!=None: dictInfo=LexSem("have-relation","S",[":ARG0",":ARG3",":ARG4"], lambda arg0,arg3,arg4:S(arg0,VP(V("be"),relation,syntR_B,arg3,arg4))) return addRoles(concept, roles, [":ARG1",":ARG2"], dictInfo, env, opts) else: return addRoles(concept, roles, [":ARG2"],LexSem("rel-role","NP",[],lambda:relation), env, opts) else: errorSyntR("haveRelRole91 with no :ARG2:\n%s"%concept) return Q("*rel*")
def paperExample(): from jsRealBclass import jsRealB, N,A,Adv,V,D,P,C,DT,NO,Q, NP,AP,AdvP,VP,S,SP,PP,CP ## example function used in the paper def pcpn(type,action,tense,moment,quantity=None,unit=None): return S(type, VP(V(action).t(tense), CP(PP(P("in"),NP(D("the"),N(moment))), None if quantity==None else NP(N("amount"),NP(NO(quantity),unit))))) print(realize(pcpn(N("flurry").n("p"),"begin","p","morning",2,N("foot")),"en")) print(realize(pcpn(N("rain"),"begin","p","evening",1,N("inch")),"en")) print(realize(pcpn(N("snow"),"stop","pr","evening"),"en")) print(realize(pcpn(NP(V("freeze").t("pr"),N("drizzle")),"start","f","morning"),"en"))
def instance2SyntR(semR): traceSyntR("instance2dsr", semR) instance = semR.instance if isinstance(instance, SemanticRep): myRole = semR.get_my_role() amrRef = instance refRole = amrRef.get_my_role() refConcept = amrRef.get_concept() # print("myRole:%s refRole:%s refConcept:%s"%(myRole,refRole,refConcept)) if isNoun(refConcept) or specialConcept.isSpecialConcept(refConcept): if myRole == ":ARG0" and refRole != None: pronoun = Pro("I") elif myRole == ":ARG1": # is object the same as the subject? parent = semR.get_parent() parentRoles = parent.get_roles() if ":ARG0" in parentRoles and ( parentRoles[":ARG0"] == amrRef or instance == parentRoles[":ARG0"].instance): pronoun = Pro("myself") else: pronoun = Pro("I") else: pronoun = Pro("me") return pronoun.pe(3).g(gender[refConcept] if refConcept in gender else "n") elif isPronoun(refConcept): pronoun = Pro("I") return addOptions(pronoun, pronounOptions[refConcept] ) if refConcept in pronounOptions else pronoun elif isVerb(refConcept): return VP(V(re.sub(r"-\d+$", "", refConcept)).t("b")) else: # clean the referenced concept and quote it return Q(generateConceptWord(refConcept)) elif is_number(instance): return NO(unquote(instance)) elif instance[0] == '"': return Q(unquote(instance)) elif instance in ['-', '+']: return instance else: errorSyntR(instance + " :undefined instance") return Q(instance)
def title_block(wInfo, lang): issueDate = wInfo.get_issue_date() noSecond = {"second": False} if lang == "en": s1 = S( NP(N("forecast").n("p")), VP( V("issue").t("pp"), PP( P("by"), Q("jsRealB"), DT(issueDate).dOpt(noSecond), P("for"), CP( C("and"), N("today"), DT(issueDate + datetime.timedelta(days=1)).dOpt( {"rtime": issueDate}))))) s2 = S( NP(D("the"), D("next"), V("schedule").t("pp"), N("forecast").n("p")), VP( V("be").t("f"), V("issue").t("pp"), DT(wInfo.get_next_issue_date()).dOpt(noSecond))) else: s1 = S( NP(N("prévision").n("p")), VP( V("émettre").t("pp"), PP( P("par"), Q("jsRealB"), DT(issueDate).dOpt(noSecond), P("pour"), CP( C("et"), Adv("aujourd'hui"), DT(issueDate + datetime.timedelta(days=1)).dOpt( {"rtime": issueDate}))))) s2 = S( NP(D("le"), A("prochain").pos("pre"), N("prévision").n("p")), VP( V("être").t("f"), V("émettre").t("pp"), DT(wInfo.get_next_issue_date()).dOpt(noSecond))) return "\n".join([realize(s1, lang, False), realize(s2, lang, False)])
def simpleModNoun(concept, neg): ## check for "simple" cases if concept in [ "all", "many", "both", "no", "too", "any", "other", "some", "one", "kind-yy", "then", "such" ]: env.put(":D", Q("kind of" if concept == "kind-yy" else concept)) if neg: env.unshift(Q("not")) if concept in ["all", "many", "both"]: opts.add("n", "p") elif concept in determiners: env.put(":D", D(concept)) elif is_number(concept): env.push(NO(concept)) elif isNoun(concept): ## prefix the :mod for nouns newNoun = N(nouns[concept].lemma) # keep only noun from NP if ":A" in env: env.insertAfter(":A", ":A", newNoun) else: env.put(":A", newNoun) if neg: env.insertBefore(":A", ":A", Q("non")) elif isAdjective(concept): ## postfix the :mod for adjectives newAdj = A(adjectives[concept].lemma) if ":A" in env: env.insertAfter(":A", ":A", newAdj) else: env.put(":A", newAdj) if neg: env.insertBefore(":A", ":A", Q("non")) elif isAdverb(concept): env.put(":D", Adv(concept)) elif isVerb(concept): env.push(V(re.sub(r"-\d+$", '', concept)).t("pr")) elif semR.roles.areEmpty(): ## equivalent to processSimpleModOther env.push(Q(generateConceptWord(concept))) else: return False return True
def haveQuant91(concept,roles,env,opts): traceSyntR("haveQuant91",concept) if ":ARG1" in roles: quant=makeSyntR(roles[":ARG1"]) else: errorSyntR("haveQuant91 without entity:%s"%concept) quant=SP(Q("*entity*")) if isinstance(quant,Terminal): quant=SP(quant) if ":ARG2" in roles: quant.add(makeSyntR(roles[":ARG2"]),0) else: quant.add(NP(D("the"),N("number"),P("of")),0) if ":ARG3" in roles: quant.add(VP(V("be"),makeSyntR(roles[":ARG3"]))) if ":ARG4" in roles: quant.add(PP(P("than"), makeSyntR(roles[":ARG4"]))) if ":ARG5" in roles: quant.add(PP(P("of"),makeSyntR(roles[":ARG5"]))) if ":ARG6" in roles: quant.add(PP(P("for"),makeSyntR(roles[":ARG6"]))) return addRoles(concept, roles, [":ARG1",":ARG2",":ARG3",":ARG4",":ARG5",":ARG6"], LexSem("qty","NP",[],lambda:quant), env, opts)
delCat(prepositions,"away") ### Verbs delCat(verbs,"contrast-01") # keep it as noun delCat(verbs,"hyperlink-91") # keep it as noun delCat(verbs,"infer-01") verbs["exemplify-01"]=LexSem("exemplify","PP",[":ARG0"],lambda _:PP(P("for"),N("example"))) def isVerb(lemma):return lemma in verbs def makeV(lemma):return verbs[lemma] # ARG0:giver / ARG1:thing given / ARG2:entity given to [give.xml] verbs['give-01']=LexSem("give","V",[":ARG0",":ARG1",":ARG2"], lambda arg0,arg1,arg2:S(arg0,VP(V("give"),arg1,pp("to",arg2)))) verbs['know-01']=LexSem("know","V",[":ARG0",":ARG1",":ARG2"], # strangely the lemma is "idk" in PropBank lambda arg0,arg1,arg2:S(arg0,VP(V("know"),arg1,pp("to",arg2)))) ## in PropBank : bear-02 :ARG0 should be the "bearer" (e.g. the mother) and :ARG1 the "bearee" ## but in AMR, :ARG1 is used as the person who is born... without :ARG0 verbs['bear-02']=LexSem("born","V",[":ARG0",":ARG1"], lambda arg0,arg1:S(arg1,VP(V("be"),V("born").t("pp"),pp("to",arg0)))) ## in PropBank: hunger-01 :ARG0 corresponds to the person who is hungry (hunger is archaic in this acception) ## so we translate with a more colloquial "be hungry [for...]" verbs['hunger-01']=LexSem("hunger","V",[":ARG0",":ARG1"], lambda arg0,arg1:S(arg0,VP(V("be"),A("hungry"),pp("for",arg1)))) ## correction of prepositions...
def wind(wInfo, period, lang): wind_terms = wInfo.get_wind(period) if wind_terms == None: return None lastSpeed = None lastDir = None jsrExprs = [] for wind_term in wind_terms: wSpeed = wind_term.infos[2] wDir = wind_term.infos[0] jsrExpr = S() # current expression if wSpeed >= 15 and wDir in jsrWindDirection: if lastSpeed != None and abs( wSpeed - lastSpeed) >= 20: # significant speed change lastSpeed = wSpeed if lang == "en": jsrExpr.add( VP(V("increase").t("pr"), PP(P("to"), NO(wSpeed)))) else: jsrExpr.add( VP(V("augmenter").t("pr"), PP(P("à"), NO(wSpeed)))) elif lastDir != None and dir_diff( wDir, lastDir): # significant direction change if lang == "en": jsrExpr.add( VP(V("become").t("pr"), jsrWindDirection[wDir][lang])) else: jsrExpr.add( VP( V("devenir").t("pr"), PP(P("de"), jsrWindDirection[wDir][lang]))) lastDir = wDir else: # realize wind and direction lastSpeed = wSpeed lastDir = wDir if lang == "en": jsrExpr.add( NP(N("wind"), jsrWindDirection[wDir][lang], NO(wSpeed), Q("km/h"))) else: jsrExpr.add( NP( N("vent").n("p"), PP(P("de"), jsrWindDirection[wDir][lang]), PP(P("de"), NO(wSpeed), Q("km/h")))) if len(wind_term.infos) > 3: # add gusting information gust = wind_term.infos[3] if gust.infos[0] == 'gust': if lang == "en": jsrExpr.add( VP( V("gust").t("pr"), PP(P("to"), NO(gust.infos[1])))) else: jsrExpr.add( PP( P("avec"), NP( N("rafale").n("p"), P("à"), NO(gust.infos[1])))) else: # add time information jsrExpr.add(jsrHour(wind_term.start, lang)) jsrExprs.append(jsrExpr) # add current expression to the list return " ".join(realize(jsrExpr, lang, False) for jsrExpr in jsrExprs)
## process args from dictInfo building the list of arguments or None argV = [env.get(arg) if arg in env else None for arg in self.args] syntR = self.lambda_(*argV) if len(env) > 0: ## add unprocessed args syntR.add(env.get(":start"), 0) syntR.add(env.get(".*")) return opts.apply(syntR) pp = lambda prep, arg: PP(P(prep), arg) if arg != None else None optD = lambda det: det if det != None else D("the") verbs['give-01'] = LexSem( "V", "give", [":ARG0", ":ARG1", ":ARG2"], lambda arg0, arg1, arg2: S(arg0, VP(V("give"), arg1, pp("to", arg2)))) nouns['envelope'] = LexSem("envelope", "N", [":D", ":A"], lambda d, a: NP(optD(d), a, N("envelope"))) nouns['boy'] = LexSem("boy", "N", [":D", ":A"], lambda d, a: NP(optD(d), a, N("boy"))) nouns['girl'] = LexSem("girl", "N", [":D", ":A"], lambda d, a: NP(optD(d), a, N("girl"))) adjectives["little"] = LexSem("little", "A", [], lambda: A("little")) adjectives["nice"] = LexSem("nice", "A", [], lambda: A("nice")) nounInfo = lambda lemma: LexSem(lemma, "N", [":D", ":A"], lambda d, a: NP( optD(d), a, N(lemma))) adjInfo = lambda lemma: LexSem(lemma, "A", [], lambda: A(lemma))
def supersetRole(semR, env, opts): traceSyntR("supersetRole", semR) env.push(VP(V("contain").t("pr"), makeSyntR(semR)))
def subsetRole(semR, env, opts): traceSyntR("subsetRole", semR) env.push(VP(V("include").t("pr"), makeSyntR(semR)))
def meaningRole(semR, env, opts): traceSyntR("meaningRole", semR) env.push(VP(V("mean").t("pr"), makeSyntR(semR)))
def employedByRole(semR, env, opts): traceSyntR("employedByRole", semR) env.push(VP(V("work").t("pr"), PP(P("for"), makeSyntR(semR))))
def pcpn(type,action,tense,moment,quantity=None,unit=None): return S(type, VP(V(action).t(tense), CP(PP(P("in"),NP(D("the"),N(moment))), None if quantity==None else NP(N("amount"),NP(NO(quantity),unit)))))
lambda t, u: S( Adv("low"), u.a(","), P("with"), temp_trend("en", "rise", t, PP(P("by"), N("morning")))), "fr": lambda t, u: S( N("minimum"), u.a(","), temp_trend("fr", "hausse", t, PP(P("en"), N("matinée")))) }, "c": { "en": lambda t, p: temp_trend("en", "rise", t, p).add( AdvP(Adv("then"), A("steady"))), "fr": lambda t, p: temp_trend("fr", "hausse", t, p).add( PP(P("pour"), Adv("ensuite"), V("demeurer").t("b"), A("stable"))) }, "d": { "en": lambda t, p: temp_trend("en", "rise", t, p).add( AdvP(Adv("then"), V("rise").t("pr"), Adv("slowly"))), "fr": lambda t, p: temp_trend("fr", "hausse", t, p).add( AdvP(Adv("puis"), NP(N("hausse"), A("graduel")))) }, "e": { "en": lambda t, p: temp_trend("en", "rise", t, p).add( AdvP(Adv("then"), V("fall").t("pr"))),
from Realization.common import realize, jsrDayPeriod, jsrHour, get_max_term, get_min_term, get_term_at precipitationTypes = { "showers":{"en":N("shower").n("p"), "fr":N("averse").n("p")}, "flurries":{"en":N("flurry").n("p"), "fr":NP(N("averse").n("p"),PP(P("de"),N("neige")))}, "wet-flurries":{"en":NP(A("wet"),N("flurry").n("p")), "fr":NP(N("averse").n("p"),PP(P("de"),N("neige"),A("fondant")))}, "blizzard":{"en":N("blizzard"), "fr":N("blizzard")}, "snow-squalls":{"en":NP(N("snow"),N("squall").n("p")), "fr":NP(N("bourrasque").n("p"),PP(P("de"),N("neige")))}, "drizzle":{"en":N("drizzle"), "fr":N("bruine")}, "freezing-drizzle" :{"en":NP(V("freeze").t("pr"),N("drizzle")), "fr":NP(N("bruine"),A("verglaçant"))}, "ice-crystals" :{"en":NP(N("ice"),N("crystal").n("p")), "fr":NP(N("cristal").n("p"),PP(P("de"),N("glace")))}, "hail":{"en":N("hail"), "fr":N("grêle")}, "ice-pellets":{"en":NP(N("ice"),N("pellet").n("p")), "fr":N("grésil")}, "snow":{"en":N("snow"), "fr":N("neige")}, "wet-snow" :{"en":NP(A("wet"),N("snow")), "fr":NP(N("neige"),N("fondant"))}, "thunderstorm":{"en":N("thunderstorm"), "fr":N("orage").n("p")}, "rain":{"en":N("rain"), "fr":N("pluie")},
def costRole(semR, env, opts): traceSyntR("costRole", semR) env.push(VP(V("cost").t("pr"), makeSyntR(semR)))
sky_condition_terminology = { ## types of sky conditions "c1":{"en":(AP(A("sunny")),AP(A("clear"))), "fr":(AP(A("ensoleillé")),AP(A("dégagé")))}, "c2":{"en":(AP(Adv("mainly"),A("sunny")),NP(Q("a"),D("few"),N("cloud").n("p"))), "fr":(AP(Adv("généralement"),A("ensoleillé")),NP(D("quelque"),N("nuage").n("p")))}, "c3":{"en":(NP(D("a"),N("mix"),PP(P("of"),CP(C("and"),N("sun"),N("cloud").n("p")))), AP(Adv("partly"),A("cloudy"))), "fr":(NP(N("alternance"),CP(C("et"),PP(P("de"),N("soleil")),PP(P("de"),N("nuage").n("p")))), AP(Adv("partiellement"),A("couvert")))}, "c4":{"en":(AP(Adv("mainly"),A("cloudy")),), "fr":(AP(Adv("généralement"),A("nuageux")),)}, "c5":{"en":(AP(A("cloudy")),), "fr":(AP(A("nuageux")),)}, "c6":{"en":(AP(A("overcast")),), "fr":(AP(A("couvert")),)}, "c7":{"en":(NP(V("increase").t("pr"),N("cloudiness")),), "fr":(NP(N("ennuagement")),)}, "c8":{"en":(NP(N("clearing")),), "fr":(NP(N("dégagement")),)}, } def sky_condition(mc,period,lang): previous_conditions=[] jsrExprs=[] def addNoRepeat(c,dn,period=None): # avoid generating same sentence twice if c not in previous_conditions: if len(sky_condition_terminology[c][lang])==1:dn=0 jsrExpr=sky_condition_terminology[c][lang][dn] if period!=None:jsrExpr.add(period) jsrExprs.append(jsrExpr)