示例#1
0
# Однако есть много городов, которых нет в списке, их нужно попытаться обработать
# Названиями могут быть существительные и прилагательные, регистр мне не будет важен
# Пример: Инженерный, Сургут (которого реально нет в списках сверху)
CITY_PROBABLY = or_(
    NOUN,
    ADJF
).interpretation(
    City.name
)

# Готовим варианты ключего слова
CITY_KEYWORD = or_(
    rule(normalized('город')),
    rule(
        caseless('г'),
        DOT.optional()
    )
).interpretation(
    City.city_type.const('город')
)

# Два основных условия: либо слово из списков назаний, тогда "ключ" не важен
# Или мы считаем названием города сущ. или прил. с "ключом"
# Поскольку "ключ" во втором случае не опционале, то регистр не так важен
CITY = or_(
    rule(CITY_KEYWORD, CITY_PROBABLY),
    rule(CITY_KEYWORD.optional(), CITY_NAME)
).interpretation(
    City
)
示例#2
0
############


FED_OKRUG_NAME = or_(
    rule(
        dictionary({
            'дальневосточный',
            'приволжский',
            'сибирский',
            'уральский',
            'центральный',
            'южный',
        })
    ),
    rule(
        caseless('северо'),
        DASH.optional(),
        dictionary({
            'западный',
            'кавказский'
        })
    )
).interpretation(
    Region.name
)

FED_OKRUG_WORDS = or_(
    rule(
        normalized('федеральный'),
        normalized('округ')
    ),
示例#3
0
# coding: utf-8
from __future__ import unicode_literals

from yargy import (rule, and_, or_, fact)
from yargy.predicates import (eq, in_, gram, normalized, caseless)

Money = fact('Money', ['amount', 'currency'])

EURO = normalized('евро')

DOLLARS = or_(normalized('доллар'), eq('$'))

RUBLES = or_(rule(normalized('рубль')),
             rule(or_(caseless('руб'), caseless('р')),
                  eq('.').optional()))

CURRENCY = or_(rule(EURO), rule(DOLLARS),
               RUBLES).interpretation(Money.currency)

INT = gram('INT')

AMOUNT_ = or_(
    rule(INT),
    rule(INT, INT),
    rule(INT, INT, INT),
    rule(INT, '.', INT),
    rule(INT, '.', INT, '.', INT),
)

FRACTION_AMOUN = rule(AMOUNT_, in_({',', '.'}), INT)
示例#4
0
            'data scientist', 'data engineer', 'engineer', 'analyst',
            'data analyst', 'data manager', 'scientist', 'researcher',
            "developer", "intern"
        ]), rule(dictionary(['DS', 'DE']), is_capitalized()),
        morph_pipeline(["аналитик", "разработчик",
                        "стажер"])).interpretation(Position.name.inflected()))

FIELD = rule(
    caseless_pipeline([
        'ML', 'DL', 'CV', 'computer vision', 'NLP', 'bi', 'machine learning',
        'deep learning', 'software', 'research', 'big data', 'python', 'c++',
        "scala", "java", 'ios', "android", 'devops', "backend", 'frontend'
    ]).interpretation(Position.field))

HEAD = rule(
    caseless('head').interpretation(Position.level), eq('of'),
    caseless_pipeline(['analytics', 'predictive analytics',
                       'data science']).interpretation(Position.field))

POSITION = or_(
    rule(LEVEL.optional(), FIELD.optional(),
         eq('-').optional(), NAME), HEAD).interpretation(Position)


# TODO: нужен метод extract с фильтрацией. Например, разбирать возможные ложные срабатывания ("аналитика"),
#  фильтровать по длине (чем больше полей заполнено, тем предпочтительнее)
#  можно выдавать "аналитик" только тогда, когда ничего более конкретного не нашлось
class PositionExtractor(Extractor):
    def __init__(self):
        super(PositionExtractor, self).__init__(POSITION)
示例#5
0
    'шестьсот': 600,
    'семьсот': 700,
    'восемьсот': 800,
    'девятьсот': 900,
    'тысяча': 10**3,
    'миллион': 10**6,
    'миллиард': 10**9,
    'триллион': 10**12,
}
DOT = eq('.')
INT = type('INT')
THOUSANDTH = rule(caseless_pipeline(['тысячных', 'тысячная'])).interpretation(const(10**-3))
HUNDREDTH = rule(caseless_pipeline(['сотых', 'сотая'])).interpretation(const(10**-2))
TENTH = rule(caseless_pipeline(['десятых', 'десятая'])).interpretation(const(10**-1))
THOUSAND = or_(
    rule(caseless('т'), DOT),
    rule(caseless('тыс'), DOT.optional()),
    rule(normalized('тысяча')),
    rule(normalized('тыща'))
).interpretation(const(10**3))
MILLION = or_(
    rule(caseless('млн'), DOT.optional()),
    rule(normalized('миллион'))
).interpretation(const(10**6))
MILLIARD = or_(
    rule(caseless('млрд'), DOT.optional()),
    rule(normalized('миллиард'))
).interpretation(const(10**9))
TRILLION = or_(
    rule(caseless('трлн'), DOT.optional()),
    rule(normalized('триллион'))
#     #in_(['€', 'EUR'])
#     eq('€'),
#     #eq('EUR')
# ).interpretation(
#     const(dsl.EURO)
# )
# EURO = caseless_pipeline(['евро', '€', 'eur'])#.interpretation(const(dsl.EURO))
EURO = or_(normalized('евро'), eq('€'),
           eq('EUR')).interpretation(const(dsl.EURO))

DOLLARS = or_(normalized('доллар'), eq('$'),
              eq('USD')).interpretation(const(dsl.DOLLARS))

RUBLES = or_(
    rule(normalized('рубль')),
    rule(or_(caseless('руб'), caseless('р'), eq('₽')),
         DOT.optional())).interpretation(const(dsl.RUBLES))

CURRENCY = or_(EURO, DOLLARS, RUBLES).interpretation(Money.currency)
# TODO: копейки и центы тоже можно выпилить для ускорения
KOPEIKA = or_(rule(normalized('копейка')),
              rule(or_(caseless('коп'), caseless('к')), DOT.optional()))

CENT = or_(normalized('цент'), eq('¢'))

EUROCENT = normalized('евроцент')

COINS_CURRENCY = or_(KOPEIKA, rule(CENT), rule(EUROCENT))

############
#
示例#7
0
from yargy import or_, rule
from yargy.interpretation import attribute, fact
import yargy.interpretation as meaning
from yargy.pipelines import morph_pipeline
from yargy.predicates import caseless, gram, normalized

from .common import Array

# Working = fact('Working', [attribute('to', default=[])])

Type = fact('Type', [attribute('value', default=[]), 'status', attribute('inversed', default=False)])

ENTER_EXIT_WORD = rule(
    or_(caseless('на'), caseless('для')).optional(),
    morph_pipeline(['вход', 'выход']).interpretation(Type.value),
).interpretation(Type)

# и на вход и на выход
ON_ENTER_AND_EXIT = rule(
    caseless('и').optional(),
    ENTER_EXIT_WORD
        .interpretation(meaning.custom(lambda p: p.value))
        .interpretation(Array.element),
    # caution: misses comma, I don't know why
    rule(
        caseless('и').optional(),
        ENTER_EXIT_WORD
            .interpretation(meaning.custom(lambda p: p.value))
            .interpretation(Array.element),
    ).optional(),
).interpretation(Array)
示例#8
0
    'восемьсот': 800,
    'девятьсот': 900,
    'тысяча': 10**3,
    'миллион': 10**6,
    'миллиард': 10**9,
    'триллион': 10**12,
}
DOT = eq('.')
INT = type('INT')
THOUSANDTH = rule(caseless_pipeline(['тысячных', 'тысячная'
                                     ])).interpretation(const(10**-3))
HUNDREDTH = rule(caseless_pipeline(['сотых',
                                    'сотая'])).interpretation(const(10**-2))
TENTH = rule(caseless_pipeline(['десятых',
                                'десятая'])).interpretation(const(10**-1))
THOUSAND = or_(rule(caseless('т'), DOT), rule(caseless('тыс'), DOT.optional()),
               rule(normalized('тысяча')),
               rule(normalized('тыща'))).interpretation(const(10**3))
MILLION = or_(rule(caseless('млн'), DOT.optional()),
              rule(normalized('миллион'))).interpretation(const(10**6))
MILLIARD = or_(rule(caseless('млрд'), DOT.optional()),
               rule(normalized('миллиард'))).interpretation(const(10**9))
TRILLION = or_(rule(caseless('трлн'), DOT.optional()),
               rule(normalized('триллион'))).interpretation(const(10**12))
MULTIPLIER = or_(THOUSANDTH, HUNDREDTH, TENTH, THOUSAND, MILLION, MILLIARD,
                 TRILLION).interpretation(Number.multiplier)
NUM_RAW = rule(
    morph_pipeline(NUMS_RAW).interpretation(Number.int.normalized().custom(
        NUMS_RAW.get)))
NUM_INT = rule(INT).interpretation(Number.int.custom(int))
NUM = or_(NUM_RAW, NUM_INT).interpretation(Number.int)
示例#9
0
from yargy import or_, rule
from yargy.interpretation import fact
from yargy.predicates import caseless, gram

from notebook.common import gnc
from notebook.time import TIME

Duration = fact('Duration', ['since', 'to'])

FROM = rule(
    or_(caseless('от'), caseless('с')),
    TIME.means(Duration.since),
)

UNTIL_PREP = or_(caseless('до'), caseless('по'))

UNTIL_TIME = rule(
    UNTIL_PREP,
    TIME.means(Duration.to),
)

UNTIL_WORD = rule(
    UNTIL_PREP,
    or_(
        gram('ADJF'),
        gram('NOUN'),
    ).match(gnc).repeatable(max=4)).means(Duration.to)

DURATION = or_(rule(FROM, UNTIL_WORD.optional()),
               rule(
                   FROM.optional(),
示例#10
0
EURO = or_(
    normalized('евро'),
    eq('€')
)

DOLLARS = or_(
    normalized('доллар'),
    eq('$')
)

RUBLES = or_(
    rule(normalized('рубль')),
    rule(
        or_(
            caseless('руб'),
            caseless('р'),
            eq('₽')
        ),
        eq('.').optional()
    )
)

CURRENCY = or_(
    rule(EURO),
    rule(DOLLARS),
    RUBLES
).interpretation(
    Money.currency
)
示例#11
0
    HOUR.interpretation(HourAndMinute.hour),
    HOUR_MINUTE_SEPARATOR.optional(),
    MINUTE.interpretation(HourAndMinute.minute),
).interpretation(HourAndMinute)

TIME = or_(
    HOUR_AND_MINUTE.interpretation(Time.time),
    HOUR.interpretation(Time.time),
    MINUTE.interpretation(Time.time),
).interpretation(Time)

# date
DAY = or_(rule(and_(gte(1), lte(31)))).interpretation(Date.day.custom(to_int))
MONTH = and_(gte(1), lte(12)).interpretation(Date.month.custom(to_int))
YEAR = and_(gte(1), lte(2099)).interpretation(Date.year.custom(to_int))
YEAR_WORDS = or_(rule(caseless("г"), "."), rule(normalized("год")))
MONTH_NAME = dictionary(MONTHS).interpretation(Date.month.normalized().custom(
    MONTHS.__getitem__))
DATE = or_(
    rule(YEAR, DATE_SEPARATOR, MONTH, DATE_SEPARATOR, DAY),
    rule(DAY, DATE_SEPARATOR, MONTH_NAME, DATE_SEPARATOR, YEAR.optional(),
         YEAR_WORDS.optional()),
    rule(DAY, DATE_SEPARATOR, MONTH, DATE_SEPARATOR, YEAR.optional(),
         YEAR_WORDS.optional()),
    rule(DAY, MONTH_NAME, YEAR.optional(), YEAR_WORDS.optional()),
).interpretation(Date)

DAYNAME = dictionary(DAYS).interpretation(
    DayName.name.normalized().custom(day))

AT = or_(rule("в"), rule("во"))
示例#12
0
REGION = rule(
    gram('ADJF').match(gnc),
    dictionary({
        'край',
        'район',
        'область',
        'губерния',
        'уезд',
    }),
).interpretation(Location.name.inflected())

gnc1 = gnc_relation()
gnc2 = gnc_relation()

FEDERAL_DISTRICT = rule(
    rule(caseless('северо'), '-').optional(),
    dictionary({
        'центральный',
        'западный',
        'южный',
        'кавказский',
        'приволжский',
        'уральский',
        'сибирский',
        'дальневосточный',
    }).match(gnc1),
    or_(
        rule(
            dictionary({'федеральный'}).match(gnc1, gnc2),
            dictionary({'округ'}).match(gnc2),
        ),
示例#13
0
    'винзавод',
    'подстанция',
    'гидроэлектростанция',
])

gnc = gnc_relation()
ADJF_PREFIX = rule(
    or_(
        rule(gram('ADJF').match(gnc)),  # международное
        rule(  # историко-просветительское
            true(),
            eq('-'),
            gram('ADJF').match(gnc),
        ),
    ),
    or_(caseless('и'), eq(',')).optional(),
).repeatable()

case = case_relation()
GENT_GROUP = rule(
    gram('gent').match(case)
).repeatable().optional()

QUOTED = rule(
    TYPE,
    in_(QUOTES),
    not_(in_(QUOTES)).repeatable(),
    in_(QUOTES),
)

TRIPLE_QUOTED = rule(
示例#14
0
        'коллектив',
        'правление',
        'совет',
    ]

gnc = gnc_relation()
ADJF_PREFIX = rule(
    or_(
        rule(gram('ADJF').match(gnc)),  # международное
        rule(  # историко-просветительское
            true(),
            eq('-'),
            gram('ADJF').match(gnc),
        ),
    ),
    or_(caseless('и'), eq(',')).optional(),
).repeatable()

case = case_relation()
GENT_GROUP = rule(
    gram('gent').match(case)
).repeatable().optional()

QUOTED = rule(
    gram('OrganisationType'),
    gram('QUOTE'),
    not_(
        or_(
            gram('QUOTE'),
            gram('END-OF-LINE'),
        )).repeatable(),
示例#15
0
    or_(
        or_(
            gram('PREP'),
            gram('Vpre'),
            gram('CONJ'),
            gram('PRCL'),
            gram('INTJ'),
        ),
        gram('POST'),
    ).optional())

case = case_relation()
GENT_GROUP = rule(gram('gent').match(case)).repeatable().optional()

#ADJF
ADJF_PREFIX_COUNTABLE = rule(or_(caseless('и'),
                                 eq(',')).optional(), ).repeatable()

ADJF_PREFIX_ADJF = and_(ADJF, TITLE).repeatable()

ADJF_PREFIX = rule(
    ADJF_PREFIX_ADJF,
    ADJF.optional(),  #Киевском государственном университете
    ADJF_PREFIX_COUNTABLE).repeatable()
#
###

### 1-ST RING RULES
R1_SIMPLE = rule(EDUORGANISATION_DICT, ).repeatable()

gnc = gnc_relation()
示例#16
0
    or_(eq('врид'), eq('врио')).optional(),
    or_(rule('пом', DOT.optional()), rule('зам', DOT.optional()),
        rule('ст', DOT.optional()),
        rule(dictionary({'заместитель'}))).optional(),
    normalized('полковой').optional(),
    or_(
        rule('сотр', DOT.optional()),
        rule(
            dictionary({
                'оперуполномоченный', 'сотрудник', 'командир',
                'уполномоченный', 'шофер', 'следователь', 'наркома',
                'работник', 'инспектор', 'комендант', 'разведчик', 'начальник',
                'секретарь', 'особоуполномоченный', 'председатель',
                'фельдъегерь', 'сотрудница', 'лейтенант', 'референт',
                'слушатель', 'руководитель', 'переводчик', 'управляющий'
            })), rule(caseless('нач'), DOT),
        rule(normalized('министра'), 'внутренних', 'дел')),
    or_(rule(eq('контрразведки')), rule(eq('особой'), eq('роты'))).optional())

pos_parser = Parser(POSITION)


def parse(d):
    for match in pos_parser.findall(d):
        b, e = match.tokens[0].span[0], match.tokens[-1].span[1]
        return d[b:e], d[e + 1:]
    return '', ''


# POSITION = rule(
#     eq('врид').optional(),
示例#17
0
#
#  FED OKRUGA
#
############

FED_OKRUG_NAME = or_(
    rule(
        dictionary({
            'дальневосточный',
            'приволжский',
            'сибирский',
            'уральский',
            'центральный',
            'южный',
        })),
    rule(caseless('северо'), DASH.optional(),
         dictionary({'западный', 'кавказский'}))).interpretation(Region.name)

FED_OKRUG_WORDS = or_(rule(normalized('федеральный'), normalized('округ')),
                      rule(caseless('фо'))).interpretation(
                          Region.type.const('федеральный округ'))

FED_OKRUG = rule(FED_OKRUG_WORDS, FED_OKRUG_NAME).interpretation(Region)

#########
#
#   RESPUBLIKA
#
############

RESPUBLIKA_WORDS = or_(rule(caseless('респ'), DOT.optional()),
示例#18
0
    or_(
        or_(
            gram('PREP'),
            gram('Vpre'),
            gram('CONJ'),
            gram('PRCL'),
            gram('INTJ'),
        ),
        gram('POST'),
    ).optional())

case = case_relation()
GENT_GROUP = rule(gram('gent').match(case)).repeatable().optional()

#ADJF
ADJF_PREFIX_COUNTABLE = rule(or_(caseless('и'), eq(',')).optional(), )

ADJF_PREFIX_ADJF = and_(ADJF, TITLE).repeatable()

ADJF_NORM = rule(
    and_(ADJF, custom(lambda s: EDUORG_DICT_REGEXP.search(s),
                      types=(str)))).repeatable()

ADJF_PREFIX = rule(
    ADJF_PREFIX_ADJF,
    ADJF.optional(),  #Киевском государственном университете
    ADJF_PREFIX_COUNTABLE).repeatable()
#
###

### 1-ST RING RULES
示例#19
0
).interpretation(
    const(dsl.EURO)
)

DOLLARS = or_(
    normalized('доллар'),
    eq('$')
).interpretation(
    const(dsl.DOLLARS)
)

RUBLES = or_(
    rule(normalized('рубль')),
    rule(
        or_(
            caseless('руб'),
            caseless('р'),
            eq('₽')
        ),
        DOT.optional()
    )
).interpretation(
    const(dsl.RUBLES)
)

CURRENCY = or_(
    EURO,
    DOLLARS,
    RUBLES
).interpretation(
    Money.currency
示例#20
0
from yargy import or_, rule
from yargy.interpretation import attribute, fact
import yargy.interpretation as meaning
from yargy.predicates import caseless, eq, in_, in_caseless, normalized

from .common import Array
from .literal import LIST_OF_NUMERALS
from .station_title import STATION_TITLE

Station = fact('Station', ['name', attribute('num', default=[])])

STATION_WORD = or_(
    rule(caseless('ст'), '.'),
    rule(normalized('станция')),
)

METRO_WORD = or_(
    rule(caseless('м'), '.'),
    rule(normalized('метро')),
)

__quotes = "„“”‚‘’'\""
LEFT_QUOTE = in_("«" + __quotes)
RIGHT_QUOTE = in_("»" + __quotes)

STATION = rule(
    STATION_WORD.optional(),
    METRO_WORD.optional(),
    LEFT_QUOTE.optional(),
    STATION_TITLE.interpretation(
        meaning.custom(lambda p: p.value)).interpretation(Station.name),
示例#21
0
from yargy import or_, rule
from yargy.interpretation import attribute, fact
import yargy.interpretation as meaning
from yargy.predicates import caseless, gram, in_caseless, normalized

from .station import FROM_STATION_TO_STATION, LIST_OF_STATIONS, STATION

Transfer = fact('Transfer', [attribute('to', default=[])])

TRANSFER = rule(
    gram('ADJF').optional(),  # пешеходный
    normalized('переход'),
    or_(
        FROM_STATION_TO_STATION.interpretation(Transfer.to),
        rule(
            or_(caseless('на'), caseless('между'), caseless('с')).optional(),
            LIST_OF_STATIONS.interpretation(Transfer.to)),
    ).optional(),
).interpretation(Transfer)

StationAndTransfer = fact('StationAndTransfer', ['station', 'transfer'])

STATION_AND_TRANSFER = rule(
    STATION.interpretation(StationAndTransfer.station),
    rule(
        in_caseless('и,'),
        TRANSFER.interpretation(meaning.custom(lambda p: p.to)).interpretation(
            StationAndTransfer.transfer),
    ).optional()).interpretation(StationAndTransfer)
示例#22
0
    attribute('currency', '-'),
    attribute('multiplier', -1),
    attribute('period', '-')
])

DOT = eq('.')
INT = type('INT')

########
#
#   CURRENCY
#
##########

EURO = or_(normalized('евро'), normalized('euro'), eq('€'),
           caseless('EUR')).interpretation(const('EUR'))

DOLLARS = or_(normalized('доллар'), normalized('дол'), normalized('dollar'),
              eq('$'), caseless('USD')).interpretation(const('USD'))

RUBLES = or_(
    rule(normalized('ruble')),
    rule(normalized('рубль')),
    rule(normalized('рубл')),
    rule(
        or_(
            caseless('руб'),
            caseless('rub'),
            #             caseless('rur'),
            caseless('р'),
            eq('₽')),
示例#23
0
文件: time.py 项目: xamgore/spbmetro
is_minute = custom(minute_re.fullmatch).activate(TOKENIZER)

HOUR_UNIT = rule(morph_pipeline(['ч', 'час', 'часы']), eq('.').optional())

MINUTE_UNIT = rule(morph_pipeline(['м', 'мин', 'минуты']), eq('.').optional())

# 17:02 ч.
TIME_DIGITAL = rule(
    is_hour.means(Time.hours),
    in_(':-.'),
    is_minute.means(Time.minutes),
    or_(HOUR_UNIT, MINUTE_UNIT).optional(),
).means(Time)

# 17ч 02
TIME_HUMAN = rule(
    is_hour.means(Time.hours),
    HOUR_UNIT,
    is_minute.means(Time.minutes),
    MINUTE_UNIT.optional(),
).means(Time)

# в 15:00
TIME = rule(caseless('в').optional(), or_(
    TIME_DIGITAL,
    TIME_HUMAN,
)).means(Time)

if __name__ == '__main__':
    print(sys.argv)
示例#24
0
############


FED_OKRUG_NAME = or_(
    rule(
        dictionary({
            'дальневосточный',
            'приволжский',
            'сибирский',
            'уральский',
            'центральный',
            'южный',
        })
    ),
    rule(
        caseless('северо'),
        DASH.optional(),
        dictionary({
            'западный',
            'кавказский'
        })
    )
).interpretation(
    Region.name
)

FED_OKRUG_WORDS = or_(
    rule(
        normalized('федеральный'),
        normalized('округ')
    ),
示例#25
0
REGION = rule(
    gram('ADJF').match(gnc),
    dictionary({
        'край',
        'район',
        'область',
        'губерния',
        'уезд',
    }).match(gnc),
).interpretation(Location.name.inflected())

gnc = gnc_relation()

FEDERAL_DISTRICT = rule(
    rule(caseless('северо'), '-').optional(),
    dictionary({
        'центральный',
        'западный',
        'южный',
        'кавказский',
        'приволжский',
        'уральский',
        'сибирский',
        'дальневосточный',
    }).match(gnc),
    or_(
        rule(
            dictionary({'федеральный'}).match(gnc),
            dictionary({'округ'}).match(gnc),
        ),
示例#26
0
REGION = rule(
    gram('ADJF').match(gnc),
    dictionary({
        'край',
        'район',
        'область',
        'губерния',
        'уезд',
    }).match(gnc),
).interpretation(Location.name.inflected())

gnc1 = gnc_relation()
gnc2 = gnc_relation()

FEDERAL_DISTRICT = rule(
    rule(caseless('северо'), '-').optional(),
    dictionary({
        'центральный',
        'западный',
        'южный',
        'кавказский',
        'приволжский',
        'уральский',
        'сибирский',
        'дальневосточный',
    }).match(gnc1),
    or_(
        rule(
            dictionary({'федеральный'}).match(gnc1, gnc2),
            dictionary({'округ'}).match(gnc2),
        ),
示例#27
0
from natasha.grammars.date import DATE

Metro = fact('Metro', ['name', 'type'])

Address = fact('Address', [attribute('parts').repeatable()])

METRO_STATIONS = rule(
    morph_pipeline({
        'Автозаводская', 'Щукинская', 'Академическая', 'Электрозаводская',
        'Александровский сад', 'Юго-Западная', 'Алексеевская', 'Южная',
        'Алма-Атинская', 'Ясенево'
    }))

METRO_WORDS = or_(
    rule(normalized('метро')),
    rule(caseless('м'), DOT.optional()),
).interpretation(Metro.type.const('метро'))

METRO = or_(rule(METRO_WORDS.optional(), METRO_STATIONS),
            rule(METRO_STATIONS, METRO_WORDS.optional())).interpretation(Metro)

STREET_LEVEL.rules.append(METRO)

ADDRESS.rule = rule(
    rule(PRE_STREET_LEVEL.interpretation(Address.parts),
         SEP.optional()).optional().repeatable(),
    STREET_LEVEL.interpretation(Address.parts),
    rule(SEP.optional(), DOM.interpretation(Address.parts)).optional(),
    rule(SEP.optional(), POST_STREET_LEVEL.interpretation(
        Address.parts)).optional().repeatable(),
).interpretation(Address)